dhcp_parsers_unittest.cc 89.1 KB
Newer Older
1
// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
2
//
3
4
5
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7

#include <config.h>
8
#include <cc/command_interpreter.h>
9
#include <cc/data.h>
10
#include <cc/simple_parser.h>
11
12
13
#include <dhcp/option.h>
#include <dhcp/option_custom.h>
#include <dhcp/option_int.h>
14
#include <dhcp/option_string.h>
15
#include <dhcp/option6_addrlst.h>
16
#include <dhcp/tests/iface_mgr_test_config.h>
17
#include <dhcpsrv/cfgmgr.h>
18
#include <dhcpsrv/subnet.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
19
#include <dhcpsrv/cfg_mac_source.h>
20
#include <dhcpsrv/parsers/dhcp_parsers.h>
Stephen Morris's avatar
Stephen Morris committed
21
#include <dhcpsrv/tests/test_libraries.h>
22
#include <dhcpsrv/testutils/config_result_check.h>
23
#include <exceptions/exceptions.h>
24
#include <hooks/hooks_parser.h>
25
#include <hooks/hooks_manager.h>
26
#include <testutils/test_to_element.h>
27
28
29

#include <gtest/gtest.h>
#include <boost/foreach.hpp>
30
#include <boost/pointer_cast.hpp>
31
#include <boost/scoped_ptr.hpp>
32
33
34
35
36
37

#include <map>
#include <string>

using namespace std;
using namespace isc;
38
using namespace isc::asiolink;
39
using namespace isc::config;
40
41
using namespace isc::data;
using namespace isc::dhcp;
42
using namespace isc::dhcp::test;
43
using namespace isc::hooks;
44
using namespace isc::test;
45
46
47
48
49
50
51
52

namespace {

/// @brief DHCP Parser test fixture class
class DhcpParserTest : public ::testing::Test {
public:
    /// @brief Constructor
    DhcpParserTest() {
53
54
55
56
57
58
59
60
61
62
        resetIfaceCfg();
    }

    /// @brief Destructor.
    virtual ~DhcpParserTest() {
        resetIfaceCfg();
    }

    /// @brief Resets selection of the interfaces from previous tests.
    void resetIfaceCfg() {
63
        CfgMgr::instance().clear();
64
65
66
67
68
    }
};


/// @brief Check BooleanParser basic functionality.
69
///
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
/// 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.
98
    bool actual_value = !test_value;
99
100
101
102
103
104
105
106
107
108
    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.
109
    actual_value = !test_value;
110
111
112
113
114
115
    parser.commit();
    EXPECT_NO_THROW((actual_value = storage->getParam(name)));
    EXPECT_EQ(test_value, actual_value);
}

/// @brief Check StringParser basic functionality
116
///
117
118
/// Verifies that the parser:
/// 1. Does not allow empty for storage.
119
/// 2. Builds with a nont string value.
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/// 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);

134
135
136
    // Verify that parser with accepts a non-string element.
    ElementPtr element = Element::create(9999);
    EXPECT_NO_THROW(parser.build(element));
137

138
139
140
141
142
143
    // Verify that commit updates storage.
    parser.commit();
    std::string actual_value;
    EXPECT_NO_THROW((actual_value = storage->getParam(name)));
    EXPECT_EQ("9999", actual_value);

144
145
    // Verify that parser will build with a string value.
    const std::string test_value = "test value";
146
    element = Element::create(test_value);
147
148
149
150
151
152
    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);
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170

    // Verify that parser with accepts a boolean true element.
    element = Element::create(true);
    EXPECT_NO_THROW(parser.build(element));

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

    // Verify that parser with accepts a boolean true element.
    element = Element::create(false);
    EXPECT_NO_THROW(parser.build(element));

    // Verify that commit updates storage.
    parser.commit();
    EXPECT_NO_THROW((actual_value = storage->getParam(name)));
    EXPECT_EQ("false", actual_value);
171
172
173
}

/// @brief Check Uint32Parser basic functionality
174
///
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
/// 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);

Josh Soref's avatar
Josh Soref committed
195
    // Verify that parser with rejects a non-integer element.
196
197
198
199
200
201
202
    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);

203
204
    // Verify that parser with rejects too large a value provided we are on
    // 64-bit platform.
205
206
207
208
209
    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);
    }
210
211
212
213
214
215

    // 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));

216
217
218
219
220
221
    // 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);

222
223
224
225
226
227
228
229
230
231
232
    // 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);
}

233
234
235
236
237
238
239
240
/// Verifies the code that parses mac sources and adds them to CfgMgr
TEST_F(DhcpParserTest, MacSources) {

    // That's an equivalent of the following snippet:
    // "mac-sources: [ \"duid\", \"ipv6\" ]";
    ElementPtr values = Element::createList();
    values->add(Element::create("duid"));
    values->add(Element::create("ipv6-link-local"));
241

242
243
244
245
    // Let's grab server configuration from CfgMgr
    SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
    ASSERT_TRUE(cfg);
    CfgMACSource& sources = cfg->getMACSources();
246

247
248
249
    // This should parse the configuration and check that it doesn't throw.
    MACSourcesListConfigParser parser;
    EXPECT_NO_THROW(parser.parse(sources, values));
250

251
252
253
254
255
256
257
258
259
260
261
    // Finally, check the sources that were configured
    CfgMACSources configured_sources =  cfg->getMACSources().get();
    ASSERT_EQ(2, configured_sources.size());
    EXPECT_EQ(HWAddr::HWADDR_SOURCE_DUID, configured_sources[0]);
    EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, configured_sources[1]);
}

