module_spec.cc 15.5 KB
Newer Older
Naoki Kambe's avatar
Naoki Kambe committed
1
// Copyright (C) 2010, 2011  Internet Systems Consortium.
2 3 4 5 6 7 8 9 10 11 12 13 14
//
// 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);
    }
}

Naoki Kambe's avatar
Naoki Kambe committed
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
// checks whether the given element is a valid statistics specification
// returns false if the specification is bad
bool
check_format(ConstElementPtr value, ConstElementPtr format_name) {
    typedef std::map<std::string, std::string> format_types;
    format_types time_formats;
    // TODO: should be added other format types if necessary
    time_formats.insert(
        format_types::value_type("date-time", "%Y-%m-%dT%H:%M:%SZ") );
    time_formats.insert(
        format_types::value_type("date", "%Y-%m-%d") );
    time_formats.insert(
        format_types::value_type("time", "%H:%M:%S") );
    BOOST_FOREACH (const format_types::value_type& f, time_formats) {
        if (format_name->stringValue() == f.first) {
            struct tm tm;
            return (strptime(value->stringValue().c_str(),
                             f.second.c_str(), &tm) != NULL);
        }
    }
    return (false);
}

void check_statistics_item_list(ConstElementPtr spec);

void
check_statistics_item_list(ConstElementPtr spec) {
    if (spec->getType() != Element::list) {
        throw ModuleSpecError("statistics is not a list of elements");
    }
    BOOST_FOREACH(ConstElementPtr item, spec->listValue()) {
        check_config_item(item);
        // additional checks for statistics
        check_leaf_item(item, "item_title", Element::string, true);
        check_leaf_item(item, "item_description", Element::string, true);
        check_leaf_item(item, "item_format", Element::string, false);
        // checks name of item_format and validation of item_default
        if (item->contains("item_format")
            && item->contains("item_default")) {
            if(!check_format(item->get("item_default"),
                             item->get("item_format"))) {
                throw ModuleSpecError(
                    "item_default not valid type of item_format");
            }
        }
    }
}

138 139
void
check_command(ConstElementPtr spec) {
140 141 142 143 144
    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"));
}

145 146
void
check_command_list(ConstElementPtr spec) {
147
    if (spec->getType() != Element::list) {
148
        throw ModuleSpecError("commands is not a list of elements");
149
    }
150
    BOOST_FOREACH(ConstElementPtr item, spec->listValue()) {
151 152 153 154
        check_command(item);
    }
}

155 156
void
check_data_specification(ConstElementPtr spec) {
157
    check_leaf_item(spec, "module_name", Element::string, true);
Jelte Jansen's avatar
Jelte Jansen committed
158
    check_leaf_item(spec, "module_description", Element::string, false);
159 160
    // config_data is not mandatory; module could just define
    // commands and have no config
161 162 163 164 165 166
    if (spec->contains("config_data")) {
        check_config_item_list(spec->get("config_data"));
    }
    if (spec->contains("commands")) {
        check_command_list(spec->get("commands"));
    }
Naoki Kambe's avatar
Naoki Kambe committed
167 168 169
    if (spec->contains("statistics")) {
        check_statistics_item_list(spec->get("statistics"));
    }
170 171
}

172
// checks whether the given element is a valid module specification
173
// throws a ModuleSpecError if the specification is bad
174 175
void
check_module_specification(ConstElementPtr def) {
176 177
    try {
        check_data_specification(def);
178
    } catch (const TypeError& te) {
179 180
        throw ModuleSpecError(te.what());
    }
181
}
182
}
183

