module_spec.cc 15.9 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 44
            isc_throw(ModuleSpecError,
                      name + " not of type " + Element::typeToName(type));
45 46 47 48 49
        }
    } 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...
50
        isc_throw(ModuleSpecError, name + " missing in " + spec->str());
51 52 53
    }
}

54
void check_config_item_list(ConstElementPtr spec);
55

56 57
void
check_config_item(ConstElementPtr spec) {
58 59 60 61
    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",
62
                    Element::nameToType(spec->get("item_type")->stringValue()),
63
                    !spec->get("item_optional")->boolValue()
64 65
                   );

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

    if (spec->get("item_type")->stringValue() == "map") {
73
        check_leaf_item(spec, "map_item_spec", Element::list, true);
Jelte Jansen's avatar
Jelte Jansen committed
74
        check_config_item_list(spec->get("map_item_spec"));
75 76 77
    } 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"));
78 79 80
    }
}

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

Naoki Kambe's avatar
Naoki Kambe committed
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
// 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;
Naoki Kambe's avatar
Naoki Kambe committed
107
            std::vector<char> buf(32);
108 109
            memset(&tm, 0, sizeof(tm));
            // reverse check
Naoki Kambe's avatar
Naoki Kambe committed
110
            return (strptime(value->stringValue().c_str(),
111
                             f.second.c_str(), &tm) != NULL
Naoki Kambe's avatar
Naoki Kambe committed
112
                    && strftime(&buf[0], buf.size(),
113
                                f.second.c_str(), &tm) != 0
Naoki Kambe's avatar
Naoki Kambe committed
114 115
                    && strncmp(value->stringValue().c_str(),
                               &buf[0], buf.size()) == 0);
Naoki Kambe's avatar
Naoki Kambe committed
116 117 118 119 120 121 122 123 124 125
        }
    }
    return (false);
}

void check_statistics_item_list(ConstElementPtr spec);

void
check_statistics_item_list(ConstElementPtr spec) {
    if (spec->getType() != Element::list) {
126
        isc_throw(ModuleSpecError, "statistics is not a list of elements");
Naoki Kambe's avatar
Naoki Kambe committed
127 128 129 130 131 132 133 134 135 136 137 138
    }
    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"))) {
139
                isc_throw(ModuleSpecError,
Naoki Kambe's avatar
Naoki Kambe committed
140 141 142 143 144 145
                    "item_default not valid type of item_format");
            }
        }
    }
}

146 147
void
check_command(ConstElementPtr spec) {
148 149 150 151 152
    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"));
}

153 154
void
check_command_list(ConstElementPtr spec) {
155
    if (spec->getType() != Element::list) {
156
        isc_throw(ModuleSpecError, "commands is not a list of elements");
157
    }
158
    BOOST_FOREACH(ConstElementPtr item, spec->listValue()) {
159 160 161 162
        check_command(item);
    }
}

163 164
void
check_data_specification(ConstElementPtr spec) {
165
    check_leaf_item(spec, "module_name", Element::string, true);
Jelte Jansen's avatar
Jelte Jansen committed
166
    check_leaf_item(spec, "module_description", Element::string, false);
167 168
    // config_data is not mandatory; module could just define
    // commands and have no config
169 170 171 172 173 174
    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
175 176 177
    if (spec->contains("statistics")) {
        check_statistics_item_list(spec->get("statistics"));
    }
178 179
}

180
// checks whether the given element is a valid module specification
181
// throws a ModuleSpecError if the specification is bad
182 183
void
check_module_specification(ConstElementPtr def) {
184 185
    try {
        check_data_specification(def);
186
    } catch (const TypeError& te) {
187
        isc_throw(ModuleSpecError, te.what());
188
    }
189
}
190
}
191

