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

#include <config.h>

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

12
#include <cc/command_interpreter.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
13
#include <config/module_spec.h>
14
#include <dhcp4/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
29
30
31
#include <hooks/hooks_manager.h>

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

35
#include <boost/foreach.hpp>
36
37
#include <boost/scoped_ptr.hpp>

Tomek Mrugalski's avatar
Tomek Mrugalski committed
38
39
40
41
#include <iostream>
#include <fstream>
#include <sstream>
#include <limits.h>
42
43
44
45

using namespace isc;
using namespace isc::asiolink;
using namespace isc::config;
46
47
using namespace isc::data;
using namespace isc::dhcp;
48
using namespace isc::dhcp::test;
49
50
using namespace isc::hooks;
using namespace std;
51
52

namespace {
53
54
55
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
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\""
    "     } ]"
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
    "}",

    // 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\""
    "     } ]"
131
132
    "}"
};
133

134
135
136
137
138
139
140
141
142
143
/// @brief Prepends the given name with the DHCP4 source directory
///
/// @param name file name of the desired file
/// @return string containing the absolute path of the file in the DHCP source
/// directory.
std::string specfile(const std::string& name) {
    return (std::string(DHCP4_SRC_DIR) + "/" + name);
}

/// @brief Tests that the spec file is valid.
144
/// Verifies that the Kea DHCPv4 configuration specification file is valid.
145
TEST(Dhcp4SpecTest, basicSpec) {
146
    (isc::config::moduleSpecFromFile(specfile("dhcp4.spec")));
147
148
149
    ASSERT_NO_THROW(isc::config::moduleSpecFromFile(specfile("dhcp4.spec")));
}

150
class Dhcp4ParserTest : public ::testing::Test {
151
152
153
154
155
156
157
158
159
160
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());
    }

161
162
public:
    Dhcp4ParserTest()
163
    : rcode_(-1) {
164
165
166
        // 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.
167
        srv_.reset(new Dhcpv4Srv(0));
168
        // Create fresh context.
169
        resetConfiguration();
170
171
    }

172
public:
Tomek Mrugalski's avatar
Tomek Mrugalski committed
173

174
    // Checks if the result of DHCP server configuration has
Tomek Mrugalski's avatar
Tomek Mrugalski committed
175
176
177
178
179
180
181
182
    // 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_);
    }

183
    ~Dhcp4ParserTest() {
184
        resetConfiguration();
185
186

        // ... and delete the hooks library marker files if present
187
188
        static_cast<void>(remove(LOAD_MARKER_FILE));
        static_cast<void>(remove(UNLOAD_MARKER_FILE));
189
190
    };

191
192
193
194
195
196
197
198
    /// @brief Returns an interface configuration used by the most of the
    /// unit tests.
    std::string genIfaceConfig() const {
        return ("\"interfaces-config\": {"
                "  \"interfaces\": [ \"*\" ]"
                "}");
    }

199
200
201
    /// @brief Create the simple configuration with single option.
    ///
    /// This function allows to set one of the parameters that configure
202
203
    /// option value. These parameters are: "name", "code", "data",
    /// "csv-format" and "space".
204
    ///
205
    /// @param param_value string holding option parameter value to be
206
207
208
    /// injected into the configuration string.
    /// @param parameter name of the parameter to be configured with
    /// param value.
209
210
    /// @return configuration string containing custom values of parameters
    /// describing an option.
211
212
213
214
215
    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;
216
            params["space"] = DHCP4_OPTION_SPACE;
217
            params["code"] = "56";
218
            params["data"] = "ABCDEF0105";
219
            params["csv-format"] = "false";
220
221
222
        } else if (parameter == "space") {
            params["name"] = "dhcp-message";
            params["space"] = param_value;
223
            params["code"] = "56";
224
            params["data"] = "ABCDEF0105";
225
            params["csv-format"] = "false";
226
        } else if (parameter == "code") {
227
            params["name"] = "dhcp-message";
228
            params["space"] = DHCP4_OPTION_SPACE;
229
            params["code"] = param_value;
230
            params["data"] = "ABCDEF0105";
231
            params["csv-format"] = "false";
232
        } else if (parameter == "data") {
233
            params["name"] = "dhcp-message";
234
            params["space"] = DHCP4_OPTION_SPACE;
235
236
            params["code"] = "56";
            params["data"] = param_value;
237
            params["csv-format"] = "false";
238
        } else if (parameter == "csv-format") {
239
            params["name"] = "dhcp-message";
240
            params["space"] = DHCP4_OPTION_SPACE;
241
            params["code"] = "56";
242
            params["data"] = "ABCDEF0105";
243
            params["csv-format"] = param_value;
244
245
246
247
        }
        return (createConfigWithOption(params));
    }

