config_parser_unittest.cc 122 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
28
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/subnet.h>
29
#include <dhcpsrv/testutils/config_result_check.h>
30
31
#include <hooks/hooks_manager.h>

32
#include "test_data_files_config.h"
33
34
#include "test_libraries.h"
#include "marker_file.h"
35

36
#include <boost/foreach.hpp>
37
#include <gtest/gtest.h>
38

39
40
#include <fstream>
#include <iostream>
41
#include <fstream>
42
#include <sstream>
43
44
#include <string>
#include <vector>
45

46
#include <arpa/inet.h>
47
#include <unistd.h>
48
49
50
51

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

namespace {

60
61
62
63
64
65
66
67
68
69
70
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")));
}

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

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

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

        valid_iface_ = ifaces.begin()->getName();
        bogus_iface_ = "nonexisting0";
89
90
91

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

        // Reset configuration for each test.
        resetConfiguration();
98
99
    }

100
101
102
103
104
105
106
107
108
    // 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());
    }

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

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

118
119
120
121
122
123
124
125
126
    // 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_);
    }

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

174
175
176
177
178
179
180
181
    /// @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
182
183
184
    std::string createConfigWithOption(const std::map<std::string,
                                       std::string>& params)
    {
185
        std::ostringstream stream;
186
        stream << "{ \"interfaces\": [ \"*\" ],"
187
188
189
            "\"preferred-lifetime\": 3000,"
            "\"rebind-timer\": 2000, "
            "\"renew-timer\": 1000, "
190
191
192
193
194
195
196
197
198
            "\"option-def\": [ {"
            "  \"name\": \"bool-option\","
            "  \"code\": 1000,"
            "  \"type\": \"boolean\","
            "  \"array\": False,"
            "  \"record-types\": \"\","
            "  \"space\": \"dhcp6\","
            "  \"encapsulate\": \"\""
            "} ],"
199
            "\"subnet6\": [ { "
200
            "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
201
202
            "    \"subnet\": \"2001:db8:1::/64\", "
            "    \"option-data\": [ {";
203
204
205
206
207
208
        bool first = true;
        typedef std::pair<std::string, std::string> ParamPair;
        BOOST_FOREACH(ParamPair param, params) {
            if (!first) {
                stream << ", ";
            } else {
209
                // cppcheck-suppress unreadVariable
210
211
212
213
                first = false;
            }
            if (param.first == "name") {
                stream << "\"name\": \"" << param.second << "\"";
214
215
            } else if (param.first == "space") {
                stream << "\"space\": \"" << param.second << "\"";
216
            } else if (param.first == "code") {
217
                stream << "\"code\": " << param.second;;
218
219
            } else if (param.first == "data") {
                stream << "\"data\": \"" << param.second << "\"";
220
221
            } else if (param.first == "csv-format") {
                stream << "\"csv-format\": " << param.second;
222
            }
223
224
225
226
227
228
229
230
        }
        stream <<
            "        } ]"
            " } ],"
            "\"valid-lifetime\": 4000 }";
        return (stream.str());
    }

231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
    /// @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.
    Subnet::OptionDescriptor
    getOptionFromSubnet(const IOAddress& subnet_address,
                        const uint16_t option_code,
                        const uint16_t expected_options_count = 1) {
250
251
        Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(subnet_address,
                                                          classify_);
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
        if (!subnet) {
            /// @todo replace toText() with the use of operator <<.
            ADD_FAILURE() << "A subnet for the specified address "
                          << subnet_address.toText()
                          << "does not exist in Config Manager";
        }
        Subnet::OptionContainerPtr options =
            subnet->getOptionDescriptors("dhcp6");
        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.
        const Subnet::OptionContainerTypeIndex& idx = options->get<1>();

        // 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.
        std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
                  Subnet::OptionContainerTypeIndex::const_iterator> range =
            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) {
            return (Subnet::OptionDescriptor(OptionPtr(), false));
        }

        return (*range.first);
    }

288
    /// @brief Parse and Execute configuration
289
    ///
290
291
292
293
294
295
296
297
298
299
300
    /// 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) {