184 185
namespace isc {
namespace config {
186 187 188 189
//
// Public functions
//

190
ModuleSpec::ModuleSpec(ConstElementPtr module_spec_element,
191 192 193 194 195
                       const bool check)
                       throw(ModuleSpecError)
                       
{
    module_specification = module_spec_element;
196
    if (check) {
197 198 199 200
        check_module_specification(module_specification);
    }
}

201
ConstElementPtr
JINMEI Tatuya's avatar
JINMEI Tatuya committed
202
ModuleSpec::getCommandsSpec() const {
203
    if (module_specification->contains("commands")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
204
        return (module_specification->get("commands"));
205
    } else {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
206
        return (ElementPtr());
207 208 209
    }
}

210
ConstElementPtr
JINMEI Tatuya's avatar
JINMEI Tatuya committed
211
ModuleSpec::getConfigSpec() const {
212
    if (module_specification->contains("config_data")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
213
        return (module_specification->get("config_data"));
214
    } else {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
215
        return (ElementPtr());
216 217 218
    }
}

Naoki Kambe's avatar
Naoki Kambe committed
219 220 221 222 223 224 225 226 227
ConstElementPtr
ModuleSpec::getStatisticsSpec() const {
    if (module_specification->contains("statistics")) {
        return (module_specification->get("statistics"));
    } else {
        return (ElementPtr());
    }
}

228
const std::string
JINMEI Tatuya's avatar
JINMEI Tatuya committed
229 230
ModuleSpec::getModuleName() const {
    return (module_specification->get("module_name")->stringValue());
231 232
}

Jelte Jansen's avatar
Jelte Jansen committed
233
const std::string
JINMEI Tatuya's avatar
JINMEI Tatuya committed
234
ModuleSpec::getModuleDescription() const {
Jelte Jansen's avatar
Jelte Jansen committed
235
    if (module_specification->contains("module_description")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
236
        return (module_specification->get("module_description")->stringValue());
Jelte Jansen's avatar
Jelte Jansen committed
237
    } else {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
238
        return (std::string(""));
Jelte Jansen's avatar
Jelte Jansen committed
239 240 241
    }
}

242
bool
243
ModuleSpec::validateConfig(ConstElementPtr data, const bool full) const {
244
    ConstElementPtr spec = module_specification->find("config_data");
245
    return (validateSpecList(spec, data, full, ElementPtr()));
246 247
}

Naoki Kambe's avatar
Naoki Kambe committed
248 249 250 251 252 253
bool
ModuleSpec::validateStatistics(ConstElementPtr data, const bool full) const {
    ConstElementPtr spec = module_specification->find("statistics");
    return (validateSpecList(spec, data, full, ElementPtr()));
}

254
bool
255
ModuleSpec::validateCommand(const std::string& command,
256
                             ConstElementPtr args,
257
                             ElementPtr errors) const
258
{
259
    if (args->getType() != Element::map) {
Jelte Jansen's avatar
Jelte Jansen committed
260 261
        errors->add(Element::create("args for command " +
                                    command + " is not a map"));
262 263 264
        return (false);
    }

265
    ConstElementPtr commands_spec = module_specification->find("commands");
266 267 268 269 270 271 272 273
    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) {
274
            return (validateSpecList(cur_command->get("command_args"),
Jelte Jansen's avatar
Jelte Jansen committed
275
                                       args, true, errors));
276 277 278 279 280 281 282 283
        }
    }

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

284
bool
285
ModuleSpec::validateConfig(ConstElementPtr data, const bool full,
286
                            ElementPtr errors) const
287
{
288
    ConstElementPtr spec = module_specification->find("config_data");
289
    return (validateSpecList(spec, data, full, errors));
290 291
}

Naoki Kambe's avatar
Naoki Kambe committed
292 293 294 295 296 297 298 299
bool
ModuleSpec::validateStatistics(ConstElementPtr data, const bool full,
                               ElementPtr errors) const
{
    ConstElementPtr spec = module_specification->find("statistics");
    return (validateSpecList(spec, data, full, errors));
}

300 301
ModuleSpec
moduleSpecFromFile(const std::string& file_name, const bool check)
Jelte Jansen's avatar
Jelte Jansen committed
302
                   throw(JSONError, ModuleSpecError)
303 304 305 306 307 308 309 310 311 312
{
    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());
    }

313
    ConstElementPtr module_spec_element = Element::fromJSON(file, file_name);
314
    if (module_spec_element->contains("module_spec")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
315
        return (ModuleSpec(module_spec_element->get("module_spec"), check));
316 317 318
    } else {
        throw ModuleSpecError("No module_spec in specification");
    }
319 320 321 322
}

ModuleSpec
moduleSpecFromFile(std::ifstream& in, const bool check)
JINMEI Tatuya's avatar
JINMEI Tatuya committed
323 324
                   throw(JSONError, ModuleSpecError)
{
325
    ConstElementPtr module_spec_element = Element::fromJSON(in);
326
    if (module_spec_element->contains("module_spec")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
327
        return (ModuleSpec(module_spec_element->get("module_spec"), check));
328 329 330
    } else {
        throw ModuleSpecError("No module_spec in specification");
    }
331 332
}

333