248
249
250
251
252
253
254
255
    /// @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.
256
257
    std::string createConfigWithOption(const std::map<std::string, std::string>& params) {
        std::ostringstream stream;
258
        stream << "{ " << genIfaceConfig() << "," <<
259
260
261
            "\"rebind-timer\": 2000, "
            "\"renew-timer\": 1000, "
            "\"subnet4\": [ { "
262
            "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
263
264
265
266
267
268
269
270
            "    \"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 {
271
                // cppcheck-suppress unreadVariable
272
273
274
275
                first = false;
            }
            if (param.first == "name") {
                stream << "\"name\": \"" << param.second << "\"";
276
277
            } else if (param.first == "space") {
                stream << "\"space\": \"" << param.second << "\"";
278
279
280
281
            } else if (param.first == "code") {
                stream << "\"code\": " << param.second << "";
            } else if (param.first == "data") {
                stream << "\"data\": \"" << param.second << "\"";
282
283
            } else if (param.first == "csv-format") {
                stream << "\"csv-format\": " << param.second;
284
285
286
287
288
289
290
291
292
            }
        }
        stream <<
            "        } ]"
            " } ],"
            "\"valid-lifetime\": 4000 }";
        return (stream.str());
    }

293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
    /// @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.
308
    OptionDescriptor
309
310
311
    getOptionFromSubnet(const IOAddress& subnet_address,
                        const uint16_t option_code,
                        const uint16_t expected_options_count = 1) {
312
        Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
313
            getCfgSubnets4()->selectSubnet(subnet_address);
314
315
316
317
318
319
        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";
        }
320
        OptionContainerPtr options =
321
            subnet->getCfgOption()->getAll(DHCP4_OPTION_SPACE);
322
323
324
325
326
327
328
329
        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.
330
        const OptionContainerTypeIndex& idx = options->get<1>();
331
332
333
334

        // 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.
335
336
        std::pair<OptionContainerTypeIndex::const_iterator,
                  OptionContainerTypeIndex::const_iterator> range =
337
338
339
340
341
342
343
            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) {
344
            return (OptionDescriptor(OptionPtr(), false));
345
346
347
348
349
        }

        return (*range.first);
    }

350
351
352
353
354
355
356
357
358
359
360
361
362
363
    /// @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);
364
        ConstElementPtr json = parseDHCP4(config);
365
        EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
366
367
        checkResult(x, 1);
        EXPECT_TRUE(errorContainsPosition(x, "<string>"));
368
369
    }

Francis Dupont's avatar
Francis Dupont committed
370
    /// @brief Test invalid option parameter value.
371
372
373
374
375
376
377
378
379
380
    ///
    /// 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);
381
        ConstElementPtr json = parseDHCP4(config);
382
        EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
383
384
        checkResult(x, 1);
        EXPECT_TRUE(errorContainsPosition(x, "<string>"));
385
386
    }

387
388
389
390
391
392
393
394
395
    /// @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.
396
    void testOption(const OptionDescriptor& option_desc,
397
398
399
400
                    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.
401
        ASSERT_TRUE(option_desc.option_);
402
        // Verify option type.
403
        EXPECT_EQ(expected_code, option_desc.option_->getType());
404
405
406
407
408
        // 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.
409
410
        util::OutputBuffer buf(option_desc.option_->getData().size());
        option_desc.option_->pack(buf);
411
412
413
414
415
        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.
416
            ASSERT_GE(buf.getLength() - option_desc.option_->getHeaderLen(),
417
418
                      expected_data_len);
        } else {
419
            ASSERT_EQ(buf.getLength() - option_desc.option_->getHeaderLen(),
420
421
                      expected_data_len);
        }
422
        // Verify that the data is correct. Do not verify suboptions and a header.
423
        const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
424
        EXPECT_EQ(0, memcmp(expected_data, data + option_desc.option_->getHeaderLen(),
425
                            expected_data_len));
426
427
    }

428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
    /// @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"));
454
        // The subnet should now hold one option with the specified option code.
455
        OptionDescriptor desc =
456
            getOptionFromSubnet(IOAddress("192.0.2.24"), option_code);
457
        ASSERT_TRUE(desc.option_);
