module_spec.cc 12.6 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Copyright (C) 2010  Internet Systems Consortium.
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
// DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
// INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
// FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
// NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
// WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15

16
#include <config/module_spec.h>
17
18

#include <sstream>
Jelte Jansen's avatar
Jelte Jansen committed
19
#include <iostream>
Jelte Jansen's avatar
Jelte Jansen committed
20
21
#include <fstream>
#include <cerrno>
22
23
24

#include <boost/foreach.hpp>

25
// todo: add more context to thrown ModuleSpecErrors?
26

27
using namespace isc::data;
28
using namespace isc::config;
29

30
namespace {
31
//
32
// Private functions
33
34
//

35
36
37
void
check_leaf_item(ConstElementPtr spec, const std::string& name,
                Element::types type, bool mandatory)
38
39
{
    if (spec->contains(name)) {
40
        if (spec->get(name)->getType() == type) {
41
42
            return;
        } else {
43
            throw ModuleSpecError(name + " not of type " + Element::typeToName(type));
44
45
46
47
48
        }
    } else if (mandatory) {
        // todo: want parent item name, and perhaps some info about location
        // in list? or just catch and throw new...
        // or make this part non-throwing and check return value...
49
        throw ModuleSpecError(name + " missing in " + spec->str());
50
51
52
    }
}

53
void check_config_item_list(ConstElementPtr spec);
54

55
56
void
check_config_item(ConstElementPtr spec) {
57
58
59
60
    check_leaf_item(spec, "item_name", Element::string, true);
    check_leaf_item(spec, "item_type", Element::string, true);
    check_leaf_item(spec, "item_optional", Element::boolean, true);
    check_leaf_item(spec, "item_default",
61
                    Element::nameToType(spec->get("item_type")->stringValue()),
62
                    !spec->get("item_optional")->boolValue()
63
64
                   );

65
    // if list, check the list specification
66
    if (Element::nameToType(spec->get("item_type")->stringValue()) == Element::list) {
67
68
69
        check_leaf_item(spec, "list_item_spec", Element::map, true);
        check_config_item(spec->get("list_item_spec"));
    }
70
71

    if (spec->get("item_type")->stringValue() == "map") {
72
        check_leaf_item(spec, "map_item_spec", Element::list, true);
Jelte Jansen's avatar
Jelte Jansen committed
73
        check_config_item_list(spec->get("map_item_spec"));
74
75
76
    } else if (spec->get("item_type")->stringValue() == "named_set") {
        check_leaf_item(spec, "named_set_item_spec", Element::map, true);
        check_config_item(spec->get("named_set_item_spec"));
77
78
79
    }
}

80
81
void
check_config_item_list(ConstElementPtr spec) {
82
    if (spec->getType() != Element::list) {
83
        throw ModuleSpecError("config_data is not a list of elements");
84
    }
85
    BOOST_FOREACH(ConstElementPtr item, spec->listValue()) {
86
87
88
89
        check_config_item(item);
    }
}

90
91
void
check_command(ConstElementPtr spec) {
92
93
94
95
96
    check_leaf_item(spec, "command_name", Element::string, true);
    check_leaf_item(spec, "command_args", Element::list, true);
    check_config_item_list(spec->get("command_args"));
}

97
98
void
check_command_list(ConstElementPtr spec) {
99
    if (spec->getType() != Element::list) {
100
        throw ModuleSpecError("commands is not a list of elements");
101
    }
102
    BOOST_FOREACH(ConstElementPtr item, spec->listValue()) {
103
104
105
106
        check_command(item);
    }
}

107
108
void
check_data_specification(ConstElementPtr spec) {
109
    check_leaf_item(spec, "module_name", Element::string, true);
Jelte Jansen's avatar
Jelte Jansen committed
110
    check_leaf_item(spec, "module_description", Element::string, false);
111
112
    // config_data is not mandatory; module could just define
    // commands and have no config
113
114
115
116
117
118
119
120
    if (spec->contains("config_data")) {
        check_config_item_list(spec->get("config_data"));
    }
    if (spec->contains("commands")) {
        check_command_list(spec->get("commands"));
    }
}

121
// checks whether the given element is a valid module specification
122
// throws a ModuleSpecError if the specification is bad
123
124
void
check_module_specification(ConstElementPtr def) {
125
126
    try {
        check_data_specification(def);
127
    } catch (const TypeError& te) {
128
129
        throw ModuleSpecError(te.what());
    }
130
}
131
}
132

