config_parser_unittest.cc 130 KB
Newer Older
1
// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//
// 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>
16
17
18
19

#include <config/ccsession.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option6_ia.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
20
#include <dhcp/iface_mgr.h>
21
22
#include <dhcp/option_custom.h>
#include <dhcp/option_int.h>
23
#include <dhcp/tests/iface_mgr_test_config.h>
24
#include <dhcp6/json_config_parser.h>
25
#include <dhcp6/dhcp6_srv.h>
26
#include <dhcpsrv/addr_utilities.h>
27
#include <dhcpsrv/cfgmgr.h>
28
#include <dhcpsrv/cfg_hosts.h>
29
#include <dhcpsrv/subnet.h>
30
#include <dhcpsrv/subnet_selector.h>
31
#include <dhcpsrv/testutils/config_result_check.h>
32
33
#include <hooks/hooks_manager.h>

34
#include "test_data_files_config.h"
35
36
#include "test_libraries.h"
#include "marker_file.h"
37

38
#include <boost/foreach.hpp>
39
#include <gtest/gtest.h>
40

41
42
#include <fstream>
#include <iostream>
43
#include <fstream>
44
#include <sstream>
45
46
#include <string>
#include <vector>
47

48
#include <arpa/inet.h>
49
#include <unistd.h>
50
51
52
53

using namespace isc;
using namespace isc::asiolink;
using namespace isc::config;
54
55
using namespace isc::data;
using namespace isc::dhcp;
56
using namespace isc::dhcp::test;
57
using namespace isc::hooks;
58
using namespace std;
59
60
61

