dhcp_parsers_unittest.cc 19.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC 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.

#include <config.h>
#include <config/ccsession.h>
#include <dhcp/option.h>
#include <dhcp/option_custom.h>
#include <dhcp/option_int.h>
20
#include <dhcpsrv/cfgmgr.h>
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/dhcp_parsers.h>
#include <exceptions/exceptions.h>

#include <gtest/gtest.h>
#include <boost/foreach.hpp>

#include <map>
#include <string>

using namespace std;
using namespace isc;
using namespace isc::dhcp;
using namespace isc::data;
using namespace isc::config;

namespace {

/// @brief DHCP Parser test fixture class
class DhcpParserTest : public ::testing::Test {
public:
    /// @brief Constructor
    ///
    DhcpParserTest() {
45
        CfgMgr::instance().deleteActiveIfaces();
46 47 48 49 50
    }
};


/// @brief Check BooleanParser basic functionality.
51
///
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
/// Verifies that the parser:
/// 1. Does not allow empty for storage.
/// 2. Rejects a non-boolean element.
/// 3. Builds with a valid true value.
/// 4. Bbuils with a valid false value.
/// 5. Updates storage upon commit.
TEST_F(DhcpParserTest, booleanParserTest) {

    const std::string name = "boolParm";

    // Verify that parser does not allow empty for storage.
    BooleanStoragePtr bs;
    EXPECT_THROW(BooleanParser(name, bs), isc::dhcp::DhcpConfigError);

    // Construct parser for testing.
    BooleanStoragePtr storage(new BooleanStorage());
    BooleanParser parser(name, storage);

    // Verify that parser with rejects a non-boolean element.
    ElementPtr wrong_element = Element::create("I am a string");
    EXPECT_THROW(parser.build(wrong_element), isc::BadValue);

    // Verify that parser will build with a valid true value.
    bool test_value = true;
    ElementPtr element = Element::create(test_value);
    ASSERT_NO_THROW(parser.build(element));

    // Verify that commit updates storage.
80
    bool actual_value = !test_value;
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
    parser.commit();
    EXPECT_NO_THROW((actual_value = storage->getParam(name)));
    EXPECT_EQ(test_value, actual_value);

    // Verify that parser will build with a valid false value.
    test_value = false;
    element->setValue(test_value);
    EXPECT_NO_THROW(parser.build(element));

    // Verify that commit updates storage.
    actual_value = ~test_value;
    parser.commit();
    EXPECT_NO_THROW((actual_value = storage->getParam(name)));
    EXPECT_EQ(test_value, actual_value);
}

/// @brief Check StringParser basic functionality
98
///
99 100
/// Verifies that the parser:
/// 1. Does not allow empty for storage.
101
/// 2. Builds with a nont string value.
102 103 104 105 106 107 108 109 110 111 112 113 114 115
/// 3. Builds with a string value.
/// 4. Updates storage upon commit.
TEST_F(DhcpParserTest, stringParserTest) {

    const std::string name = "strParm";

    // Verify that parser does not allow empty for storage.
    StringStoragePtr bs;
    EXPECT_THROW(StringParser(name, bs), isc::dhcp::DhcpConfigError);

    // Construct parser for testing.
    StringStoragePtr storage(new StringStorage());
    StringParser parser(name, storage);

116 117 118
    // Verify that parser with accepts a non-string element.
    ElementPtr element = Element::create(9999);
    EXPECT_NO_THROW(parser.build(element));
119

120 121 122 123 124 125
    // Verify that commit updates storage.
    parser.commit();
    std::string actual_value;
    EXPECT_NO_THROW((actual_value = storage->getParam(name)));
    EXPECT_EQ("9999", actual_value);

126 127
    // Verify that parser will build with a string value.
    const std::string test_value = "test value";
128
    element = Element::create(test_value);
129 130 131 132 133 134 135 136 137
    ASSERT_NO_THROW(parser.build(element));

    // Verify that commit updates storage.
    parser.commit();
    EXPECT_NO_THROW((actual_value = storage->getParam(name)));
    EXPECT_EQ(test_value, actual_value);
}

/// @brief Check Uint32Parser basic functionality
138
///
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
/// Verifies that the parser:
/// 1. Does not allow empty for storage.
/// 2. Rejects a non-integer element.
/// 3. Rejects a negative value.
/// 4. Rejects too large a value.
/// 5. Builds with value of zero.
/// 6. Builds with a value greater than zero.
/// 7. Updates storage upon commit.
TEST_F(DhcpParserTest, uint32ParserTest) {

    const std::string name = "intParm";

    // Verify that parser does not allow empty for storage.
    Uint32StoragePtr bs;
    EXPECT_THROW(Uint32Parser(name, bs), isc::dhcp::DhcpConfigError);

    // Construct parser for testing.
    Uint32StoragePtr storage(new Uint32Storage());
    Uint32Parser parser(name, storage);

    // Verify that parser with rejects a non-interger element.
    ElementPtr wrong_element = Element::create("I am a string");
    EXPECT_THROW(parser.build(wrong_element), isc::BadValue);

    // Verify that parser with rejects a negative value.
    ElementPtr int_element = Element::create(-1);
    EXPECT_THROW(parser.build(int_element), isc::BadValue);

167 168
    // Verify that parser with rejects too large a value provided we are on
    // 64-bit platform.
169 170 171 172 173
    if (sizeof(long) > sizeof(uint32_t)) {
        long max = (long)(std::numeric_limits<uint32_t>::max()) + 1;
        int_element->setValue(max);
        EXPECT_THROW(parser.build(int_element), isc::BadValue);
    }
174 175 176 177 178 179

    // Verify that parser will build with value of zero.
    int test_value = 0;
    int_element->setValue((long)test_value);
    ASSERT_NO_THROW(parser.build(int_element));

180 181 182 183 184 185
    // Verify that commit updates storage.
    parser.commit();
    uint32_t actual_value = 0;
    EXPECT_NO_THROW((actual_value = storage->getParam(name)));
    EXPECT_EQ(test_value, actual_value);

186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
    // Verify that parser will build with a valid positive value.
    test_value = 77;
    int_element->setValue((long)test_value);
    ASSERT_NO_THROW(parser.build(int_element));

    // Verify that commit updates storage.
    parser.commit();
    EXPECT_NO_THROW((actual_value = storage->getParam(name)));
    EXPECT_EQ(test_value, actual_value);
}

/// @brief Check InterfaceListParser  basic functionality
///
/// Verifies that the parser:
/// 1. Does not allow empty for storage.
201 202 203 204
/// 2. Does not allow name other than "interfaces"
/// 3. Parses list of interfaces and adds them to CfgMgr
/// 4. Parses 'all' keyword and sets a CfgMgr flag which indicates that
///    server will listen on all interfaces.
205 206
TEST_F(DhcpParserTest, interfaceListParserTest) {

207
    const std::string name = "interfaces";
208 209 210 211

    // Verify that parser constructor fails if parameter name isn't "interface"
    EXPECT_THROW(InterfaceListConfigParser("bogus_name"), isc::BadValue);

212 213
    boost::scoped_ptr<InterfaceListConfigParser>
        parser(new InterfaceListConfigParser(name));
214 215 216
    ElementPtr list_element = Element::createList();
    list_element->add(Element::create("eth0"));
    list_element->add(Element::create("eth1"));
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246

    // Make sure there are no interfaces added yet.
    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));

    // This should parse the configuration and add eth0 and eth1 to the list
    // of interfaces that server should listen on.
    parser->build(list_element);
    parser->commit();

    // Use CfgMgr instance to check if eth0 and eth1 was added, and that
    // eth2 was not added.
    CfgMgr& cfg_mgr = CfgMgr::instance();
    EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
    EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
    EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));

    // Add keyword all to the configuration. This should activate all
    // interfaces, including eth2, even though it has not been explicitly
    // added.
    list_element->add(Element::create("all"));

    // Reset parser's state.
    parser.reset(new InterfaceListConfigParser(name));
    parser->build(list_element);
    parser->commit();

    EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
    EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
    EXPECT_TRUE(cfg_mgr.isActiveIface("eth2"));