/// @brief Check MACSourcesListConfigParser rejecting empty list
///
/// Verifies that the code rejects an empty mac-sources list.
TEST_F(DhcpParserTest, MacSourcesEmpty) {
262
263
264

    // That's an equivalent of the following snippet:
    // "mac-sources: [ \"duid\", \"ipv6\" ]";
265
    ElementPtr values = Element::createList();
266

267
268
269
270
271
272
273
274
275
276
    // Let's grab server configuration from CfgMgr
    SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
    ASSERT_TRUE(cfg);
    CfgMACSource& sources = cfg->getMACSources();

    // This should throw, because if specified, at least one MAC source
    // has to be specified.
    MACSourcesListConfigParser parser;
    EXPECT_THROW(parser.parse(sources, values), DhcpConfigError);
}
277

278
279
280
281
282
283
284
285
286
287
/// @brief Check MACSourcesListConfigParser rejecting empty list
///
/// Verifies that the code rejects fake mac source.
TEST_F(DhcpParserTest, MacSourcesBogus) {

    // That's an equivalent of the following snippet:
    // "mac-sources: [ \"duid\", \"ipv6\" ]";
    ElementPtr values = Element::createList();
    values->add(Element::create("from-ebay"));
    values->add(Element::create("just-guess-it"));
288

289
    // Let's grab server configuration from CfgMgr
290
291
    SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
    ASSERT_TRUE(cfg);
292
    CfgMACSource& sources = cfg->getMACSources();
293

294
295
296
    // This should throw, because these are not valid sources.
    MACSourcesListConfigParser parser;
    EXPECT_THROW(parser.parse(sources, values), DhcpConfigError);
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
/// Verifies the code that properly catches duplicate entries
/// in mac-sources definition.
TEST_F(DhcpParserTest, MacSourcesDuplicate) {

    // That's an equivalent of the following snippet:
    // "mac-sources: [ \"duid\", \"ipv6\" ]";
    ElementPtr values = Element::createList();
    values->add(Element::create("ipv6-link-local"));
    values->add(Element::create("duid"));
    values->add(Element::create("duid"));
    values->add(Element::create("duid"));

    // Let's grab server configuration from CfgMgr
    SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
    ASSERT_TRUE(cfg);
    CfgMACSource& sources = cfg->getMACSources();

    // This should parse the configuration and check that it throws.
    MACSourcesListConfigParser parser;
    EXPECT_THROW(parser.parse(sources, values), DhcpConfigError);
}


322
/// @brief Test Fixture class which provides basic structure for testing
323
324
325
326
327
/// configuration parsing.  This is essentially the same structure provided
/// by dhcp servers.
class ParseConfigTest : public ::testing::Test {
public:
    /// @brief Constructor
328
329
    ParseConfigTest()
        :family_(AF_INET6) {
330
        reset_context();
331
        CfgMgr::instance().clear();
332
333
    }

334
335
    ~ParseConfigTest() {
        reset_context();
336
        CfgMgr::instance().clear();
337
338
    }

339
    /// @brief Parses a configuration.
340
341
    ///
    /// Parse the given configuration, populating the context storage with
342
343
    /// the parsed elements.
    ///
344
345
346
    /// @param config_set is the set of elements to parse.
    /// @return returns an ConstElementPtr containing the numeric result
    /// code and outcome comment.
347
    isc::data::ConstElementPtr parseElementSet(isc::data::ConstElementPtr
348
                                               config_set) {
349
350
351
352
353
354
355
356
357
358
        // Answer will hold the result.
        ConstElementPtr answer;
        if (!config_set) {
            answer = isc::config::createAnswer(1,
                                 string("Can't parse NULL config"));
            return (answer);
        }

        ConfigPair config_pair;
        try {
359
            // Iterate over the config elements.
360
361
362
            const std::map<std::string, ConstElementPtr>& values_map =
                                                      config_set->mapValue();
            BOOST_FOREACH(config_pair, values_map) {
363
364
365

                // These are the simple parsers. No need to go through
                // the ParserPtr hooplas with them.
366
367
368
                if ((config_pair.first == "option-data") ||
                    (config_pair.first == "option-def") ||
                    (config_pair.first == "dhcp-ddns")) {
369
370
                    continue;
                }
371
372
373

                // We also don't care about the default values that may be been
                // inserted
374
375
376
377
                if ((config_pair.first == "preferred-lifetime") ||
                    (config_pair.first == "valid-lifetime") ||
                    (config_pair.first == "renew-timer") ||
                    (config_pair.first == "rebind-timer")) {
378
379
380
                    continue;
                }

381
                if (config_pair.first == "hooks-libraries") {
382
383
384
385
386
387
                    HooksLibrariesParser hook_parser;
                    HooksConfig&  libraries =
                        CfgMgr::instance().getStagingCfg()->getHooksConfig();
                    hook_parser.parse(libraries, config_pair.second);
                    libraries.verifyLibraries(config_pair.second->getPosition());
                    libraries.loadLibraries();
388
389
                    continue;
                }
390
391
            }

392
393
394
395
396
397
            // The option definition parser is the next one to be run.
            std::map<std::string, ConstElementPtr>::const_iterator
                                def_config = values_map.find("option-def");
            if (def_config != values_map.end()) {

                CfgOptionDefPtr cfg_def = CfgMgr::instance().getStagingCfg()->getCfgOptionDef();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
398
                OptionDefListParser def_list_parser;
399
400
401
                def_list_parser.parse(cfg_def, def_config->second);
            }

402
            // The option values parser is the next one to be run.
403
            std::map<std::string, ConstElementPtr>::const_iterator
404
405
                                option_config = values_map.find("option-data");
            if (option_config != values_map.end()) {
406
407
                CfgOptionPtr cfg_option = CfgMgr::instance().getStagingCfg()->getCfgOption();

408
                OptionDataListParser option_list_parser(family_);
409
                option_list_parser.parse(cfg_option, option_config->second);
410
411
            }

412
413
414
415
416
417
418
419
420
421
            // The dhcp-ddns parser is the next one to be run.
            std::map<std::string, ConstElementPtr>::const_iterator
                                d2_client_config = values_map.find("dhcp-ddns");
            if (d2_client_config != values_map.end()) {
                // Used to be done by parser commit
                D2ClientConfigParser parser;
                D2ClientConfigPtr cfg = parser.parse(d2_client_config->second);
                CfgMgr::instance().setD2ClientConfig(cfg);
            }

422
423
424
425
426
427
428
429
430
431
432
433
434
435
            // 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);
    }

436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
    /// @brief DHCP-specific method that sets global, and option specific defaults
    ///
    /// This method sets the defaults in the global scope, in option definitions,
    /// and in option data.
    ///
    /// @param global pointer to the Element tree that holds configuration
    /// @param global_defaults array with global default values
    /// @param option_defaults array with option-data default values
    /// @param option_def_defaults array with default values for option definitions
    /// @return number of default values inserted.
    size_t setAllDefaults(isc::data::ElementPtr global,
                          const SimpleDefaults& global_defaults,
                          const SimpleDefaults& option_defaults,
                          const SimpleDefaults& option_def_defaults) {
        size_t cnt = 0;
        // Set global defaults first.
        cnt = SimpleParser::setDefaults(global, global_defaults);

Josh Soref's avatar
Josh Soref committed
454
        // Now set option definition defaults for each specified option definition
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
        ConstElementPtr option_defs = global->get("option-def");
        if (option_defs) {
            BOOST_FOREACH(ElementPtr single_def, option_defs->listValue()) {
                cnt += SimpleParser::setDefaults(single_def, option_def_defaults);
            }
        }

        ConstElementPtr options = global->get("option-data");
        if (options) {
            BOOST_FOREACH(ElementPtr single_option, options->listValue()) {
                cnt += SimpleParser::setDefaults(single_option, option_defaults);
            }
        }

        return (cnt);
    }

472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
    /// This table defines default values for option definitions in DHCPv6
    static const SimpleDefaults OPTION6_DEF_DEFAULTS;

    /// This table defines default values for option definitions in DHCPv4
    static const SimpleDefaults OPTION4_DEF_DEFAULTS;

    /// This table defines default values for options in DHCPv6
    static const SimpleDefaults OPTION6_DEFAULTS;

    /// This table defines default values for options in DHCPv4
    static const SimpleDefaults OPTION4_DEFAULTS;

    /// This table defines default values for both DHCPv4 and DHCPv6
    static const SimpleDefaults GLOBAL6_DEFAULTS;

487
488
489
490
491
492
493
494
    /// @brief sets all default values for DHCPv4 and DHCPv6
    ///
    /// This function largely duplicates what SimpleParser4 and SimpleParser6 classes
    /// provide. However, since there are tons of unit-tests in dhcpsrv that need
    /// this functionality and there are good reasons to keep those classes in
    /// src/bin/dhcp{4,6}, the most straightforward way is to simply copy the
    /// minimum code here. Hence this method.
    ///
495
496
497
498
    /// @todo - TKM, I think this is fairly hideous and we should figure out a
    /// a way to not have to replicate in this fashion.  It may be minimum code
    /// now, but it won't be fairly soon.
    ///
499
500
501
502
503
504
505
506
507
508
    /// @param config configuration structure to be filled with default values
    /// @param v6 true = DHCPv6, false = DHCPv4
    void setAllDefaults(ElementPtr config, bool v6) {
        if (v6) {
            setAllDefaults(config, GLOBAL6_DEFAULTS, OPTION6_DEFAULTS,
                           OPTION6_DEF_DEFAULTS);
        } else {
            setAllDefaults(config, GLOBAL6_DEFAULTS, OPTION4_DEFAULTS,
                           OPTION4_DEF_DEFAULTS);
        }
509
510
511

        /// D2 client configuration code is in this library
        ConstElementPtr d2_client = config->get("dhcp-ddns");
512
        if (d2_client) {
513
            D2ClientConfigParser::setAllDefaults(d2_client);
514
515
516
        }
    }

517
518
    /// @brief Convenience method for parsing a configuration
    ///
519
    /// Given a configuration string, convert it into Elements
520
    /// and parse them.
521
522
    /// @param config is the configuration string to parse
    ///
Josh Soref's avatar
Josh Soref committed
523
    /// @return returns 0 if the configuration parsed successfully,
524
    /// non-zero otherwise failure.
525
    int parseConfiguration(const std::string& config, bool v6 = false) {
526
527
528
529
530
531
        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) {
532
            setAllDefaults(json, v6);
533

534
            ConstElementPtr status = parseElementSet(json);
535
536
            ConstElementPtr comment = parseAnswer(rcode_, status);
            error_text_ = comment->stringValue();
537
538
539
            // If error was reported, the error string should contain
            // position of the data element which caused failure.
            if (rcode_ != 0) {
540
                std::cout << "Error text:" << error_text_ << std::endl;
541
542
                EXPECT_TRUE(errorContainsPosition(status, "<string>"));
            }
543
544
545
546
547
        }

        return (rcode_);
    }

548
    /// @brief Find an option for a given space and code within the parser
549
550
551
552
553
    /// 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.
554
    /// ASSERT_ tests don't work inside functions that return values
555
556
557
    OptionPtr getOptionPtr(std::string space, uint32_t code)
    {
        OptionPtr option_ptr;
558
559
        OptionContainerPtr options = CfgMgr::instance().getStagingCfg()->
            getCfgOption()->getAll(space);
560
561
562
563
        // Should always be able to get options list even if it is empty.
        EXPECT_TRUE(options);
        if (options) {
            // Attempt to find desired option.
564
            const OptionContainerTypeIndex& idx = options->get<1>();
565
            const OptionContainerTypeRange& range = idx.equal_range(code);
566
567
568
            int cnt = std::distance(range.first, range.second);
            EXPECT_EQ(1, cnt);
            if (cnt == 1) {
569
                OptionDescriptor desc = *(idx.begin());
570
                option_ptr = desc.option_;
571
572
573
574
                EXPECT_TRUE(option_ptr);
            }
        }

575
        return (option_ptr);
576
577
    }

578
    /// @brief Wipes the contents of the context to allowing another parsing
579
580
581
    /// during a given test if needed.
    void reset_context(){
        // Note set context universe to V6 as it has to be something.
582
        CfgMgr::instance().clear();
583
        family_ = AF_INET6;
584
585
586

        // Ensure no hooks libraries are loaded.
        HooksManager::unloadLibraries();
587
588
589
590

        // Set it to minimal, disabled config
        D2ClientConfigPtr tmp(new D2ClientConfig());
        CfgMgr::instance().setD2ClientConfig(tmp);
591
592
    }

593
594
595
596
    /// Allows the tests to interrogate the state of the libraries (if required).
    const isc::hooks::HookLibsCollection& getLibraries() {
        return (CfgMgr::instance().getStagingCfg()->getHooksConfig().get());
    }
597

598
599
    /// @brief specifies IP protocol family (AF_INET or AF_INET6)
    uint16_t family_;
600
601
602

    /// @brief Error string if the parsing failed
    std::string error_text_;
603
604
};

605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
/// This table defines default values for option definitions in DHCPv6
const SimpleDefaults ParseConfigTest::OPTION6_DEF_DEFAULTS = {
    { "record-types", Element::string,  ""},
    { "space",        Element::string,  "dhcp6"},
    { "array",        Element::boolean, "false"},
    { "encapsulate",  Element::string,  "" }
};

/// This table defines default values for option definitions in DHCPv4
const SimpleDefaults ParseConfigTest::OPTION4_DEF_DEFAULTS = {
    { "record-types", Element::string,  ""},
    { "space",        Element::string,  "dhcp4"},
    { "array",        Element::boolean, "false"},
    { "encapsulate",  Element::string,  "" }
};

/// This table defines default values for options in DHCPv6
const SimpleDefaults ParseConfigTest::OPTION6_DEFAULTS = {
    { "space",        Element::string,  "dhcp6"},
    { "csv-format",   Element::boolean, "true"}
};

/// This table defines default values for options in DHCPv4
const SimpleDefaults ParseConfigTest::OPTION4_DEFAULTS = {
    { "space",        Element::string,  "dhcp4"},
    { "csv-format",   Element::boolean, "true"}
};

/// This table defines default values for both DHCPv4 and DHCPv6
const SimpleDefaults ParseConfigTest::GLOBAL6_DEFAULTS = {
    { "renew-timer",        Element::integer, "900" },
    { "rebind-timer",       Element::integer, "1800" },
    { "preferred-lifetime", Element::integer, "3600" },
    { "valid-lifetime",     Element::integer, "7200" }
};

/// @brief Option configuration class
///
/// This class handles option-def and option-data which can be recovered
/// using the toElement() method
class CfgOptionsTest : public CfgToElement {
public:
    /// @brief Constructor
    ///
    /// @param cfg the server configuration where to get option-{def,data}
    CfgOptionsTest(SrvConfigPtr cfg) :
        cfg_option_def_(cfg->getCfgOptionDef()),
        cfg_option_(cfg->getCfgOption()) { }

Tomek Mrugalski's avatar
Tomek Mrugalski committed
654
    /// @brief Unparse a configuration object
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
    ///
    /// @return a pointer to unparsed configuration (a map with
    /// not empty option-def and option-data lists)
    ElementPtr toElement() const {
        ElementPtr result = Element::createMap();
        // Set option-def
        ConstElementPtr option_def = cfg_option_def_->toElement();
        if (!option_def->empty()) {
            result->set("option-def", option_def);
        }
        // Set option-data
        ConstElementPtr option_data = cfg_option_->toElement();
        if (!option_data->empty()) {
            result->set("option-data", option_data);
        }
        return (result);
    }

    /// @brief Run a toElement test (Element version)
    ///
    /// Use the runToElementTest template but add defaults to the config
    ///
    /// @param family the address family
    /// @param config the expected result without defaults
    void runCfgOptionsTest(uint16_t family, ConstElementPtr expected) {
        ConstElementPtr option_def = expected->get("option-def");
        if (option_def) {
            SimpleParser::setListDefaults(option_def,
                                          family == AF_INET ?
                                          ParseConfigTest::OPTION4_DEF_DEFAULTS :
                                          ParseConfigTest::OPTION6_DEF_DEFAULTS);
        }
        ConstElementPtr option_data = expected->get("option-data");
        if (option_data) {
            SimpleParser::setListDefaults(option_data,
                                          family == AF_INET ?
                                          ParseConfigTest::OPTION4_DEFAULTS :
                                          ParseConfigTest::OPTION6_DEFAULTS);
        }
        runToElementTest<CfgOptionsTest>(expected, *this);
    }

    /// @brief Run a toElement test
    ///
    /// Use the runToElementTest template but add defaults to the config
    ///
    /// @param family the address family
    /// @param expected the expected result without defaults
    void runCfgOptionsTest(uint16_t family, std::string config) {
        ConstElementPtr json;
        ASSERT_NO_THROW(json = Element::fromJSON(config)) << config;
706
        runCfgOptionsTest(family, json);
707
708
709
710
711
712
713
714
715
716
    }

private:
    /// @brief Pointer to option definitions configuration.
    CfgOptionDefPtr cfg_option_def_;

    /// @brief Reference to options (data) configuration.
    CfgOptionPtr cfg_option_;
};

717
/// @brief Check basic parsing of option definitions.
718
///
719
/// Note that this tests basic operation of the OptionDefinitionListParser and
720
/// OptionDefinitionParser.  It uses a simple configuration consisting of
721
722
723
724
725
726
727
728
729
730
/// 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\","
731
        "      \"array\": false,"
732
733
734
735
736
737
738
739
        "      \"record-types\": \"\","
        "      \"space\": \"isc\","
        "      \"encapsulate\": \"\""
        "  } ]"
        "}";

    // Verify that the configuration string parses.
    int rcode = parseConfiguration(config);
740
    ASSERT_EQ(0, rcode);
741

742

743
    // Verify that the option definition can be retrieved.
744
    OptionDefinitionPtr def =
745
        CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get("isc", 100);
746
747
748
749
750
751
752
753
    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());