namespace {

62
63
64
65
66
67
68
69
70
71
72
std::string specfile(const std::string& name) {
    return (std::string(DHCP6_SRC_DIR) + "/" + name);
}

/// @brief Tests that the spec file is valid.
/// Verifies that the DHCP6 configuration specification file is valid.
TEST(Dhcp6SpecTest, basicSpec) {
    ASSERT_NO_THROW(isc::config::
                    moduleSpecFromFile(specfile("dhcp6.spec")));
}

73
74
class Dhcp6ParserTest : public ::testing::Test {
public:
Stephen Morris's avatar
Stephen Morris committed
75
76
    Dhcp6ParserTest() :rcode_(-1), srv_(0) {
        // srv_(0) means to not open any sockets. We don't want to
77
78
        // deal with sockets here, just check if configuration handling
        // is sane.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
79

80
81
        const IfaceMgr::IfaceCollection& ifaces =
            IfaceMgr::instance().getIfaces();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
82
83
84

        // There must be some interface detected
        if (ifaces.empty()) {
85
            // We can't use ASSERT in constructor
Tomek Mrugalski's avatar
Tomek Mrugalski committed
86
87
88
89
90
            ADD_FAILURE() << "No interfaces detected.";
        }

        valid_iface_ = ifaces.begin()->getName();
        bogus_iface_ = "nonexisting0";
91
92
93

        if (IfaceMgr::instance().getIface(bogus_iface_)) {
            ADD_FAILURE() << "The '" << bogus_iface_ << "' exists on this system"
94
95
                          << " while the test assumes that it doesn't, to execute"
                          << " some negative scenarios. Can't continue this test.";
96
        }
97
98
99

        // Reset configuration for each test.
        resetConfiguration();
100
101
    }

102
103
104
105
106
107
108
109
110
    // Check that no hooks libraries are loaded.  This is a pre-condition for
    // a number of tests, so is checked in one place.  As this uses an
    // ASSERT call - and it is not clear from the documentation that Gtest
    // predicates can be used in a constructor - the check is placed in SetUp.
    void SetUp() {
        std::vector<std::string> libraries = HooksManager::getLibraryNames();
        ASSERT_TRUE(libraries.empty());
    }

111
    ~Dhcp6ParserTest() {
112
113
        // Reset configuration database after each test.
        resetConfiguration();
114
115
116
117

        // ... and delete the hooks library marker files if present
        unlink(LOAD_MARKER_FILE);
        unlink(UNLOAD_MARKER_FILE);
118
119
    };

120
121
122
123
124
125
126
127
128
    // Checks if config_result (result of DHCP server configuration) has
    // expected code (0 for success, other for failures).
    // Also stores result in rcode_ and comment_.
    void checkResult(ConstElementPtr status, int expected_code) {
        ASSERT_TRUE(status);
        comment_ = parseAnswer(rcode_, status);
        EXPECT_EQ(expected_code, rcode_);
    }

129
130
131
    /// @brief Create the simple configuration with single option.
    ///
    /// This function allows to set one of the parameters that configure
132
133
    /// option value. These parameters are: "name", "code", "data" and
    /// "csv-format".
134
    ///
135
    /// @param param_value string holding option parameter value to be
136
137
138
139
140
    /// injected into the configuration string.
    /// @param parameter name of the parameter to be configured with
    /// param value.
    std::string createConfigWithOption(const std::string& param_value,
                                       const std::string& parameter) {
141
142
143
        std::map<std::string, std::string> params;
        if (parameter == "name") {
            params["name"] = param_value;
144
145
            params["space"] = "dhcp6";
            params["code"] = "38";
146
            params["data"] = "ABCDEF0105";
147
            params["csv-format"] = "False";
148
        } else if (parameter == "space") {
149
150
151
            params["name"] = "subscriber-id";
            params["space"] = param_value;
            params["code"] = "38";
152
            params["data"] = "ABCDEF0105";
153
            params["csv-format"] = "False";
154
        } else if (parameter == "code") {
155
156
            params["name"] = "subscriber-id";
            params["space"] = "dhcp6";
157
            params["code"] = param_value;
158
            params["data"] = "ABCDEF0105";
159
            params["csv-format"] = "False";
160
        } else if (parameter == "data") {
161
162
163
            params["name"] = "subscriber-id";
            params["space"] = "dhcp6";
            params["code"] = "38";
164
            params["data"] = param_value;
165
166
            params["csv-format"] = "False";
        } else if (parameter == "csv-format") {
167
168
169
            params["name"] = "subscriber-id";
            params["space"] = "dhcp6";
            params["code"] = "38";
170
            params["data"] = "ABCDEF0105";
171
            params["csv-format"] = param_value;
172
173
174
175
        }
        return (createConfigWithOption(params));
    }

176
177
178
179
180
181
182
183
    /// @brief Create simple configuration with single option.
    ///
    /// This function creates a configuration for a single option with
    /// custom values for all parameters that describe the option.
    ///
    /// @params params map holding parameters and their values.
    /// @return configuration string containing custom values of parameters
    /// describing an option.
Mukund Sivaraman's avatar
Mukund Sivaraman committed
184
185
186
    std::string createConfigWithOption(const std::map<std::string,
                                       std::string>& params)
    {
187
        std::ostringstream stream;
188
        stream << "{ \"interfaces\": [ \"*\" ],"
189
190
191
            "\"preferred-lifetime\": 3000,"
            "\"rebind-timer\": 2000, "
            "\"renew-timer\": 1000, "
192
193
194
195
196
197
198
199
200
            "\"option-def\": [ {"
            "  \"name\": \"bool-option\","
            "  \"code\": 1000,"
            "  \"type\": \"boolean\","
            "  \"array\": False,"
            "  \"record-types\": \"\","
            "  \"space\": \"dhcp6\","
            "  \"encapsulate\": \"\""
            "} ],"
201
            "\"subnet6\": [ { "
202
            "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
203
204
            "    \"subnet\": \"2001:db8:1::/64\", "
            "    \"option-data\": [ {";
205
206
207
208
209
210
        bool first = true;
        typedef std::pair<std::string, std::string> ParamPair;
        BOOST_FOREACH(ParamPair param, params) {
            if (!first) {
                stream << ", ";
            } else {
211
                // cppcheck-suppress unreadVariable
212
213
214
215
                first = false;
            }
            if (param.first == "name") {
                stream << "\"name\": \"" << param.second << "\"";
216
217
            } else if (param.first == "space") {
                stream << "\"space\": \"" << param.second << "\"";
218
            } else if (param.first == "code") {
219
                stream << "\"code\": " << param.second;;
220
221
            } else if (param.first == "data") {
                stream << "\"data\": \"" << param.second << "\"";
222
223
            } else if (param.first == "csv-format") {
                stream << "\"csv-format\": " << param.second;
224
            }
225
226
227
228
229
230
231
232
        }
        stream <<
            "        } ]"
            " } ],"
            "\"valid-lifetime\": 4000 }";
        return (stream.str());
    }

233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
    /// @brief Returns an option from the subnet.
    ///
    /// This function returns an option from a subnet to which the
    /// specified subnet address belongs. The option is identified
    /// by its code.
    ///
    /// @param subnet_address Address which belongs to the subnet from
    /// which the option is to be returned.
    /// @param option_code Code of the option to be returned.
    /// @param expected_options_count Expected number of options in
    /// the particular subnet.
    ///
    /// @return Descriptor of the option. If the descriptor holds a
    /// NULL option pointer, it means that there was no such option
    /// in the subnet.
248
    OptionDescriptor
249
250
251
    getOptionFromSubnet(const IOAddress& subnet_address,
                        const uint16_t option_code,
                        const uint16_t expected_options_count = 1) {
252
253
        Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
            selectSubnet(subnet_address, classify_);
254
255
256
257
        if (!subnet) {
            /// @todo replace toText() with the use of operator <<.
            ADD_FAILURE() << "A subnet for the specified address "
                          << subnet_address.toText()
258
                          << " does not exist in Config Manager";
259
        }
260
        OptionContainerPtr options =
261
            subnet->getCfgOption()->getAll("dhcp6");
262
263
264
265
266
267
268
269
        if (expected_options_count != options->size()) {
            ADD_FAILURE() << "The number of options in the subnet '"
                          << subnet_address.toText() << "' is different "
                " than expected number of options '"
                          << expected_options_count << "'";
        }

        // Get the search index. Index #1 is to search using option code.
270
        const OptionContainerTypeIndex& idx = options->get<1>();
271
272
273
274

        // Get the options for specified index. Expecting one option to be
        // returned but in theory we may have multiple options with the same
        // code so we get the range.
275
276
        std::pair<OptionContainerTypeIndex::const_iterator,
                  OptionContainerTypeIndex::const_iterator> range =
277
278
279
280
281
282
283
            idx.equal_range(option_code);
        if (std::distance(range.first, range.second) > 1) {
            ADD_FAILURE() << "There is more than one option having the"
                " option code '" << option_code << "' in a subnet '"
                          << subnet_address.toText() << "'. Expected "
                " at most one option";
        } else if (std::distance(range.first, range.second) == 0) {
284
            return (OptionDescriptor(OptionPtr(), false));
285
286
287
288
289
        }

        return (*range.first);
    }

290
    /// @brief Parse and Execute configuration
291
    ///
292
293
294
295
296
297
298
299
300
301
302
    /// Parses a configuration and executes a configuration of the server.
    /// If the operation fails, the current test will register a failure.
    ///
    /// @param config Configuration to parse
    /// @param operation Operation being performed.  In the case of an error,
    ///        the error text will include the string "unable to <operation>.".
    ///
    /// @return true if the configuration succeeded, false if not.  In the
    ///         latter case, a failure will have been added to the current test.
    bool
    executeConfiguration(const std::string& config, const char* operation) {
303
304
305
        ConstElementPtr status;
        try {
            ElementPtr json = Element::fromJSON(config);
306
            status = configureDhcp6Server(srv_, json);
307

308
        } catch (const std::exception& ex) {
309
310
            ADD_FAILURE() << "Unable to " << operation << ". "
                   << "The following configuration was used: " << std::endl
311
312
313
                   << config << std::endl
                   << " and the following error message was returned:"
                   << ex.what() << std::endl;
314
            return (false);
315
316
        }

317
        // The status object must not be NULL
318
        if (!status) {
319
320
321
            ADD_FAILURE() << "Unable to " << operation << ". "
                   << "The configuration function returned a null pointer.";
            return (false);
322
        }
323
324
325
326

        // Store the answer if we need it.

        // Returned value should be 0 (configuration success)
327
328
        comment_ = parseAnswer(rcode_, status);
        if (rcode_ != 0) {
329
330
331
332
333
334
335
336
            string reason = "";
            if (comment_) {
                reason = string(" (") + comment_->stringValue() + string(")");
            }
            ADD_FAILURE() << "Unable to " << operation << ". "
                   << "The configuration function returned error code "
                   << rcode_ << reason;
            return (false);
337
        }
338
339
340
341
342
343
344
345
346
347
348

        return (true);
    }

