host_reservation_parser_unittest.cc 53.9 KB
Newer Older
1
// Copyright (C) 2014-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

#include <config.h>

9
#include <asiolink/io_address.h>
10
#include <cc/data.h>
11
12
#include <dhcp/duid.h>
#include <dhcp/hwaddr.h>
13
14
15
#include <dhcp/option_int.h>
#include <dhcp/option4_addrlst.h>
#include <dhcp/option6_addrlst.h>
16
#include <dhcpsrv/cfgmgr.h>
17
#include <dhcpsrv/cfg_hosts_util.h>
18
#include <dhcpsrv/host.h>
19
#include <dhcpsrv/parsers/dhcp_parsers.h>
20
#include <dhcpsrv/parsers/host_reservation_parser.h>
21
#include <dhcpsrv/testutils/config_result_check.h>
22
#include <testutils/test_to_element.h>
23
#include <boost/pointer_cast.hpp>
24
#include <boost/algorithm/string.hpp>
25
#include <gtest/gtest.h>
26
#include <iterator>
27
#include <sstream>
28
#include <string>
29
#include <vector>
30

31
using namespace isc::asiolink;
32
33
using namespace isc::data;
using namespace isc::dhcp;
34
using namespace isc::test;
35
36
37

namespace {

38
39
40
/// @brief Holds a type of the last identifier in @c IdentifierType enum.
///
/// This value must be updated when new identifiers are added to the enum.
41
// const Host::IdentifierType LAST_IDENTIFIER_TYPE = Host::IDENT_CIRCUIT_ID;
42

43
44
45
46
47
48
49
50
51
52
53
54
55
56
/// @brief Test fixture class for @c HostReservationParser.
class HostReservationParserTest : public ::testing::Test {
protected:

    /// @brief Setup for each test.
    ///
    /// Clears the configuration in the @c CfgMgr.
    virtual void SetUp();

    /// @brief Cleans up after each test.
    ///
    /// Clears the configuration in the @c CfgMgr.
    virtual void TearDown();

57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
    /// @brief Checks if the reservation is in the range of reservations.
    ///
    /// @param resrv Reservation to be searched for.
    /// @param range Range of reservations returned by the @c Host object
    /// in which the reservation will be searched.
    bool
    reservationExists(const IPv6Resrv& resrv, const IPv6ResrvRange& range) {
        for (IPv6ResrvIterator it = range.first; it != range.second;
             ++it) {
            if (resrv == it->second) {
                return (true);
            }
        }
        return (false);
    }

73
74
75
76
77
78
79
    /// @brief Retrieves DHCP option from a host.
    ///
    /// @param host Reference to a host object for which an option should be
    /// retrieved.
    /// @param option_space Option space name.
    /// @param option_code Code of an option to be retrieved.
    ///
80
81
    /// @return Pointer to the option retrieved or NULL pointer if option
    /// hasn't been found.
82
83
84
    OptionPtr
    retrieveOption(const Host& host, const std::string& option_space,
                   const uint16_t option_code) const {
85
        if ((option_space != DHCP6_OPTION_SPACE) && (option_space != DHCP4_OPTION_SPACE)) {
86
87
88
89
90
            return (OptionPtr());
        }

        // Retrieve a pointer to the appropriate container depending if we're
        // interested in DHCPv4 or DHCPv6 options.
91
        ConstCfgOptionPtr cfg_option = (option_space == DHCP4_OPTION_SPACE ?
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
                                        host.getCfgOption4() : host.getCfgOption6());

        // Retrieve options.
        OptionContainerPtr options = cfg_option->getAll(option_space);
        if (options) {
            const OptionContainerTypeIndex& idx = options->get<1>();
            OptionContainerTypeIndex::const_iterator it = idx.find(option_code);
            if (it != idx.end()) {
                return (it->option_);
            }
        }

        return (OptionPtr());
    }

    /// @brief This test verifies that it is possible to specify an empty list
    /// of options for a host.
    ///
    /// @tparam ParserType Type of the parser to be tested.
    template<typename ParserType>
    void testEmptyOptionList() const {
        // Create configuration with empty option list. Note that we have to
        // add reservation for at least one resource because host declarations
        // without any reservations are rejected. Thus, we have added hostname.
        std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
            "\"hostname\": \"foo.isc.org\","
            "\"option-data\": [ ]"
            "}";

        ElementPtr config_element = Element::fromJSON(config);

123
124
        ParserType parser;
        ASSERT_NO_THROW(parser.parse(SubnetID(10), config_element));
125
126
127
128
129
130
131
132
133
134
135
136

        // Retrieve a host.
        HostCollection hosts;
        CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
        ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));
        ASSERT_EQ(1, hosts.size());

        // There should be no options assigned to a host.
        EXPECT_TRUE(hosts[0]->getCfgOption4()->empty());
        EXPECT_TRUE(hosts[0]->getCfgOption6()->empty());
    }

Andrei Pavel's avatar
Andrei Pavel committed
137
    /// @brief This test verifies that the parser can parse a DHCPv4
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
    /// reservation configuration including a specific identifier.
    ///
    /// @param identifier_name Identifier name.
    /// @param identifier_type Identifier type.
    void testIdentifier4(const std::string& identifier_name,
                         const std::string& identifier_value,
                         const Host::IdentifierType& expected_identifier_type,
                         const std::vector<uint8_t>& expected_identifier) const {
        std::ostringstream config;
        config << "{ \"" << identifier_name << "\": \"" << identifier_value
               << "\","
               << "\"ip-address\": \"192.0.2.112\","
               << "\"hostname\": \"\" }";

        ElementPtr config_element = Element::fromJSON(config.str());

154
155
        HostReservationParser4 parser;
        ASSERT_NO_THROW(parser.parse(SubnetID(10), config_element));
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170

        CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
        HostCollection hosts;
        ASSERT_NO_THROW(hosts = cfg_hosts->getAll(expected_identifier_type,
                                                  &expected_identifier[0],
                                                  expected_identifier.size()));

        ASSERT_EQ(1, hosts.size());

        EXPECT_EQ(10, hosts[0]->getIPv4SubnetID());
        EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
        EXPECT_EQ("192.0.2.112", hosts[0]->getIPv4Reservation().toText());
        EXPECT_TRUE(hosts[0]->getHostname().empty());
    }

Andrei Pavel's avatar
Andrei Pavel committed
171
    /// @brief This test verifies that the parser returns an error when
172
173
174
175
176
177
178
    /// configuration is invalid.
    ///
    /// @param config JSON configuration to be tested.
    /// @tparam ParserType Type of the parser class to use.
    template<typename ParserType>
    void testInvalidConfig(const std::string& config) const {
        ElementPtr config_element = Element::fromJSON(config);
179
180
        ParserType parser;
        EXPECT_THROW(parser.parse(SubnetID(10), config_element), DhcpConfigError);
181
182
    }

183
184
185
186
187
188
    /// @brief HW Address object used by tests.
    HWAddrPtr hwaddr_;

    /// @brief DUID object used by tests.
    DuidPtr duid_;

