config_parser_unittest.cc 231 KB
Newer Older
Francis Dupont's avatar
Francis Dupont committed
1
// Copyright (C) 2012-2018 Internet Systems Consortium, Inc. ("ISC")
2
//
3
4
5
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7
8
9
10
11

#include <config.h>

#include <arpa/inet.h>
#include <gtest/gtest.h>

12
#include <cc/command_interpreter.h>
13
#include <dhcp4/dhcp4_srv.h>
14
#include <dhcp4/ctrl_dhcp4_srv.h>
15
#include <dhcp4/json_config_parser.h>
16
#include <dhcp/option4_addrlst.h>
17
18
#include <dhcp/option_custom.h>
#include <dhcp/option_int.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
19
#include <dhcp/docsis3_option_defs.h>
20
#include <dhcp/classify.h>
21
#include <dhcp/tests/iface_mgr_test_config.h>
22
23
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/cfgmgr.h>
24
#include <dhcpsrv/cfg_expiration.h>
25
#include <dhcpsrv/cfg_hosts.h>
26
#include <dhcpsrv/cfg_subnets4.h>
27
#include <dhcpsrv/testutils/config_result_check.h>
28
#include <dhcpsrv/testutils/test_config_backend_dhcp4.h>
29
#include <process/config_ctl_info.h>
30
31
32
33
#include <hooks/hooks_manager.h>

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

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

Tomek Mrugalski's avatar
Tomek Mrugalski committed
41
42
43
44
#include <iostream>
#include <fstream>
#include <sstream>
#include <limits.h>
45
46
47
48

using namespace isc;
using namespace isc::asiolink;
using namespace isc::config;
49
50
using namespace isc::data;
using namespace isc::dhcp;
51
using namespace isc::dhcp::test;
52
53
using namespace isc::hooks;
using namespace std;
54
55