458
459
460
        testOption(desc, option_code, expected_data, expected_data_len);
    }

461
462
463
464
465
466
467
468
469
470
471
472
473
    /// @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) {
474
        CfgMgr::instance().clear();
475
        ConstElementPtr json;
476
477
        ConstElementPtr status;
        try {
478
            json = parseJSON(config);
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
            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);
    }

514
515
516
517
518
519
520
521
    /// @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() {
522
        string config = "{ " + genIfaceConfig() + "," +
523
            "\"hooks-libraries\": [ ], "
524
525
            "\"valid-lifetime\": 4000, "
            "\"subnet4\": [ ], "
526
            "\"dhcp-ddns\": { \"enable-updates\" : false }, "
527
            "\"option-def\": [ ], "
528
            "\"option-data\": [ ] }";
529
530
        static_cast<void>(executeConfiguration(config,
                                               "reset configuration database"));
531
        CfgMgr::instance().clear();
532
    }
533

534
535
    /// @brief Retrieve an option associated with a host.
    ///
536
537
    /// The option is retrieved from the "dhcp4" option space.
    ///
538
539
540
541
    /// @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.
    ///
542
    /// @return Pointer to an option or NULL pointer if not found.
543
544
545
    template<typename ReturnType>
    ReturnType
    retrieveOption(const Host& host, const uint16_t option_code) const {
546
        return (retrieveOption<ReturnType>(host, DHCP4_OPTION_SPACE, option_code));
547
548
549
550
551
552
553
554
555
    }

    /// @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.
    ///
556
    /// @return Pointer to an option or NULL pointer if not found.
557
558
559
560
    template<typename ReturnType>
    ReturnType
    retrieveOption(const Host& host, const std::string& space,
                   const uint16_t option_code) const {
561
562
        ConstCfgOptionPtr cfg_option = host.getCfgOption4();
        if (cfg_option) {
563
            OptionDescriptor opt_desc = cfg_option->get(space, option_code);
564
565
566
567
568
569
570
            if (opt_desc.option_) {
                return (boost::dynamic_pointer_cast<
                        typename ReturnType::element_type>(opt_desc.option_));
            }
        }
        return (ReturnType());
    }
571

572
573
574
575
576
577
578
579
580
581
582

    /// @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;
583
        ConstElementPtr json;
584

585
        EXPECT_NO_THROW(json = parseDHCP4(config, true));
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
        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);
    }

604
605
606
607
    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
608
609
610
611
};

/// 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
612
TEST_F(Dhcp4ParserTest, bogusCommand) {
613
614
615
616

    ConstElementPtr x;

    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_,
617
                    parseJSON("{\"bogus\": 5}")));
618
619

    // returned value must be 1 (configuration parse error)
Tomek Mrugalski's avatar
Tomek Mrugalski committed
620
    checkResult(x, 1);
621
622
623

    // it should be refused by syntax too
    EXPECT_THROW(parseDHCP4("{\"bogus\": 5}"), Dhcp4ParseError);
624
625
626
627
628
}

/// 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
629
TEST_F(Dhcp4ParserTest, emptySubnet) {
630

631
632
    ConstElementPtr json;
    EXPECT_NO_THROW(json = parseDHCP4("{ " + genIfaceConfig() + "," +
633
634
635
                                      "\"rebind-timer\": 2000, "
                                      "\"renew-timer\": 1000, "
                                      "\"subnet4\": [  ], "
636
637
638
639
                                      "\"valid-lifetime\": 4000 }"));

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

    // returned value should be 0 (success)
Tomek Mrugalski's avatar
Tomek Mrugalski committed
642
    checkResult(status, 0);
643
644
}

645
646
647
648
/// 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) {

649
    string config = "{ " + genIfaceConfig() + "," +
650
651
        "\"rebind-timer\": 2000, "
        "\"subnet4\": [ { "
652
        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
653
654
655
        "    \"subnet\": \"192.0.2.0/24\" } ],"
        "\"valid-lifetime\": 4000 }";

656
657
    ConstElementPtr json;
    ASSERT_NO_THROW(json = parseDHCP4(config));
658

659
    ConstElementPtr status;
660
661
662
663
664
    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));

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

665
    Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
666
        getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
667
    ASSERT_TRUE(subnet);
668
    EXPECT_FALSE(subnet->getT1().unspecified());
669
    EXPECT_FALSE(subnet->getT2().unspecified());
670
    EXPECT_EQ(900, subnet->getT1()); // that's the default value