301
302
303
        ConstElementPtr status;
        try {
            ElementPtr json = Element::fromJSON(config);
304
            status = configureDhcp6Server(srv_, json);
305
        } catch (const std::exception& ex) {
306
307
            ADD_FAILURE() << "Unable to " << operation << ". "
                   << "The following configuration was used: " << std::endl
308
309
310
                   << config << std::endl
                   << " and the following error message was returned:"
                   << ex.what() << std::endl;
311
            return (false);
312
313
        }

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

        // Store the answer if we need it.

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

        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() {
346
        string config = "{ \"interfaces\": [ \"*\" ],"
347
348
349
350
351
352
            "\"hooks-libraries\": [ ],"
            "\"preferred-lifetime\": 3000,"
            "\"rebind-timer\": 2000, "
            "\"renew-timer\": 1000, "
            "\"valid-lifetime\": 4000, "
            "\"subnet6\": [ ], "
353
            "\"dhcp-ddns\": { \"enable-updates\" : false }, "
354
355
356
357
            "\"option-def\": [ ], "
            "\"option-data\": [ ] }";
        static_cast<void>(executeConfiguration(config,
                                               "reset configuration database"));
358
359
360
361
        // 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.
362
        CfgMgr::instance().clear();
363
364
        // Create fresh context.
        globalContext()->copyContext(ParserContext(Option::V6));
365
366
    }

367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
    /// @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
382
        EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
383
384
        checkResult(x, 1);
        EXPECT_TRUE(errorContainsPosition(x, "<string>"));
385
386
    }

387
388
389
390
391
392
393
394
395
396
397
398
399
    /// @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));
400
401
        checkResult(x, 1);
        EXPECT_TRUE(errorContainsPosition(x, "<string>"));
402
403
    }

404
405
406
407
408
409
410
    /// @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.
411
412
    /// @param extra_data if true extra data is allowed in an option
    /// after tested data.
413
414
    void testOption(const Subnet::OptionDescriptor& option_desc,
                    uint16_t expected_code, const uint8_t* expected_data,
415
416
                    size_t expected_data_len,
                    bool extra_data = false) {
417
418
419
420
421
422
423
424
425
426
427
        // Check if option descriptor contains valid option pointer.
        ASSERT_TRUE(option_desc.option);
        // Verify option type.
        EXPECT_EQ(expected_code, option_desc.option->getType());
        // 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.
        util::OutputBuffer buf(option_desc.option->getData().size());
        option_desc.option->pack(buf);
428
429
430
431
432
433
434
435
436
437
438
        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.
            ASSERT_GE(buf.getLength() - option_desc.option->getHeaderLen(),
                      expected_data_len);
        } else {
            ASSERT_EQ(buf.getLength() - option_desc.option->getHeaderLen(),
                      expected_data_len);
        }
439
        // Verify that the data is correct. Do not verify suboptions and a header.
440
        const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
441
442
        EXPECT_EQ(0, memcmp(expected_data, data + option_desc.option->getHeaderLen(),
                            expected_data_len));
443
444
    }

445
446
447
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
474
475
476
477
    /// @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) {
        std::string config = createConfigWithOption(params);
        ASSERT_TRUE(executeConfiguration(config, "parse option configuration"));
        // The subnet should now hold one option with the specified code.
        Subnet::OptionDescriptor desc =
            getOptionFromSubnet(IOAddress("2001:db8:1::5"), option_code);
        ASSERT_TRUE(desc.option);
        testOption(desc, option_code, expected_data, expected_data_len);
    }

478
479
480
481
482
    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)
483
    isc::dhcp::ClientClasses classify_; ///< used in client classification
484
485
};

486
487
488
// 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.
489
490
491
492
TEST_F(Dhcp6ParserTest, version) {

    ConstElementPtr x;

Stephen Morris's avatar
Stephen Morris committed
493
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_,
494
495
496
                    Element::fromJSON("{\"version\": 0}")));

    // returned value must be 0 (configuration accepted)
497
    checkResult(x, 0);
498
499
}

500
501
/// The goal of this test is to verify that the code accepts only
/// valid commands and malformed or unsupported parameters are rejected.
502
TEST_F(Dhcp6ParserTest, bogusCommand) {
503
504
505

    ConstElementPtr x;

Stephen Morris's avatar
Stephen Morris committed
506
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_,
507
508
509
                    Element::fromJSON("{\"bogus\": 5}")));

    // returned value must be 1 (configuration parse error)