    /// @brief Reset configuration database.
    ///
    /// This function resets configuration data base by removing all subnets
    /// option-data, and hooks libraries. The reset must be performed after each
    /// test to make sure that contents of the database do not affect the
    /// results of subsequent tests.
    void resetConfiguration() {
349
        string config = "{ \"interfaces\": [ \"*\" ],"
350
351
352
353
354
355
            "\"hooks-libraries\": [ ],"
            "\"preferred-lifetime\": 3000,"
            "\"rebind-timer\": 2000, "
            "\"renew-timer\": 1000, "
            "\"valid-lifetime\": 4000, "
            "\"subnet6\": [ ], "
356
            "\"dhcp-ddns\": { \"enable-updates\" : false }, "
357
358
359
360
            "\"option-def\": [ ], "
            "\"option-data\": [ ] }";
        static_cast<void>(executeConfiguration(config,
                                               "reset configuration database"));
361
362
363
364
        // The default setting is to listen on all interfaces. In order to
        // properly test interface configuration we disable listening on
        // all interfaces before each test and later check that this setting
        // has been overriden by the configuration used in the test.
365
        CfgMgr::instance().clear();
366
367
        // Create fresh context.
        globalContext()->copyContext(ParserContext(Option::V6));
368
369
    }

370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
    /// @brief Test invalid option parameter value.
    ///
    /// This test function constructs the simple configuration
    /// string and injects invalid option configuration into it.
    /// It expects that parser will fail with provided option code.
    ///
    /// @param param_value string holding invalid option parameter value
    /// to be injected into configuration string.
    /// @param parameter name of the parameter to be configured with
    /// param_value (can be any of "name", "code", "data")
    void testInvalidOptionParam(const std::string& param_value,
                                const std::string& parameter) {
        ConstElementPtr x;
        std::string config = createConfigWithOption(param_value, parameter);
        ElementPtr json = Element::fromJSON(config);
Stephen Morris's avatar
Stephen Morris committed
385
        EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
386
387
        checkResult(x, 1);
        EXPECT_TRUE(errorContainsPosition(x, "<string>"));
388
        CfgMgr::instance().clear();
389
390
    }

391
392
393
394
395
396
397
398
399
400
401
402
403
    /// @brief Test invalid option paramater value.
    ///
    /// This test function constructs the simple configuration
    /// string and injects invalid option configuration into it.
    /// It expects that parser will fail with provided option code.
    ///
    /// @param params Map of parameters defining an option.
    void
    testInvalidOptionParam(const std::map<std::string, std::string>& params) {
        ConstElementPtr x;
        std::string config = createConfigWithOption(params);
        ElementPtr json = Element::fromJSON(config);
        EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
404
405
        checkResult(x, 1);
        EXPECT_TRUE(errorContainsPosition(x, "<string>"));
406
        CfgMgr::instance().clear();
407
408
    }

409
410
411
412
413
414
415
    /// @brief Test option against given code and data.
    ///
    /// @param option_desc option descriptor that carries the option to
    /// be tested.
    /// @param expected_code expected code of the option.
    /// @param expected_data expected data in the option.
    /// @param expected_data_len length of the reference data.
416
417
    /// @param extra_data if true extra data is allowed in an option
    /// after tested data.
418
    void testOption(const OptionDescriptor& option_desc,
419
                    uint16_t expected_code, const uint8_t* expected_data,
420
421
                    size_t expected_data_len,
                    bool extra_data = false) {
422
        // Check if option descriptor contains valid option pointer.
423
        ASSERT_TRUE(option_desc.option_);
424
        // Verify option type.
425
        EXPECT_EQ(expected_code, option_desc.option_->getType());
426
427
428
429
430
        // We may have many different option types being created. Some of them
        // have dedicated classes derived from Option class. In such case if
        // we want to verify the option contents against expected_data we have
        // to prepare raw buffer with the contents of the option. The easiest
        // way is to call pack() which will prepare on-wire data.
431
432
        util::OutputBuffer buf(option_desc.option_->getData().size());
        option_desc.option_->pack(buf);
433
434
435
436
437
        if (extra_data) {
            // The length of the buffer must be at least equal to size of the
            // reference data but it can sometimes be greater than that. This is
            // because some options carry suboptions that increase the overall
            // length.
438
            ASSERT_GE(buf.getLength() - option_desc.option_->getHeaderLen(),
439
440
                      expected_data_len);
        } else {
441
            ASSERT_EQ(buf.getLength() - option_desc.option_->getHeaderLen(),
442
443
                      expected_data_len);
        }
444
        // Verify that the data is correct. Do not verify suboptions and a header.
445
        const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
446
        EXPECT_EQ(0, memcmp(expected_data, data + option_desc.option_->getHeaderLen(),
447
                            expected_data_len));
448
449
    }

450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
    /// @brief Test option configuration.
    ///
    /// This function creates a configuration for a specified option using
    /// a map of parameters specified as the argument. The map holds
    /// name/value pairs which identifies option's configuration parameters:
    /// - name
    /// - space
    /// - code
    /// - data
    /// - csv-format.
    /// This function applies a new server configuration and checks that the
    /// option being configured is inserted into CfgMgr. The raw contents of
    /// this option are compared with the binary data specified as expected
    /// data passed to this function.
    ///
    /// @param params Map of parameters defining an option.
    /// @param option_code Option code.
    /// @param expected_data Array containing binary data expected to be stored
    /// in the configured option.
    /// @param expected_data_len Length of the array holding reference data.
    void testConfiguration(const std::map<std::string, std::string>& params,
                           const uint16_t option_code,
                           const uint8_t* expected_data,
                           const size_t expected_data_len) {
474
475
        CfgMgr::instance().clear();

476
477
        std::string config = createConfigWithOption(params);
        ASSERT_TRUE(executeConfiguration(config, "parse option configuration"));
478

479
        // The subnet should now hold one option with the specified code.
480
        OptionDescriptor desc =
481
            getOptionFromSubnet(IOAddress("2001:db8:1::5"), option_code);
482
        ASSERT_TRUE(desc.option_);
483
        testOption(desc, option_code, expected_data, expected_data_len);
484
        CfgMgr::instance().clear();
485
486
    }

487
488
489
490
491
    int rcode_;          ///< Return code (see @ref isc::config::parseAnswer)
    Dhcpv6Srv srv_;      ///< Instance of the Dhcp6Srv used during tests
    ConstElementPtr comment_; ///< Comment (see @ref isc::config::parseAnswer)
    string valid_iface_; ///< Valid network interface name (present in system)
    string bogus_iface_; ///< invalid network interface name (not in system)
492
    isc::dhcp::ClientClasses classify_; ///< used in client classification
493
494
};

495
496
497
// Goal of this test is a verification if a very simple config update
// with just a bumped version number. That's the simplest possible
// config update.
498
499
500
501
TEST_F(Dhcp6ParserTest, version) {

    ConstElementPtr x;

Stephen Morris's avatar
Stephen Morris committed
502
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_,
503
504
505
                    Element::fromJSON("{\"version\": 0}")));

    // returned value must be 0 (configuration accepted)