754
755
756
757
758
759
760
761
762

    // Check if libdhcp++ runtime options have been updated.
    OptionDefinitionPtr def_libdhcp = LibDHCP::getRuntimeOptionDef("isc", 100);
    ASSERT_TRUE(def_libdhcp);

    // The LibDHCP should return a separate instance of the option definition
    // but the values should be equal.
    EXPECT_TRUE(def_libdhcp != def);
    EXPECT_TRUE(*def_libdhcp == *def);
763
764
765
766

    // Check if it can be unparsed.
    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
    cfg.runCfgOptionsTest(family_, config);
767
768
}

769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
/// @brief Check minimal parsing of option definitions.
///
/// Same than basic but without optional parameters set to their default.
TEST_F(ParseConfigTest, minimalOptionDefTest) {

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

    // Verify that the configuration string parses.
    int rcode = parseConfiguration(config);
786
    ASSERT_EQ(0, rcode);
787
788
789
790
791
792
793
794
795
796
797
798
799


    // Verify that the option definition can be retrieved.
    OptionDefinitionPtr def =
        CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get("isc", 100);
    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());
800
801
802
803

    // Check if it can be unparsed.
    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
    cfg.runCfgOptionsTest(family_, config);
804
805
}

806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
/// @brief Check parsing of option definitions using default dhcp6 space.
///
/// Same than minimal but using the fact the default universe is V6
/// so the default space is dhcp6
TEST_F(ParseConfigTest, defaultSpaceOptionDefTest) {

    // Configuration string.
    std::string config =
        "{ \"option-def\": [ {"
        "      \"name\": \"foo\","
        "      \"code\": 10000,"
        "      \"type\": \"ipv6-address\""
        "  } ]"
        "}";

    // Verify that the configuration string parses.
822
    int rcode = parseConfiguration(config, true);
823
    ASSERT_EQ(0, rcode);
824
825
826
827


    // Verify that the option definition can be retrieved.
    OptionDefinitionPtr def =
828
        CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get(DHCP6_OPTION_SPACE, 10000);
829
830
831
832
833
834
835
836
    ASSERT_TRUE(def);

    // Verify that the option definition is correct.
    EXPECT_EQ("foo", def->getName());
    EXPECT_EQ(10000, def->getCode());
    EXPECT_FALSE(def->getArrayType());
    EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, def->getType());
    EXPECT_TRUE(def->getEncapsulatedSpace().empty());
