dhcp_parsers_unittest.cc 66.2 KB
Newer Older
1
// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//
// 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>
17
#include <cc/data.h>
18
19
20
#include <dhcp/option.h>
#include <dhcp/option_custom.h>
#include <dhcp/option_int.h>
21
#include <dhcp/option6_addrlst.h>
22
#include <dhcp/tests/iface_mgr_test_config.h>
23
#include <dhcpsrv/cfgmgr.h>
24
#include <dhcpsrv/subnet.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
25
#include <dhcpsrv/cfg_mac_source.h>
26
#include <dhcpsrv/parsers/dhcp_parsers.h>
Stephen Morris's avatar
Stephen Morris committed
27
#include <dhcpsrv/tests/test_libraries.h>
28
#include <dhcpsrv/testutils/config_result_check.h>
29
#include <exceptions/exceptions.h>
30
#include <hooks/hooks_manager.h>
31
32
33

#include <gtest/gtest.h>
#include <boost/foreach.hpp>
34
#include <boost/pointer_cast.hpp>
35
36
37
38
39
40
41

#include <map>
#include <string>

using namespace std;
using namespace isc;
using namespace isc::config;
42
43
using namespace isc::data;
using namespace isc::dhcp;
44
using namespace isc::dhcp::test;
45
using namespace isc::hooks;
46
47
48
49
50
51
52
53

namespace {

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

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

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


/// @brief Check BooleanParser basic functionality.
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
98
/// 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.
99
    bool actual_value = !test_value;
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
    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
117
///
118
119
/// Verifies that the parser:
/// 1. Does not allow empty for storage.
120
/// 2. Builds with a nont string value.
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/// 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);

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

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

145
146
    // Verify that parser will build with a string value.
    const std::string test_value = "test value";
147
    element = Element::create(test_value);
148
149
150
151
152
153
154
155
156
    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
157
///
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/// 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);

186
187
    // Verify that parser with rejects too large a value provided we are on
    // 64-bit platform.
188
189
190
191
192
    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);
    }
193
194
195
196
197
198

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

199
200
201
202
203
204
    // 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);

205
206
207
208
209
210
211
212
213
214
215
    // 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);
}

216
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
247
248
249
250
/// @brief Check MACSourcesListConfigParser  basic functionality
///
/// Verifies that the parser:
/// 1. Does not allow empty for storage.
/// 2. Does not allow name other than "mac-sources"
/// 3. Parses list of mac sources and adds them to CfgMgr
TEST_F(DhcpParserTest, MacSourcesListConfigParserTest) {

    const std::string valid_name = "mac-sources";
    const std::string bogus_name = "bogus-name";

    ParserContextPtr parser_context(new ParserContext(Option::V6));

    // Verify that parser constructor fails if parameter name isn't "mac-sources"
    EXPECT_THROW(MACSourcesListConfigParser(bogus_name, parser_context),
                 isc::BadValue);

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

    boost::scoped_ptr<MACSourcesListConfigParser>
        parser(new MACSourcesListConfigParser(valid_name, parser_context));

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

    // Use CfgMgr instance to check if eth0 and eth1 was added, and that
    // eth2 was not added.
    SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
    ASSERT_TRUE(cfg);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
251
    CfgMACSources configured_sources =  cfg->getMACSources().get();
252
253

    ASSERT_EQ(2, configured_sources.size());
Tomek Mrugalski's avatar
Tomek Mrugalski committed
254
255
    EXPECT_EQ(HWAddr::HWADDR_SOURCE_DUID, configured_sources[0]);
    EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, configured_sources[1]);
256
257
}