133
134
namespace isc {
namespace config {
135
136
137
138
//
// Public functions
//

139
ModuleSpec::ModuleSpec(ConstElementPtr module_spec_element,
140
141
142
143
144
                       const bool check)
                       throw(ModuleSpecError)
                       
{
    module_specification = module_spec_element;
145
    if (check) {
146
147
148
149
        check_module_specification(module_specification);
    }
}

150
ConstElementPtr
JINMEI Tatuya's avatar
JINMEI Tatuya committed
151
ModuleSpec::getCommandsSpec() const {
152
    if (module_specification->contains("commands")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
153
        return (module_specification->get("commands"));
154
    } else {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
155
        return (ElementPtr());
156
157
158
    }
}

159
ConstElementPtr
JINMEI Tatuya's avatar
JINMEI Tatuya committed
160
ModuleSpec::getConfigSpec() const {
161
    if (module_specification->contains("config_data")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
162
        return (module_specification->get("config_data"));
163
    } else {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
164
        return (ElementPtr());
165
166
167
    }
}

168
const std::string
JINMEI Tatuya's avatar
JINMEI Tatuya committed
169
170
ModuleSpec::getModuleName() const {
    return (module_specification->get("module_name")->stringValue());
171
172
}

Jelte Jansen's avatar
Jelte Jansen committed
173
const std::string
JINMEI Tatuya's avatar
JINMEI Tatuya committed
174
ModuleSpec::getModuleDescription() const {
Jelte Jansen's avatar
Jelte Jansen committed
175
    if (module_specification->contains("module_description")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
176
        return (module_specification->get("module_description")->stringValue());
Jelte Jansen's avatar
Jelte Jansen committed
177
    } else {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
178
        return (std::string(""));
Jelte Jansen's avatar
Jelte Jansen committed
179
180
181
    }
}

182
bool
183
ModuleSpec::validateConfig(ConstElementPtr data, const bool full) const {
184
    ConstElementPtr spec = module_specification->find("config_data");
185
    return (validateSpecList(spec, data, full, ElementPtr()));
186
187
}

188
bool
189
ModuleSpec::validateCommand(const std::string& command,
190
                             ConstElementPtr args,
191
                             ElementPtr errors) const
192
{
193
    if (args->getType() != Element::map) {
Jelte Jansen's avatar
Jelte Jansen committed
194
195
        errors->add(Element::create("args for command " +
                                    command + " is not a map"));
196
197
198
        return (false);
    }

199
    ConstElementPtr commands_spec = module_specification->find("commands");
200
201
202
203
204
205
206
207
    if (!commands_spec) {
        // there are no commands according to the spec.
        errors->add(Element::create("The given module has no commands"));
        return (false);
    }

    BOOST_FOREACH(ConstElementPtr cur_command, commands_spec->listValue()) {
        if (cur_command->get("command_name")->stringValue() == command) {
208
            return (validateSpecList(cur_command->get("command_args"),
Jelte Jansen's avatar
Jelte Jansen committed
209
                                       args, true, errors));
210
211
212
213
214
215
216
217
        }
    }

    // this command is unknown
    errors->add(Element::create("Unknown command " + command));
    return (false);
}

218
bool
219
ModuleSpec::validateConfig(ConstElementPtr data, const bool full,
220
                            ElementPtr errors) const
221
{
222
    ConstElementPtr spec = module_specification->find("config_data");
223
    return (validateSpecList(spec, data, full, errors));
224
225
}

226
227
ModuleSpec
moduleSpecFromFile(const std::string& file_name, const bool check)
Jelte Jansen's avatar
Jelte Jansen committed
228
                   throw(JSONError, ModuleSpecError)
229
230
231
232
233
234
235
236
237
238
{
    std::ifstream file;

    file.open(file_name.c_str());
    if (!file) {
        std::stringstream errs;
        errs << "Error opening " << file_name << ": " << strerror(errno);
        throw ModuleSpecError(errs.str());
    }

239
    ConstElementPtr module_spec_element = Element::fromJSON(file, file_name);
240
    if (module_spec_element->contains("module_spec")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
241
        return (ModuleSpec(module_spec_element->get("module_spec"), check));
242
243
244
    } else {
        throw ModuleSpecError("No module_spec in specification");
    }
245
246
247
248
}

ModuleSpec
moduleSpecFromFile(std::ifstream& in, const bool check)
JINMEI Tatuya's avatar
JINMEI Tatuya committed
249
250
                   throw(JSONError, ModuleSpecError)
{
251
    ConstElementPtr module_spec_element = Element::fromJSON(in);
252
    if (module_spec_element->contains("module_spec")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
253
        return (ModuleSpec(module_spec_element->get("module_spec"), check));
254
255
256
    } else {
        throw ModuleSpecError("No module_spec in specification");
    }
257
258
}

259

260
namespace {
261
262
263
264
//
// private functions
//

265
266
267
//
// helper functions for validation
//
268
269
bool
check_type(ConstElementPtr spec, ConstElementPtr element) {
270
    std::string cur_item_type;
271
    cur_item_type = spec->get("item_type")->stringValue();
272
    if (cur_item_type == "any") {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
273
        return (true);
274
    }
275
    switch (element->getType()) {
276
        case Element::integer:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
277
            return (cur_item_type == "integer");
278
279
            break;
        case Element::real:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
280
            return (cur_item_type == "real");
281
282
            break;
        case Element::boolean:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
283
            return (cur_item_type == "boolean");
284
285
            break;
        case Element::string:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
286
            return (cur_item_type == "string");
287
288
            break;
        case Element::list:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
289
            return (cur_item_type == "list");
290
291
            break;
        case Element::map:
292
            return (cur_item_type == "map" ||
293
                    cur_item_type == "named_set");
294
295
            break;
    }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
296
    return (false);
297
}
298
}
299
300

bool
301
ModuleSpec::validateItem(ConstElementPtr spec, ConstElementPtr data,
302
303
                          const bool full, ElementPtr errors) const
{
304
    if (!check_type(spec, data)) {
305
306
307
        // we should do some proper error feedback here
        // std::cout << "type mismatch; not " << spec->get("item_type") << ": " << data << std::endl;
        // std::cout << spec << std::endl;
308
309
310
        if (errors) {
            errors->add(Element::create("Type mismatch"));
        }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
311
        return (false);
312
    }
313
    if (data->getType() == Element::list) {
314
315
        ConstElementPtr list_spec = spec->get("list_item_spec");
        BOOST_FOREACH(ConstElementPtr list_el, data->listValue()) {
316
            if (!check_type(list_spec, list_el)) {
317
318
319
                if (errors) {
                    errors->add(Element::create("Type mismatch"));
                }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
320
                return (false);
321
            }
322
            if (list_spec->get("item_type")->stringValue() == "map") {
323
                if (!validateItem(list_spec, list_el, full, errors)) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
324
                    return (false);
325
326
                }
            }
327
328
        }
    }
329
    if (data->getType() == Element::map) {
Jelte Jansen's avatar
Jelte Jansen committed
330
331
        // either a normal 'map' or a 'named set' (determined by which
        // subspecification it has)
332
333
334
335
336
337
338
339
        if (spec->contains("map_item_spec")) {
            if (!validateSpecList(spec->get("map_item_spec"), data, full, errors)) {
                return (false);
            }
        } else {
            typedef std::pair<std::string, ConstElementPtr> maptype;

            BOOST_FOREACH(maptype m, data->mapValue()) {
340
                if (!validateItem(spec->get("named_set_item_spec"), m.second, full, errors)) {
341
342
343
                    return (false);
                }
            }
344
345
        }
    }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
346
    return (true);
347
348
349
350
}

// spec is a map with item_name etc, data is a map
bool
351
ModuleSpec::validateSpec(ConstElementPtr spec, ConstElementPtr data,
352
353
                          const bool full, ElementPtr errors) const
{
354
355
    std::string item_name = spec->get("item_name")->stringValue();
    bool optional = spec->get("item_optional")->boolValue();
356
    ConstElementPtr data_el;
357
    data_el = data->get(item_name);
358
    
359
    if (data_el) {
360
        if (!validateItem(spec, data_el, full, errors)) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
361
            return (false);
362
363
        }
    } else {
364
365
366
367
        if (!optional && full) {
            if (errors) {
                errors->add(Element::create("Non-optional value missing"));
            }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
368
            return (false);
369
370
        }
    }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
371
    return (true);
372
373
374
375
}

// spec is a list of maps, data is a map
bool
376
ModuleSpec::validateSpecList(ConstElementPtr spec, ConstElementPtr data,
377
378
                               const bool full, ElementPtr errors) const
{
379
    bool validated = true;
380
    std::string cur_item_name;
381
    BOOST_FOREACH(ConstElementPtr cur_spec_el, spec->listValue()) {
382
        if (!validateSpec(cur_spec_el, data, full, errors)) {
383
            validated = false;
384
385
        }
    }
386
387
388
389
390

    typedef std::pair<std::string, ConstElementPtr> maptype;
    
    BOOST_FOREACH(maptype m, data->mapValue()) {
        bool found = false;
391
        // Ignore 'version' as a config element
392
        if (m.first.compare("version") != 0) {
393
394
395
396
            BOOST_FOREACH(ConstElementPtr cur_spec_el, spec->listValue()) {
                if (cur_spec_el->get("item_name")->stringValue().compare(m.first) == 0) {
                    found = true;
                }
397
            }
398
399
400
401
402
            if (!found) {
                validated = false;
                if (errors) {
                    errors->add(Element::create("Unknown item " + m.first));
                }
403
404
405
406
407
            }
        }
    }

    return (validated);
408
409
}

410
411
}
}