namespace {
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
const char* PARSER_CONFIGS[] = {
    // CONFIGURATION 0: one subnet with one pool, no user contexts
    "{"
    "    \"interfaces-config\": {"
    "        \"interfaces\": [\"*\" ]"
    "    },"
    "    \"valid-lifetime\": 4000,"
    "    \"rebind-timer\": 2000,"
    "    \"renew-timer\": 1000,"
    "    \"subnet4\": [ {"
    "        \"pools\": [ "
    "            { \"pool\":  \"192.0.2.0/28\" }"
    "        ],"
    "        \"subnet\": \"192.0.2.0/24\""
    "     } ]"
    "}",

    // Configuration 1: one pool with empty user context
    "{"
    "    \"interfaces-config\": {"
    "        \"interfaces\": [\"*\" ]"
    "    },"
    "    \"valid-lifetime\": 4000,"
    "    \"rebind-timer\": 2000,"
    "    \"renew-timer\": 1000,"
    "    \"subnet4\": [ {"
    "        \"pools\": [ "
    "            { \"pool\":  \"192.0.2.0/28\","
    "                \"user-context\": {"
    "                }"
    "            }"
    "        ],"
    "        \"subnet\": \"192.0.2.0/24\""
    "     } ]"
    "}",

    // Configuration 2: one pool with user context containing lw4over6 parameters
    "{"
    "    \"interfaces-config\": {"
    "        \"interfaces\": [\"*\" ]"
    "    },"
    "    \"valid-lifetime\": 4000,"
    "    \"rebind-timer\": 2000,"
    "    \"renew-timer\": 1000,"
    "    \"subnet4\": [ {"
    "        \"pools\": [ "
    "            { \"pool\":  \"192.0.2.0/28\","
    "                \"user-context\": {"
    "                    \"integer-param\": 42,"
    "                    \"string-param\": \"Sagittarius\","
    "                    \"bool-param\": true"
    "                }"
    "            }"
    "        ],"
    "        \"subnet\": \"192.0.2.0/24\""
    "     } ]"
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
    "}",

    // Configuration 3: one min-max pool with user context containing lw4over6 parameters
    "{"
    "    \"interfaces-config\": {"
    "        \"interfaces\": [\"*\" ]"
    "    },"
    "    \"valid-lifetime\": 4000,"
    "    \"rebind-timer\": 2000,"
    "    \"renew-timer\": 1000,"
    "    \"subnet4\": [ {"
    "        \"pools\": [ "
    "            { \"pool\":  \"192.0.2.0 - 192.0.2.15\","
    "                \"user-context\": {"
    "                    \"integer-param\": 42,"
    "                    \"string-param\": \"Sagittarius\","
    "                    \"bool-param\": true"
    "                }"
    "            }"
    "        ],"
    "        \"subnet\": \"192.0.2.0/24\""
    "     } ]"
Francis Dupont's avatar
Francis Dupont committed
134
135
    "}",

Francis Dupont's avatar
Francis Dupont committed
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
    // Configuration 4: two host databases
    "{"
    "    \"interfaces-config\": {"
    "        \"interfaces\": [\"*\" ]"
    "    },"
    "    \"valid-lifetime\": 4000,"
    "    \"rebind-timer\": 2000,"
    "    \"renew-timer\": 1000,"
    "    \"hosts-databases\": [ {"
    "        \"type\": \"mysql\","
    "        \"name\": \"keatest1\","
    "        \"user\": \"keatest\","
    "        \"password\": \"keatest\""
    "      },{"
    "        \"type\": \"mysql\","
    "        \"name\": \"keatest2\","
    "        \"user\": \"keatest\","
    "        \"password\": \"keatest\""
    "      }"
    "    ]"
    "}",

158
    // Configuration 5 for comments
Francis Dupont's avatar
Francis Dupont committed
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
    "{"
    "    \"comment\": \"A DHCPv4 server\","
    "    \"interfaces-config\": {"
    "        \"comment\": \"Use wildcard\","
    "        \"interfaces\": [ \"*\" ] },"
    "    \"option-def\": [ {"
    "        \"comment\": \"An option definition\","
    "        \"name\": \"foo\","
    "        \"code\": 100,"
    "        \"type\": \"ipv4-address\","
    "        \"space\": \"isc\""
    "     } ],"
    "    \"option-data\": [ {"
    "        \"comment\": \"Set option value\","
    "        \"name\": \"dhcp-message\","
    "        \"data\": \"ABCDEF0105\","
    "        \"csv-format\": false"
    "     } ],"
    "    \"client-classes\": ["
    "        {"
    "           \"comment\": \"match all\","
    "           \"name\": \"all\","
    "           \"test\": \"'' == ''\""
    "        },"
    "        {"
    "           \"name\": \"none\""
    "        },"
    "        {"
    "           \"comment\": \"a comment\","
    "           \"name\": \"both\","
    "           \"user-context\": {"
    "               \"version\": 1"
    "           }"
    "        }"
    "        ],"
    "    \"control-socket\": {"
    "        \"socket-type\": \"unix\","
    "        \"socket-name\": \"/tmp/kea4-ctrl-socket\","
    "        \"user-context\": { \"comment\": \"Indirect comment\" }"
    "    },"
    "    \"shared-networks\": [ {"
    "        \"comment\": \"A shared network\","
    "        \"name\": \"foo\","
    "        \"subnet4\": ["
    "        { "
    "            \"comment\": \"A subnet\","
    "            \"subnet\": \"192.0.1.0/24\","
    "            \"id\": 100,"
    "            \"pools\": ["
    "            {"
    "                 \"comment\": \"A pool\","
    "                 \"pool\": \"192.0.1.1-192.0.1.10\""
    "            }"
    "            ],"
    "            \"reservations\": ["
    "            {"
    "                 \"comment\": \"A host reservation\","
    "                 \"hw-address\": \"AA:BB:CC:DD:EE:FF\","
    "                 \"hostname\": \"foo.example.com\","
    "                 \"option-data\": [ {"
    "                     \"comment\": \"An option in a reservation\","
    "                     \"name\": \"domain-name\","
    "                     \"data\": \"example.com\""
    "                 } ]"
    "            }"
    "            ]"
    "        }"
    "        ]"
    "     } ],"
    "    \"dhcp-ddns\": {"
    "        \"comment\": \"No dynamic DNS\","
    "        \"enable-updates\": false"
    "    }"
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
    "}",

    // Configuration 6: config databases
    "{ \n"
    "    \"interfaces-config\": { \n"
    "        \"interfaces\": [\"*\" ] \n"
    "    }, \n"
    "    \"valid-lifetime\": 4000, \n"
    "    \"rebind-timer\": 2000, \n"
    "    \"renew-timer\": 1000, \n"
    "    \"config-control\": { \n"
    "       \"config-databases\": [ { \n"
    "               \"type\": \"mysql\", \n"
    "               \"name\": \"keatest1\", \n"
    "               \"user\": \"keatest\", \n"
    "               \"password\": \"keatest\" \n"
    "           },{ \n"
    "               \"type\": \"mysql\", \n"
    "               \"name\": \"keatest2\", \n"
    "               \"user\": \"keatest\", \n"
    "               \"password\": \"keatest\" \n"
    "           } \n"
    "       ] \n"
    "   } \n"
    "} \n"
257
};
258
259

class Dhcp4ParserTest : public ::testing::Test {
260
261
262
263
264
265
266
267
268
269
protected:
    // 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.
    virtual void SetUp() {
        std::vector<std::string> libraries = HooksManager::getLibraryNames();
        ASSERT_TRUE(libraries.empty());
    }

270
271
public:
    Dhcp4ParserTest()
272
    : rcode_(-1) {
273
274
275
        // Open port 0 means to not do anything at all. We don't want to
        // deal with sockets here, just check if configuration handling
        // is sane.
276
        srv_.reset(new ControlledDhcpv4Srv(0));
277
        // Create fresh context.
278
        resetConfiguration();
279
280
    }

281
public:
Tomek Mrugalski's avatar
Tomek Mrugalski committed
282

283
    // Checks if the result of DHCP server configuration has
Tomek Mrugalski's avatar
Tomek Mrugalski committed
284
285
286
287
288
    // 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);
289
        EXPECT_EQ(expected_code, rcode_) << "error text:" << comment_->stringValue();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
290
291
    }

292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
    /// @brief Convenience method for running configuration
    ///
    /// This method does not throw, but signals errors using gtest macros.
    ///
    /// @param config text to be parsed as JSON
    /// @param expected_code expected code (see cc/command_interpreter.h)
    /// @param exp_error expected text error (check skipped if empty)
    void configure(std::string config, int expected_code,
                   std::string exp_error = "") {
        ConstElementPtr json;
        ASSERT_NO_THROW(json = parseDHCP4(config, true));

        ConstElementPtr status;
        EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
        ASSERT_TRUE(status);

        int rcode;
        ConstElementPtr comment = parseAnswer(rcode, status);
        EXPECT_EQ(expected_code, rcode);

        string text;
        ASSERT_NO_THROW(text = comment->stringValue());

        if (expected_code != rcode) {
            std::cout << "Reported status: " << text << std::endl;
        }

        if ((rcode != 0)) {
            if (!exp_error.empty()) {
                EXPECT_EQ(exp_error, text);
            }
        }
    }

326
    ~Dhcp4ParserTest() {
327
        resetConfiguration();
328
329

        // ... and delete the hooks library marker files if present
330
331
        static_cast<void>(remove(LOAD_MARKER_FILE));
        static_cast<void>(remove(UNLOAD_MARKER_FILE));
332
333
    };

334
335
336
337
338
339
340
341
    /// @brief Returns an interface configuration used by the most of the
    /// unit tests.
    std::string genIfaceConfig() const {
        return ("\"interfaces-config\": {"
                "  \"interfaces\": [ \"*\" ]"
                "}");
    }

342
343
344
    /// @brief Create the simple configuration with single option.
    ///
    /// This function allows to set one of the parameters that configure
345
346
    /// option value. These parameters are: "name", "code", "data",
    /// "csv-format" and "space".
347
    ///
348
    /// @param param_value string holding option parameter value to be
349
350
351
    /// injected into the configuration string.
    /// @param parameter name of the parameter to be configured with
    /// param value.
352
353
    /// @return configuration string containing custom values of parameters
    /// describing an option.
354
355
356
357
358
    std::string createConfigWithOption(const std::string& param_value,
                                       const std::string& parameter) {
        std::map<std::string, std::string> params;
        if (parameter == "name") {
            params["name"] = param_value;
359
            params["space"] = DHCP4_OPTION_SPACE;
360
            params["code"] = "56";
361
            params["data"] = "ABCDEF0105";
362
            params["csv-format"] = "false";
363
364
365
        } else if (parameter == "space") {
            params["name"] = "dhcp-message";
            params["space"] = param_value;
366
            params["code"] = "56";
367
            params["data"] = "ABCDEF0105";
368
            params["csv-format"] = "false";
369
        } else if (parameter == "code") {
370
            params["name"] = "dhcp-message";
371
            params["space"] = DHCP4_OPTION_SPACE;
372
            params["code"] = param_value;
373
            params["data"] = "ABCDEF0105";
374
            params["csv-format"] = "false";
375
        } else if (parameter == "data") {
376
            params["name"] = "dhcp-message";
377
            params["space"] = DHCP4_OPTION_SPACE;
378
379
            params["code"] = "56";
            params["data"] = param_value;
380
            params["csv-format"] = "false";
381
        } else if (parameter == "csv-format") {
382
            params["name"] = "dhcp-message";
383
            params["space"] = DHCP4_OPTION_SPACE;
384
            params["code"] = "56";
385
            params["data"] = "ABCDEF0105";
386
            params["csv-format"] = param_value;
387
388
389
390
        }
        return (createConfigWithOption(params));
    }

391
392
393
394
395
396
397
398
    /// @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.
399
400
    std::string createConfigWithOption(const std::map<std::string, std::string>& params) {
        std::ostringstream stream;
401
        stream << "{ " << genIfaceConfig() << "," <<
402
403
404
            "\"rebind-timer\": 2000, "
            "\"renew-timer\": 1000, "
            "\"subnet4\": [ { "
405
            "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
406
407
408
409
410
411
412
413
            "    \"subnet\": \"192.0.2.0/24\", "
            "    \"option-data\": [ {";
        bool first = true;
        typedef std::pair<std::string, std::string> ParamPair;
        BOOST_FOREACH(ParamPair param, params) {
            if (!first) {
                stream << ", ";
            } else {
414
                // cppcheck-suppress unreadVariable
415
416
417
418
                first = false;
            }
            if (param.first == "name") {
                stream << "\"name\": \"" << param.second << "\"";
419
420
            } else if (param.first == "space") {
                stream << "\"space\": \"" << param.second << "\"";
421
422
423
424
            } else if (param.first == "code") {
                stream << "\"code\": " << param.second << "";
            } else if (param.first == "data") {
                stream << "\"data\": \"" << param.second << "\"";
425
426
            } else if (param.first == "csv-format") {
                stream << "\"csv-format\": " << param.second;
427
428
429
430
431
432
433
434
435
            }
        }
        stream <<
            "        } ]"
            " } ],"
            "\"valid-lifetime\": 4000 }";
        return (stream.str());
    }

436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
    /// @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.
451
    OptionDescriptor
452
453
454
    getOptionFromSubnet(const IOAddress& subnet_address,
                        const uint16_t option_code,
                        const uint16_t expected_options_count = 1) {
455
        Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
456
            getCfgSubnets4()->selectSubnet(subnet_address);
457
458
459
460
461
462
        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";
        }
463
        OptionContainerPtr options =
464
            subnet->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
465
466
467
468
469
470
471
472
        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.
473
        const OptionContainerTypeIndex& idx = options->get<1>();
474
475
476
477