258
/// @brief Test Fixture class which provides basic structure for testing
259
260
261
262
263
264
265
/// configuration parsing.  This is essentially the same structure provided
/// by dhcp servers.
class ParseConfigTest : public ::testing::Test {
public:
    /// @brief Constructor
    ParseConfigTest() {
        reset_context();
266
        CfgMgr::instance().clear();
267
268
    }

269
270
    ~ParseConfigTest() {
        reset_context();
271
        CfgMgr::instance().clear();
272
273
    }

274
    /// @brief Parses a configuration.
275
276
    ///
    /// Parse the given configuration, populating the context storage with
277
278
    /// the parsed elements.
    ///
279
280
281
    /// @param config_set is the set of elements to parse.
    /// @return returns an ConstElementPtr containing the numeric result
    /// code and outcome comment.
282
    isc::data::ConstElementPtr parseElementSet(isc::data::ConstElementPtr
283
284
285
286
287
288
289
290
291
292
293
294
295
296
                                           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 {
297
            // Iterate over the config elements.
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
            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.
314
            std::map<std::string, ConstElementPtr>::const_iterator
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
                                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.
    ///
337
338
339
340
341
    /// Creates a parser for the appropriate element and stores a pointer to it
    /// in the appropriate class variable.
    ///
    /// Note that the method currently it only supports option-defs, option-data
    /// and hooks-libraries.
342
343
    ///
    /// @param config_id is the name of the configuration element.
344
345
346
    ///
    /// @return returns a shared pointer to DhcpConfigParser.
    ///
347
    /// @throw throws NotImplemented if element name isn't supported.
348
349
    ParserPtr createConfigParser(const std::string& config_id) {
        ParserPtr parser;
350
351
        int family = parser_context_->universe_ == Option::V4 ?
            AF_INET : AF_INET6;
352
        if (config_id.compare("option-data") == 0) {
353
            parser.reset(new OptionDataListParser(config_id, CfgOptionPtr(),
354
                                                  family));
355

356
        } else if (config_id.compare("option-def") == 0) {
357
            parser.reset(new OptionDefListParser(config_id,
358
                                                 parser_context_));
359
360
361
362
363

        } else if (config_id.compare("hooks-libraries") == 0) {
            parser.reset(new HooksLibrariesParser(config_id));
            hooks_libraries_parser_ =
                boost::dynamic_pointer_cast<HooksLibrariesParser>(parser);
364
365
        } else if (config_id.compare("dhcp-ddns") == 0) {
            parser.reset(new D2ClientConfigParser(config_id));
366
367
368
369
370
371
372
373
374
        } else {
            isc_throw(NotImplemented,
                "Parser error: configuration parameter not supported: "
                << config_id);
        }

        return (parser);
    }

375
376
    /// @brief Convenience method for parsing a configuration
    ///
377
    /// Given a configuration string, convert it into Elements
378
    /// and parse them.
379
380
    /// @param config is the configuration string to parse
    ///
381
    /// @return retuns 0 if the configuration parsed successfully,
382
    /// non-zero otherwise failure.
383
    int parseConfiguration(const std::string& config) {
384
385
386
387
388
389
390
        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);
391
392
            ConstElementPtr comment = parseAnswer(rcode_, status);
            error_text_ = comment->stringValue();
393
394
395
396
397
            // If error was reported, the error string should contain
            // position of the data element which caused failure.
            if (rcode_ != 0) {
                EXPECT_TRUE(errorContainsPosition(status, "<string>"));
            }
398
399
400
401
402
        }

        return (rcode_);
    }

403
    /// @brief Find an option for a given space and code within the parser
404
405
406
407
408
    /// 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.
409
    /// ASSERT_ tests don't work inside functions that return values
410
411
412
    OptionPtr getOptionPtr(std::string space, uint32_t code)
    {
        OptionPtr option_ptr;
413
414
        OptionContainerPtr options = CfgMgr::instance().getStagingCfg()->
            getCfgOption()->getAll(space);
415
416
417
418
        // Should always be able to get options list even if it is empty.
        EXPECT_TRUE(options);
        if (options) {
            // Attempt to find desired option.
419
            const OptionContainerTypeIndex& idx = options->get<1>();
420
            const OptionContainerTypeRange& range = idx.equal_range(code);
421
422
423
            int cnt = std::distance(range.first, range.second);
            EXPECT_EQ(1, cnt);
            if (cnt == 1) {
424
                OptionDescriptor desc = *(idx.begin());
425
                option_ptr = desc.option_;
426
427
428
429
                EXPECT_TRUE(option_ptr);
            }
        }

430
        return (option_ptr);
431
432
    }

433
    /// @brief Wipes the contents of the context to allowing another parsing
434
435
436
    /// during a given test if needed.
    void reset_context(){
        // Note set context universe to V6 as it has to be something.
437
        CfgMgr::instance().clear();
438
        parser_context_.reset(new ParserContext(Option::V6));
439
440
441

        // Ensure no hooks libraries are loaded.
        HooksManager::unloadLibraries();
442
443
444
445

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

448
449
450
451
452
    /// @brief Parsers used in the parsing of the configuration
    ///
    /// Allows the tests to interrogate the state of the parsers (if required).
    boost::shared_ptr<HooksLibrariesParser> hooks_libraries_parser_;

453
454
    /// @brief Parser context - provides storage for options and definitions
    ParserContextPtr parser_context_;
455
456
457

    /// @brief Error string if the parsing failed
    std::string error_text_;
458
459
460
};

/// @brief Check Basic parsing of option definitions.
461
///
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
/// 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);

485

486
    // Verify that the option definition can be retrieved.
487
    OptionDefinitionPtr def =
488
        CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get("isc", 100);
489
490
491
492
493
494
495
496
497
498
499
    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.
500
///
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
/// 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,"
522
        "    \"data\": \"192.0.2.0\","
523
524
525
526
527
528
529
530
531
532
533
534
535
536
        "    \"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 "
537
                      " #0 192.0.2.0 ( ipv4-address ) \n";
538
539
540
541

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

542
543
544
// 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.
545
TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) {
546
547
548
549
550
551
552
553
554
    std::string config =
        "{ \"option-data\": [ {"
        "    \"name\": \"swap-server\","
        "    \"space\": \"dhcp4\","
        "    \"code\": 16,"
        "    \"data\": \"192.0.2.0\""
        " } ]"
        "}";

555
556
557
    // The default universe is V6. We need to change it to use dhcp4 option
    // space.
    parser_context_->universe_ = Option::V4;
558
559
    int rcode = 0;
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
560
    ASSERT_EQ(0, rcode);
561

562
    // Verify that the option data is correct.
563
    OptionCustomPtr addr_opt = boost::dynamic_pointer_cast<
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
        OptionCustom>(getOptionPtr("dhcp4", 16));
    ASSERT_TRUE(addr_opt);
    EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());

    // Explicitly enable csv-format.
    CfgMgr::instance().clear();
    config =
        "{ \"option-data\": [ {"
        "    \"name\": \"swap-server\","
        "    \"space\": \"dhcp4\","
        "    \"code\": 16,"
        "    \"csv-format\": True,"
        "    \"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<
        OptionCustom>(getOptionPtr("dhcp4", 16));
    ASSERT_TRUE(addr_opt);
    EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());

    // Explicitly disable csv-format and use hex instead.
    CfgMgr::instance().clear();
    config =
        "{ \"option-data\": [ {"
        "    \"name\": \"swap-server\","
        "    \"space\": \"dhcp4\","
        "    \"code\": 16,"
        "    \"csv-format\": False,"
        "    \"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<
        OptionCustom>(getOptionPtr("dhcp4", 16));
    ASSERT_TRUE(addr_opt);
    EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());
}