837
838
839
840

    // Check if it can be unparsed.
    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
    cfg.runCfgOptionsTest(family_, config);
841
842
}

843
/// @brief Check basic parsing of options.
844
///
845
846
847
848
849
850
851
852
853
854
855
856
/// 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\","
857
        "      \"space\": \"isc\""
858
859
860
861
862
        " } ], "
        " \"option-data\": [ {"
        "    \"name\": \"foo\","
        "    \"space\": \"isc\","
        "    \"code\": 100,"
863
        "    \"data\": \"192.0.2.0\","
864
        "    \"csv-format\": true"
865
866
867
868
869
        " } ]"
        "}";

    // Verify that the configuration string parses.
    int rcode = parseConfiguration(config);
870
    ASSERT_EQ(0, rcode);
871
872
873
874
875

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

876
    // Verify that the option data is correct.
877
    std::string val = "type=00100, len=00004: 192.0.2.0 (ipv4-address)";
878
879

    EXPECT_EQ(val, opt_ptr->toText());
880
881
882
883

    // Check if it can be unparsed.
    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
    cfg.runCfgOptionsTest(family_, config);
884
885
}

886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
/// @brief Check minimal parsing of options.
///
/// Same than basic but without optional parameters set to their default.
TEST_F(ParseConfigTest, minimalOptionDataTest) {

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

    // Verify that the configuration string parses.
    int rcode = parseConfiguration(config);
908
    ASSERT_EQ(0, rcode);
909
910
911
912
913

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

914
    // Verify that the option data is correct.
915
916
917
    std::string val = "type=00100, len=00004: 192.0.2.0 (ipv4-address)";

    EXPECT_EQ(val, opt_ptr->toText());
918
919
920
921
922
923

    ElementPtr expected = Element::fromJSON(config);
    ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
    opt_data->set("code", Element::create(100));
    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
    cfg.runCfgOptionsTest(family_, expected);
924
925
}