189
190
    /// @brief Vector holding circuit id used by tests.
    std::vector<uint8_t> circuit_id_;
191
192
193

    /// @brief Vector holding client id used by tests.
    std::vector<uint8_t> client_id_;
194
195
196
197
198
};

void
HostReservationParserTest::SetUp() {
    CfgMgr::instance().clear();
199
200
201
202
203
204
205
206
207
    // Initialize HW address used by tests.
    const uint8_t hwaddr_data[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
    hwaddr_ = HWAddrPtr(new HWAddr(hwaddr_data, sizeof(hwaddr_data),
                                   HTYPE_ETHER));

    // Initialize DUID used by tests.
    const uint8_t duid_data[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
                                  0x08, 0x09, 0x0A };
    duid_ = DuidPtr(new DUID(duid_data, sizeof(duid_data)));
208
209
210

    const std::string circuit_id_str = "howdy";
    circuit_id_.assign(circuit_id_str.begin(), circuit_id_str.end());
211
212
213
214
215

    client_id_.push_back(0x01); // Client identifier type.
    // Often client id comprises HW address.
    client_id_.insert(client_id_.end(), hwaddr_->hwaddr_.begin(),
                      hwaddr_->hwaddr_.end());
216
217
218
219
220
221
222
}

void
HostReservationParserTest::TearDown() {
    CfgMgr::instance().clear();
}

223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
/// @brief class of subnet_id reservations
class CfgHostsSubnet : public CfgToElement {
public:
    /// @brief constructor
    CfgHostsSubnet(ConstCfgHostsPtr hosts, SubnetID id)
        : hosts_(hosts), id_(id) { }

    /// @brief unparse method
    ElementPtr toElement() const;

private:
    /// @brief the host reservation configuration
    ConstCfgHostsPtr hosts_;

    /// @brief the subnet ID
    SubnetID id_;
};

ElementPtr
CfgHostsSubnet::toElement() const {
    CfgHostsList list;
    try {
        list.internalize(hosts_->toElement());
    } catch (const std::exception& ex) {
        ADD_FAILURE() << "CfgHostsSubnet::toElement: " << ex.what();
    }
    ElementPtr result = boost::const_pointer_cast<Element>(list.get(id_));

    // Strip
    for (size_t i = 0; i < result->size(); ++i) {
        ElementPtr resv = result->getNonConst(i);
        ConstElementPtr ip_address = resv->get("ip-address");
        if (ip_address && (ip_address->stringValue() == "0.0.0.0")) {
            resv->remove("ip-address");
        }
        ConstElementPtr ip_addresses = resv->get("ip-addresses");
        if (ip_addresses && ip_addresses->empty()) {
            resv->remove("ip-addresses");
        }
        ConstElementPtr prefixes = resv->get("prefixes");
        if (prefixes && prefixes->empty()) {
            resv->remove("prefixes");
        }
        ConstElementPtr hostname = resv->get("hostname");
        if (hostname && hostname->stringValue().empty()) {
            resv->remove("hostname");
        }
        ConstElementPtr next_server = resv->get("next-server");
        if (next_server && (next_server->stringValue() == "0.0.0.0")) {
            resv->remove("next-server");
        }
        ConstElementPtr server_hostname = resv->get("server-hostname");
        if (server_hostname && server_hostname->stringValue().empty()) {
            resv->remove("server-hostname");
        }
        ConstElementPtr boot_file_name = resv->get("boot-file-name");
        if (boot_file_name && boot_file_name->stringValue().empty()) {
            resv->remove("boot-file-name");
        }
        ConstElementPtr client_classess = resv->get("client-classes");
        if (client_classess && client_classess->empty()) {
            resv->remove("client-classes");
        }
        ConstElementPtr option_data = resv->get("option-data");
        if (option_data && option_data->empty()) {
            resv->remove("option-data");
        }
    }
    return (result);
}

Andrei Pavel's avatar
Andrei Pavel committed
294
// This test verifies that the parser can parse the reservation entry for
295
// which hw-address is a host identifier.
296
TEST_F(HostReservationParserTest, dhcp4HWaddr) {
297
298
    testIdentifier4("hw-address", "1:2:3:4:5:6", Host::IDENT_HWADDR,
                    hwaddr_->hwaddr_);
299
300
}

301
// This test verifies that the parser can parse the reservation entry for
302
303
// which DUID is a host identifier.
TEST_F(HostReservationParserTest, dhcp4DUID) {
304
305
306
    testIdentifier4("duid", "01:02:03:04:05:06:07:08:09:0A",
                    Host::IDENT_DUID, duid_->getDuid());
}
307

308
309
310
311
312
313
// This test verifies that the parser can parse the reservation entry for
// which DUID specified as a string of hexadecimal digits with '0x' prefix
// is a host identifier
TEST_F(HostReservationParserTest, dhcp4DUIDWithPrefix) {
    testIdentifier4("duid", "0x0102030405060708090A",
                    Host::IDENT_DUID, duid_->getDuid());
314
315
}

316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
// This test verifies that the parser can parse a reservation entry for
// which circuit-id is an identifier. The circuit-id is specified as
// a string in quotes.
TEST_F(HostReservationParserTest, dhcp4CircuitIdStringInQuotes) {
    testIdentifier4("circuit-id", "'howdy'", Host::IDENT_CIRCUIT_ID,
                    circuit_id_);
}

// This test verifies that the parser can parse a reservation entry for
// which circuit-id is an identifier. The circuit-id is specified in
// hexadecimal format.
TEST_F(HostReservationParserTest, dhcp4CircuitIdHex) {
    testIdentifier4("circuit-id", "686F776479", Host::IDENT_CIRCUIT_ID,
                    circuit_id_);
}

332
333
334
335
336
337
338
339
// This test verifies that the parser can parse a reservation entry for
// which circuit-id is an identifier. The circuit-id is specified in
// hexadecimal format with a '0x' prefix.
TEST_F(HostReservationParserTest, dhcp4CircuitIdHexWithPrefix) {
    testIdentifier4("circuit-id", "0x686F776479", Host::IDENT_CIRCUIT_ID,
                    circuit_id_);
}

340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
// This test verifies that the parser can parse a reservation entry for
// which client-id is an identifier. The client-id is specified in
// hexadecimal format.
TEST_F(HostReservationParserTest, dhcp4ClientIdHex) {
    testIdentifier4("client-id", "01010203040506", Host::IDENT_CLIENT_ID,
                    client_id_);
}

// This test verifies that the parser can parse a reservation entry for
// which client-id is an identifier. The client-id is specified in
// hexadecimal format with a '0x' prefix.
TEST_F(HostReservationParserTest, dhcp4ClientIdHexWithPrefix) {
    testIdentifier4("client-id", "0x01010203040506", Host::IDENT_CLIENT_ID,
                    client_id_);
}

356
357
358
// This test verifies that the parser can parse the reservation entry
// when IPv4 address is specified, but hostname is not.
TEST_F(HostReservationParserTest, dhcp4NoHostname) {
359
    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0a\","
360
361
362
363
        "\"ip-address\": \"192.0.2.10\" }";

    ElementPtr config_element = Element::fromJSON(config);

364
365
    HostReservationParser4 parser;
    ASSERT_NO_THROW(parser.parse(SubnetID(10), config_element));
366
367
368
369
370
371
372
373
374
375
376

    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
    HostCollection hosts;
    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));

    ASSERT_EQ(1, hosts.size());

    EXPECT_EQ(10, hosts[0]->getIPv4SubnetID());
    EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
    EXPECT_EQ("192.0.2.10", hosts[0]->getIPv4Reservation().toText());
    EXPECT_TRUE(hosts[0]->getHostname().empty());