        // 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.
478
479
        std::pair<OptionContainerTypeIndex::const_iterator,
                  OptionContainerTypeIndex::const_iterator> range =
480
481
482
483
484
485
486
            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) {
487
            return (OptionDescriptor(OptionPtr(), false));
488
489
490
491
492
        }

        return (*range.first);
    }

493
494
495
496
497
498
499
500
501
502
503
504
505
506
    /// @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);
507
        ConstElementPtr json = parseDHCP4(config);
508
        EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
509
510
        checkResult(x, 1);
        EXPECT_TRUE(errorContainsPosition(x, "<string>"));
511
512
    }

Francis Dupont's avatar
Francis Dupont committed
513
    /// @brief Test invalid option parameter value.
514
515
516
517
518
519
520
521
522
523
    ///
    /// 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);
524
        ConstElementPtr json = parseDHCP4(config);
525
        EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
526
527
        checkResult(x, 1);
        EXPECT_TRUE(errorContainsPosition(x, "<string>"));
528
529
    }

530
531
532
533
534
535
536
537
538
    /// @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.
    /// @param extra_data if true extra data is allowed in an option
    /// after tested data.
539
    void testOption(const OptionDescriptor& option_desc,
540
541
542
543
                    uint16_t expected_code, const uint8_t* expected_data,
                    size_t expected_data_len,
                    bool extra_data = false) {
        // Check if option descriptor contains valid option pointer.
544
        ASSERT_TRUE(option_desc.option_);
545
        // Verify option type.
546
        EXPECT_EQ(expected_code, option_desc.option_->getType());
547
548
549
550
551
        // 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.
552
553
        util::OutputBuffer buf(option_desc.option_->getData().size());
        option_desc.option_->pack(buf);
554
555
556
557
558
        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.
559
            ASSERT_GE(buf.getLength() - option_desc.option_->getHeaderLen(),
560
561
                      expected_data_len);
        } else {
562
            ASSERT_EQ(buf.getLength() - option_desc.option_->getHeaderLen(),
563
564
                      expected_data_len);
        }