926
927
928
929
930
931
932
933
934
/// @brief Check parsing of options with escape characters.
///
/// 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 and that its content
/// has the actual character (e.g. an actual backslash, not double backslash).
TEST_F(ParseConfigTest, escapedOptionDataTest) {

935
    family_ = AF_INET;
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952

    // We need to use double escapes here. The first backslash will
    // be consumed by C++ preprocessor, so the actual string will
    // have two backslash characters: \\SMSBoot\\x64\\wdsnbp.com.
    //
    std::string config =
        "{\"option-data\": [ {"
        "    \"name\": \"boot-file-name\","
        "    \"data\": \"\\\\SMSBoot\\\\x64\\\\wdsnbp.com\""
        " } ]"
        "}";

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

    // Verify that the option can be retrieved.
953
    OptionPtr opt = getOptionPtr(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
954
955
956
957
958
959
960
961
962
963
964
965
966
    ASSERT_TRUE(opt);

    util::OutputBuffer buf(100);

    uint8_t exp[] = { DHO_BOOT_FILE_NAME, 23, '\\', 'S', 'M', 'S', 'B', 'o', 'o',
                      't', '\\', 'x', '6', '4', '\\', 'w', 'd', 's', 'n', 'b',
                      'p', '.', 'c', 'o', 'm' };
    ASSERT_EQ(25, sizeof(exp));

    opt->pack(buf);
    EXPECT_EQ(Option::OPTION4_HDR_LEN + 23, buf.getLength());

    EXPECT_TRUE(0 == memcmp(buf.getData(), exp, 25));
967
968
969
970
971
972

    ElementPtr expected = Element::fromJSON(config);
    ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
    opt_data->set("code", Element::create(DHO_BOOT_FILE_NAME));
    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
    cfg.runCfgOptionsTest(family_, expected);
973
974
}

975
976
977
// This test checks behavior of the configuration parser for option data
// for different values of csv-format parameter and when there is an option
// definition present.
978
TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) {
979
980
981
982
983
984
985
986
987
    std::string config =
        "{ \"option-data\": [ {"
        "    \"name\": \"swap-server\","
        "    \"space\": \"dhcp4\","
        "    \"code\": 16,"
        "    \"data\": \"192.0.2.0\""
        " } ]"
        "}";

988
989
    // The default universe is V6. We need to change it to use dhcp4 option
    // space.
990
    family_ = AF_INET;
991
992
    int rcode = 0;
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
993
    ASSERT_EQ(0, rcode);
994

995
    // Verify that the option data is correct.
996
    OptionCustomPtr addr_opt = boost::dynamic_pointer_cast<
997
        OptionCustom>(getOptionPtr(DHCP4_OPTION_SPACE, 16));
998
999
1000
    ASSERT_TRUE(addr_opt);
    EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());