377
378
379
380
381
382
383
384
385
386
387
388
389

    // lower duid value
    CfgMgr::instance().setFamily(AF_INET);
    CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
    runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);

    CfgMgr::instance().setFamily(AF_INET6);
    CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10));
    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);

    CfgMgr::instance().setFamily(AF_INET);
    CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
390
391
}

392
393
394
395
396
397
398
399
// This test verifies that it is possible to specify DHCPv4 client classes
// within the host reservation.
TEST_F(HostReservationParserTest, dhcp4ClientClasses) {
    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
        "\"client-classes\": [ \"foo\", \"bar\" ] }";

    ElementPtr config_element = Element::fromJSON(config);

400
401
    HostReservationParser4 parser;
    ASSERT_NO_THROW(parser.parse(SubnetID(10), config_element));
402
403
404
405
406
407
408
409
410
411
412

    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
    HostCollection hosts;
    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(hwaddr_));

    ASSERT_EQ(1, hosts.size());

    const ClientClasses& classes = hosts[0]->getClientClasses4();
    ASSERT_EQ(2, classes.size());
    EXPECT_EQ(1, classes.count("foo"));
    EXPECT_EQ(1, classes.count("bar"));
413
414
415
416
417
418
419
420
421
422
423
424

    CfgMgr::instance().setFamily(AF_INET);
    CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
    runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);

    CfgMgr::instance().setFamily(AF_INET6);
    CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10));
    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);

    CfgMgr::instance().setFamily(AF_INET);
    CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
425
426
}

427
// This test verifies that the parser can parse reservation entry
428
// containing next-server, server-hostname and boot-file-name values for
429
430
431
432
// DHCPv4 message fields.
TEST_F(HostReservationParserTest, dhcp4MessageFields) {
    std::string config = "{ \"hw-address\": \"1:2:3:4:5:6\","
        "\"next-server\": \"192.0.2.11\","
433
        "\"server-hostname\": \"some-name.example.org\","
434
435
436
437
        "\"boot-file-name\": \"/tmp/some-file.efi\" }";

    ElementPtr config_element = Element::fromJSON(config);

438
439
    HostReservationParser4 parser;
    ASSERT_NO_THROW(parser.parse(SubnetID(10), config_element));
440
441
442
443
444
445
446
447
448
449
450
451
452

    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
    HostCollection hosts;
    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(Host::IDENT_HWADDR,
                                              &hwaddr_->hwaddr_[0],
                                              hwaddr_->hwaddr_.size()));

    ASSERT_EQ(1, hosts.size());

    EXPECT_EQ(10, hosts[0]->getIPv4SubnetID());
    EXPECT_EQ("192.0.2.11", hosts[0]->getNextServer().toText());
    EXPECT_EQ("some-name.example.org", hosts[0]->getServerHostname());
    EXPECT_EQ("/tmp/some-file.efi", hosts[0]->getBootFileName());
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470

    // canonize hw-address
    config_element->set("hw-address",
                        Element::create(std::string("01:02:03:04:05:06")));
    ElementPtr expected = Element::createList();
    expected->add(config_element);

    CfgMgr::instance().setFamily(AF_INET);
    CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
    runToElementTest<CfgHostsSubnet>(expected, cfg_subnet);

    CfgMgr::instance().setFamily(AF_INET6);
    CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10));
    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);

    CfgMgr::instance().setFamily(AF_INET);
    CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
471
472
}

473
474
475
476
477
478
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
// This test verifies that the invalid value of the next server is rejected.
TEST_F(HostReservationParserTest, invalidNextServer) {
    // Invalid IPv4 address.
    std::string config = "{ \"hw-address\": \"1:2:3:4:5:6\","
        "\"next-server\": \"192.0.2.foo\" }";
    testInvalidConfig<HostReservationParser4>(config);

    // Broadcast address.
    config = "{ \"hw-address\": \"1:2:3:4:5:6\","
        "\"next-server\": \"255.255.255.255\" }";
    testInvalidConfig<HostReservationParser4>(config);

    // IPv6 address.
    config = "{ \"hw-address\": \"1:2:3:4:5:6\","
        "\"next-server\": \"2001:db8:1::1\" }";
    testInvalidConfig<HostReservationParser4>(config);
}

// This test verifies that the invalid server hostname is rejected.
TEST_F(HostReservationParserTest, invalidServerHostname) {
    std::ostringstream config;
    config << "{ \"hw-address\": \"1:2:3:4:5:6\","
        "\"server-hostname\": \"";
    config << std::string(64, 'a');
    config << "\" }";
    testInvalidConfig<HostReservationParser4>(config.str());
}

// This test verifies that the invalid boot file name is rejected.
TEST_F(HostReservationParserTest, invalidBootFileName) {
    std::ostringstream config;
    config << "{ \"hw-address\": \"1:2:3:4:5:6\","
        "\"boot-file-name\": \"";
    config << std::string(128, 'a');
    config << "\" }";
    testInvalidConfig<HostReservationParser4>(config.str());
}
510

511
512
513
514
515
516
// This test verifies that the configuration parser for host reservations
// throws an exception when IPv6 address is specified for IPv4 address
// reservation.
TEST_F(HostReservationParserTest, dhcp4IPv6Address) {
    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
        "\"ip-address\": \"2001:db8:1::1\" }";
517
    testInvalidConfig<HostReservationParser4>(config);
518
519
520
521
522
523
524
}

// This test verifies that the configuration parser for host reservations
// throws an exception when no HW address nor DUID is specified.
TEST_F(HostReservationParserTest, noIdentifier) {
    std::string config = "{ \"ip-address\": \"192.0.2.112\","
        "\"hostname\": \"\" }";
525
    testInvalidConfig<HostReservationParser4>(config);
526
527
}

528
529
530
531
// This test verifies  that the configuration parser for host reservations
// throws an exception when neither ip address nor hostname is specified.
TEST_F(HostReservationParserTest, noResource) {
    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\" }";
532
    testInvalidConfig<HostReservationParser4>(config);
533
534
535
536
537
538
539
540
541
542
}