565
        // Verify that the data is correct. Do not verify suboptions and a header.
566
        const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
567
        EXPECT_EQ(0, memcmp(expected_data, data + option_desc.option_->getHeaderLen(),
568
                            expected_data_len));
569
570
    }

571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
    /// @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"));
597
        // The subnet should now hold one option with the specified option code.
598
        OptionDescriptor desc =
599
            getOptionFromSubnet(IOAddress("192.0.2.24"), option_code);
600
        ASSERT_TRUE(desc.option_);
601
602
603
        testOption(desc, option_code, expected_data, expected_data_len);
    }

604
605
606
607
608
609
610
611
612
613
614
615
616
    /// @brief Parse and Execute configuration
    ///
    /// 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) {
617
        CfgMgr::instance().clear();
618
        ConstElementPtr json;
619
620
        ConstElementPtr status;
        try {
621
            json = parseJSON(config);
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
            status = configureDhcp4Server(*srv_, json);
        } catch (const std::exception& ex) {
            ADD_FAILURE() << "Unable to " << operation << ". "
                   << "The following configuration was used: " << std::endl
                   << config << std::endl
                   << " and the following error message was returned:"
                   << ex.what() << std::endl;
            return (false);
        }

        // The status object must not be NULL
        if (!status) {
            ADD_FAILURE() << "Unable to " << operation << ". "
                   << "The configuration function returned a null pointer.";
            return (false);
        }

        // Store the answer if we need it.

        // Returned value should be 0 (configuration success)
        comment_ = parseAnswer(rcode_, status);
        if (rcode_ != 0) {
            string reason = "";
            if (comment_) {
                reason = string(" (") + comment_->stringValue() + string(")");
            }
            ADD_FAILURE() << "Unable to " << operation << ". "
                   << "The configuration function returned error code "
                   << rcode_ << reason;
            return (false);
        }

        return (true);
    }