1001
1002
1003
    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
    cfg.runCfgOptionsTest(family_, config);

1004
1005
1006
1007
1008
1009
1010
    // Explicitly enable csv-format.
    CfgMgr::instance().clear();
    config =
        "{ \"option-data\": [ {"
        "    \"name\": \"swap-server\","
        "    \"space\": \"dhcp4\","
        "    \"code\": 16,"
1011
        "    \"csv-format\": true,"
1012
1013
1014
1015
1016
1017
1018
1019
        "    \"data\": \"192.0.2.0\""
        " } ]"
        "}";
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    ASSERT_EQ(0, rcode);

    // Verify that the option data is correct.
    addr_opt = boost::dynamic_pointer_cast<
1020
        OptionCustom>(getOptionPtr(DHCP4_OPTION_SPACE, 16));
1021
1022
1023
    ASSERT_TRUE(addr_opt);
    EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());

1024
1025
    // To make runToElementTest to work the csv-format must be removed...

1026
1027
1028
1029
1030
1031
1032
    // Explicitly disable csv-format and use hex instead.
    CfgMgr::instance().clear();
    config =
        "{ \"option-data\": [ {"
        "    \"name\": \"swap-server\","
        "    \"space\": \"dhcp4\","
        "    \"code\": 16,"
1033
        "    \"csv-format\": false,"
1034
1035
1036
1037
1038
1039
1040
1041
        "    \"data\": \"C0000200\""
        " } ]"
        "}";
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    ASSERT_EQ(0, rcode);

    // Verify that the option data is correct.
    addr_opt = boost::dynamic_pointer_cast<
1042
        OptionCustom>(getOptionPtr(DHCP4_OPTION_SPACE, 16));
1043
1044
    ASSERT_TRUE(addr_opt);
    EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());
1045
1046
1047

    CfgOptionsTest cfg2(CfgMgr::instance().getStagingCfg());
    cfg2.runCfgOptionsTest(family_, config);
1048
1049
1050
1051
1052
}