506
    checkResult(x, 0);
507
508
}

509
510
/// The goal of this test is to verify that the code accepts only
/// valid commands and malformed or unsupported parameters are rejected.
511
TEST_F(Dhcp6ParserTest, bogusCommand) {
512
513
514

    ConstElementPtr x;

Stephen Morris's avatar
Stephen Morris committed
515
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_,
516
517
518
                    Element::fromJSON("{\"bogus\": 5}")));

    // returned value must be 1 (configuration parse error)
519
    checkResult(x, 1);
520
521
}

522
523
/// The goal of this test is to verify if configuration without any
/// subnets defined can be accepted.
524
TEST_F(Dhcp6ParserTest, emptySubnet) {
525

526
    ConstElementPtr status;
527

Stephen Morris's avatar
Stephen Morris committed
528
    EXPECT_NO_THROW(status = configureDhcp6Server(srv_,
529
                    Element::fromJSON("{ \"interfaces\": [ \"*\" ],"
530
531
532
533
534
535
                                      "\"preferred-lifetime\": 3000,"
                                      "\"rebind-timer\": 2000, "
                                      "\"renew-timer\": 1000, "
                                      "\"subnet6\": [  ], "
                                      "\"valid-lifetime\": 4000 }")));

536
    // returned value should be 0 (success)
537
    checkResult(status, 0);
538
539
}