657
658
659
660
661
662
663
664
    /// @brief Reset configuration database.
    ///
    /// This function resets configuration data base by
    /// removing all subnets and option-data. Reset must
    /// be performed after each test to make sure that
    /// contents of the database do not affect result of
    /// subsequent tests.
    void resetConfiguration() {
665
        string config = "{ " + genIfaceConfig() + "," +
666
            "\"hooks-libraries\": [ ], "
667
668
            "\"valid-lifetime\": 4000, "
            "\"subnet4\": [ ], "
669
            "\"dhcp-ddns\": { \"enable-updates\" : false }, "
670
            "\"option-def\": [ ], "
671
            "\"option-data\": [ ] }";
672
673
        static_cast<void>(executeConfiguration(config,
                                               "reset configuration database"));
674
        CfgMgr::instance().clear();
675
    }
676

677
678
    /// @brief Retrieve an option associated with a host.
    ///
679
680
    /// The option is retrieved from the "dhcp4" option space.
    ///
681
682
683
684
    /// @param host Reference to a host for which an option should be retrieved.
    /// @param option_code Option code.
    /// @tparam ReturnType Type of the pointer object returned.
    ///
685
    /// @return Pointer to an option or NULL pointer if not found.
686
687
688
    template<typename ReturnType>
    ReturnType
    retrieveOption(const Host& host, const uint16_t option_code) const {
689
        return (retrieveOption<ReturnType>(host, DHCP4_OPTION_SPACE, option_code));
690
691
692
693
694
695
696
697
698
    }

    /// @brief Retrieve an option associated with a host.
    ///
    /// @param host Reference to a host for which an option should be retrieved.
    /// @param space Option space from which option should be retrieved.
    /// @param option_code Option code.
    /// @tparam ReturnType Type of the pointer object returned.
    ///
699
    /// @return Pointer to an option or NULL pointer if not found.
700
701
702
703
    template<typename ReturnType>
    ReturnType
    retrieveOption(const Host& host, const std::string& space,
                   const uint16_t option_code) const {
704
705
        ConstCfgOptionPtr cfg_option = host.getCfgOption4();
        if (cfg_option) {
706
            OptionDescriptor opt_desc = cfg_option->get(space, option_code);
707
708
709
710
711
712
713
            if (opt_desc.option_) {
                return (boost::dynamic_pointer_cast<
                        typename ReturnType::element_type>(opt_desc.option_));
            }
        }
        return (ReturnType());
    }
714

715
716
717
718
719
720
721
    /// @brief Checks if specified subnet is part of the collection
    ///
    /// @param col collection of subnets to be inspected
    /// @param subnet text notation (e.g. 192.0.2.0/24)
    /// @param t1 expected renew-timer value
    /// @param t2 expected rebind-timer value
    /// @param valid expected valid-lifetime value
722
723
    /// @return the subnet that was examined
    Subnet4Ptr
724
725
726
727
    checkSubnet(const Subnet4Collection& col, std::string subnet,
                uint32_t t1, uint32_t t2, uint32_t valid) {
        const auto& index = col.get<SubnetPrefixIndexTag>();
        auto subnet_it = index.find(subnet);
728
729
730
731
        if (subnet_it == index.cend()) {
            ADD_FAILURE() << "Unable to find expected subnet " << subnet;
            return (Subnet4Ptr());
        }
732
733
734
735
736
        Subnet4Ptr s = *subnet_it;

        EXPECT_EQ(t1, s->getT1());
        EXPECT_EQ(t2, s->getT2());
        EXPECT_EQ(valid, s->getValid());
737
738

        return (s);
739
    }
740
741
742
743
744
745
746
747
748
749
750

    /// @brief This utility method attempts to configure using specified
    ///        config and then returns requested pool from requested subnet
    ///
    /// @param config configuration to be applied
    /// @param subnet_index index of the subnet to be returned (0 - the first subnet)
    /// @param pool_index index of the pool within a subnet (0 - the first pool)
    /// @param pool [out] Pool pointer will be stored here (if found)
    void getPool(const std::string& config, size_t subnet_index,
                 size_t pool_index, PoolPtr& pool) {
        ConstElementPtr status;
751
        ConstElementPtr json;
752

753
        EXPECT_NO_THROW(json = parseDHCP4(config, true));
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
        EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
        ASSERT_TRUE(status);
        checkResult(status, 0);

        ConstCfgSubnets4Ptr subnets4 = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
        ASSERT_TRUE(subnets4);

        const Subnet4Collection* subnets = subnets4->getAll();
        ASSERT_TRUE(subnets);
        ASSERT_GE(subnets->size(), subnet_index + 1);

        const PoolCollection pools = subnets->at(subnet_index)->getPools(Lease::TYPE_V4);
        ASSERT_GE(pools.size(), pool_index + 1);

        pool = pools.at(pool_index);
        EXPECT_TRUE(pool);
    }

772
773
774
775
    boost::scoped_ptr<Dhcpv4Srv> srv_;  ///< DHCP4 server under test
    int rcode_;                         ///< Return code from element parsing
    ConstElementPtr comment_;           ///< Reason for parse fail
    isc::dhcp::ClientClasses classify_; ///< used in client classification