// 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.
612
TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) {
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
654
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
    // 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;
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    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,"
        "    \"csv-format\": True,"
        "    \"data\": \"0\""
        " } ]"
        "}";
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    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,"
        "    \"csv-format\": False,"
        "    \"data\": \"0\""
        " } ]"
        "}";
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    ASSERT_EQ(0, rcode);
    OptionPtr opt = getOptionPtr("dhcp6", 25000);
    ASSERT_TRUE(opt);
    ASSERT_EQ(1, opt->getData().size());
    EXPECT_EQ(0, opt->getData()[0]);

    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,"
        "    \"data\": \"123456\""
        " } ]"
        "}";
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    EXPECT_EQ(0, rcode);
    opt = getOptionPtr("dhcp6", 25000);
    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]);
684
685
}

686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
// 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,"
        "    \"csv-format\": True,"
        "    \"data\": \"2001:db8:1::1\""
        " } ]"
        "}";
    int rcode = 0;
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    EXPECT_EQ(0, rcode);
    Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
        Option6AddrLst>(getOptionPtr("dhcp6", 23));
    ASSERT_TRUE(opt);
    ASSERT_EQ(1, opt->getAddresses().size());
    EXPECT_EQ( "2001:db8:1::1", opt->getAddresses()[0].toText());
}

// 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\","
        "    \"csv-format\": True,"
        "    \"data\": \"2001:db8:1::1\""
        " } ]"
        "}";
    int rcode = 0;
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    EXPECT_EQ(0, rcode);
    Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
        Option6AddrLst>(getOptionPtr("dhcp6", 23));
    ASSERT_TRUE(opt);
    ASSERT_EQ(1, opt->getAddresses().size());
    EXPECT_EQ( "2001:db8:1::1", opt->getAddresses()[0].toText());
}

// 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;
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    EXPECT_EQ(0, rcode);
    Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
        Option6AddrLst>(getOptionPtr("dhcp6", 23));
    ASSERT_TRUE(opt);
    ASSERT_EQ(1, opt->getAddresses().size());
    EXPECT_EQ( "2001:db8:1::10", opt->getAddresses()[0].toText());

    CfgMgr::instance().clear();
    // This time using an option code.
    config =
        "{ \"option-data\": [ {"
        "    \"code\": 23,"
        "    \"data\": \"2001:db8:1::20\""
        " } ]"
        "}";
    rcode = 0;
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    EXPECT_EQ(0, rcode);
    opt = boost::dynamic_pointer_cast<Option6AddrLst>(getOptionPtr("dhcp6",
                                                                   23));
    ASSERT_TRUE(opt);
    ASSERT_EQ(1, opt->getAddresses().size());
    EXPECT_EQ( "2001:db8:1::20", opt->getAddresses()[0].toText());
}