540
541
/// The goal of this test is to verify if defined subnet uses global
/// parameter timer definitions.
542
TEST_F(Dhcp6ParserTest, subnetGlobalDefaults) {
543

544
    ConstElementPtr status;
545

546
    string config = "{ \"interfaces\": [ \"*\" ],"
547
548
549
550
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
551
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
552
553
554
555
556
        "    \"subnet\": \"2001:db8:1::/64\" } ],"
        "\"valid-lifetime\": 4000 }";

    ElementPtr json = Element::fromJSON(config);

Stephen Morris's avatar
Stephen Morris committed
557
    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
558

559
    // check if returned status is OK
560
    checkResult(status, 0);
561

562
563
    // Now check if the configuration was indeed handled and we have
    // expected pool configured.
564
565
    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
566
567
568
569
570
    ASSERT_TRUE(subnet);
    EXPECT_EQ(1000, subnet->getT1());
    EXPECT_EQ(2000, subnet->getT2());
    EXPECT_EQ(3000, subnet->getPreferred());
    EXPECT_EQ(4000, subnet->getValid());
571
572
573
574
575

    // Check that subnet-id is 1
    EXPECT_EQ(1, subnet->getID());
}

576
// This test checks that multiple subnets can be defined and handled properly.
577
578
TEST_F(Dhcp6ParserTest, multipleSubnets) {
    ConstElementPtr x;
579
580
    // Collection of four subnets for which ids should be autogenerated
    // - ids are unspecified or set to 0.
581
582
583
584
585
    string config = "{ \"interfaces\": [ \"*\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
586
        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
587
588
589
        "    \"subnet\": \"2001:db8:1::/64\" "
        " },"
        " {"
590
        "    \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
591
592
        "    \"subnet\": \"2001:db8:2::/64\", "
        "    \"id\": 0"
593
594
        " },"
        " {"
595
        "    \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
596
597
598
        "    \"subnet\": \"2001:db8:3::/64\" "
        " },"
        " {"
599
        "    \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
600
601
602
603
604
605
        "    \"subnet\": \"2001:db8:4::/64\" "
        " } ],"
        "\"valid-lifetime\": 4000 }";

    int cnt = 0; // Number of reconfigurations

606
    ElementPtr json = Element::fromJSON(config);
607

608
    do {
609
        EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
610
        checkResult(x, 0);
611

612
613
614
615
        CfgMgr::instance().commit();

        const Subnet6Collection* subnets =
            CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
616
617
618
619
620
621
622
623
624
625
626
627
628
629
        ASSERT_TRUE(subnets);
        ASSERT_EQ(4, subnets->size()); // We expect 4 subnets

        // Check subnet-ids of each subnet (it should be monotonously increasing)
        EXPECT_EQ(1, subnets->at(0)->getID());
        EXPECT_EQ(2, subnets->at(1)->getID());
        EXPECT_EQ(3, subnets->at(2)->getID());
        EXPECT_EQ(4, subnets->at(3)->getID());

        // Repeat reconfiguration process 10 times and check that the subnet-id
        // is set to the same value. Technically, just two iterations would be
        // sufficient, but it's nice to have a test that exercises reconfiguration
        // a bit.
    } while (++cnt < 10);
630
631
}