334
namespace {
335 336 337 338
//
// private functions
//

339 340 341
//
// helper functions for validation
//
342 343
bool
check_type(ConstElementPtr spec, ConstElementPtr element) {
344
    std::string cur_item_type;
345
    cur_item_type = spec->get("item_type")->stringValue();
346
    if (cur_item_type == "any") {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
347
        return (true);
348
    }
349
    switch (element->getType()) {
350
        case Element::integer:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
351
            return (cur_item_type == "integer");
352 353
            break;
        case Element::real:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
354
            return (cur_item_type == "real");
355 356
            break;
        case Element::boolean:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
357
            return (cur_item_type == "boolean");
358 359
            break;
        case Element::string:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
360
            return (cur_item_type == "string");
361 362
            break;
        case Element::list:
JINMEI Tatuya's avatar
JINMEI Tatuya committed
363
            return (cur_item_type == "list");
364 365
            break;
        case Element::map:
366
            return (cur_item_type == "map" ||
367
                    cur_item_type == "named_set");
368 369
            break;
    }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
370
    return (false);
371
}
372
}
373 374

bool
375
ModuleSpec::validateItem(ConstElementPtr spec, ConstElementPtr data,
376 377
                          const bool full, ElementPtr errors) const
{
378
    if (!check_type(spec, data)) {
379 380 381
        // 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;
382 383 384
        if (errors) {
            errors->add(Element::create("Type mismatch"));
        }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
385
        return (false);
386
    }
387
    if (data->getType() == Element::list) {
388 389
        ConstElementPtr list_spec = spec->get("list_item_spec");
        BOOST_FOREACH(ConstElementPtr list_el, data->listValue()) {
390
            if (!check_type(list_spec, list_el)) {
391 392 393
                if (errors) {
                    errors->add(Element::create("Type mismatch"));
                }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
394
                return (false);
395
            }
396
            if (list_spec->get("item_type")->stringValue() == "map") {
397
                if (!validateItem(list_spec, list_el, full, errors)) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
398
                    return (false);
399 400
                }
            }
401 402
        }
    }
403
    if (data->getType() == Element::map) {
Jelte Jansen's avatar
Jelte Jansen committed
404 405
        // either a normal 'map' or a 'named set' (determined by which
        // subspecification it has)
406 407 408 409 410 411 412 413
        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()) {
414
                if (!validateItem(spec->get("named_set_item_spec"), m.second, full, errors)) {
415 416 417
                    return (false);
                }
            }
418 419
        }
    }
Naoki Kambe's avatar
Naoki Kambe committed
420 421 422 423 424 425 426 427
    if (spec->contains("item_format")) {
        if (!check_format(data, spec->get("item_format"))) {
            if (errors) {
                errors->add(Element::create("Format mismatch"));
            }
            return (false);
        }
    }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
428
    return (true);
429 430 431 432
}

// spec is a map with item_name etc, data is a map
bool
433
ModuleSpec::validateSpec(ConstElementPtr spec, ConstElementPtr data,
434 435
                          const bool full, ElementPtr errors) const
{
436 437
    std::string item_name = spec->get("item_name")->stringValue();
    bool optional = spec->get("item_optional")->boolValue();
438
    ConstElementPtr data_el;
439
    data_el = data->get(item_name);
440
    
441
    if (data_el) {
442
        if (!validateItem(spec, data_el, full, errors)) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
443
            return (false);
444 445
        }
    } else {
446 447 448 449
        if (!optional && full) {
            if (errors) {
                errors->add(Element::create("Non-optional value missing"));
            }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
450
            return (false);
451 452
        }
    }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
453
    return (true);
454 455 456 457
}

// spec is a list of maps, data is a map
bool
458
ModuleSpec::validateSpecList(ConstElementPtr spec, ConstElementPtr data,
459 460
                               const bool full, ElementPtr errors) const
{
461
    bool validated = true;
462
    std::string cur_item_name;
463
    BOOST_FOREACH(ConstElementPtr cur_spec_el, spec->listValue()) {
464
        if (!validateSpec(cur_spec_el, data, full, errors)) {
465
            validated = false;
466 467
        }
    }
468 469 470 471 472

    typedef std::pair<std::string, ConstElementPtr> maptype;
    
    BOOST_FOREACH(maptype m, data->mapValue()) {
        bool found = false;
473
        // Ignore 'version' as a config element
474
        if (m.first.compare("version") != 0) {
475 476 477 478
            BOOST_FOREACH(ConstElementPtr cur_spec_el, spec->listValue()) {
                if (cur_spec_el->get("item_name")->stringValue().compare(m.first) == 0) {
                    found = true;
                }
479
            }
480 481 482 483 484
            if (!found) {
                validated = false;
                if (errors) {
                    errors->add(Element::create("Unknown item " + m.first));
                }
485 486 487 488 489
            }
        }
    }

    return (validated);
490 491
}

492 493
}
}