// This test verifies that the parser can parse the reservation entry
// when IP address is not specified, but hostname is specified.
TEST_F(HostReservationParserTest, noIPAddress) {
    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
        "\"hostname\": \"foo.example.com\" }";

    ElementPtr config_element = Element::fromJSON(config);

543
544
    HostReservationParser4 parser;
    ASSERT_NO_THROW(parser.parse(SubnetID(10), config_element));
545
546
547
548
549
550
551
552
553
554
555

    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
    HostCollection hosts;
    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));

    ASSERT_EQ(1, hosts.size());

    EXPECT_EQ(10, hosts[0]->getIPv4SubnetID());
    EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
    EXPECT_EQ("0.0.0.0", hosts[0]->getIPv4Reservation().toText());
    EXPECT_EQ("foo.example.com", hosts[0]->getHostname());
556
557
558
559
560
561
562
563
564
565
566
567
568
569

    // lower duid value
    boost::algorithm::to_lower(config);
    CfgMgr::instance().setFamily(AF_INET);
    CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
    runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);

    CfgMgr::instance().setFamily(AF_INET6);
    CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10));
    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);

    CfgMgr::instance().setFamily(AF_INET);
    CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
570
571
572
573
574
575
576
577
}

// This test verifies  that the configuration parser for host reservations
// throws an exception when hostname is empty, and IP address is not
// specified.
TEST_F(HostReservationParserTest, emptyHostname) {
    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
        "\"hostname\": \"\" }";
578
    testInvalidConfig<HostReservationParser4>(config);
579
580
}

581
582
583
584
585
// This test verifies that the configuration parser for host reservations
// throws an exception when invalid IP address is specified.
TEST_F(HostReservationParserTest, malformedAddress) {
    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
        "\"ip-address\": \"192.0.2.bogus\" }";
586
    testInvalidConfig<HostReservationParser4>(config);
587
588
}

589
590
591
592
593
// This test verifies that the configuration parser for host reservations
// throws an exception when zero IP address is specified.
TEST_F(HostReservationParserTest, zeroAddress) {
    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
        "\"ip-address\": \"0.0.0.0\" }";
594
    testInvalidConfig<HostReservationParser4>(config);
595
596
597
598
599
600
601
}

// This test verifies that the configuration parser for host reservations
// throws an exception when broadcast IP address is specified.
TEST_F(HostReservationParserTest, bcastAddress) {
    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
        "\"ip-address\": \"255.255.255.255\" }";
602
    testInvalidConfig<HostReservationParser4>(config);
603
604
}

605
// This test verifies that the configuration parser for host reservations
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
// throws an exception when invalid next server address is specified.
TEST_F(HostReservationParserTest, malformedNextServer) {
    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
        "\"next-server\": \"192.0.2.bogus\" }";
    testInvalidConfig<HostReservationParser4>(config);
}

// This test verifies that the configuration parser for host reservations
// throws an exception when zero next server address is specified.
TEST_F(HostReservationParserTest, zeroNextServer) {
    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
        "\"next-server\": \"0.0.0.0\" }";
    testInvalidConfig<HostReservationParser4>(config);
}

// This test verifies that the configuration parser for host reservations
// throws an exception when broadcast next server address is specified.
TEST_F(HostReservationParserTest, bcastNextServer) {
    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
        "\"next-server\": \"255.255.255.255\" }";
    testInvalidConfig<HostReservationParser4>(config);
}

// This test verifies that the configuration parser for host reservations
630
631
// throws an exception when unsupported parameter is specified.
TEST_F(HostReservationParserTest, invalidParameterName) {
632
633
634
    // The "ip-addresses" parameter name is incorrect for the DHCPv4
    // case - it is only valid for DHCPv6 case. Trying to set this
    // parameter should result in error.
635
636
    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
        "\"hostname\": \"foo.bar.isc.org\","
637
        "\"ip-addresses\": \"2001:db8:1::1\" }";
638
    testInvalidConfig<HostReservationParser4>(config);
639
640
}

Andrei Pavel's avatar
Andrei Pavel committed
641
// This test verifies that the parser can parse the IPv6 reservation entry for
642
643
644
645
646
647
648
649
650
651
// which hw-address is a host identifier.
TEST_F(HostReservationParserTest, dhcp6HWaddr) {
    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
        "\"ip-addresses\": [ \"2001:db8:1::1\", \"2001:db8:1::2\" ],"
        "\"prefixes\": [ \"2001:db8:2000:0101::/64\", "
        "\"2001:db8:2000:0102::/64\" ],"
        "\"hostname\": \"foo.example.com\" }";

    ElementPtr config_element = Element::fromJSON(config);

652
653
    HostReservationParser6 parser;
    ASSERT_NO_THROW(parser.parse(SubnetID(10), config_element));
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668

    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
    HostCollection hosts;
    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(hwaddr_, DuidPtr()));

    ASSERT_EQ(1, hosts.size());

    EXPECT_EQ(0, hosts[0]->getIPv4SubnetID());
    EXPECT_EQ(10, hosts[0]->getIPv6SubnetID());
    EXPECT_EQ("foo.example.com", hosts[0]->getHostname());

    IPv6ResrvRange addresses = hosts[0]->
        getIPv6Reservations(IPv6Resrv::TYPE_NA);
    ASSERT_EQ(2, std::distance(addresses.first, addresses.second));

669
670
    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
                                            IOAddress("2001:db8:1::1")),
671
                                  addresses));
672
673
    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
                                            IOAddress("2001:db8:1::2")),
674
675
676
677
                                  addresses));

    IPv6ResrvRange prefixes = hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_PD);
    ASSERT_EQ(2, std::distance(prefixes.first, prefixes.second));
678
679
    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_PD,
                                            IOAddress("2001:db8:2000:0101::"),
680
681
                                            64),
                                  prefixes));
682
683
    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_PD,
                                            IOAddress("2001:db8:2000:0102::"),
684
685
686
                                            64),
                                  prefixes));

687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
    // canonize prefixes
    config_element->set("prefixes",
                        Element::fromJSON("[ \"2001:db8:2000:101::/64\", "
                                          "\"2001:db8:2000:102::/64\" ]"));
    ElementPtr expected = Element::createList();
    expected->add(config_element);

    CfgMgr::instance().setFamily(AF_INET6);
    CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
    runToElementTest<CfgHostsSubnet>(expected, cfg_subnet);

    CfgMgr::instance().setFamily(AF_INET);
    CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(10));
    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);

    CfgMgr::instance().setFamily(AF_INET6);
    CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
705
706
}

Andrei Pavel's avatar
Andrei Pavel committed
707
// This test verifies that the parser can parse the IPv6 reservation entry for
708
709
710
711
712
713
714
715
716
// which DUID is a host identifier.
TEST_F(HostReservationParserTest, dhcp6DUID) {
    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
        "\"ip-addresses\": [ \"2001:db8:1::100\", \"2001:db8:1::200\" ],"
        "\"prefixes\": [ ],"
        "\"hostname\": \"foo.example.com\" }";

    ElementPtr config_element = Element::fromJSON(config);

717
718
    HostReservationParser6 parser;
    ASSERT_NO_THROW(parser.parse(SubnetID(12), config_element));
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733

    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
    HostCollection hosts;
    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));

    ASSERT_EQ(1, hosts.size());

    EXPECT_EQ(0, hosts[0]->getIPv4SubnetID());
    EXPECT_EQ(12, hosts[0]->getIPv6SubnetID());
    EXPECT_EQ("foo.example.com", hosts[0]->getHostname());

    IPv6ResrvRange addresses = hosts[0]->
        getIPv6Reservations(IPv6Resrv::TYPE_NA);
    ASSERT_EQ(2, std::distance(addresses.first, addresses.second));