632
633
634
// This checks that it is possible to assign arbitrary ids for subnets.
TEST_F(Dhcp6ParserTest, multipleSubnetsExplicitIDs) {
    ConstElementPtr x;
635
    // Four subnets with arbitrary subnet ids.
636
637
638
639
640
    string config = "{ \"interfaces\": [ \"*\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
641
        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
642
643
644
645
        "    \"subnet\": \"2001:db8:1::/64\", "
        "    \"id\": 1024"
        " },"
        " {"
646
        "    \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
647
648
649
650
        "    \"subnet\": \"2001:db8:2::/64\", "
        "    \"id\": 100"
        " },"
        " {"
651
        "    \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
652
653
654
655
        "    \"subnet\": \"2001:db8:3::/64\", "
        "    \"id\": 1"
        " },"
        " {"
656
        "    \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
657
658
659
660
661
662
663
        "    \"subnet\": \"2001:db8:4::/64\", "
        "    \"id\": 34"
        " } ],"
        "\"valid-lifetime\": 4000 }";

    int cnt = 0; // Number of reconfigurations

664
    ElementPtr json = Element::fromJSON(config);
665

666
    do {
667
        EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
668
        checkResult(x, 0);
669

670
671
672
673
        CfgMgr::instance().commit();

        const Subnet6Collection* subnets =
            CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
674
675
676
677
678
679
680
681
682
683
684
        ASSERT_TRUE(subnets);
        ASSERT_EQ(4, subnets->size()); // We expect 4 subnets

        // Check that subnet ids are as expected.
        EXPECT_EQ(1024, subnets->at(0)->getID());
        EXPECT_EQ(100, subnets->at(1)->getID());
        EXPECT_EQ(1, subnets->at(2)->getID());
        EXPECT_EQ(34, subnets->at(3)->getID());

        // Repeat reconfiguration process 10 times and check that the subnet-id
        // is set to the same value.
685
    } while (++cnt < 3);
686
687
688
689
690
691
692
693
694
695
696
}

// CHeck that the configuration with two subnets having the same id is rejected.
TEST_F(Dhcp6ParserTest, multipleSubnetsOverlapingIDs) {
    ConstElementPtr x;
    // Four subnets, two of them have the same id.
    string config = "{ \"interfaces\": [ \"*\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
697
        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
698
699
700
701
        "    \"subnet\": \"2001:db8:1::/64\", "
        "    \"id\": 1024"
        " },"
        " {"
702
        "    \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
703
704
705
706
        "    \"subnet\": \"2001:db8:2::/64\", "
        "    \"id\": 100"
        " },"
        " {"
707
        "    \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
708
709
710
711
        "    \"subnet\": \"2001:db8:3::/64\", "
        "    \"id\": 1024"
        " },"
        " {"
712
        "    \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
713
714
715
716
717
718
719
720
        "    \"subnet\": \"2001:db8:4::/64\", "
        "    \"id\": 34"
        " } ],"
        "\"valid-lifetime\": 4000 }";

    ElementPtr json = Element::fromJSON(config);

    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
721
    checkResult(x, 1);
722
    EXPECT_TRUE(errorContainsPosition(x, "<string>"));
723
724
725
}


Tomek Mrugalski's avatar
Tomek Mrugalski committed
726
727
728
729
730
731
732
733
734
735
736
// Goal of this test is to verify that a previously configured subnet can be
// deleted in subsequent reconfiguration.
TEST_F(Dhcp6ParserTest, reconfigureRemoveSubnet) {
    ConstElementPtr x;

    // All four subnets
    string config4 = "{ \"interfaces\": [ \"*\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
737
        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
738
739
        "    \"subnet\": \"2001:db8:1::/64\", "
        "    \"id\": 1"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
740
741
        " },"
        " {"
742
        "    \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
743
744
        "    \"subnet\": \"2001:db8:2::/64\", "
        "    \"id\": 2"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
745
746
        " },"
        " {"
747
        "    \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
748
749
        "    \"subnet\": \"2001:db8:3::/64\", "
        "    \"id\": 3"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
750
751
        " },"
        " {"
752
        "    \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
753
754
        "    \"subnet\": \"2001:db8:4::/64\", "
        "    \"id\": 4"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
755
756
757
758
759
760
761
762
763
        " } ],"
        "\"valid-lifetime\": 4000 }";

    // Three subnets (the last one removed)
    string config_first3 = "{ \"interfaces\": [ \"*\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
764
        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
765
766
        "    \"subnet\": \"2001:db8:1::/64\", "
        "    \"id\": 1"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
767
768
        " },"
        " {"
769
        "    \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
770
771
        "    \"subnet\": \"2001:db8:2::/64\", "
        "    \"id\": 2"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
772
773
        " },"
        " {"
774
        "    \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
775
776
        "    \"subnet\": \"2001:db8:3::/64\", "
        "    \"id\": 3"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
777
778
779
780
781
782
783
784
785
        " } ],"
        "\"valid-lifetime\": 4000 }";

    // Second subnet removed
    string config_second_removed = "{ \"interfaces\": [ \"*\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
786
        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
787
788
        "    \"subnet\": \"2001:db8:1::/64\", "
        "    \"id\": 1"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
789
790
        " },"
        " {"
791
        "    \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
792
793
        "    \"subnet\": \"2001:db8:3::/64\", "
        "    \"id\": 3"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
794
795
        " },"
        " {"
796
        "    \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
797
798
        "    \"subnet\": \"2001:db8:4::/64\", "
        "    \"id\": 4"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
799
800
801
802
803
804
805
806
        " } ],"
        "\"valid-lifetime\": 4000 }";

    // CASE 1: Configure 4 subnets, then reconfigure and remove the
    // last one.

    ElementPtr json = Element::fromJSON(config4);
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
807
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
808

809
810
811
812
    CfgMgr::instance().commit();

    const Subnet6Collection* subnets =
        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
813
814
815
816
817
818
    ASSERT_TRUE(subnets);
    ASSERT_EQ(4, subnets->size()); // We expect 4 subnets

    // Do the reconfiguration (the last subnet is removed)
    json = Element::fromJSON(config_first3);
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
819
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
820

821
822
823
    CfgMgr::instance().commit();

    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
824
825
826
827
828
829
830
831
832
833
834
835
    ASSERT_TRUE(subnets);
    ASSERT_EQ(3, subnets->size()); // We expect 3 subnets now (4th is removed)

    EXPECT_EQ(1, subnets->at(0)->getID());
    EXPECT_EQ(2, subnets->at(1)->getID());
    EXPECT_EQ(3, subnets->at(2)->getID());

    /// CASE 2: Configure 4 subnets, then reconfigure and remove one
    /// from in between (not first, not last)

    json = Element::fromJSON(config4);
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
836
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
837

838
839
    CfgMgr::instance().commit();

Tomek Mrugalski's avatar
Tomek Mrugalski committed
840
841
842
    // Do reconfiguration
    json = Element::fromJSON(config_second_removed);
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
843
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
844

845
846
847
    CfgMgr::instance().commit();

    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
848
849
850
851
852
853
854
855
856
857
858
    ASSERT_TRUE(subnets);
    ASSERT_EQ(3, subnets->size()); // We expect 4 subnets

    EXPECT_EQ(1, subnets->at(0)->getID());
    // The second subnet (with subnet-id = 2) is no longer there
    EXPECT_EQ(3, subnets->at(1)->getID());
    EXPECT_EQ(4, subnets->at(2)->getID());
}