247 248
}

249
/// @brief Test Implementation of abstract OptionDataParser class. Allows
250
/// testing basic option parsing.
251 252 253
class UtestOptionDataParser : public OptionDataParser {
public:

254 255
    UtestOptionDataParser(const std::string&,
        OptionStoragePtr options, ParserContextPtr global_context)
256 257 258 259 260 261 262 263 264 265 266 267 268
        :OptionDataParser("", options, global_context) {
    }

    static OptionDataParser* factory(const std::string& param_name,
        OptionStoragePtr options, ParserContextPtr global_context) {
        return (new UtestOptionDataParser(param_name, options, global_context));
    }

protected:
    // Dummy out last two params since test derivation doesn't use them.
    virtual OptionDefinitionPtr findServerSpaceOptionDefinition (
                std::string&, uint32_t) {
        OptionDefinitionPtr def;
269
        // always return empty
270 271 272 273
        return (def);
    }
};

274
/// @brief Test Fixture class which provides basic structure for testing
275 276 277 278 279 280 281 282 283
/// configuration parsing.  This is essentially the same structure provided
/// by dhcp servers.
class ParseConfigTest : public ::testing::Test {
public:
    /// @brief Constructor
    ParseConfigTest() {
        reset_context();
    }

284 285 286 287
    ~ParseConfigTest() {
        reset_context();
    }

288
    /// @brief Parses a configuration.
289 290
    ///
    /// Parse the given configuration, populating the context storage with
291 292
    /// the parsed elements.
    ///
293 294 295
    /// @param config_set is the set of elements to parse.
    /// @return returns an ConstElementPtr containing the numeric result
    /// code and outcome comment.
296
    isc::data::ConstElementPtr parseElementSet(isc::data::ConstElementPtr
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
                                           config_set) {
        // Answer will hold the result.
        ConstElementPtr answer;
        if (!config_set) {
            answer = isc::config::createAnswer(1,
                                 string("Can't parse NULL config"));
            return (answer);
        }

        // option parsing must be done last, so save it if we hit if first
        ParserPtr option_parser;

        ConfigPair config_pair;
        try {
            // Iteraate over the config elements.
            const std::map<std::string, ConstElementPtr>& values_map =
                                                      config_set->mapValue();
            BOOST_FOREACH(config_pair, values_map) {
                // Create the parser based on element name.
                ParserPtr parser(createConfigParser(config_pair.first));
                // Options must be parsed last
                if (config_pair.first == "option-data") {
                    option_parser = parser;
                } else {
                    // Anything else  we can call build straight away.
                    parser->build(config_pair.second);
                    parser->commit();
                }
            }

            // The option values parser is the next one to be run.
328
            std::map<std::string, ConstElementPtr>::const_iterator
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
                                option_config = values_map.find("option-data");
            if (option_config != values_map.end()) {
                option_parser->build(option_config->second);
                option_parser->commit();
            }

            // Everything was fine. Configuration is successful.
            answer = isc::config::createAnswer(0, "Configuration committed.");
        } catch (const isc::Exception& ex) {
            answer = isc::config::createAnswer(1,
                        string("Configuration parsing failed: ") + ex.what());

        } catch (...) {
            answer = isc::config::createAnswer(1,
                                        string("Configuration parsing failed"));
        }

        return (answer);
    }