510
    checkResult(x, 1);
511
512
}

513
514
/// The goal of this test is to verify if configuration without any
/// subnets defined can be accepted.
515
TEST_F(Dhcp6ParserTest, emptySubnet) {
516

517
    ConstElementPtr status;
518

Stephen Morris's avatar
Stephen Morris committed
519
    EXPECT_NO_THROW(status = configureDhcp6Server(srv_,
520
                    Element::fromJSON("{ \"interfaces\": [ \"*\" ],"
521
522
523
524
525
526
                                      "\"preferred-lifetime\": 3000,"
                                      "\"rebind-timer\": 2000, "
                                      "\"renew-timer\": 1000, "
                                      "\"subnet6\": [  ], "
                                      "\"valid-lifetime\": 4000 }")));

527
    // returned value should be 0 (success)
528
    checkResult(status, 0);
529
530
}

531
532
/// The goal of this test is to verify if defined subnet uses global
/// parameter timer definitions.
533
TEST_F(Dhcp6ParserTest, subnetGlobalDefaults) {
534

535
    ConstElementPtr status;
536

537
    string config = "{ \"interfaces\": [ \"*\" ],"
538
539
540
541
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
542
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
543
544
545
546
547
        "    \"subnet\": \"2001:db8:1::/64\" } ],"
        "\"valid-lifetime\": 4000 }";

    ElementPtr json = Element::fromJSON(config);

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

550
    // check if returned status is OK
551
    checkResult(status, 0);
552

553
554
    // Now check if the configuration was indeed handled and we have
    // expected pool configured.
555
556
    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
        classify_);
557
558
559
560
561
    ASSERT_TRUE(subnet);
    EXPECT_EQ(1000, subnet->getT1());
    EXPECT_EQ(2000, subnet->getT2());
    EXPECT_EQ(3000, subnet->getPreferred());
    EXPECT_EQ(4000, subnet->getValid());
562
563
564
565
566

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

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

    int cnt = 0; // Number of reconfigurations

597
    ElementPtr json = Element::fromJSON(config);
598

599
    do {
600
        EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
601
        checkResult(x, 0);
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617

        const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
        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);
618
619
}

620
621
622
// This checks that it is possible to assign arbitrary ids for subnets.
TEST_F(Dhcp6ParserTest, multipleSubnetsExplicitIDs) {
    ConstElementPtr x;
623
    // Four subnets with arbitrary subnet ids.
624
625
626
627
628
    string config = "{ \"interfaces\": [ \"*\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
629
        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
630
631
632
633
        "    \"subnet\": \"2001:db8:1::/64\", "
        "    \"id\": 1024"
        " },"
        " {"
634
        "    \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
635
636
637
638
        "    \"subnet\": \"2001:db8:2::/64\", "
        "    \"id\": 100"
        " },"
        " {"
639
        "    \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
640
641
642
643
        "    \"subnet\": \"2001:db8:3::/64\", "
        "    \"id\": 1"
        " },"
        " {"
644
        "    \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
645
646
647
648
649
650
651
        "    \"subnet\": \"2001:db8:4::/64\", "
        "    \"id\": 34"
        " } ],"
        "\"valid-lifetime\": 4000 }";

    int cnt = 0; // Number of reconfigurations

652
    ElementPtr json = Element::fromJSON(config);
653

654
    do {
655
        EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
656
        checkResult(x, 0);
657
658
659
660
661
662
663
664
665
666
667
668
669

        const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
        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.
670
    } while (++cnt < 3);
671
672
673
674
675
676
677
678
679
680
681
}

// 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\": [ { "
682
        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
683
684
685
686
        "    \"subnet\": \"2001:db8:1::/64\", "
        "    \"id\": 1024"
        " },"
        " {"
687
        "    \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
688
689
690
691
        "    \"subnet\": \"2001:db8:2::/64\", "
        "    \"id\": 100"
        " },"
        " {"
692
        "    \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
693
694
695
696
        "    \"subnet\": \"2001:db8:3::/64\", "
        "    \"id\": 1024"
        " },"
        " {"
697
        "    \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