// This test checks behavior of the configuration parser for option data
// for different values of csv-format parameter and when there is no
// option definition.
1053
TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) {
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
    // This option doesn't have any definition. It is ok to use such
    // an option but the data should be specified in hex, not as CSV.
    // Note that the parser will by default use the CSV format for the
    // data but only in case there is a suitable option definition.
    std::string config =
        "{ \"option-data\": [ {"
        "    \"name\": \"foo-name\","
        "    \"space\": \"dhcp6\","
        "    \"code\": 25000,"
        "    \"data\": \"1, 2, 5\""
        " } ]"
        "}";
    int rcode = 0;
1067
    ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
    EXPECT_NE(0, rcode);

    CfgMgr::instance().clear();
    // The data specified here will work both for CSV format and hex format.
    // What we want to test here is that when the csv-format is enforced, the
    // parser will fail because of lack of an option definition.
    config =
        "{ \"option-data\": [ {"
        "    \"name\": \"foo-name\","
        "    \"space\": \"dhcp6\","
        "    \"code\": 25000,"
1079
        "    \"csv-format\": true,"
1080
1081
1082
        "    \"data\": \"0\""
        " } ]"
        "}";
1083
    ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
    EXPECT_NE(0, rcode);

    CfgMgr::instance().clear();
    // The same test case as above, but for the data specified in hex should
    // be successful.
    config =
        "{ \"option-data\": [ {"
        "    \"name\": \"foo-name\","
        "    \"space\": \"dhcp6\","
        "    \"code\": 25000,"
1094
        "    \"csv-format\": false,"
1095
1096
1097
        "    \"data\": \"0\""
        " } ]"
        "}";
1098
    ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
1099
    ASSERT_EQ(0, rcode);
1100
    OptionPtr opt = getOptionPtr(DHCP6_OPTION_SPACE, 25000);
1101
1102
1103
1104
    ASSERT_TRUE(opt);
    ASSERT_EQ(1, opt->getData().size());
    EXPECT_EQ(0, opt->getData()[0]);

1105
1106
1107
1108
1109
1110
1111
    ElementPtr expected = Element::fromJSON(config);
    ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
    opt_data->remove("name");
    opt_data->set("data", Element::create(std::string("00")));
    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
    cfg.runCfgOptionsTest(family_, expected);

1112
1113
1114
1115
1116
1117
1118
1119
1120
    CfgMgr::instance().clear();
    // When csv-format is not specified, the parser will check if the definition
    // exists or not. Since there is no definition, the parser will accept the
    // data in hex.
    config =
        "{ \"option-data\": [ {"
        "    \"name\": \"foo-name\","
        "    \"space\": \"dhcp6\","
        "    \"code\": 25000,"
1121
        "    \"csv-format\": false,"
1122
1123
1124
        "    \"data\": \"123456\""
        " } ]"
        "}";
1125
    ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
1126
    EXPECT_EQ(0, rcode);
1127
    opt = getOptionPtr(DHCP6_OPTION_SPACE, 25000);
1128
1129
1130
1131
1132
    ASSERT_TRUE(opt);
    ASSERT_EQ(3, opt->getData().size());
    EXPECT_EQ(0x12, opt->getData()[0]);
    EXPECT_EQ(0x34, opt->getData()[1]);
    EXPECT_EQ(0x56, opt->getData()[2]);
1133
1134
1135
1136
1137
1138

    expected = Element::fromJSON(config);
    opt_data = expected->get("option-data")->getNonConst(0);
    opt_data->remove("name");
    CfgOptionsTest cfg2(CfgMgr::instance().getStagingCfg());
    cfg2.runCfgOptionsTest(family_, expected);
1139
1140
}

1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
// This test verifies that the option name is not mandatory, if the option
// code has been specified.
TEST_F(ParseConfigTest, optionDataNoName) {
    std::string config =
        "{ \"option-data\": [ {"
        "    \"space\": \"dhcp6\","
        "    \"code\": 23,"
        "    \"data\": \"2001:db8:1::1\""
        " } ]"
        "}";
    int rcode = 0;
1152
    ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
1153
1154
    EXPECT_EQ(0, rcode);
    Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
1155
        Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, 23));
1156
1157
1158
    ASSERT_TRUE(opt);
    ASSERT_EQ(1, opt->getAddresses().size());
    EXPECT_EQ( "2001:db8:1::1", opt->getAddresses()[0].toText());
1159
1160
1161
1162
1163
1164

    ElementPtr expected = Element::fromJSON(config);
    ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
    opt_data->set("name", Element::create(std::string("dns-servers")));
    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
    cfg.runCfgOptionsTest(family_, expected);
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
}

// This test verifies that the option code is not mandatory, if the option
// name has been specified.
TEST_F(ParseConfigTest, optionDataNoCode) {
    std::string config =
        "{ \"option-data\": [ {"
        "    \"space\": \"dhcp6\","
        "    \"name\": \"dns-servers\","
        "    \"data\": \"2001:db8:1::1\""
        " } ]"
        "}";
    int rcode = 0;
1178
    ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
1179
1180
    EXPECT_EQ(0, rcode);
    Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
1181
        Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, 23));
1182
1183
1184
    ASSERT_TRUE(opt);
    ASSERT_EQ(1, opt->getAddresses().size());
    EXPECT_EQ( "2001:db8:1::1", opt->getAddresses()[0].toText());
1185
1186
1187
1188
1189
1190

    ElementPtr expected = Element::fromJSON(config);
    ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
    opt_data->set("code", Element::create(D6O_NAME_SERVERS));
    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
    cfg.runCfgOptionsTest(family_, expected);
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
}

// This test verifies that the option data configuration with a minimal
// set of parameters works as expected.
TEST_F(ParseConfigTest, optionDataMinimal) {
    std::string config =
        "{ \"option-data\": [ {"
        "    \"name\": \"dns-servers\","
        "    \"data\": \"2001:db8:1::10\""
        " } ]"
        "}";
    int rcode = 0;
1203
    ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
1204
1205
    EXPECT_EQ(0, rcode);
    Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
1206
        Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, 23));
1207
1208
1209
1210
    ASSERT_TRUE(opt);
    ASSERT_EQ(1, opt->getAddresses().size());
    EXPECT_EQ( "2001:db8:1::10", opt->getAddresses()[0].toText());

1211
1212
1213
1214
1215
1216
1217
    ElementPtr expected = Element::fromJSON(config);
    ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
    opt_data->set("code", Element::create(D6O_NAME_SERVERS));
    opt_data->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
    cfg.runCfgOptionsTest(family_, expected);

1218
1219
1220
1221
1222
1223
1224
1225
1226
    CfgMgr::instance().clear();
    // This time using an option code.
    config =
        "{ \"option-data\": [ {"
        "    \"code\": 23,"
        "    \"data\": \"2001:db8:1::20\""
        " } ]"
        "}";
    rcode = 0;
1227
    ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