734
735
    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
                                            IOAddress("2001:db8:1::100")),
736
                                  addresses));
737
738
    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
                                            IOAddress("2001:db8:1::200")),
739
740
741
742
                                  addresses));

    IPv6ResrvRange prefixes = hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_PD);
    ASSERT_EQ(0, std::distance(prefixes.first, prefixes.second));
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759

    // remove prefixes and lower duid value
    config_element->remove("prefixes");
    config = prettyPrint(config_element);
    boost::algorithm::to_lower(config);

    CfgMgr::instance().setFamily(AF_INET6);
    CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(12));
    runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);

    CfgMgr::instance().setFamily(AF_INET);
    CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(12));
    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);

    CfgMgr::instance().setFamily(AF_INET6);
    CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
760
761
}

762
763
764
765
766
767
768
769
// This test verifies that host reservation parser for DHCPv6 rejects
// "circuit-id" as a host identifier.
TEST_F(HostReservationParserTest, dhcp6CircuitId) {
    // Use DHCPv4 specific identifier 'circuit-id' with DHCPv6 parser.
    std::string config = "{ \"circuit-id\": \"'howdy'\","
        "\"ip-addresses\": [ \"2001:db8:1::100\", \"2001:db8:1::200\" ],"
        "\"prefixes\": [ ],"
        "\"hostname\": \"foo.example.com\" }";
770
    testInvalidConfig<HostReservationParser6>(config);
771
772
}

773
774
775
// This test verifies that host reservation parser for DHCPv6 rejects
// "client-id" as a host identifier.
TEST_F(HostReservationParserTest, dhcp6ClientId) {
776
    // Use DHCPv4 specific identifier 'client-id' with DHCPv6 parser.
777
778
779
780
781
782
783
    std::string config = "{ \"client-id\": \"01010203040506\","
        "\"ip-addresses\": [ \"2001:db8:1::100\", \"2001:db8:1::200\" ],"
        "\"prefixes\": [ ],"
        "\"hostname\": \"foo.example.com\" }";
    testInvalidConfig<HostReservationParser6>(config);
}

Andrei Pavel's avatar
Andrei Pavel committed
784
// This test verifies that the parser can parse the IPv6 reservation entry
785
786
787
788
789
790
791
792
// which lacks hostname parameter.
TEST_F(HostReservationParserTest, dhcp6NoHostname) {
    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
        "\"ip-addresses\": [ \"2001:db8:1::100\", \"2001:db8:1::200\" ],"
        "\"prefixes\": [ ] }";

    ElementPtr config_element = Element::fromJSON(config);

793
794
    HostReservationParser6 parser;
    ASSERT_NO_THROW(parser.parse(SubnetID(12), config_element));
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818

    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
    HostCollection hosts;
    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));

    ASSERT_EQ(1, hosts.size());

    EXPECT_EQ(0, hosts[0]->getIPv4SubnetID());
    EXPECT_EQ(12, hosts[0]->getIPv6SubnetID());
    EXPECT_TRUE(hosts[0]->getHostname().empty());

    IPv6ResrvRange addresses = hosts[0]->
        getIPv6Reservations(IPv6Resrv::TYPE_NA);
    ASSERT_EQ(2, std::distance(addresses.first, addresses.second));

    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
                                            IOAddress("2001:db8:1::100")),
                                  addresses));
    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
                                            IOAddress("2001:db8:1::200")),
                                  addresses));

    IPv6ResrvRange prefixes = hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_PD);
    ASSERT_EQ(0, std::distance(prefixes.first, prefixes.second));
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835

    // remove prefixes and lower duid value
    config_element->remove("prefixes");
    config = prettyPrint(config_element);
    boost::algorithm::to_lower(config);

    CfgMgr::instance().setFamily(AF_INET6);
    CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(12));
    runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);

    CfgMgr::instance().setFamily(AF_INET);
    CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(12));
    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);

    CfgMgr::instance().setFamily(AF_INET6);
    CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(10));
    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
836
837
}

838
839
840
841
842
843
844
845
// This test verifies that it is possible to specify DHCPv4 client classes
// within the host reservation.
TEST_F(HostReservationParserTest, dhcp6ClientClasses) {
    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
        "\"client-classes\": [ \"foo\", \"bar\" ] }";

    ElementPtr config_element = Element::fromJSON(config);

846
847
    HostReservationParser6 parser;
    ASSERT_NO_THROW(parser.parse(SubnetID(10), config_element));
848
849
850
851
852
853
854
855
856
857
858
859

    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
    HostCollection hosts;
    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(Host::IDENT_DUID,
                                              &duid_->getDuid()[0],
                                              duid_->getDuid().size()));
    ASSERT_EQ(1, hosts.size());

    const ClientClasses& classes = hosts[0]->getClientClasses6();
    ASSERT_EQ(2, classes.size());
    EXPECT_EQ(1, classes.count("foo"));
    EXPECT_EQ(1, classes.count("bar"));
860
861
862
863
864
865
866
867
868
869
870
871
872
873

    // lower duid value
    boost::algorithm::to_lower(config);
    CfgMgr::instance().setFamily(AF_INET6);
    CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
    runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);

    CfgMgr::instance().setFamily(AF_INET);
    CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(10));
    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);

    CfgMgr::instance().setFamily(AF_INET6);
    CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
874
}
875

876
877
878
879
880
881
// This test verifies that the configuration parser throws an exception
// when IPv4 address is specified for IPv6 reservation.
TEST_F(HostReservationParserTest, dhcp6IPv4Address) {
    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
        "\"ip-addresses\": [ \"192.0.2.3\", \"2001:db8:1::200\" ],"
        "\"prefixes\": [ ] }";
882
    testInvalidConfig<HostReservationParser6>(config);
883
884
}

885
886
887
888
889
890
// This test verifies that the configuration parser throws an exception
// when empty address has been specified.
TEST_F(HostReservationParserTest, dhcp6NullAddress) {
    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
        "\"ip-addresses\": [ \"\" ],"
        "\"prefixes\": [ ] }";
891
    testInvalidConfig<HostReservationParser6>(config);
892
893
894
895
896
897
898
}

// This test verifies that the configuration parser throws an exception
// when invalid prefix length is specified.
TEST_F(HostReservationParserTest, dhcp6InvalidPrefixLength) {
    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
        "\"prefixes\": [ \"2001:db8:1::/abc\" ] }";
899
    testInvalidConfig<HostReservationParser6>(config);
900
901
902
903
904
905
906
}