698
699
700
701
702
703
704
705
        "    \"subnet\": \"2001:db8:4::/64\", "
        "    \"id\": 34"
        " } ],"
        "\"valid-lifetime\": 4000 }";

    ElementPtr json = Element::fromJSON(config);

    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
706
    checkResult(x, 1);
707
    EXPECT_TRUE(errorContainsPosition(x, "<string>"));
708
709
710
}


Tomek Mrugalski's avatar
Tomek Mrugalski committed
711
712
713
714
715
716
717
718
719
720
721
// 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\": [ { "
722
        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
723
724
        "    \"subnet\": \"2001:db8:1::/64\", "
        "    \"id\": 1"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
725
726
        " },"
        " {"
727
        "    \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
728
729
        "    \"subnet\": \"2001:db8:2::/64\", "
        "    \"id\": 2"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
730
731
        " },"
        " {"
732
        "    \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
733
734
        "    \"subnet\": \"2001:db8:3::/64\", "
        "    \"id\": 3"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
735
736
        " },"
        " {"
737
        "    \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
738
739
        "    \"subnet\": \"2001:db8:4::/64\", "
        "    \"id\": 4"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
740
741
742
743
744
745
746
747
748
        " } ],"
        "\"valid-lifetime\": 4000 }";

    // Three subnets (the last one removed)
    string config_first3 = "{ \"interfaces\": [ \"*\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
749
        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
750
751
        "    \"subnet\": \"2001:db8:1::/64\", "
        "    \"id\": 1"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
752
753
        " },"
        " {"
754
        "    \"pools\": [ { \"pool\": \"2001:db8:2::/80\" } ],"
755
756
        "    \"subnet\": \"2001:db8:2::/64\", "
        "    \"id\": 2"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
757
758
        " },"
        " {"
759
        "    \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
760
761
        "    \"subnet\": \"2001:db8:3::/64\", "
        "    \"id\": 3"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
762
763
764
765
766
767
768
769
770
        " } ],"
        "\"valid-lifetime\": 4000 }";

    // Second subnet removed
    string config_second_removed = "{ \"interfaces\": [ \"*\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
771
        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
772
773
        "    \"subnet\": \"2001:db8:1::/64\", "
        "    \"id\": 1"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
774
775
        " },"
        " {"
776
        "    \"pools\": [ { \"pool\": \"2001:db8:3::/80\" } ],"
777
778
        "    \"subnet\": \"2001:db8:3::/64\", "
        "    \"id\": 3"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
779
780
        " },"
        " {"
781
        "    \"pools\": [ { \"pool\": \"2001:db8:4::/80\" } ],"
782
783
        "    \"subnet\": \"2001:db8:4::/64\", "
        "    \"id\": 4"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
784
785
786
787
788
789
790
791
        " } ],"
        "\"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));
792
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
793
794
795
796
797
798
799
800

    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
    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));
801
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
802
803
804
805
806
807
808
809
810
811
812
813
814
815

    subnets = CfgMgr::instance().getSubnets6();
    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));
816
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
817
818
819
820

    // Do reconfiguration
    json = Element::fromJSON(config_second_removed);
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
821
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
822
823
824
825
826
827
828
829
830
831
832
833
834

    subnets = CfgMgr::instance().getSubnets6();
    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());
}



835
836
// This test checks if it is possible to override global values
// on a per subnet basis.
837
TEST_F(Dhcp6ParserTest, subnetLocal) {
838

839
    ConstElementPtr status;
840

841
    string config = "{ \"interfaces\": [ \"*\" ],"
842
843
844
845
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
846
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
847
848
849
850
851
852
853
854
855
        "    \"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
856
    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
857

858
    // returned value should be 0 (configuration success)
859
    checkResult(status, 0);
860

861
862
    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
                                                      classify_);
863
864
865
866
867
868
869
    ASSERT_TRUE(subnet);
    EXPECT_EQ(1, subnet->getT1());
    EXPECT_EQ(2, subnet->getT2());
    EXPECT_EQ(3, subnet->getPreferred());
    EXPECT_EQ(4, subnet->getValid());
}

870
871
872
873
874
875
// 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
876
877
    // There should be at least one interface

878
    string config = "{ \"interfaces\": [ \"*\" ],"