776
777
778
779
};

/// The goal of this test is to verify that the code accepts only
/// valid commands and malformed or unsupported parameters are rejected.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
780
TEST_F(Dhcp4ParserTest, bogusCommand) {
781
782
783
784

    ConstElementPtr x;

    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_,
785
                    parseJSON("{\"bogus\": 5}")));
786
787

    // returned value must be 1 (configuration parse error)
Tomek Mrugalski's avatar
Tomek Mrugalski committed
788
    checkResult(x, 1);
789
790
791

    // it should be refused by syntax too
    EXPECT_THROW(parseDHCP4("{\"bogus\": 5}"), Dhcp4ParseError);
792
793
}

794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
/// The goal of this test is to verify empty interface-config is accepted.
TEST_F(Dhcp4ParserTest, emptyInterfaceConfig) {

    ConstElementPtr json;
    EXPECT_NO_THROW(json = parseDHCP4("{ \"rebind-timer\": 2000, "
                                      "\"renew-timer\": 1000, "
                                      "\"valid-lifetime\": 4000 }"));

    ConstElementPtr status;
    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));

    // returned value should be 0 (success)
    checkResult(status, 0);
}

809
810
811
/// The goal of this test is to verify if wrongly defined subnet will
/// be rejected. Properly defined subnet must include at least one
/// pool definition.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
812
TEST_F(Dhcp4ParserTest, emptySubnet) {
813

814
815
816
817
818
819
    std::string config = "{ " + genIfaceConfig() + "," +
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet4\": [  ], "
        "\"valid-lifetime\": 4000 }";

820
    ConstElementPtr json;
821
822
    EXPECT_NO_THROW(json = parseDHCP4(config));
    extractConfig(config);
823
824
825

    ConstElementPtr status;
    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
826
827

    // returned value should be 0 (success)
Tomek Mrugalski's avatar
Tomek Mrugalski committed
828
    checkResult(status, 0);
829
830
}

831
832
833
834
/// Check that the renew-timer doesn't have to be specified, in which case
/// it is marked unspecified in the Subnet.
TEST_F(Dhcp4ParserTest, unspecifiedRenewTimer) {

835
    string config = "{ " + genIfaceConfig() + "," +
836
837
        "\"rebind-timer\": 2000, "
        "\"subnet4\": [ { "
838
        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
839
840
841
        "    \"subnet\": \"192.0.2.0/24\" } ],"
        "\"valid-lifetime\": 4000 }";

842
843
    ConstElementPtr json;
    ASSERT_NO_THROW(json = parseDHCP4(config));
844
    extractConfig(config);
845

846
    ConstElementPtr status;
847
848
849
850
851
    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));

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

852
    Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
853
        getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
854
    ASSERT_TRUE(subnet);
855
856

    EXPECT_TRUE(subnet->getT1().unspecified());
857
858
859
860
861
862
863
864
865
866
867
868
    EXPECT_FALSE(subnet->getT2().unspecified());
    EXPECT_EQ(2000, subnet->getT2());
    EXPECT_EQ(4000, subnet->getValid());

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

/// Check that the rebind-timer doesn't have to be specified, in which case
/// it is marked unspecified in the Subnet.
TEST_F(Dhcp4ParserTest, unspecifiedRebindTimer) {

869
    string config = "{ " + genIfaceConfig() + "," +
870
871
        "\"renew-timer\": 1000, "
        "\"subnet4\": [ { "
872
        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
873
874
875
        "    \"subnet\": \"192.0.2.0/24\" } ],"
        "\"valid-lifetime\": 4000 }";

876
877
    ConstElementPtr json;
    ASSERT_NO_THROW(json = parseDHCP4(config));
878
    extractConfig(config);
879

880
    ConstElementPtr status;
881
882
883
884
885
    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));

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

886
    Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
887
        getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
888
889
890
    ASSERT_TRUE(subnet);
    EXPECT_FALSE(subnet->getT1().unspecified());
    EXPECT_EQ(1000, subnet->getT1());
891
    EXPECT_TRUE(subnet->getT2().unspecified());
892
893
894
895
896
897
    EXPECT_EQ(4000, subnet->getValid());

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

898
899
/// The goal of this test is to verify if defined subnet uses global
/// parameter timer definitions.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
900
TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) {
901

902
    string config = "{ " + genIfaceConfig() + "," +
903
904
905
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet4\": [ { "
906
        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
907
908
909
        "    \"subnet\": \"192.0.2.0/24\" } ],"
        "\"valid-lifetime\": 4000 }";

910
911
    ConstElementPtr json;
    ASSERT_NO_THROW(json = parseDHCP4(config));
912
    extractConfig(config);
913

914
    ConstElementPtr status;
915
916
917
    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));

    // check if returned status is OK
Tomek Mrugalski's avatar
Tomek Mrugalski committed
918
    checkResult(status, 0);
919
920
921

    // Now check if the configuration was indeed handled and we have
    // expected pool configured.
922
    Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