    /// @brief Create an element parser based on the element name.
    ///
351 352 353
    /// Note that currently it only supports option-defs and option-data,
    ///
    /// @param config_id is the name of the configuration element.
354 355 356 357 358 359
    /// @return returns a raw pointer to DhcpConfigParser. Note caller is
    /// responsible for deleting it once no longer needed.
    /// @throw throws NotImplemented if element name isn't supported.
    DhcpConfigParser* createConfigParser(const std::string& config_id) {
        DhcpConfigParser* parser = NULL;
        if (config_id.compare("option-data") == 0) {
360 361
            parser = new OptionDataListParser(config_id,
                                          parser_context_->options_,
362 363 364
                                          parser_context_,
                                          UtestOptionDataParser::factory);
        } else if (config_id.compare("option-def") == 0) {
365
            parser  = new OptionDefListParser(config_id,
366 367 368 369 370 371 372 373 374 375
                                          parser_context_->option_defs_);
        } else {
            isc_throw(NotImplemented,
                "Parser error: configuration parameter not supported: "
                << config_id);
        }

        return (parser);
    }

376 377
    /// @brief Convenicee method for parsing a configuration
    ///
378
    /// Given a configuration string, convert it into Elements
379
    /// and parse them.
380 381
    /// @param config is the configuration string to parse
    ///
382
    /// @return retuns 0 if the configuration parsed successfully,
383
    /// non-zero otherwise failure.
384
    int parseConfiguration (std::string &config) {
385 386 387 388 389 390 391 392 393 394 395 396 397
        int rcode_ = 1;
        // Turn config into elements.
        // Test json just to make sure its valid.
        ElementPtr json = Element::fromJSON(config);
        EXPECT_TRUE(json);
        if (json) {
            ConstElementPtr status = parseElementSet(json);
            ConstElementPtr comment_ = parseAnswer(rcode_, status);
        }

        return (rcode_);
    }

398
    /// @brief Find an option definition for a given space and code within
399 400 401 402 403
    /// the parser context.
    /// @param space is the space name of the desired option.
    /// @param code is the numeric "type" of the desired option.
    /// @return returns an OptionDefinitionPtr which points to the found
    /// definition or is empty.
404
    /// ASSERT_ tests don't work inside functions that return values
405 406 407
    OptionDefinitionPtr getOptionDef(std::string space, uint32_t code)
    {
        OptionDefinitionPtr def;
408
        OptionDefContainerPtr defs =
409 410 411 412 413 414 415 416 417 418 419 420 421
                            parser_context_->option_defs_->getItems(space);
        // Should always be able to get definitions list even if it is empty.
        EXPECT_TRUE(defs);
        if (defs) {
            // Attempt to find desired definiton.
            const OptionDefContainerTypeIndex& idx = defs->get<1>();
            const OptionDefContainerTypeRange& range = idx.equal_range(code);
            int cnt = std::distance(range.first, range.second);
            EXPECT_EQ(1, cnt);
            if (cnt == 1) {
                def = *(idx.begin());
            }
        }
422
        return (def);
423 424
    }

425
    /// @brief Find an option for a given space and code within the parser
426 427 428 429 430
    /// context.
    /// @param space is the space name of the desired option.
    /// @param code is the numeric "type" of the desired option.
    /// @return returns an OptionPtr which points to the found
    /// option or is empty.
431
    /// ASSERT_ tests don't work inside functions that return values
432 433 434
    OptionPtr getOptionPtr(std::string space, uint32_t code)
    {
        OptionPtr option_ptr;
435
        Subnet::OptionContainerPtr options =
436 437 438 439 440 441
                            parser_context_->options_->getItems(space);
        // Should always be able to get options list even if it is empty.
        EXPECT_TRUE(options);
        if (options) {
            // Attempt to find desired option.
            const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
442
            const Subnet::OptionContainerTypeRange& range =
443 444 445 446
                                                        idx.equal_range(code);
            int cnt = std::distance(range.first, range.second);
            EXPECT_EQ(1, cnt);
            if (cnt == 1) {
447 448
                Subnet::OptionDescriptor desc = *(idx.begin());
                option_ptr = desc.option;
449 450 451 452
                EXPECT_TRUE(option_ptr);
            }
        }

453
        return (option_ptr);
454 455
    }

456
    /// @brief Wipes the contents of the context to allowing another parsing
457 458 459
    /// during a given test if needed.
    void reset_context(){
        // Note set context universe to V6 as it has to be something.
460 461 462
        CfgMgr::instance().deleteSubnets4();
        CfgMgr::instance().deleteSubnets6();
        CfgMgr::instance().deleteOptionDefs();
463 464 465 466 467 468 469 470
        parser_context_.reset(new ParserContext(Option::V6));
    }