// This test verifies that the configuration parser throws an exception
// when empty prefix is specified.
TEST_F(HostReservationParserTest, dhcp6NullPrefix) {
    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
        "\"prefixes\": [ \"/64\" ] }";
907
    testInvalidConfig<HostReservationParser6>(config);
908
909
910
911
912
913
914
}

// This test verifies that the configuration parser throws an exception
// when only slash is specified for the prefix..
TEST_F(HostReservationParserTest, dhcp6NullPrefix2) {
    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
        "\"prefixes\": [ \"/\" ] }";
915
    testInvalidConfig<HostReservationParser6>(config);
916
917
918
919
920
921
922
}

// This test verifies that the configuration parser throws an exception
// when the same address is reserved twice.
TEST_F(HostReservationParserTest, dhcp6DuplicatedAddress) {
    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
        "\"ip-addresses\": [ \"2001:db8:1::1\", \"2001:db8:1::1\" ] }";
923
    testInvalidConfig<HostReservationParser6>(config);
924
925
926
927
928
929
930
}

// This test verifies that the configuration parser throws an exception
// when the same prefix is reserved twice.
TEST_F(HostReservationParserTest, dhcp6DuplicatedPrefix) {
    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
        "\"prefixes\": [ \"2001:db8:0101::/64\", \"2001:db8:0101::/64\" ] }";
931
    testInvalidConfig<HostReservationParser6>(config);
932
933
}

934
935
936
937
938
939
940
941
942
// This test verifies that the configuration parser for host reservations
// throws an exception when unsupported parameter is specified.
TEST_F(HostReservationParserTest, dhcp6invalidParameterName) {
    // The "ip-address" parameter name is incorrect for the DHCPv6
    // case - it is only valid for DHCPv4 case. Trying to set this
    // parameter should result in error.
    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
        "\"hostname\": \"foo.bar.isc.org\","
        "\"ip-address\": \"192.0.2.3\" }";
943
    testInvalidConfig<HostReservationParser6>(config);
944
945
}

946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
// This test verifies that it is possible to specify DHCPv4 options for
// a host.
TEST_F(HostReservationParserTest, options4) {
    // Create configuration with three options for a host.
    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
        "\"option-data\": ["
        "{"
           "\"name\": \"name-servers\","
           "\"data\": \"172.16.15.10, 172.16.15.20\""
        "},"
        "{"
           "\"name\": \"log-servers\","
           "\"code\": 7,"
           "\"csv-format\": true,"
           "\"space\": \"dhcp4\","
           "\"data\": \"172.16.15.23\""
        "},"
        "{"
           "\"name\": \"default-ip-ttl\","
           "\"data\": \"64\""
        "} ]"
        "}";

    ElementPtr config_element = Element::fromJSON(config);

971
972
    HostReservationParser4 parser;
    ASSERT_NO_THROW(parser.parse(SubnetID(10), config_element));
973
974
975
976
977
978
979
980

    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
    HostCollection hosts;
    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(hwaddr_));
    ASSERT_EQ(1, hosts.size());

    // Retrieve and sanity check name servers.
    Option4AddrLstPtr opt_dns = boost::dynamic_pointer_cast<
981
        Option4AddrLst>(retrieveOption(*hosts[0], DHCP4_OPTION_SPACE, DHO_NAME_SERVERS));
982
983
984
985
986
987
988
989
    ASSERT_TRUE(opt_dns);
    Option4AddrLst::AddressContainer dns_addrs = opt_dns->getAddresses();
    ASSERT_EQ(2, dns_addrs.size());
    EXPECT_EQ("172.16.15.10", dns_addrs[0].toText());
    EXPECT_EQ("172.16.15.20", dns_addrs[1].toText());

    // Retrieve and sanity check log servers.
    Option4AddrLstPtr opt_log = boost::dynamic_pointer_cast<
990
        Option4AddrLst>(retrieveOption(*hosts[0], DHCP4_OPTION_SPACE, DHO_LOG_SERVERS));
991
992
993
994
995
996
997
    ASSERT_TRUE(opt_log);
    Option4AddrLst::AddressContainer log_addrs = opt_log->getAddresses();
    ASSERT_EQ(1, log_addrs.size());
    EXPECT_EQ("172.16.15.23", log_addrs[0].toText());

    // Retrieve and sanity check default IP TTL.
    OptionUint8Ptr opt_ttl = boost::dynamic_pointer_cast<
998
        OptionUint8>(retrieveOption(*hosts[0], DHCP4_OPTION_SPACE, DHO_DEFAULT_IP_TTL));
999
1000
    ASSERT_TRUE(opt_ttl);
    EXPECT_EQ(64, opt_ttl->getValue());
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026

    // Canonize the config
    ElementPtr option = config_element->get("option-data")->getNonConst(0);
    option->set("code", Element::create(DHO_NAME_SERVERS));
    option->set("space", Element::create(std::string(DHCP4_OPTION_SPACE)));
    option->set("csv-format", Element::create(true));
    option = config_element->get("option-data")->getNonConst(1);
    option = config_element->get("option-data")->getNonConst(2);
    option->set("code", Element::create(DHO_DEFAULT_IP_TTL));
    option->set("space", Element::create(std::string(DHCP4_OPTION_SPACE)));
    option->set("csv-format", Element::create(true));
    ElementPtr expected = Element::createList();
    expected->add(config_element);

    // Try to unparse it.
    CfgMgr::instance().setFamily(AF_INET);
    CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
    runToElementTest<CfgHostsSubnet>(expected, cfg_subnet);

    CfgMgr::instance().setFamily(AF_INET6);
    CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10));
    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);

    CfgMgr::instance().setFamily(AF_INET);
    CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
}

// This test verifies that it is possible to specify DHCPv6 options for
// a host.
TEST_F(HostReservationParserTest, options6) {
    // Create configuration with three options for a host.
    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
        "\"option-data\": ["
        "{"
           "\"name\": \"dns-servers\","
           "\"data\": \"2001:db8:1::1, 2001:db8:1::2\""
        "},"
        "{"
           "\"name\": \"nis-servers\","
           "\"code\": 27,"
           "\"csv-format\": true,"
           "\"space\": \"dhcp6\","
           "\"data\": \"2001:db8:1::1204\""
        "},"
        "{"
           "\"name\": \"preference\","
           "\"data\": \"11\""
        "} ]"
        "}";

    ElementPtr config_element = Element::fromJSON(config);

1054
1055
    HostReservationParser6 parser;
    ASSERT_NO_THROW(parser.parse(SubnetID(10), config_element));
1056
1057
1058
1059
1060
1061
1062
1063
1064

    // One host should have been added to the configuration.
    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
    HostCollection hosts;
    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));
    ASSERT_EQ(1, hosts.size());

    // Retrieve and sanity check DNS servers option.
    Option6AddrLstPtr opt_dns = boost::dynamic_pointer_cast<
1065
        Option6AddrLst>(retrieveOption(*hosts[0], DHCP6_OPTION_SPACE, D6O_NAME_SERVERS));