671
672
673
674
675
676
677
678
679
680
681
682
    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) {

683
    string config = "{ " + genIfaceConfig() + "," +
684
685
        "\"renew-timer\": 1000, "
        "\"subnet4\": [ { "
686
        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
687
688
689
        "    \"subnet\": \"192.0.2.0/24\" } ],"
        "\"valid-lifetime\": 4000 }";

690
691
    ConstElementPtr json;
    ASSERT_NO_THROW(json = parseDHCP4(config));
692

693
    ConstElementPtr status;
694
695
696
697
698
    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));

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

699
    Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
700
        getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
701
702
703
    ASSERT_TRUE(subnet);
    EXPECT_FALSE(subnet->getT1().unspecified());
    EXPECT_EQ(1000, subnet->getT1());
704
705
    EXPECT_FALSE(subnet->getT2().unspecified());
    EXPECT_EQ(1800, subnet->getT2()); // that's the default value
706
707
708
709
710
711
    EXPECT_EQ(4000, subnet->getValid());

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

712
713
/// The goal of this test is to verify if defined subnet uses global
/// parameter timer definitions.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
714
TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) {
715

716
    string config = "{ " + genIfaceConfig() + "," +
717
718
719
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet4\": [ { "
720
        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
721
722
723
        "    \"subnet\": \"192.0.2.0/24\" } ],"
        "\"valid-lifetime\": 4000 }";

724
725
    ConstElementPtr json;
    ASSERT_NO_THROW(json = parseDHCP4(config));
726

727
    ConstElementPtr status;
728
729
730
    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));

    // check if returned status is OK
Tomek Mrugalski's avatar
Tomek Mrugalski committed
731
    checkResult(status, 0);
732
733
734

    // Now check if the configuration was indeed handled and we have
    // expected pool configured.
735
    Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
736
        getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
737
738
739
740
    ASSERT_TRUE(subnet);
    EXPECT_EQ(1000, subnet->getT1());
    EXPECT_EQ(2000, subnet->getT2());
    EXPECT_EQ(4000, subnet->getValid());
741
742
743

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

746
747
748
749
750
// 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;
751
752
    // Collection of four subnets for which subnet ids should be
    // autogenerated - ids are unspecified or set to 0.
753
    string config = "{ " + genIfaceConfig() + "," +
754
755
756
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet4\": [ { "
757
        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
758
759
760
        "    \"subnet\": \"192.0.2.0/24\" "
        " },"
        " {"
761
        "    \"pools\": [ { \"pool\": \"192.0.3.101 - 192.0.3.150\" } ],"
762
763
        "    \"subnet\": \"192.0.3.0/24\", "
        "    \"id\": 0 "
764
765
        " },"
        " {"
766
        "    \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
767
768
769
        "    \"subnet\": \"192.0.4.0/24\" "
        " },"
        " {"
770
        "    \"pools\": [ { \"pool\": \"192.0.5.101 - 192.0.5.150\" } ],"
771
772
773
774
        "    \"subnet\": \"192.0.5.0/24\" "
        " } ],"
        "\"valid-lifetime\": 4000 }";

775
776
    ConstElementPtr json;
    ASSERT_NO_THROW(json = parseDHCP4(config));
777
778
779
780
781

    int cnt = 0; // Number of reconfigurations

    do {
        EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
782
        checkResult(x, 0);
783

784
785
786
787
        CfgMgr::instance().commit();

        const Subnet4Collection* subnets =
            CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
        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);
}

804
805
806
// This test checks that it is possible to assign arbitrary ids for subnets.
TEST_F(Dhcp4ParserTest, multipleSubnetsExplicitIDs) {
    ConstElementPtr x;
807
    // Four subnets with arbitrary subnet ids.
808
    string config = "{ " + genIfaceConfig() + "," +
809
810
811
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet4\": [ { "
812
        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
813
814
815
816
        "    \"subnet\": \"192.0.2.0/24\", "
        "    \"id\": 1024 "
        " },"
        " {"
817
        "    \"pools\": [ { \"pool\": \"192.0.3.101 - 192.0.3.150\" } ],"
818
819
820
821
        "    \"subnet\": \"192.0.3.0/24\", "
        "    \"id\": 100 "
        " },"
        " {"
822
        "    \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
823
824
825
826
        "    \"subnet\": \"192.0.4.0/24\", "
        "    \"id\": 1 "
        " },"
        " {"
827
        "    \"pools\": [ { \"pool\": \"192.0.5.101 - 192.0.5.150\" } ],"
828
829
830
831
832
        "    \"subnet\": \"192.0.5.0/24\", "
        "    \"id\": 34 "
        " } ],"
        "\"valid-lifetime\": 4000 }";

833
834
    ConstElementPtr json;
    ASSERT_NO_THROW(json = parseDHCP4(config));
835
836
837
838

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

841
842
843
844
        CfgMgr::instance().commit();

        const Subnet4Collection* subnets =
            CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
845
846
847
848
849
850
851
852
853
854
855
        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.
856
    } while (++cnt < 3);