192 193
namespace isc {
namespace config {
194 195 196 197
//
// Public functions
//

198
ModuleSpec::ModuleSpec(ConstElementPtr module_spec_element,
199 200 201 202 203
                       const bool check)
                       throw(ModuleSpecError)
                       
{
    module_specification = module_spec_element;
204
    if (check) {
205 206 207 208
        check_module_specification(module_specification);
    }
}

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

218
ConstElementPtr
JINMEI Tatuya's avatar
JINMEI Tatuya committed
219
ModuleSpec::getConfigSpec() const {
220
    if (module_specification->contains("config_data")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
221
        return (module_specification->get("config_data"));
222
    } else {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
223
        return (ElementPtr());
224 225 226
    }
}

Naoki Kambe's avatar
Naoki Kambe committed
227 228 229 230 231 232 233 234 235
ConstElementPtr
ModuleSpec::getStatisticsSpec() const {
    if (module_specification->contains("statistics")) {
        return (module_specification->get("statistics"));
    } else {
        return (ElementPtr());
    }
}

236
const std::string
JINMEI Tatuya's avatar
JINMEI Tatuya committed
237 238
ModuleSpec::getModuleName() const {
    return (module_specification->get("module_name")->stringValue());
239 240
}

Jelte Jansen's avatar
Jelte Jansen committed
241
const std::string
JINMEI Tatuya's avatar
JINMEI Tatuya committed
242
ModuleSpec::getModuleDescription() const {
Jelte Jansen's avatar
Jelte Jansen committed
243
    if (module_specification->contains("module_description")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
244
        return (module_specification->get("module_description")->stringValue());
Jelte Jansen's avatar
Jelte Jansen committed
245
    } else {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
246
        return (std::string(""));
Jelte Jansen's avatar
Jelte Jansen committed
247 248 249
    }
}

250
bool
251
ModuleSpec::validateConfig(ConstElementPtr data, const bool full) const {
252
    ConstElementPtr spec = module_specification->find("config_data");
253
    return (validateSpecList(spec, data, full, ElementPtr()));
254 255
}

Naoki Kambe's avatar
Naoki Kambe committed
256 257 258 259 260 261
bool
ModuleSpec::validateStatistics(ConstElementPtr data, const bool full) const {
    ConstElementPtr spec = module_specification->find("statistics");
    return (validateSpecList(spec, data, full, ElementPtr()));
}

262
bool
263
ModuleSpec::validateCommand(const std::string& command,
264
                             ConstElementPtr args,
265
                             ElementPtr errors) const
266
{
267
    if (args->getType() != Element::map) {
Jelte Jansen's avatar
Jelte Jansen committed
268 269
        errors->add(Element::create("args for command " +
                                    command + " is not a map"));
270 271 272
        return (false);
    }

273
    ConstElementPtr commands_spec = module_specification->find("commands");
274 275 276 277 278 279 280 281
    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) {
282
            return (validateSpecList(cur_command->get("command_args"),
Jelte Jansen's avatar
Jelte Jansen committed
283
                                       args, true, errors));
284 285 286 287 288 289 290 291
        }
    }

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

292
bool
293
ModuleSpec::validateConfig(ConstElementPtr data, const bool full,
294
                            ElementPtr errors) const
295
{
296
    ConstElementPtr spec = module_specification->find("config_data");
297
    return (validateSpecList(spec, data, full, errors));
298 299
}

Naoki Kambe's avatar
Naoki Kambe committed
300 301 302 303 304 305 306 307
bool
ModuleSpec::validateStatistics(ConstElementPtr data, const bool full,
                               ElementPtr errors) const
{
    ConstElementPtr spec = module_specification->find("statistics");
    return (validateSpecList(spec, data, full, errors));
}

308 309
ModuleSpec
moduleSpecFromFile(const std::string& file_name, const bool check)
Jelte Jansen's avatar
Jelte Jansen committed
310
                   throw(JSONError, ModuleSpecError)
311 312 313 314 315 316 317
{
    std::ifstream file;

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

321
    ConstElementPtr module_spec_element = Element::fromJSON(file, file_name);
322
    if (module_spec_element->contains("module_spec")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
323
        return (ModuleSpec(module_spec_element->get("module_spec"), check));
324
    } else {
325
        isc_throw(ModuleSpecError, "No module_spec in specification");
326
    }
327 328 329 330
}

ModuleSpec
moduleSpecFromFile(std::ifstream& in, const bool check)
JINMEI Tatuya's avatar
JINMEI Tatuya committed
331 332
                   throw(JSONError, ModuleSpecError)
{
333
    ConstElementPtr module_spec_element = Element::fromJSON(in);
334
    if (module_spec_element->contains("module_spec")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
335
        return (ModuleSpec(module_spec_element->get("module_spec"), check));
336
    } else {
337
        isc_throw(ModuleSpecError, "No module_spec in specification");
338
    }
339 340
}

341

342
namespace {
343 344 345 346
//
// private functions
//

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

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

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

// spec is a list of maps, data is a map
bool
466
ModuleSpec::validateSpecList(ConstElementPtr spec, ConstElementPtr data,
467 468
                               const bool full, ElementPtr errors) const
{
469
    bool validated = true;
470
    BOOST_FOREACH(ConstElementPtr cur_spec_el, spec->listValue()) {
471
        if (!validateSpec(cur_spec_el, data, full, errors)) {
472
            validated = false;
473 474
        }
    }
475 476 477 478 479

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

    return (validated);
497 498
}

499 500
}
}