    /// @brief Parser context - provides storage for options and definitions
    ParserContextPtr parser_context_;
};

/// @brief Check Basic parsing of option definitions.
471
///
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
/// Note that this tests basic operation of the OptionDefinitionListParser and
/// OptionDefinitionParser.  It uses a simple configuration consisting of one
/// one definition and verifies that it is parsed and committed to storage
/// correctly.
TEST_F(ParseConfigTest, basicOptionDefTest) {

    // Configuration string.
    std::string config =
        "{ \"option-def\": [ {"
        "      \"name\": \"foo\","
        "      \"code\": 100,"
        "      \"type\": \"ipv4-address\","
        "      \"array\": False,"
        "      \"record-types\": \"\","
        "      \"space\": \"isc\","
        "      \"encapsulate\": \"\""
        "  } ]"
        "}";

    // Verify that the configuration string parses.
    int rcode = parseConfiguration(config);
    ASSERT_TRUE(rcode == 0);

    // Verify that the option definition can be retrieved.
496
    OptionDefinitionPtr def = getOptionDef("isc", 100);
497 498 499 500 501 502 503 504 505 506 507
    ASSERT_TRUE(def);

    // Verify that the option definition is correct.
    EXPECT_EQ("foo", def->getName());
    EXPECT_EQ(100, def->getCode());
    EXPECT_FALSE(def->getArrayType());
    EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType());
    EXPECT_TRUE(def->getEncapsulatedSpace().empty());
}

/// @brief Check Basic parsing of options.
508
///
509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551
/// Note that this tests basic operation of the OptionDataListParser and
/// OptionDataParser.  It uses a simple configuration consisting of one
/// one definition and matching option data.  It verifies that the option
/// is parsed and committed to storage correctly.
TEST_F(ParseConfigTest, basicOptionDataTest) {

    // Configuration string.
    std::string config =
        "{ \"option-def\": [ {"
        "      \"name\": \"foo\","
        "      \"code\": 100,"
        "      \"type\": \"ipv4-address\","
        "      \"array\": False,"
        "      \"record-types\": \"\","
        "      \"space\": \"isc\","
        "      \"encapsulate\": \"\""
        " } ], "
        " \"option-data\": [ {"
        "    \"name\": \"foo\","
        "    \"space\": \"isc\","
        "    \"code\": 100,"
        "    \"data\": \"192.168.2.1\","
        "    \"csv-format\": True"
        " } ]"
        "}";

    // Verify that the configuration string parses.
    int rcode = parseConfiguration(config);
    ASSERT_TRUE(rcode == 0);

    // Verify that the option can be retrieved.
    OptionPtr opt_ptr = getOptionPtr("isc", 100);
    ASSERT_TRUE(opt_ptr);

    // Verify that the option definition is correct.
    std::string val = "type=100, len=4, data fields:\n "
                      " #0 192.168.2.1 ( ipv4-address ) \n";

    EXPECT_EQ(val, opt_ptr->toText());
}

};  // Anonymous namespace