764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
// This test verifies that the option data configuration with a minimal
// set of parameters works as expected when option definition is
// created in the configruation file.
TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) {
    // Configuration string.
    std::string config =
        "{ \"option-def\": [ {"
        "      \"name\": \"foo-name\","
        "      \"code\": 2345,"
        "      \"type\": \"ipv6-address\","
        "      \"array\": True,"
        "      \"record-types\": \"\","
        "      \"space\": \"dhcp6\","
        "      \"encapsulate\": \"\""
        "  } ],"
        "  \"option-data\": [ {"
        "    \"name\": \"foo-name\","
        "    \"data\": \"2001:db8:1::10, 2001:db8:1::123\""
        " } ]"
        "}";

    int rcode = 0;
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    EXPECT_EQ(0, rcode);
    Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
        Option6AddrLst>(getOptionPtr("dhcp6", 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());

    CfgMgr::instance().clear();
    // Do the same test but now use an option code.
    config =
        "{ \"option-def\": [ {"
        "      \"name\": \"foo-name\","
        "      \"code\": 2345,"
        "      \"type\": \"ipv6-address\","
        "      \"array\": True,"
        "      \"record-types\": \"\","
        "      \"space\": \"dhcp6\","
        "      \"encapsulate\": \"\""
        "  } ],"
        "  \"option-data\": [ {"
        "    \"code\": 2345,"
        "    \"data\": \"2001:db8:1::10, 2001:db8:1::123\""
        " } ]"
        "}";

    rcode = 0;
    ASSERT_NO_THROW(rcode = parseConfiguration(config));
    EXPECT_EQ(0, rcode);
    opt = boost::dynamic_pointer_cast<Option6AddrLst>(getOptionPtr("dhcp6",
                                                                   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());

}
824

825
826
};  // Anonymous namespace

Stephen Morris's avatar
Stephen Morris committed
827
828
/// These tests check basic operation of the HooksLibrariesParser.

829
830
// hooks-libraries that do not contain anything.
TEST_F(ParseConfigTest, noHooksLibrariesTest) {
Stephen Morris's avatar
Stephen Morris committed
831

832
833
    // Configuration with hooks-libraries not present.
    string config = "{ \"hooks-libraries\": [] }";
Stephen Morris's avatar
Stephen Morris committed
834
835
836

    // Verify that the configuration string parses.
    int rcode = parseConfiguration(config);
837
838
839
840
841
842
843
844
845
846
847
848
    ASSERT_TRUE(rcode == 0) << error_text_;

    // Check that the parser recorded no change to the current state
    // (as the test starts with no hooks libraries loaded).
    std::vector<std::string> libraries;
    bool changed;
    hooks_libraries_parser_->getLibraries(libraries, changed);
    EXPECT_TRUE(libraries.empty());
    EXPECT_FALSE(changed);

    // Load a single library and repeat the parse.
    vector<string> basic_library;
849
    basic_library.push_back(string(CALLOUT_LIBRARY_1));
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
    HooksManager::loadLibraries(basic_library);

    rcode = parseConfiguration(config);
    ASSERT_TRUE(rcode == 0) << error_text_;

    // This time the change should have been recorded.
    hooks_libraries_parser_->getLibraries(libraries, changed);
    EXPECT_TRUE(libraries.empty());
    EXPECT_TRUE(changed);

    // But repeating it again and we are back to no change.
    rcode = parseConfiguration(config);
    ASSERT_TRUE(rcode == 0) << error_text_;
    hooks_libraries_parser_->getLibraries(libraries, changed);
    EXPECT_TRUE(libraries.empty());
    EXPECT_FALSE(changed);
Stephen Morris's avatar
Stephen Morris committed
866
867
868
869

}


870
TEST_F(ParseConfigTest, validHooksLibrariesTest) {
Stephen Morris's avatar
Stephen Morris committed
871

872
    // Configuration string.  This contains a set of valid libraries.
Stephen Morris's avatar
Stephen Morris committed
873
874
875
876
877
    const std::string quote("\"");
    const std::string comma(", ");

    const std::string config =
        std::string("{ ") +
878
            std::string("\"hooks-libraries\": [") +
879
880
                quote + std::string(CALLOUT_LIBRARY_1) + quote + comma +
                quote + std::string(CALLOUT_LIBRARY_2)  + quote +
Stephen Morris's avatar
Stephen Morris committed
881
882
883
884
885
            std::string("]") +
        std::string("}");

    // Verify that the configuration string parses.
    int rcode = parseConfiguration(config);
886
    ASSERT_TRUE(rcode == 0) << error_text_;
Stephen Morris's avatar
Stephen Morris committed
887

888
889
890
891
892
893
894
    // Check that the parser holds two libraries and the configuration is
    // recorded as having changed.
    std::vector<std::string> libraries;
    bool changed;
    hooks_libraries_parser_->getLibraries(libraries, changed);
    EXPECT_EQ(2, libraries.size());
    EXPECT_TRUE(changed);
Stephen Morris's avatar
Stephen Morris committed
895
896
897
898

    // The expected libraries should be the list of libraries specified
    // in the given order.
    std::vector<std::string> expected;
899
900
    expected.push_back(CALLOUT_LIBRARY_1);
    expected.push_back(CALLOUT_LIBRARY_2);
901
902
903
904
905
    EXPECT_TRUE(expected == libraries);

    // Parse the string again.
    rcode = parseConfiguration(config);
    ASSERT_TRUE(rcode == 0) << error_text_;
Stephen Morris's avatar
Stephen Morris committed
906

907
908
909
910
    // The list has not changed, and this is what we should see.
    hooks_libraries_parser_->getLibraries(libraries, changed);
    EXPECT_EQ(2, libraries.size());
    EXPECT_FALSE(changed);
Stephen Morris's avatar
Stephen Morris committed
911
912
}

913
// Check with a set of libraries, some of which are invalid.
Stephen Morris's avatar
Stephen Morris committed
914
915
TEST_F(ParseConfigTest, invalidHooksLibrariesTest) {

916
    /// @todo Initialize global library context to null
Stephen Morris's avatar
Stephen Morris committed
917
918
919
920
921
922
923
924

    // Configuration string.  This contains an invalid library which should
    // trigger an error in the "build" stage.
    const std::string quote("\"");
    const std::string comma(", ");

    const std::string config =
        std::string("{ ") +
925
            std::string("\"hooks-libraries\": [") +
926
                quote + std::string(CALLOUT_LIBRARY_1) + quote + comma +
Stephen Morris's avatar
Stephen Morris committed
927
                quote + std::string(NOT_PRESENT_LIBRARY) + quote + comma +
928
                quote + std::string(CALLOUT_LIBRARY_2)  + quote +
Stephen Morris's avatar
Stephen Morris committed
929
930
931
            std::string("]") +
        std::string("}");

932
933
    // Verify that the configuration fails to parse. (Syntactically it's OK,
    // but the library is invalid).
Stephen Morris's avatar
Stephen Morris committed
934
    int rcode = parseConfiguration(config);
935
    ASSERT_FALSE(rcode == 0) << error_text_;
Stephen Morris's avatar
Stephen Morris committed
936

937
938
939
    // Check that the message contains the library in error.
    EXPECT_FALSE(error_text_.find(NOT_PRESENT_LIBRARY) == string::npos) <<
        "Error text returned from parse failure is " << error_text_;
Stephen Morris's avatar
Stephen Morris committed
940
}
941

942
943
944
/// @brief Checks that a valid, enabled D2 client configuration works correctly.
TEST_F(ParseConfigTest, validD2Config) {

945
    // Configuration string containing valid values.
946
947
948
949
    std::string config_str =
        "{ \"dhcp-ddns\" :"
        "    {"
        "     \"enable-updates\" : true, "
950
        "     \"server-ip\" : \"192.0.2.0\", "
951
        "     \"server-port\" : 3432, "
952
953
954
        "     \"sender-ip\" : \"192.0.2.1\", "
        "     \"sender-port\" : 3433, "
        "     \"max-queue-size\" : 2048, "
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
        "     \"ncr-protocol\" : \"UDP\", "
        "     \"ncr-format\" : \"JSON\", "
        "     \"always-include-fqdn\" : true, "
        "     \"override-no-update\" : true, "
        "     \"override-client-update\" : true, "
        "     \"replace-client-name\" : true, "
        "     \"generated-prefix\" : \"test.prefix\", "
        "     \"qualifying-suffix\" : \"test.suffix.\" "
        "    }"
        "}";

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

    // Verify that DHCP-DDNS is enabled and we can fetch the configuration.
971
    EXPECT_TRUE(CfgMgr::instance().ddnsEnabled());
972
973
974
975
976
977
    D2ClientConfigPtr d2_client_config;
    ASSERT_NO_THROW(d2_client_config = CfgMgr::instance().getD2ClientConfig());
    ASSERT_TRUE(d2_client_config);

    // Verify that the configuration values are as expected.
    EXPECT_TRUE(d2_client_config->getEnableUpdates());
978
    EXPECT_EQ("192.0.2.0", d2_client_config->getServerIp().toText());
979
    EXPECT_EQ(3432, d2_client_config->getServerPort());
980
981
982
983
984
985
986
987
    EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
    EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
    EXPECT_TRUE(d2_client_config->getAlwaysIncludeFqdn());
    EXPECT_TRUE(d2_client_config->getOverrideNoUpdate());
    EXPECT_TRUE(d2_client_config->getOverrideClientUpdate());
    EXPECT_TRUE(d2_client_config->getReplaceClientName());
    EXPECT_EQ("test.prefix", d2_client_config->getGeneratedPrefix());
    EXPECT_EQ("test.suffix.", d2_client_config->getQualifyingSuffix());
988
989

    // Another valid Configuration string.
990
    // This one is disabled, has IPV6 server ip, control flags false,
991
992
993
994
    // empty prefix/suffix
    std::string config_str2 =
        "{ \"dhcp-ddns\" :"
        "    {"
995
        "     \"enable-updates\" : false, "
996
        "     \"server-ip\" : \"2001:db8::\", "
997
        "     \"server-port\" : 43567, "
998
999
1000
        "     \"sender-ip\" : \"2001:db8::1\", "
        "     \"sender-port\" : 3433, "
        "     \"max-queue-size\" : 2048, "
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
        "     \"ncr-protocol\" : \"UDP\", "
        "     \"ncr-format\" : \"JSON\", "
        "     \"always-include-fqdn\" : false, "
        "     \"override-no-update\" : false, "
        "     \"override-client-update\" : false, "
        "     \"replace-client-name\" : false, "
        "     \"generated-prefix\" : \"\", "
        "     \"qualifying-suffix\" : \"\" "
        "    }"
        "}";

    // Verify that the configuration string parses.
    rcode = parseConfiguration(config_str2);
    ASSERT_TRUE(rcode == 0) << error_text_;

1016
1017
    // Verify that DHCP-DDNS is disabled and we can fetch the configuration.
    EXPECT_FALSE(CfgMgr::instance().ddnsEnabled());
1018
1019
1020
1021
    ASSERT_NO_THROW(d2_client_config = CfgMgr::instance().getD2ClientConfig());
    ASSERT_TRUE(d2_client_config);

    // Verify that the configuration values are as expected.
1022
    EXPECT_FALSE(d2_client_config->getEnableUpdates());
1023
    EXPECT_EQ("2001:db8::", d2_client_config->getServerIp().toText());
1024
1025
1026
1027
1028
1029
1030
1031
1032
    EXPECT_EQ(43567, d2_client_config->getServerPort());
    EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
    EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
    EXPECT_FALSE(d2_client_config->getAlwaysIncludeFqdn());
    EXPECT_FALSE(d2_client_config->getOverrideNoUpdate());
    EXPECT_FALSE(d2_client_config->getOverrideClientUpdate());
    EXPECT_FALSE(d2_client_config->getReplaceClientName());
    EXPECT_EQ("", d2_client_config->getGeneratedPrefix());
    EXPECT_EQ("", d2_client_config->getQualifyingSuffix());
1033
1034
1035
1036
1037
1038
}

/// @brief Checks that D2 client can be configured with enable flag of
/// false only.
TEST_F(ParseConfigTest, validDisabledD2Config) {

1039
    // Configuration string.  This defines a disabled D2 client config.
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
    std::string config_str =
        "{ \"dhcp-ddns\" :"
        "    {"
        "     \"enable-updates\" : false"
        "    }"
        "}";

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

    // Verify that DHCP-DDNS is disabled.
1052
    EXPECT_FALSE(CfgMgr::instance().ddnsEnabled());
1053
1054
1055
1056
1057
1058
1059
1060

    // Make sure fetched config agrees.
    D2ClientConfigPtr d2_client_config;
    ASSERT_NO_THROW(d2_client_config = CfgMgr::instance().getD2ClientConfig());
    EXPECT_TRUE(d2_client_config);
    EXPECT_FALSE(d2_client_config->getEnableUpdates());
}

1061
1062
1063
1064
/// @brief Checks that given a partial configuration, parser supplies
/// default values
TEST_F(ParseConfigTest, parserDefaultsD2Config) {

1065
1066
1067
    // Configuration string.  This defines an enabled D2 client config
    // with the mandatory parameter in such a case, all other parameters
    // are optional and their default values will be used.
1068
    std::string config_str =
1069
1070
        "{ \"dhcp-ddns\" :"
        "    {"
1071
1072
        "     \"enable-updates\" : true, "
        "     \"qualifying-suffix\" : \"test.suffix.\" "
1073
        "    }"
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
        "}";

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

    // Verify that DHCP-DDNS is enabled.
    EXPECT_TRUE(CfgMgr::instance().ddnsEnabled());

    // Make sure fetched config is correct.
    D2ClientConfigPtr d2_client_config;
    ASSERT_NO_THROW(d2_client_config = CfgMgr::instance().getD2ClientConfig());
    EXPECT_TRUE(d2_client_config);
    EXPECT_TRUE(d2_client_config->getEnableUpdates());
    EXPECT_EQ(D2ClientConfig::DFT_SERVER_IP,
              d2_client_config->getServerIp().toText());
    EXPECT_EQ(D2ClientConfig::DFT_SERVER_PORT,
              d2_client_config->getServerPort());
    EXPECT_EQ(dhcp_ddns::stringToNcrProtocol(D2ClientConfig::DFT_NCR_PROTOCOL),
              d2_client_config->getNcrProtocol());
    EXPECT_EQ(dhcp_ddns::stringToNcrFormat(D2ClientConfig::DFT_NCR_FORMAT),
              d2_client_config->getNcrFormat());
1096
1097
    EXPECT_EQ(D2ClientConfig::DFT_ALWAYS_INCLUDE_FQDN,
              d2_client_config->getAlwaysIncludeFqdn());
1098
1099
1100
1101
1102
1103
1104
1105
    EXPECT_EQ(D2ClientConfig::DFT_OVERRIDE_NO_UPDATE,
              d2_client_config->getOverrideNoUpdate());
    EXPECT_EQ(D2ClientConfig::DFT_OVERRIDE_CLIENT_UPDATE,
              d2_client_config->getOverrideClientUpdate());
    EXPECT_EQ(D2ClientConfig::DFT_REPLACE_CLIENT_NAME,
              d2_client_config->getReplaceClientName());
    EXPECT_EQ(D2ClientConfig::DFT_GENERATED_PREFIX,
              d2_client_config->getGeneratedPrefix());
1106
    EXPECT_EQ("test.suffix.",
1107
1108
1109
1110
1111
1112
1113
              d2_client_config->getQualifyingSuffix());
}


/// @brief Check various invalid D2 client configurations.
TEST_F(ParseConfigTest, invalidD2Config) {
    std::string invalid_configs[] = {
1114
        // Must supply at least enable-updates
1115
1116
1117
1118
        "{ \"dhcp-ddns\" :"
        "    {"
        "    }"
        "}",
1119
1120
1121
1122
1123
1124
        // Must supply qualifying-suffix when updates are enabled
        "{ \"dhcp-ddns\" :"
        "    {"
        "     \"enable-updates\" : true"
        "    }"
        "}",
1125
1126
1127
1128
        // Invalid server ip value
        "{ \"dhcp-ddns\" :"
        "    {"
        "     \"enable-updates\" : true, "
1129
        "     \"server-ip\" : \"x192.0.2.0\", "
1130
        "     \"server-port\" : 53001, "
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
        "     \"ncr-protocol\" : \"UDP\", "
        "     \"ncr-format\" : \"JSON\", "
        "     \"always-include-fqdn\" : true, "
        "     \"override-no-update\" : true, "
        "     \"override-client-update\" : true, "
        "     \"replace-client-name\" : true, "
        "     \"generated-prefix\" : \"test.prefix\", "
        "     \"qualifying-suffix\" : \"test.suffix.\" "
        "    }"
        "}",
        // Unknown protocol
        "{ \"dhcp-ddns\" :"
        "    {"
        "     \"enable-updates\" : true, "
1145
        "     \"server-ip\" : \"192.0.2.0\", "
1146
        "     \"server-port\" : 53001, "
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
        "     \"ncr-protocol\" : \"Bogus\", "
        "     \"ncr-format\" : \"JSON\", "
        "     \"always-include-fqdn\" : true, "
        "     \"override-no-update\" : true, "
        "     \"override-client-update\" : true, "
        "     \"replace-client-name\" : true, "
        "     \"generated-prefix\" : \"test.prefix\", "
        "     \"qualifying-suffix\" : \"test.suffix.\" "
        "    }"
        "}",
        // Unsupported protocol
        "{ \"dhcp-ddns\" :"
        "    {"
        "     \"enable-updates\" : true, "
1161
        "     \"server-ip\" : \"192.0.2.0\", "
1162
        "     \"server-port\" : 53001, "
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
        "     \"ncr-protocol\" : \"TCP\", "
        "     \"ncr-format\" : \"JSON\", "
        "     \"always-include-fqdn\" : true, "
        "     \"override-no-update\" : true, "
        "     \"override-client-update\" : true, "
        "     \"replace-client-name\" : true, "
        "     \"generated-prefix\" : \"test.prefix\", "
        "     \"qualifying-suffix\" : \"test.suffix.\" "
        "    }"
        "}",
        // Unknown format
        "{ \"dhcp-ddns\" :"
        "    {"
        "     \"enable-updates\" : true, "
1177
        "     \"server-ip\" : \"192.0.2.0\", "
1178
        "     \"server-port\" : 53001, "
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
        "     \"ncr-protocol\" : \"UDP\", "
        "     \"ncr-format\" : \"Bogus\", "
        "     \"always-include-fqdn\" : true, "
        "     \"override-no-update\" : true, "
        "     \"override-client-update\" : true, "
        "     \"replace-client-name\" : true, "
        "     \"generated-prefix\" : \"test.prefix\", "
        "     \"qualifying-suffix\" : \"test.suffix.\" "
        "    }"
        "}",
1189
        // Invalid Port
1190
1191
1192
        "{ \"dhcp-ddns\" :"
        "    {"
        "     \"enable-updates\" : true, "
1193
        "     \"server-ip\" : \"192.0.2.0\", "
1194
        "     \"server-port\" : \"bogus\", "
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
        "     \"ncr-protocol\" : \"UDP\", "
        "     \"ncr-format\" : \"JSON\", "
        "     \"always-include-fqdn\" : true, "
        "     \"override-no-update\" : true, "
        "     \"override-client-update\" : true, "
        "     \"replace-client-name\" : true, "
        "     \"generated-prefix\" : \"test.prefix\", "
        "     \"qualifying-suffix\" : \"test.suffix.\" "
        "    }"
        "}",
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
        // Mismatched server and sender IPs
        "{ \"dhcp-ddns\" :"
        "    {"
        "     \"enable-updates\" : true, "
        "     \"server-ip\" : \"192.0.2.0\", "
        "     \"server-port\" : 3432, "
        "     \"sender-ip\" : \"3001::5\", "
        "     \"sender-port\" : 3433, "
        "     \"max-queue-size\" : 2048, "
        "     \"ncr-protocol\" : \"UDP\", "
        "     \"ncr-format\" : \"JSON\", "
        "     \"always-include-fqdn\" : true, "
        "     \"override-no-update\" : true, "
        "     \"override-client-update\" : true, "
        "     \"replace-client-name\" : true, "
        "     \"generated-prefix\" : \"test.prefix\", "
        "     \"qualifying-suffix\" : \"test.suffix.\" "
        "    }"
        "}",
        // Identical server and sender IP/port
        "{ \"dhcp-ddns\" :"
        "    {"
        "     \"enable-updates\" : true, "
        "     \"server-ip\" : \"3001::5\", "
        "     \"server-port\" : 3433, "
        "     \"sender-ip\" : \"3001::5\", "
        "     \"sender-port\" : 3433, "
        "     \"max-queue-size\" : 2048, "
        "     \"ncr-protocol\" : \"UDP\", "
        "     \"ncr-format\" : \"JSON\", "
        "     \"always-include-fqdn\" : true, "
        "     \"override-no-update\" : true, "
        "     \"override-client-update\" : true, "
        "     \"replace-client-name\" : true, "
        "     \"generated-prefix\" : \"test.prefix\", "
        "     \"qualifying-suffix\" : \"test.suffix.\" "
        "    }"
        "}",
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
        // stop
        ""
    };

    // Fetch the original config.
    D2ClientConfigPtr original_config;
    ASSERT_NO_THROW(original_config = CfgMgr::instance().getD2ClientConfig());

    // Iterate through the invalid configuration strings, attempting to
    // parse each one.  They should fail to parse, but fail gracefully.
    D2ClientConfigPtr current_config;
    int i = 0;
    while (!invalid_configs[i].empty()) {
        // Verify that the configuration string parses without throwing.
        int rcode = parseConfiguration(invalid_configs[i]);

        // Verify that parse result indicates a parsing error.
        ASSERT_TRUE(rcode != 0) << "Invalid config #: " << i
                                << " should not have passed!";

        // Verify that the "official" config still matches the original config.
        ASSERT_NO_THROW(current_config =
                        CfgMgr::instance().getD2ClientConfig());
        EXPECT_EQ(*original_config, *current_config);
        ++i;
    }
}

1271
1272
1273
1274
1275
1276
1277
1278
1279
/// @brief DHCP Configuration Parser Context test fixture.
class ParserContextTest : public ::testing::Test {
public:
    /// @brief Constructor
    ParserContextTest() { }

    /// @brief Check that the storages of the specific type hold the
    /// same value.
    ///
1280
1281
    /// This function assumes that the ref_values storage holds parameter
    /// called 'foo'.
1282
1283
1284
1285
1286
1287
    ///
    /// @param ref_values A storage holding reference value. In the typical
    /// case it is a storage held in the original context, which is assigned
    /// to another context.
    /// @param values A storage holding value to be checked.
    /// @tparam ContainerType A type of the storage.
1288
    template<typename ContainerType>
1289
1290
    void checkValueEq(const boost::shared_ptr<ContainerType>& ref_values,
                      const boost::shared_ptr<ContainerType>& values) {
1291
1292
        ASSERT_NO_THROW(values->getParam("foo"));
        EXPECT_EQ(ref_values->getParam("foo"), values->getParam("foo"));
1293
1294
    }

1295
    /// @brief Check that the storages of the specific type hold the same
1296
    /// position of the parameter.
1297
    ///
1298
    /// @param name A name of the parameter to check.
1299
1300
1301
1302
1303
    /// @param ref_values A storage holding reference position. In the typical
    /// case it is a storage held in the original context, which is assigned
    /// to another context.
    /// @param values A storage holding position to be checked.
    /// @tparam ContainerType A type of the storage.
1304
    template<typename ContainerType>
1305
1306
    void checkPositionEq(const std::string& name,
                         const boost::shared_ptr<ContainerType>& ref_values,
1307
1308
                         const boost::shared_ptr<ContainerType>& values) {
        // Verify that the position is correct.
1309
1310
        EXPECT_EQ(ref_values->getPosition(name).line_,
                  values->getPosition(name).line_);
1311

1312
1313
        EXPECT_EQ(ref_values->getPosition(name).pos_,
                  values->getPosition(name).pos_);
1314

1315
1316
        EXPECT_EQ(ref_values->getPosition(name).file_,
                  values->getPosition(name).file_);
1317
1318
    }

1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
    /// @brief Check that the storages of the specific type hold different
    /// value.
    ///
    /// This function assumes that the ref_values storage holds exactly
    /// one parameter called 'foo'.
    ///
    /// @param ref_values A storage holding reference value. In the typical
    /// case it is a storage held in the original context, which is assigned
    /// to another context.
    /// @param values A storage holding value to be checked.
    /// @tparam ContainerType A type of the storage.
    /// @tparam ValueType A type of the value in the container.
1331
    template<typename ContainerType>
1332
1333
    void checkValueNeq(const boost::shared_ptr<ContainerType>& ref_values,
                       const boost::shared_ptr<ContainerType>& values) {
1334
1335
        ASSERT_NO_THROW(values->getParam("foo"));
        EXPECT_NE(ref_values->getParam("foo"), values->getParam("foo"));
1336
1337
    }

1338
    /// @brief Check that the storages of the specific type hold different
1339
1340
    /// position.
    ///
1341
    /// @param name A name of the parameter to be checked.
1342
1343
1344
1345
1346
    /// @param ref_values A storage holding reference position. In the typical
    /// case it is a storage held in the original context, which is assigned
    /// to another context.
    /// @param values A storage holding position to be checked.
    /// @tparam ContainerType A type of the storage.
1347
    template<typename ContainerType>
1348
1349
    void checkPositionNeq(const std::string& name,
                          const boost::shared_ptr<ContainerType>& ref_values,
1350
1351
                          const boost::shared_ptr<ContainerType>& values) {
        // At least one of the position fields must be different.
1352
1353
1354
1355
1356
1357
        EXPECT_TRUE((ref_values->getPosition(name).line_ !=
                     values->getPosition(name).line_) ||
                    (ref_values->getPosition(name).pos_ !=
                     values->getPosition(name).pos_) ||
                    (ref_values->getPosition(name).file_ !=
                     values->getPosition(name).file_));
1358
1359
    }

1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
    /// @brief Test copy constructor or assignment operator when values
    /// being copied are NULL.
    ///
    /// @param copy Indicates that copy constructor should be tested
    /// (if true), or assignment operator (if false).
    void testCopyAssignmentNull(const bool copy) {
        ParserContext ctx(Option::V6);
        // Release all pointers in the context.
        ctx.boolean_values_.reset();
        ctx.uint32_values_.reset();
        ctx.string_values_.reset();
        ctx.hooks_libraries_.reset();

        // Even if the fields of the context are NULL, it should get
        // copied.
        ParserContextPtr ctx_new(new ParserContext(Option::V6));
        if (copy) {
            ASSERT_NO_THROW(ctx_new.reset(new ParserContext(ctx)));
        } else {
            *ctx_new = ctx;
        }

        // The resulting context has its fields equal to NULL.
        EXPECT_FALSE(ctx_new->boolean_values_);
        EXPECT_FALSE(ctx_new->uint32_values_);
        EXPECT_FALSE(ctx_new->string_values_);
        EXPECT_FALSE(ctx_new->hooks_libraries_);

    }

    /// @brief Test copy constructor or assignment operator.
    ///
    /// @param copy Indicates that copy constructor should be tested (if true),
    /// or assignment operator (if false).
    void testCopyAssignment(const bool copy) {
        // Create new context. It will be later copied/assigned to another
        // context.
        ParserContext ctx(Option::V6);

        // Set boolean parameter 'foo'.
        ASSERT_TRUE(ctx.boolean_values_);
1401
1402
        ctx.boolean_values_->setParam("foo", true,
                                      Element::Position("kea.conf", 123, 234));
1403

1404
1405
1406
1407
1408
1409
1410
1411
1412
        // Set various parameters to test that position is copied between
        // contexts.
        ctx.boolean_values_->setParam("pos0", true,
                                      Element::Position("kea.conf", 1, 2));
        ctx.boolean_values_->setParam("pos1", true,
                                      Element::Position("kea.conf", 10, 20));
        ctx.boolean_values_->setParam("pos2", true,
                                      Element::Position("kea.conf", 100, 200));

1413
1414
        // Set uint32 parameter 'foo'.
        ASSERT_TRUE(ctx.uint32_values_);
1415
1416
        ctx.uint32_values_->setParam("foo", 123,
                                     Element::Position("kea.conf", 123, 234));
1417

1418
1419
1420
1421
1422
1423
1424
1425
1426
        // Set various parameters to test that position is copied between
        // contexts.
        ctx.uint32_values_->setParam("pos0", 123,
                                      Element::Position("kea.conf", 1, 2));
        ctx.uint32_values_->setParam("pos1", 123,
                                      Element::Position("kea.conf", 10, 20));
        ctx.uint32_values_->setParam("pos2", 123,
                                      Element::Position("kea.conf", 100, 200));

1427
1428
        // Ser string parameter 'foo'.
        ASSERT_TRUE(ctx.string_values_);
1429
1430
        ctx.string_values_->setParam("foo", "some string",
                                     Element::Position("kea.conf", 123, 234));
1431

1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
        // Set various parameters to test that position is copied between
        // contexts.
        ctx.string_values_->setParam("pos0", "some string",
                                      Element::Position("kea.conf", 1, 2));
        ctx.string_values_->setParam("pos1", "some string",
                                      Element::Position("kea.conf", 10, 20));
        ctx.string_values_->setParam("pos2", "some string",
                                      Element::Position("kea.conf", 100, 200));


1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
        // Allocate container for hooks libraries and add one library name.
        ctx.hooks_libraries_.reset(new std::vector<std::string>());
        ctx.hooks_libraries_->push_back("library1");

        // We will use ctx_new to assign another context to it or copy
        // construct.
        ParserContextPtr ctx_new(new ParserContext(Option::V4));;
        if (copy) {
            ctx_new.reset(new ParserContext(ctx));
        } else {
            *ctx_new = ctx;
        }

        // New context has the same boolean value.
        ASSERT_TRUE(ctx_new->boolean_values_);
        {
            SCOPED_TRACE("Check that boolean values are equal in both"
                         " contexts");
1460
            checkValueEq(ctx.boolean_values_, ctx_new->boolean_values_);
1461
1462
        }

1463
        // New context has the same boolean values' positions.
1464
1465
1466
        {
            SCOPED_TRACE("Check that positions of boolean values are equal"
                         " in both contexts");
1467
1468
1469
1470
1471
1472
            checkPositionEq("pos0", ctx.boolean_values_,
                            ctx_new->boolean_values_);
            checkPositionEq("pos1", ctx.boolean_values_,
                            ctx_new->boolean_values_);
            checkPositionEq("pos2", ctx.boolean_values_,
                            ctx_new->boolean_values_);
1473
1474
        }

1475
1476
1477
1478
1479
        // New context has the same uint32 value.
        ASSERT_TRUE(ctx_new->uint32_values_);
        {
            SCOPED_TRACE("Check that uint32_t values are equal in both"
                         " contexts");
1480
            checkValueEq(ctx.uint32_values_, ctx_new->uint32_values_);
1481
1482
        }

1483
1484
1485
1486
        // New context has the same uint32 values' positions.
        {
            SCOPED_TRACE("Check that positions of uint32 values are equal"
                         " in both contexts");
1487
1488
1489
1490
1491
1492
            checkPositionEq("pos0", ctx.uint32_values_,
                            ctx_new->uint32_values_);
            checkPositionEq("pos1", ctx.uint32_values_,
                            ctx_new->uint32_values_);
            checkPositionEq("pos2", ctx.uint32_values_,
                            ctx_new->uint32_values_);
1493
1494
        }

1495
1496
1497
1498
        // New context has the same uint32 value position.
        {
            SCOPED_TRACE("Check that positions of uint32_t values are equal"
                         " in both contexts");