857
858
859
860
861
}

// Check that the configuration with two subnets having the same id is rejected.
TEST_F(Dhcp4ParserTest, multipleSubnetsOverlapingIDs) {
    ConstElementPtr x;
862
    // Four subnets, two of them having the same id.
863
    string config = "{ " + genIfaceConfig() + "," +
864
865
866
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet4\": [ { "
867
        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
868
869
870
871
        "    \"subnet\": \"192.0.2.0/24\", "
        "    \"id\": 1024 "
        " },"
        " {"
872
        "    \"pools\": [ { \"pool\": \"192.0.3.101 - 192.0.3.150\" } ],"
873
874
875
876
        "    \"subnet\": \"192.0.3.0/24\", "
        "    \"id\": 100 "
        " },"
        " {"
877
        "    \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
878
879
880
881
        "    \"subnet\": \"192.0.4.0/24\", "
        "    \"id\": 1024 "
        " },"
        " {"
882
        "    \"pools\": [ { \"pool\": \"192.0.5.101 - 192.0.5.150\" } ],"
883
884
885
886
887
        "    \"subnet\": \"192.0.5.0/24\", "
        "    \"id\": 34 "
        " } ],"
        "\"valid-lifetime\": 4000 }";

888
889
    ConstElementPtr json;
    ASSERT_NO_THROW(json = parseDHCP4(config));
890
891

    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
892
    checkResult(x, 1);
893
    EXPECT_TRUE(errorContainsPosition(x, "<string>"));
894
895
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
896
897
898
899
900
901
// 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
902
    string config4 = "{ " + genIfaceConfig() + "," +
Tomek Mrugalski's avatar
Tomek Mrugalski committed
903
904
905
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet4\": [ { "
906
        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
907
908
        "    \"subnet\": \"192.0.2.0/24\", "
        "    \"id\": 1 "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
909
910
        " },"
        " {"
911
        "    \"pools\": [ { \"pool\": \"192.0.3.101 - 192.0.3.150\" } ],"
912
913
        "    \"subnet\": \"192.0.3.0/24\", "
        "    \"id\": 2 "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
914
915
        " },"
        " {"
916
        "    \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
917
918
        "    \"subnet\": \"192.0.4.0/24\", "
        "    \"id\": 3 "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
919
920
        " },"
        " {"
921
        "    \"pools\": [ { \"pool\": \"192.0.5.101 - 192.0.5.150\" } ],"
922
923
        "    \"subnet\": \"192.0.5.0/24\", "
        "    \"id\": 4 "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
924
925
926
927
        " } ],"
        "\"valid-lifetime\": 4000 }";

    // Three subnets (the last one removed)
928
    string config_first3 = "{ " + genIfaceConfig() + "," +
Tomek Mrugalski's avatar
Tomek Mrugalski committed
929
930
931
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet4\": [ { "
932
        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
933
934
        "    \"subnet\": \"192.0.2.0/24\", "
        "    \"id\": 1 "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
935
936
        " },"
        " {"
937
        "    \"pools\": [ { \"pool\":  \"192.0.3.101 - 192.0.3.150\" } ],"
938
939
        "    \"subnet\": \"192.0.3.0/24\", "
        "    \"id\": 2 "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
940
941
        " },"
        " {"
942
        "    \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
943
944
        "    \"subnet\": \"192.0.4.0/24\", "
        "    \"id\": 3 "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
945
946
947
948
        " } ],"
        "\"valid-lifetime\": 4000 }";

    // Second subnet removed
949
    string config_second_removed = "{ " + genIfaceConfig() + "," +
Tomek Mrugalski's avatar
Tomek Mrugalski committed
950
951
952
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet4\": [ { "
953
        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
954
955
        "    \"subnet\": \"192.0.2.0/24\", "
        "    \"id\": 1 "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
956
957
        " },"
        " {"
958
        "    \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
959
960
        "    \"subnet\": \"192.0.4.0/24\", "
        "    \"id\": 3 "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
961
962
        " },"
        " {"
963
        "    \"pools\": [ { \"pool\": \"192.0.5.101 - 192.0.5.150\" } ],"
964
965
        "    \"subnet\": \"192.0.5.0/24\", "
        "    \"id\": 4 "
Tomek Mrugalski's avatar
Tomek Mrugalski committed
966
967
968
969
970
971
        " } ],"
        "\"valid-lifetime\": 4000 }";

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

972
973
    ConstElementPtr json;
    ASSERT_NO_THROW(json = parseDHCP4(config4));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
974
    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
975
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
976

977
978
    const Subnet4Collection* subnets =
        CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->getAll();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
979
980
981
    ASSERT_TRUE(subnets);
    ASSERT_EQ(4, subnets->size()); // We expect 4 subnets

982
983
    CfgMgr::instance().clear();

Tomek Mrugalski's avatar
Tomek Mrugalski committed
984
    // Do the reconfiguration (the last subnet is removed)
985
    ASSERT_NO_THROW(json = parseDHCP4(config_first3));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
986
    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
987
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
988

989
    subnets = CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->getAll();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
990
991
992
993
994
995
996
997
    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());