923
        getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
924
925
926
927
    ASSERT_TRUE(subnet);
    EXPECT_EQ(1000, subnet->getT1());
    EXPECT_EQ(2000, subnet->getT2());
    EXPECT_EQ(4000, subnet->getValid());
928
929
930

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

933
934
935
936
937
// Goal of this test is to verify that multiple subnets get unique
// subnet-ids. Also, test checks that it's possible to do reconfiguration
// multiple times.
TEST_F(Dhcp4ParserTest, multipleSubnets) {
    ConstElementPtr x;
938
939
    // Collection of four subnets for which subnet ids should be
    // autogenerated - ids are unspecified or set to 0.
940
    string config = "{ " + genIfaceConfig() + "," +
941
942
943
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet4\": [ { "
944
        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
945
946
947
        "    \"subnet\": \"192.0.2.0/24\" "
        " },"
        " {"
948
        "    \"pools\": [ { \"pool\": \"192.0.3.101 - 192.0.3.150\" } ],"
949
950
        "    \"subnet\": \"192.0.3.0/24\", "
        "    \"id\": 0 "
951
952
        " },"
        " {"
953
        "    \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
954
955
956
        "    \"subnet\": \"192.0.4.0/24\" "
        " },"
        " {"
957
        "    \"pools\": [ { \"pool\": \"192.0.5.101 - 192.0.5.150\" } ],"
958
959
960
961
        "    \"subnet\": \"192.0.5.0/24\" "
        " } ],"
        "\"valid-lifetime\": 4000 }";

962
963
    ConstElementPtr json;
    ASSERT_NO_THROW(json = parseDHCP4(config));
964
    extractConfig(config);
965
966
967
968
969

    int cnt = 0; // Number of reconfigurations

    do {
        EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
970
        checkResult(x, 0);
971

972
973
974
975
        CfgMgr::instance().commit();

        const Subnet4Collection* subnets =
            CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
        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);
}

992
993
994
// This test checks that it is possible to assign arbitrary ids for subnets.
TEST_F(Dhcp4ParserTest, multipleSubnetsExplicitIDs) {
    ConstElementPtr x;
995
    // Four subnets with arbitrary subnet ids.
996
    string config = "{ " + genIfaceConfig() + "," +
997
998
999
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet4\": [ { "
1000
        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
1001
1002
1003
1004
        "    \"subnet\": \"192.0.2.0/24\", "
        "    \"id\": 1024 "
        " },"
        " {"
1005
        "    \"pools\": [ { \"pool\": \"192.0.3.101 - 192.0.3.150\" } ],"
1006
1007
1008
1009
        "    \"subnet\": \"192.0.3.0/24\", "
        "    \"id\": 100 "
        " },"
        " {"
1010
        "    \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
1011
1012
1013
1014
        "    \"subnet\": \"192.0.4.0/24\", "
        "    \"id\": 1 "
        " },"
        " {"
1015
        "    \"pools\": [ { \"pool\": \"192.0.5.101 - 192.0.5.150\" } ],"
1016
1017
1018
1019
1020
        "    \"subnet\": \"192.0.5.0/24\", "
        "    \"id\": 34 "
        " } ],"
        "\"valid-lifetime\": 4000 }";

1021
1022
    ConstElementPtr json;
    ASSERT_NO_THROW(json = parseDHCP4(config));
1023
    extractConfig(config);
1024
1025
1026
1027

    int cnt = 0; // Number of reconfigurations
    do {
        EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
1028
        checkResult(x, 0);
1029

1030
1031
1032
1033
        CfgMgr::instance().commit();

        const Subnet4Collection* subnets =
            CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
        ASSERT_TRUE(subnets);
        ASSERT_EQ(4, subnets->size()); // We expect 4 subnets

        // Verify 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.
1045
    } while (++cnt < 3);
1046
1047
1048
}