1228
    EXPECT_EQ(0, rcode);
1229
    opt = boost::dynamic_pointer_cast<Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE,
1230
1231
1232
1233
                                                                   23));
    ASSERT_TRUE(opt);
    ASSERT_EQ(1, opt->getAddresses().size());
    EXPECT_EQ( "2001:db8:1::20", opt->getAddresses()[0].toText());
1234
1235
1236
1237
1238
1239
1240

    expected = Element::fromJSON(config);
    opt_data = expected->get("option-data")->getNonConst(0);
    opt_data->set("name", Element::create(std::string("dns-servers")));
    opt_data->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
    CfgOptionsTest cfg2(CfgMgr::instance().getStagingCfg());
    cfg2.runCfgOptionsTest(family_, expected);
1241
1242
}

1243
1244
// This test verifies that the option data configuration with a minimal
// set of parameters works as expected when option definition is
Josh Soref's avatar
Josh Soref committed
1245
// created in the configuration file.
1246
1247
1248
1249
1250
1251
1252
TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) {
    // Configuration string.
    std::string config =
        "{ \"option-def\": [ {"
        "      \"name\": \"foo-name\","
        "      \"code\": 2345,"
        "      \"type\": \"ipv6-address\","
1253
        "      \"array\": true,"
1254
        "      \"space\": \"dhcp6\""
1255
1256
1257
1258
1259
1260
1261
1262
        "  } ],"
        "  \"option-data\": [ {"
        "    \"name\": \"foo-name\","
        "    \"data\": \"2001:db8:1::10, 2001:db8:1::123\""
        " } ]"
        "}";

    int rcode = 0;
1263
    ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
1264
1265
    EXPECT_EQ(0, rcode);
    Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
1266
        Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, 2345));
1267
1268
1269
1270
1271
    ASSERT_TRUE(opt);
    ASSERT_EQ(2, opt->getAddresses().size());
    EXPECT_EQ("2001:db8:1::10", opt->getAddresses()[0].toText());
    EXPECT_EQ("2001:db8:1::123", opt->getAddresses()[1].toText());

1272
1273
1274
1275
1276
1277
1278
    ElementPtr expected = Element::fromJSON(config);
    ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
    opt_data->set("code", Element::create(2345));
    opt_data->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
    cfg.runCfgOptionsTest(family_, expected);

1279
1280
1281
1282
1283
1284
1285
    CfgMgr::instance().clear();
    // Do the same test but now use an option code.
    config =
        "{ \"option-def\": [ {"
        "      \"name\": \"foo-name\","
        "      \"code\": 2345,"
        "      \"type\": \"ipv6-address\","
1286
        "      \"array\": true,"
1287
        "      \"space\": \"dhcp6\""
1288
1289
1290
1291
1292
1293
1294
1295
        "  } ],"
        "  \"option-data\": [ {"
        "    \"code\": 2345,"
        "    \"data\": \"2001:db8:1::10, 2001:db8:1::123\""
        " } ]"
        "}";

    rcode = 0;
1296
    ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
1297
    EXPECT_EQ(0, rcode);
1298
    opt = boost::dynamic_pointer_cast<Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE,
1299
1300
1301
1302
1303
1304
                                                                   2345));
    ASSERT_TRUE(opt);
    ASSERT_EQ(2, opt->getAddresses().size());
    EXPECT_EQ("2001:db8:1::10", opt->getAddresses()[0].toText());
    EXPECT_EQ("2001:db8:1::123", opt->getAddresses()[1].toText());

1305
1306
1307
1308
1309
1310
    expected = Element::fromJSON(config);
    opt_data = expected->get("option-data")->getNonConst(0);
    opt_data->set("name", Element::create(std::string("foo-name")));
    opt_data->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
    CfgOptionsTest cfg2(CfgMgr::instance().getStagingCfg());
    cfg2.runCfgOptionsTest(family_, expected);
1311
}
1312

1313
1314
1315
// This test verifies an empty option data configuration is supported.
TEST_F(ParseConfigTest, emptyOptionData) {
    // Configuration string.
JINMEI Tatuya's avatar
JINMEI Tatuya committed
1316
    const std::string config =
1317
1318
1319
1320
1321
1322
        "{ \"option-data\": [ {"
        "    \"name\": \"dhcp4o6-server-addr\""
        " } ]"
        "}";

    int rcode = 0;
1323
    ASSERT_NO_THROW(rcode = parseConfiguration(config, true));
1324
    EXPECT_EQ(0, rcode);
JINMEI Tatuya's avatar
JINMEI Tatuya committed
1325
    const Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
1326
        Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, D6O_DHCPV4_O_DHCPV6_SERVER));
1327
1328
    ASSERT_TRUE(opt);
    ASSERT_EQ(0, opt->getAddresses().size());
1329
1330
1331
1332
1333
1334
1335
1336
1337

    ElementPtr expected = Element::fromJSON(config);
    ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
    opt_data->set("code", Element::create(D6O_DHCPV4_O_DHCPV6_SERVER));
    opt_data->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
    opt_data->set("csv-format", Element::create(false));
    opt_data->set("data", Element::create(std::string("")));
    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
    cfg.runCfgOptionsTest(family_, expected);
1338
1339
}

Francis Dupont's avatar
Francis Dupont committed
1340
// This test verifies an option data without suboptions is supported
Josh Soref's avatar
Josh Soref committed
1341
TEST_F(ParseConfigTest, optionDataNoSubOption) {
Francis Dupont's avatar
Francis Dupont committed
1342
1343
1344
1345
1346
1347
1348
    // Configuration string.
    const std::string config =
        "{ \"option-data\": [ {"
        "    \"name\": \"vendor-encapsulated-options\""
        " } ]"
        "}";

Francis Dupont's avatar