module_spec.cc 15.8 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
// 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
106
            std::vector<char> buf(32);
107 108
            memset(&tm, 0, sizeof(tm));
            // reverse check
Naoki Kambe's avatar
Naoki Kambe committed
109
            return (strptime(value->stringValue().c_str(),
110
                             f.second.c_str(), &tm) != NULL
Naoki Kambe's avatar
Naoki Kambe committed
111
                    && strftime(&buf[0], buf.size(),
112
                                f.second.c_str(), &tm) != 0
Naoki Kambe's avatar
Naoki Kambe committed
113 114
                    && strncmp(value->stringValue().c_str(),
                               &buf[0], buf.size()) == 0);
Naoki Kambe's avatar
Naoki Kambe committed
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
        }
    }
    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");
            }
        }
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

307 308
ModuleSpec
moduleSpecFromFile(const std::string& file_name, const bool check)
Jelte Jansen's avatar
Jelte Jansen committed
309
                   throw(JSONError, ModuleSpecError)
310 311 312 313 314 315 316 317 318 319
{
    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());
    }

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

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

340

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

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

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

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

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

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

    return (validated);
496 497
}

498 499
}
}