879
880
881
882
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
883
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
884
        "    \"interface\": \"" + valid_iface_ + "\","
885
886
887
888
889
890
891
892
893
        "    \"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)
894
    checkResult(status, 0);
895

896
897
    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
                                                      classify_);
898
    ASSERT_TRUE(subnet);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
899
900
901
902
903
904
905
906
907
908
909
    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

910
    string config = "{ \"interfaces\": [ \"*\" ],"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
911
912
913
914
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
915
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
Tomek Mrugalski's avatar
Tomek Mrugalski committed
916
917
918
919
920
921
922
923
924
925
        "    \"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)
926
927
    checkResult(status, 1);
    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
928

929
930
    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
                                                      classify_);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
931
    EXPECT_FALSE(subnet);
932
933
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
934

935
936
937
938
939
940
// This test checks if it is not allowed to define global interface
// parameter.
TEST_F(Dhcp6ParserTest, interfaceGlobal) {

    ConstElementPtr status;

941
    string config = "{ \"interfaces\": [ \"*\" ],"
942
943
944
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
945
        "\"interface\": \"" + valid_iface_ + "\"," // Not valid. Can be defined in subnet only
946
        "\"subnet6\": [ { "
947
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
948
949
950
951
952
953
954
955
956
        "    \"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)
957
958
    checkResult(status, 1);
    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
959
960
}

961
962
963
964
965
966
967
968
969
970

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

971
    const string config = "{ "
972
973
974
975
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
976
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
977
978
979
980
981
982
        "    \"interface-id\": \"" + valid_interface_id + "\","
        "    \"subnet\": \"2001:db8:1::/64\" } ],"
        "\"valid-lifetime\": 4000 }";

    ElementPtr json = Element::fromJSON(config);

983
    ConstElementPtr status;
984
985
    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));

986
    // Returned value should be 0 (configuration success)
987
    checkResult(status, 0);
988

989
    // Try to get a subnet based on bogus interface-id option
990
991
    OptionBuffer tmp(bogus_interface_id.begin(), bogus_interface_id.end());
    OptionPtr ifaceid(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
992
    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(ifaceid, classify_);
993
994
    EXPECT_FALSE(subnet);

995
    // Now try to get subnet for valid interface-id value
996
997
    tmp = OptionBuffer(valid_interface_id.begin(), valid_interface_id.end());
    ifaceid.reset(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
998
    subnet = CfgMgr::instance().getSubnet6(ifaceid, classify_);
999
1000
1001
1002
1003
1004
1005
1006
1007
    ASSERT_TRUE(subnet);
    EXPECT_TRUE(ifaceid->equal(subnet->getInterfaceId()));
}


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

1008
    const string config = "{ \"interfaces\": [ \"*\" ],"
1009
1010
1011
1012
1013
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"interface-id\": \"foobar\"," // Not valid. Can be defined in subnet only
        "\"subnet6\": [ { "
1014
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
1015
1016
1017
1018
1019
        "    \"subnet\": \"2001:db8:1::/64\" } ],"
        "\"valid-lifetime\": 4000 }";

    ElementPtr json = Element::fromJSON(config);

1020
    ConstElementPtr status;
1021
1022
    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));

1023
    // Returned value should be 1 (parse error)
1024
1025
    checkResult(status, 1);
    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
1026
1027
1028
1029
1030
1031
}

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

1032
    const string config = "{ \"preferred-lifetime\": 3000,"
1033
1034
1035
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
1036
        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
1037
1038
1039
1040
1041
1042
1043
        "    \"interface\": \"" + valid_iface_ + "\","
        "    \"interface-id\": \"foobar\","
        "    \"subnet\": \"2001:db8:1::/64\" } ],"
        "\"valid-lifetime\": 4000 }";

    ElementPtr json = Element::fromJSON(config);

1044
    ConstElementPtr status;
1045
1046
    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));

1047
    // Returned value should be 1 (configuration error)
1048
1049
    checkResult(status, 1);
    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
1050
1051
}