998
999
    CfgMgr::instance().clear();

Tomek Mrugalski's avatar
Tomek Mrugalski committed
1000
1001
1002
1003
    /// CASE 2: Configure 4 subnets, then reconfigure and remove one
    /// from in between (not first, not last)

    /// @todo: Uncomment subnet removal test as part of #3281.
1004
    ASSERT_NO_THROW(json = parseDHCP4(config4));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1005
    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
1006
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1007

1008
1009
    CfgMgr::instance().clear();

Tomek Mrugalski's avatar
Tomek Mrugalski committed
1010
    // Do reconfiguration
1011
    ASSERT_NO_THROW(json = parseDHCP4(config_second_removed));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1012
    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
1013
    checkResult(x, 0);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1014

1015
    subnets = CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->getAll();
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
    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());

}

/// @todo: implement subnet removal test as part of #3281.
1027

1028
1029
1030
1031
// Checks if the next-server defined as global parameter is taken into
// consideration.
TEST_F(Dhcp4ParserTest, nextServerGlobal) {

1032
    string config = "{ " + genIfaceConfig() + "," +
1033
1034
1035
1036
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"next-server\": \"1.2.3.4\", "
        "\"subnet4\": [ { "
1037
        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
1038
1039
1040
        "    \"subnet\": \"192.0.2.0/24\" } ],"
        "\"valid-lifetime\": 4000 }";

1041
1042
    ConstElementPtr json;
    ASSERT_NO_THROW(json = parseDHCP4(config));
1043

1044
    ConstElementPtr status;
1045
1046
1047
1048
1049
1050
1051
    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));

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

    // Now check if the configuration was indeed handled and we have
    // expected pool configured.
1052
    Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
1053
        getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
1054
1055
1056
1057
1058
1059
1060
1061
    ASSERT_TRUE(subnet);
    EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
}

// Checks if the next-server defined as subnet parameter is taken into
// consideration.
TEST_F(Dhcp4ParserTest, nextServerSubnet) {

1062
    string config = "{ " + genIfaceConfig() + "," +
1063
1064
1065
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet4\": [ { "
1066
        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
1067
1068
1069
1070
        "    \"next-server\": \"1.2.3.4\", "
        "    \"subnet\": \"192.0.2.0/24\" } ],"
        "\"valid-lifetime\": 4000 }";

1071
1072
    ConstElementPtr json;
    ASSERT_NO_THROW(json = parseDHCP4(config));
1073

1074
    ConstElementPtr status;
1075
1076
1077
1078
1079
1080
1081
    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));

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

    // Now check if the configuration was indeed handled and we have
    // expected pool configured.
1082
    Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
1083
        getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
1084
1085
1086
1087
    ASSERT_TRUE(subnet);
    EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
1088
1089
1090
// Test checks several negative scenarios for next-server configuration: bogus
// address, IPv6 adddress and empty string.
TEST_F(Dhcp4ParserTest, nextServerNegative) {
1091
    IfaceMgrTestConfig test_config(true);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1092
1093

    // Config with junk instead of next-server address
1094
    string config_bogus1 = "{ " + genIfaceConfig() + "," +
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1095
1096
1097
        "\"rebind-timer\": 2000, "
        "\"renew-timer\": 1000, "
        "\"subnet4\": [ { "
1098
        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"