859
860
// This test checks if it is possible to override global values
// on a per subnet basis.
861
TEST_F(Dhcp6ParserTest, subnetLocal) {
862

863
    ConstElementPtr status;
864

865
    string config = "{ \"interfaces\": [ \"*\" ],"
866
867
868
869
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
870
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
871
872
873
874
875
876
877
878
879
        "    \"renew-timer\": 1, "
        "    \"rebind-timer\": 2, "
        "    \"preferred-lifetime\": 3,"
        "    \"valid-lifetime\": 4,"
        "    \"subnet\": \"2001:db8:1::/64\" } ],"
        "\"valid-lifetime\": 4000 }";

    ElementPtr json = Element::fromJSON(config);

Stephen Morris's avatar
Stephen Morris committed
880
    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
881

882
    // returned value should be 0 (configuration success)
883
    checkResult(status, 0);
884

885
886
    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
887
888
889
890
891
892
893
    ASSERT_TRUE(subnet);
    EXPECT_EQ(1, subnet->getT1());
    EXPECT_EQ(2, subnet->getT2());
    EXPECT_EQ(3, subnet->getPreferred());
    EXPECT_EQ(4, subnet->getValid());
}

894
895
896
897
898
899
// This test checks if it is possible to define a subnet with an
// interface defined.
TEST_F(Dhcp6ParserTest, subnetInterface) {

    ConstElementPtr status;

Tomek Mrugalski's avatar
Tomek Mrugalski committed
900
901
    // There should be at least one interface

902
    string config = "{ \"interfaces\": [ \"*\" ],"
903
904
905
906
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
907
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
908
        "    \"interface\": \"" + valid_iface_ + "\","
909
910
911
912
913
914
915
916
917
        "    \"subnet\": \"2001:db8:1::/64\" } ],"
        "\"valid-lifetime\": 4000 }";
    cout << config << endl;

    ElementPtr json = Element::fromJSON(config);

    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));

    // returned value should be 0 (configuration success)
918
    checkResult(status, 0);
919

920
921
    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
922
    ASSERT_TRUE(subnet);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
923
924
925
926
927
928
929
930
931
932
933
    EXPECT_EQ(valid_iface_, subnet->getIface());
}

// This test checks if invalid interface name will be rejected in
// Subnet6 definition.
TEST_F(Dhcp6ParserTest, subnetInterfaceBogus) {

    ConstElementPtr status;

    // There should be at least one interface

934
    string config = "{ \"interfaces\": [ \"*\" ],"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
935
936
937
938
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
939
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
940
941
942
943
944
945
946
947
948
949
        "    \"interface\": \"" + bogus_iface_ + "\","
        "    \"subnet\": \"2001:db8:1::/64\" } ],"
        "\"valid-lifetime\": 4000 }";
    cout << config << endl;

    ElementPtr json = Element::fromJSON(config);

    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));

    // returned value should be 1 (configuration error)
950
951
    checkResult(status, 1);
    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
952

953
954
    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
955
    EXPECT_FALSE(subnet);
956
957
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
958