// Check that the configuration with two subnets having the same id is rejected.
Josh Soref's avatar
Josh Soref committed
1049
TEST_F(Dhcp4ParserTest, multipleSubnetsOverlappingIDs) {
1050
    ConstElementPtr x;
1051
    // Four subnets, two of them having the same id.
1052
    string config = "{ " + genIfaceConfig() + "," +
1053
1054
1055
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet4\": [ { "
1056
        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
1057
1058
1059
1060
        "    \"subnet\": \"192.0.2.0/24\", "
        "    \"id\": 1024 "
        " },"
        " {"
1061
        "    \"pools\": [ { \"pool\": \"192.0.3.101 - 192.0.3.150\" } ],"
1062
1063
1064
1065
        "    \"subnet\": \"192.0.3.0/24\", "
        "    \"id\": 100 "
        " },"
        " {"
1066
        "    \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
1067
1068
1069
1070
        "    \"subnet\": \"192.0.4.0/24\", "
        "    \"id\": 1024 "
        " },"
        " {"
1071
        "    \"pools\": [ { \"pool\": \"192.0.5.101 - 192.0.5.150\" } ],"
1072
1073
1074
1075
1076
        "    \"subnet\": \"192.0.5.0/24\", "
        "    \"id\": 34 "
        " } ],"
        "\"valid-lifetime\": 4000 }";

1077
1078
    ConstElementPtr json;
    ASSERT_NO_THROW(json = parseDHCP4(config));
1079
1080

    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
1081
    checkResult(x, 1);
1082
    EXPECT_TRUE(errorContainsPosition(x, "<string>"));
1083
1084
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
1085
1086
1087
1088
1089
1090
// Goal of this test is to verify that a previously configured subnet can be
// deleted in subsequent reconfiguration.
TEST_F(Dhcp4ParserTest, reconfigureRemoveSubnet) {
    ConstElementPtr x;

    // All four subnets
1091
    string config4 = "{ " + genIfaceConfig() + "," +
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1092
1093
1094
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet4\": [ { "
1095
        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
1096
1097
        "    \"subnet\": \"192.0.2.0/24\", "
        "    \"id\": 1 "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1098
1099
        " },"
        " {"
1100
        "    \"pools\": [ { \"pool\": \"192.0.3.101 - 192.0.3.150\" } ],"
1101
1102
        "    \"subnet\": \"192.0.3.0/24\", "
        "    \"id\": 2 "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1103
1104
        " },"
        " {"
1105
        "    \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
1106
1107
        "    \"subnet\": \"192.0.4.0/24\", "
        "    \"id\": 3 "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1108
1109
        " },"
        " {"
1110
        "    \"pools\": [ { \"pool\": \"192.0.5.101 - 192.0.5.150\" } ],"
1111
1112
        "    \"subnet\": \"192.0.5.0/24\", "
        "    \"id\": 4 "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1113
1114
1115
1116
        " } ],"
        "\"valid-lifetime\": 4000 }";

    // Three subnets (the last one removed)
1117
    string config_first3 = "{ " + genIfaceConfig() + "," +
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1118
1119
1120
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet4\": [ { "
1121
        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
1122
1123
        "    \"subnet\": \"192.0.2.0/24\", "
        "    \"id\": 1 "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1124
1125
        " },"
        " {"
1126
        "    \"pools\": [ { \"pool\":  \"192.0.3.101 - 192.0.3.150\" } ],"
1127
1128
        "    \"subnet\": \"192.0.3.0/24\", "
        "    \"id\": 2 "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1129
1130
        " },"
        " {"
1131
        "    \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
1132
1133
        "    \"subnet\": \"192.0.4.0/24\", "
        "    \"id\": 3 "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1134
1135
1136
1137
        " } ],"
        "\"valid-lifetime\": 4000 }";

    // Second subnet removed
1138
    string config_second_removed = "{ " + genIfaceConfig() + "," +
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1139
1140
1141
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet4\": [ { "
1142
        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
1143
1144
        "    \"subnet\": \"192.0.2.0/24\", "
        "    \"id\": 1 "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1145
1146
        " },"
        " {"
1147
        "    \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
1148
1149
        "    \"subnet\": \"192.0.4.0/24\", "
        "    \"id\": 3 "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1150
1151
        " },"
        " {"
1152
        "    \"pools\": [ { \"pool\": \"192.0.5.101 - 192.0.5.150\" } ],"
1153
1154
        "    \"subnet\": \"192.0.5.0/24\", "
        "    \"id\": 4 "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1155
1156
1157
1158
1159
1160
        " } ],"
        "\"valid-lifetime\": 4000 }";

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

1161
1162
    ConstElementPtr json;
    ASSERT_NO_THROW(json = parseDHCP4(config4));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1163
    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
1164
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1165

1166
1167
    const Subnet4Collection* subnets =
        CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->getAll();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1168
1169
1170
    ASSERT_TRUE(subnets);
    ASSERT_EQ(4, subnets->size()); // We expect 4 subnets

1171
1172
    CfgMgr::instance().clear();

Tomek Mrugalski's avatar
Tomek Mrugalski committed
1173
    // Do the reconfiguration (the last subnet is removed)
1174
    ASSERT_NO_THROW(json = parseDHCP4(config_first3));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1175
    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
1176
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1177

1178
    subnets = CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->getAll();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1179
1180
1181
1182
1183
1184
1185
1186
    ASSERT_TRUE(subnets);
    ASSERT_EQ(3, subnets->size()); // We expect 3 subnets now (4th is removed)

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

1187
1188
    CfgMgr::instance().clear();

Tomek Mrugalski's avatar
Tomek Mrugalski committed
1189
1190
    /// CASE 2: Configure 4 subnets, then reconfigure and remove one
    /// from in between (not first, not last)
1191
    ASSERT_NO_THROW(json = parseDHCP4(config4));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1192
    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
1193
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1194

1195
1196
    CfgMgr::instance().clear();

Tomek Mrugalski's avatar
Tomek Mrugalski committed
1197
    // Do reconfiguration
1198
    ASSERT_NO_THROW(json = parseDHCP4(config_second_removed));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1199
    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
1200
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1201