1066
1067
1068
1069
1070
1071
1072
1073
    ASSERT_TRUE(opt_dns);
    Option6AddrLst::AddressContainer dns_addrs = opt_dns->getAddresses();
    ASSERT_EQ(2, dns_addrs.size());
    EXPECT_EQ("2001:db8:1::1", dns_addrs[0].toText());
    EXPECT_EQ("2001:db8:1::2", dns_addrs[1].toText());

    // Retrieve and sanity check NIS servers option.
    Option6AddrLstPtr opt_nis = boost::dynamic_pointer_cast<
1074
        Option6AddrLst>(retrieveOption(*hosts[0], DHCP6_OPTION_SPACE, D6O_NIS_SERVERS));
1075
1076
1077
1078
1079
1080
1081
    ASSERT_TRUE(opt_nis);
    Option6AddrLst::AddressContainer nis_addrs = opt_nis->getAddresses();
    ASSERT_EQ(1, nis_addrs.size());
    EXPECT_EQ("2001:db8:1::1204", nis_addrs[0].toText());

    // Retrieve and sanity check preference option.
    OptionUint8Ptr opt_prf = boost::dynamic_pointer_cast<
1082
        OptionUint8>(retrieveOption(*hosts[0], DHCP6_OPTION_SPACE, D6O_PREFERENCE));
1083
1084
    ASSERT_TRUE(opt_prf);
    EXPECT_EQ(11, opt_prf->getValue());
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110

    // Canonize the config
    ElementPtr option = config_element->get("option-data")->getNonConst(0);
    option->set("code", Element::create(D6O_NAME_SERVERS));
    option->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
    option->set("csv-format", Element::create(true));
    option = config_element->get("option-data")->getNonConst(1);
    option = config_element->get("option-data")->getNonConst(2);
    option->set("code", Element::create(D6O_PREFERENCE));
    option->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
    option->set("csv-format", Element::create(true));
    config = prettyPrint(config_element);
    boost::algorithm::to_lower(config);
    
    // Try to unparse it.
    CfgMgr::instance().setFamily(AF_INET6);
    CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
    runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);

    CfgMgr::instance().setFamily(AF_INET);
    CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(10));
    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);

    CfgMgr::instance().setFamily(AF_INET6);
    CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
1111
1112
1113
}

// This test verifies that it is possible to specify an empty list of
1114
// DHCPv4 options for a host declaration.
1115
1116
1117
1118
1119
TEST_F(HostReservationParserTest, options4Empty) {
    testEmptyOptionList<HostReservationParser4>();
}

// This test verifies that it is possible to specify an empty list of
1120
// DHCPv6 options for a host declaration.
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
TEST_F(HostReservationParserTest, options6Empty) {
    testEmptyOptionList<HostReservationParser6>();
}

// This test checks that specifying DHCPv6 options for the DHCPv4 host
// reservation parser is not allowed.
TEST_F(HostReservationParserTest, options4InvalidOptionSpace) {
    // Create configuration specifying DHCPv6 option for a DHCPv4 host
    // reservation parser.
    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
        "\"option-data\": ["
        "{"
           "\"name\": \"dns-servers\","
           "\"space\": \"dhcp6\","
           "\"data\": \"2001:db8:1::1, 2001:db8:1::2\""
        "} ]"
        "}";

    testInvalidConfig<HostReservationParser4>(config);
}

// This test checks that specifying DHCPv4 options for the DHCPv6 host
// reservation parser is not allowed.
TEST_F(HostReservationParserTest, options6InvalidOptionSpace) {
    // Create configuration specifying DHCPv4 option for a DHCPv6 host
    // reservation parser.
    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
        "\"option-data\": ["
        "{"
           "\"name\": \"name-servers\","
           "\"space\": \"dhcp4\","
           "\"data\": \"172.16.15.10, 172.16.15.20\""
        "} ]"
        "}";

    testInvalidConfig<HostReservationParser6>(config);
}

1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
// This test verifies that host identifiers for DHCPv4 are mutually exclusive.
TEST_F(HostReservationParserTest, mutuallyExclusiveIdentifiers4) {
    std::vector<std::string> identifiers;
    identifiers.push_back("hw-address");
    identifiers.push_back("duid");
    identifiers.push_back("circuit-id");

    for (unsigned int i = 0; i < identifiers.size(); ++i) {
        // j points to an index of the next identifier. If it
        // overflows, we set it to 0.
        unsigned int j = (i + 1) % (identifiers.size());
        Host::IdentifierType first = static_cast<Host::IdentifierType>(i);
        Host::IdentifierType second = static_cast<Host::IdentifierType>(j);

        SCOPED_TRACE("Using identifiers " + Host::getIdentifierName(first)
                     + " and " + Host::getIdentifierName(second));

        // Create configuration with two different identifiers.
        std::ostringstream config;
        config << "{ \"" << Host::getIdentifierName(first) << "\": \"121314151617\","
            "\"" << Host::getIdentifierName(second) << "\": \"0A0B0C0D0E0F\","
            "\"ip-address\": \"192.0.2.3\" }";
        testInvalidConfig<HostReservationParser4>(config.str());
    }
}

// This test verifies that host identifiers for DHCPv6 are mutually exclusive.
TEST_F(HostReservationParserTest, mutuallyExclusiveIdentifiers6) {
    std::vector<std::string> identifiers;
    identifiers.push_back("hw-address");
    identifiers.push_back("duid");

    for (unsigned int i = 0; i < identifiers.size(); ++i) {
        // j points to an index of the next identifier. If it
        // overflows, we set it to 0.
        unsigned int j = (i + 1) % (identifiers.size());

        SCOPED_TRACE("Using identifiers " + identifiers[i] + " and "
                     + identifiers[j]);

        // Create configuration with two different identifiers.
        std::ostringstream config;
        config << "{ \"" << identifiers[i] << "\": \"121314151617\","
            "\"" << identifiers[j] << "\": \"0A0B0C0D0E0F\","
            "\"ip-addresses\": \"2001:db8:1::1\" }";
        testInvalidConfig<HostReservationParser6>(config.str());
    }
}

1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
/// @brief Test fixture class for @ref HostReservationIdsParser.
class HostReservationIdsParserTest : public ::testing::Test {
public:

    /// @brief Constructor.
    ///
    /// Clears current configuration.
    HostReservationIdsParserTest() {
        CfgMgr::instance().clear();
    }

    /// @brief Destructor.
    ///
    /// Clears current configuration.
    virtual ~HostReservationIdsParserTest() {
        CfgMgr::instance().clear();
    }

    /// @brief Test verifies that invalid configuration causes an error.
    ///
    /// @param config Configuration string.
    /// @tparam ParserType @ref HostReservationIdsParser4 or
    /// @ref HostReservationIdsParser6
    template<typename ParserType>
    void testInvalidConfig(const std::string& config) const {
        ElementPtr config_element = Element::fromJSON(config);
        ParserType parser;
1235
        EXPECT_THROW(parser.parse(config_element), DhcpConfigError);
1236
1237
1238
1239
1240
1241
1242
    }

};