959
960
961
962
963
964
// This test checks if it is not allowed to define global interface
// parameter.
TEST_F(Dhcp6ParserTest, interfaceGlobal) {

    ConstElementPtr status;

965
    string config = "{ \"interfaces\": [ \"*\" ],"
966
967
968
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
969
        "\"interface\": \"" + valid_iface_ + "\"," // Not valid. Can be defined in subnet only
970
        "\"subnet6\": [ { "
971
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
972
973
974
975
976
977
978
979
980
        "    \"subnet\": \"2001:db8:1::/64\" } ],"
        "\"valid-lifetime\": 4000 }";
    cout << config << endl;

    ElementPtr json = Element::fromJSON(config);

    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));

    // returned value should be 1 (parse error)
981
982
    checkResult(status, 1);
    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
983
984
}

985
986
987
988
989
990
991
992
993
994

// This test checks if it is possible to define a subnet with an
// interface-id option defined.
TEST_F(Dhcp6ParserTest, subnetInterfaceId) {

    const string valid_interface_id = "foobar";
    const string bogus_interface_id = "blah";

    // There should be at least one interface

995
    const string config = "{ "
996
997
998
999
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
1000
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
1001
1002
1003
1004
1005
1006
        "    \"interface-id\": \"" + valid_interface_id + "\","
        "    \"subnet\": \"2001:db8:1::/64\" } ],"
        "\"valid-lifetime\": 4000 }";

    ElementPtr json = Element::fromJSON(config);

1007
    ConstElementPtr status;
1008
1009
    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));

1010
    // Returned value should be 0 (configuration success)
1011
    checkResult(status, 0);
1012

1013
    // Try to get a subnet based on bogus interface-id option
1014
    OptionBuffer tmp(bogus_interface_id.begin(), bogus_interface_id.end());
1015
1016
1017
1018
1019
    SubnetSelector selector;
    selector.first_relay_linkaddr_ = IOAddress("5000::1");
    selector.interface_id_.reset(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
        selectSubnet(selector);
1020
1021
    EXPECT_FALSE(subnet);

1022
    // Now try to get subnet for valid interface-id value
1023
    tmp = OptionBuffer(valid_interface_id.begin(), valid_interface_id.end());
1024
1025
1026
    selector.interface_id_.reset(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
    subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
        selectSubnet(selector);
1027
    ASSERT_TRUE(subnet);
1028
    EXPECT_TRUE(selector.interface_id_->equals(subnet->getInterfaceId()));
1029
1030
1031
1032
1033
1034
1035
}


// This test checks if it is not allowed to define global interface
// parameter.
TEST_F(Dhcp6ParserTest, interfaceIdGlobal) {

1036
    const string config = "{ \"interfaces\": [ \"*\" ],"
1037
1038
1039
1040
1041
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"interface-id\": \"foobar\"," // Not valid. Can be defined in subnet only
        "\"subnet6\": [ { "
1042
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
1043
1044
1045
1046
1047
        "    \"subnet\": \"2001:db8:1::/64\" } ],"
        "\"valid-lifetime\": 4000 }";

    ElementPtr json = Element::fromJSON(config);

1048
    ConstElementPtr status;
1049
1050
    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));

1051
    // Returned value should be 1 (parse error)
1052
1053
    checkResult(status, 1);
    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
1054
1055
1056
1057
1058
1059
}

// This test checks if it is not possible to define a subnet with an
// interface (i.e. local subnet) and interface-id (remote subnet) defined.
TEST_F(Dhcp6ParserTest, subnetInterfaceAndInterfaceId) {

1060
    const string config = "{ \"preferred-lifetime\": 3000,"
1061
1062
1063
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
1064
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
1065
1066
1067
1068
1069
1070
1071
        "    \"interface\": \"" + valid_iface_ + "\","
        "    \"interface-id\": \"foobar\","
        "    \"subnet\": \"2001:db8:1::/64\" } ],"
        "\"valid-lifetime\": 4000 }";

    ElementPtr json = Element::fromJSON(config);

1072
    ConstElementPtr status;
1073
1074
    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));

1075
    // Returned value should be 1 (configuration error)
1076
1077
    checkResult(status, 1);
    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
1078
1079
}

1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
// This test checks that multiple pools can be defined and handled properly.
// The test defines 2 subnets, each with 2 pools.
TEST_F(Dhcp6ParserTest, multiplePools) {
    // Collection with two subnets, each with 2 pools.
    string config = "{ \"interfaces\": [ \"*\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
        "    \"pools\": [ "
        "        { \"pool\": \"2001:db8:1::/96\" },"
        "        { \"pool\": \"2001:db8:1:0:abcd::/112\" }"
        "    ],"
        "    \"subnet\": \"2001:db8:1::/64\" "
        " },"
        " {"
        "    \"pools\": [ "
        "    { \"pool\": \"2001:db8:2::1 - 2001:db8:2::ff\" },"
        "    { \"pool\": \"2001:db8:2::300 - 2001:db8:2::3ff\" }"
        "    ],"