1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
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
// 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\" }"
        "    ],"
        "    \"subnet\": \"2001:db8:2::/64\""
        " } ],"
        "\"valid-lifetime\": 4000 }";
    ElementPtr json;
    ASSERT_NO_THROW(json = Element::fromJSON(config));

    ConstElementPtr status;
    ASSERT_NO_THROW(status = configureDhcp6Server(srv_, json));
    checkResult(status, 0);

    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
    ASSERT_TRUE(subnets);
    ASSERT_EQ(2, subnets->size()); // We expect 2 subnets

    // Check the first subnet
    const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_NA);
    ASSERT_EQ(2, pools1.size());
    EXPECT_EQ("type=IA_NA, 2001:db8:1::-2001:db8:1::ffff:ffff, delegated_len=128",
              pools1[0]->toText());
    EXPECT_EQ("type=IA_NA, 2001:db8:1:0:abcd::-2001:db8:1:0:abcd::ffff, delegated_len=128",
              pools1[1]->toText());
    // There shouldn't be any TA or PD pools
    EXPECT_TRUE(subnets->at(0)->getPools(Lease::TYPE_TA).empty());
    EXPECT_TRUE(subnets->at(0)->getPools(Lease::TYPE_PD).empty());

    // Check the second subnet
    const PoolCollection& pools2 = subnets->at(1)->getPools(Lease::TYPE_NA);
    ASSERT_EQ(2, pools2.size());
    EXPECT_EQ("type=IA_NA, 2001:db8:2::1-2001:db8:2::ff, delegated_len=128",
              pools2[0]->toText());
    EXPECT_EQ("type=IA_NA, 2001:db8:2::300-2001:db8:2::3ff, delegated_len=128",
              pools2[1]->toText());
    // There shouldn't be any TA or PD pools
    EXPECT_TRUE(subnets->at(0)->getPools(Lease::TYPE_TA).empty());
    EXPECT_TRUE(subnets->at(0)->getPools(Lease::TYPE_PD).empty());
}
1108
1109


1110
1111
// Test verifies that a subnet with pool values that do not belong to that
// pool are rejected.
1112
TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
1113

1114
    ConstElementPtr status;
1115

1116
    string config = "{ \"interfaces\": [ \"*\" ],"
1117
1118
1119
1120
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
1121
        "    \"pools\": [ { \"pool\": \"4001:db8:1::/80\" } ],"
1122
1123
        "    \"subnet\": \"2001:db8:1::/64\" } ],"
        "\"valid-lifetime\": 4000 }";
1124

1125
1126
1127

    ElementPtr json = Element::fromJSON(config);

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

1130
    // returned value must be 1 (values error)
1131
    // as the pool does not belong to that subnet
1132
1133
    checkResult(status, 1);
    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
1134
1135
}

1136
1137
1138
// Goal of this test is to verify if pools can be defined
// using prefix/length notation. There is no separate test for min-max
// notation as it was tested in several previous tests.
1139
1140
// Note this test also verifies that subnets can be configured without
// prefix delegation pools.
1141
TEST_F(Dhcp6ParserTest, poolPrefixLen) {
1142
1143
1144

    ConstElementPtr x;

1145
    string config = "{ \"interfaces\": [ \"*\" ],"
1146
1147
1148
1149
        "\"preferred-lifetime\": 3000,"
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet6\": [ { "
1150
        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
1151
1152
1153
1154
1155
        "    \"subnet\": \"2001:db8:1::/64\" } ],"
        "\"valid-lifetime\": 4000 }";

    ElementPtr json = Element::fromJSON(config);

Stephen Morris's avatar
Stephen Morris committed
1156
    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
1157
1158

    // returned value must be 1 (configuration parse error)
1159
    checkResult(x, 0);
1160

1161
1162
    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
                                                      classify_);
1163
1164
1165
1166
1167
1168
1169
    ASSERT_TRUE(subnet);
    EXPECT_EQ(1000, subnet->getT1());
    EXPECT_EQ(2000, subnet->getT2());
    EXPECT_EQ(3000, subnet->getPreferred());
    EXPECT_EQ(4000, subnet->getValid());
}

1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
// Goal of this test is to verify the basic parsing of a prefix delegation
// pool. It uses a single, valid pd pool.
TEST_F(Dhcp6ParserTest, pdPoolBasics) {

    ConstElementPtr x;

    // Define a single valid pd pool.
    string config =
        "{ \"interfaces\": [ \"*\" ],"
        "\"preferred-lifetime\": 3000,"
        "\"