// Test that list of supported DHCPv4 identifiers list is correctly
// parsed.
TEST_F(HostReservationIdsParserTest, dhcp4Identifiers) {
1243
1244
    std::string config =
        "[ \"circuit-id\", \"duid\", \"hw-address\", \"client-id\" ]";
1245
1246
1247
1248

    ElementPtr config_element = Element::fromJSON(config);

    HostReservationIdsParser4 parser;
1249
    ASSERT_NO_THROW(parser.parse(config_element));
1250

1251
1252
1253
    ConstCfgHostOperationsPtr cfg = CfgMgr::instance().getStagingCfg()->
        getCfgHostOperations4();
    const CfgHostOperations::IdentifierTypes& ids = cfg->getIdentifierTypes();
1254
    ASSERT_EQ(4, ids.size());
1255

1256
    CfgHostOperations::IdentifierTypes::const_iterator id = ids.begin();
1257
1258
1259
    EXPECT_EQ(*id++, Host::IDENT_CIRCUIT_ID);
    EXPECT_EQ(*id++, Host::IDENT_DUID);
    EXPECT_EQ(*id++, Host::IDENT_HWADDR);
1260
    EXPECT_EQ(*id++, Host::IDENT_CLIENT_ID);
1261
1262

    runToElementTest<CfgHostOperations>(config, *cfg);
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
}

// Test that list of supported DHCPv6 identifiers list is correctly
// parsed.
TEST_F(HostReservationIdsParserTest, dhcp6Identifiers) {
    std::string config = "[ \"duid\", \"hw-address\" ]";

    ElementPtr config_element = Element::fromJSON(config);

    HostReservationIdsParser6 parser;
1273
    ASSERT_NO_THROW(parser.parse(config_element));
1274

1275
1276
1277
    ConstCfgHostOperationsPtr cfg = CfgMgr::instance().getStagingCfg()->
        getCfgHostOperations6();
    const CfgHostOperations::IdentifierTypes& ids = cfg->getIdentifierTypes();
1278
1279
    ASSERT_EQ(2, ids.size());

1280
    CfgHostOperations::IdentifierTypes::const_iterator id = ids.begin();
1281
1282
    EXPECT_EQ(*id++, Host::IDENT_DUID);
    EXPECT_EQ(*id++, Host::IDENT_HWADDR);
1283
1284

    runToElementTest<CfgHostOperations>(config, *cfg);
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
}

// Test that invalid DHCPv4 identifier causes error.
TEST_F(HostReservationIdsParserTest, dhcp4InvalidIdentifier) {
    // Create configuration including unsupported identifier.
    std::string config = "[ \"unsupported-id\" ]";
    testInvalidConfig<HostReservationIdsParser4>(config);
}

// Test that invalid DHCPv6 identifier causes error.
TEST_F(HostReservationIdsParserTest, dhcp6InvalidIdentifier) {
    // Create configuration including unsupported identifier for DHCPv6.
    // The circuit-id is only supported in DHCPv4.
    std::string config = "[ \"circuit-id\" ]";
    testInvalidConfig<HostReservationIdsParser6>(config);
}

// Check that all supported identifiers are used when 'auto' keyword
// is specified for DHCPv4 case.
TEST_F(HostReservationIdsParserTest, dhcp4AutoIdentifiers) {
    std::string config = "[ \"auto\" ]";
    ElementPtr config_element = Element::fromJSON(config);

    HostReservationIdsParser4 parser;
1309
    ASSERT_NO_THROW(parser.parse(config_element));
1310

1311
1312
1313
    ConstCfgHostOperationsPtr cfg = CfgMgr::instance().getStagingCfg()->
        getCfgHostOperations4();
    const CfgHostOperations::IdentifierTypes& ids = cfg->getIdentifierTypes();
1314
    ASSERT_EQ(4, ids.size());
1315

1316
    CfgHostOperations::IdentifierTypes::const_iterator id = ids.begin();
1317
1318
1319
    EXPECT_EQ(*id++, Host::IDENT_HWADDR);
    EXPECT_EQ(*id++, Host::IDENT_DUID);
    EXPECT_EQ(*id++, Host::IDENT_CIRCUIT_ID);
1320
    EXPECT_EQ(*id++, Host::IDENT_CLIENT_ID);
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
}

// This test verifies that use of "auto" together with an explicit
// identifier causes an error. "auto" is placed before the explicit
// identifier.
TEST_F(HostReservationIdsParserTest, dhcp4AutoBeforeIdentifier) {
    std::string config = "[ \"auto\", \"duid\" ]";
    testInvalidConfig<HostReservationIdsParser4>(config);
}

// This test verifies that use of "auto" together with an explicit
// identifier causes an error. "auto" is placed after the explicit
// identifier.
TEST_F(HostReservationIdsParserTest, dhcp4AutoAfterIdentifier) {
    std::string config = "[ \"duid\", \"auto\" ]";
    testInvalidConfig<HostReservationIdsParser4>(config);
}

// Test that empty list of identifier types is not allowed.
TEST_F(HostReservationIdsParserTest, dhcp4EmptyList) {
    std::string config = "[ ]";
    testInvalidConfig<HostReservationIdsParser4>(config);
}

// Check that all supported identifiers are used when 'auto' keyword
// is specified for DHCPv6 case.
TEST_F(HostReservationIdsParserTest, dhcp6AutoIdentifiers) {
    std::string config = "[ \"auto\" ]";
    ElementPtr config_element = Element::fromJSON(config);

    HostReservationIdsParser6 parser;
1352
    ASSERT_NO_THROW(parser.parse(config_element));
1353

1354
1355
1356
    ConstCfgHostOperationsPtr cfg = CfgMgr::instance().getStagingCfg()->
        getCfgHostOperations6();
    const CfgHostOperations::IdentifierTypes& ids = cfg->getIdentifierTypes();
1357
1358
    ASSERT_EQ(2, ids.size());

1359
    CfgHostOperations::IdentifierTypes::const_iterator id = ids.begin();
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
    EXPECT_EQ(*id++, Host::IDENT_HWADDR);
    EXPECT_EQ(*id++, Host::IDENT_DUID);
}

// This test verifies that use of "auto" together with an explicit
// identifier causes an error. "auto" is placed before the explicit
// identifier.
TEST_F(HostReservationIdsParserTest, dhcp6AutoBeforeIdentifier) {
    std::string config = "[ \"auto\", \"duid\" ]";
    testInvalidConfig<HostReservationIdsParser6>(config);
}

// This test verifies that use of "auto" together with an explicit
// identifier causes an error. "auto" is placed after the explicit
// identifier.
TEST_F(HostReservationIdsParserTest, dhcp6AutoAfterIdentifier) {
    std::string config = "[ \"duid\", \"auto\" ]";
    testInvalidConfig<HostReservationIdsParser6>(config);
}

// Test that empty list of identifier types is not allowed.
TEST_F(HostReservationIdsParserTest, dhcp6EmptyList) {
    std::string config = "[ ]";
    testInvalidConfig<HostReservationIdsParser6>(config);
}
1385

1386
} // end of anonymous namespace