1 // Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC")
2 // Copyright (C) 2016-2017 Deutsche Telekom AG.
3 //
4 // Author: Andrei Pavel <andrei.pavel@qualitance.com>
5 //
6 // Licensed under the Apache License, Version 2.0 (the "License");
7 // you may not use this file except in compliance with the License.
8 // You may obtain a copy of the License at
9 //
10 // http://www.apache.org/licenses/LICENSE-2.0
11 //
12 // Unless required by applicable law or agreed to in writing, software
13 // distributed under the License is distributed on an "AS IS" BASIS,
14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 // See the License for the specific language governing permissions and
16 // limitations under the License.
17
18 #include <config.h>
19
20 #include <asiolink/io_address.h>
21 #include <dhcpsrv/testutils/test_utils.h>
22 #include <exceptions/exceptions.h>
23 #include <dhcpsrv/host.h>
24 #include <dhcpsrv/cql_host_data_source.h>
25 #include <dhcpsrv/testutils/generic_host_data_source_unittest.h>
26 #include <dhcpsrv/testutils/host_data_source_utils.h>
27 #include <dhcpsrv/host_mgr.h>
28 #include <dhcpsrv/host_data_source_factory.h>
29 #include <cql/cql_connection.h>
30 #include <cql/cql_exchange.h>
31 #include <cql/testutils/cql_schema.h>
32
33 #include <gtest/gtest.h>
34
35 #include <algorithm>
36 #include <iostream>
37 #include <sstream>
38 #include <string>
39 #include <utility>
40
41 using namespace isc;
42 using namespace isc::asiolink;
43 using namespace isc::db;
44 using namespace isc::db::test;
45 using namespace isc::dhcp;
46 using namespace isc::dhcp::test;
47 using namespace isc::data;
48 using namespace std;
49
50 namespace {
51
52 class CqlHostDataSourceTest : public GenericHostDataSourceTest {
53 public:
54 /// @brief Clears the database and opens connection to it.
initializeTest()55 void initializeTest() {
56 // Ensure we have the proper schema with no transient data.
57 createCqlSchema();
58
59 // Connect to the database
60 try {
61 HostMgr::create();
62 HostMgr::addBackend(validCqlConnectionString());
63 } catch (...) {
64 std::cerr << "*** ERROR: unable to open database. The test\n"
65 "*** environment is broken and must be fixed before\n"
66 "*** the CQL tests will run correctly.\n"
67 "*** The reason for the problem is described in the\n"
68 "*** accompanying exception output.\n";
69 throw;
70 }
71
72 hdsptr_ = HostMgr::instance().getHostDataSource();
73 hdsptr_->setIPReservationsUnique(true);
74 }
75
76 /// @brief Destroys the HDS and the schema.
destroyTest()77 void destroyTest() {
78 try {
79 hdsptr_->rollback();
80 } catch (...) {
81 // Rollback should never fail, as Cassandra doesn't support transactions
82 // (commit and rollback are both no-op).
83 }
84 HostMgr::delAllBackends();
85 hdsptr_.reset();
86 // If data wipe enabled, delete transient data otherwise destroy the schema
87 destroyCqlSchema();
88 }
89
90 /// @brief Constructor
91 ///
92 /// Deletes everything from the database and opens it.
CqlHostDataSourceTest()93 CqlHostDataSourceTest() {
94 initializeTest();
95 }
96
97 /// @brief Destructor
98 ///
99 /// Rolls back all pending transactions. The deletion of hdsptr_ will close
100 /// the database. Then reopen it and delete everything created by the test.
~CqlHostDataSourceTest()101 virtual ~CqlHostDataSourceTest() {
102 destroyTest();
103 }
104
105 /// @brief Reopen the database
106 ///
107 /// Closes the database and re-open it. Anything committed should be
108 /// visible.
109 ///
110 /// Parameter is ignored for CQL backend as the v4 and v6 hosts share
111 /// the same database.
reopen(Universe)112 void reopen(Universe) {
113 HostMgr::create();
114 HostMgr::addBackend(validCqlConnectionString());
115 hdsptr_ = HostMgr::instance().getHostDataSource();
116 }
117
118 /// @brief Returns number of IPv4 options currently stored in DB.
countDBOptions4()119 virtual int countDBOptions4() {
120 int result = 0;
121
122 const CqlHostDataSource* cql_host_mgr = dynamic_cast<const CqlHostDataSource*>(&(*hdsptr_));
123 ConstHostCollection all = cql_host_mgr->getAllHosts();
124
125 for (ConstHostCollection::const_iterator it = all.begin();
126 it != all.end(); ++it) {
127 ConstCfgOptionPtr cfg_option4 = (*it)->getCfgOption4();
128 std::list<std::string> option_spaces4 = cfg_option4->getOptionSpaceNames();
129 std::list<std::string> vendor_spaces4 = cfg_option4->getVendorIdsSpaceNames();
130 option_spaces4.insert(option_spaces4.end(), vendor_spaces4.begin(),
131 vendor_spaces4.end());
132 for (const std::string& space : option_spaces4) {
133 OptionContainerPtr options = cfg_option4->getAll(space);
134 result += options->size();
135 }
136 }
137
138 return (result);
139 }
140
141 /// @brief Returns number of IPv6 options currently stored in DB.
countDBOptions6()142 virtual int countDBOptions6() {
143 int result = 0;
144
145 const CqlHostDataSource* cql_host_mgr = dynamic_cast<const CqlHostDataSource*>(&(*hdsptr_));
146 ConstHostCollection all = cql_host_mgr->getAllHosts();
147
148 for (ConstHostCollection::const_iterator it = all.begin();
149 it != all.end(); ++it) {
150 ConstCfgOptionPtr cfg_option6 = (*it)->getCfgOption6();
151 std::list<std::string> option_spaces6 = cfg_option6->getOptionSpaceNames();
152 std::list<std::string> vendor_spaces6 = cfg_option6->getVendorIdsSpaceNames();
153 option_spaces6.insert(option_spaces6.end(), vendor_spaces6.begin(),
154 vendor_spaces6.end());
155 for (const std::string& space : option_spaces6) {
156 OptionContainerPtr options = cfg_option6->getAll(space);
157 result += options->size();
158 }
159 }
160
161 return (result);
162 }
163
164 /// @brief Returns number of IPv6 reservations currently stored in DB.
countDBReservations6()165 virtual int countDBReservations6() {
166 int result = 0;
167
168 const CqlHostDataSource* cql_host_mgr = dynamic_cast<const CqlHostDataSource*>(&(*hdsptr_));
169 ConstHostCollection all = cql_host_mgr->getAllHosts();
170
171 for (ConstHostCollection::const_iterator it = all.begin();
172 it != all.end(); ++it) {
173 IPv6ResrvRange reservations = (*it)->getIPv6Reservations();
174 result += std::distance(reservations.first, reservations.second);
175 }
176
177 return (result);
178 }
179
180 };
181
182 /// @brief Check that database can be opened
183 ///
184 /// This test checks if the CqlHostDataSource can be instantiated. This happens
185 /// only if the database can be opened. Note that this is not part of the
186 /// CqlHostMgr test fixture set. This test checks that the database can be
187 /// opened: the fixtures assume that and check basic operations.
188
TEST(CqlHostDataSource,OpenDatabase)189 TEST(CqlHostDataSource, OpenDatabase) {
190
191 // Ensure we have the proper schema with no transient data.
192 createCqlSchema();
193
194 // Check that host manager opens the database correctly and tidy up. If it
195 // fails, print the error message.
196 try {
197 HostMgr::create();
198 EXPECT_NO_THROW(HostMgr::addBackend(validCqlConnectionString()));
199 HostMgr::delBackend("cql");
200 } catch (const isc::Exception& ex) {
201 FAIL() << "*** ERROR: unable to open database, reason:\n"
202 << " " << ex.what() << "\n"
203 << "*** The test environment is broken and must be fixed\n"
204 << "*** before the CQL tests will run correctly.\n";
205 }
206
207 // Check that host manager opens the database correctly with a longer
208 // timeout. If it fails, print the error message.
209 try {
210 // CQL specifies the timeout values in ms, not seconds. Therefore
211 // we need to add extra 000 to the "connect-timeout=10" string.
212 string connection_string = validCqlConnectionString() + string(" ") +
213 string(VALID_TIMEOUT) + string("000");
214 HostMgr::create();
215 EXPECT_NO_THROW(HostMgr::addBackend(connection_string));
216 HostMgr::delBackend("cql");
217 } catch (const isc::Exception& ex) {
218 FAIL() << "*** ERROR: unable to open database, reason:\n"
219 << " " << ex.what() << "\n"
220 << "*** The test environment is broken and must be fixed\n"
221 << "*** before the CQL tests will run correctly.\n";
222 }
223
224 // Check that attempting to get an instance of the host data source when
225 // none is set returns empty pointer.
226 EXPECT_FALSE(HostMgr::instance().getHostDataSource());
227
228 // Check that wrong specification of backend throws an exception.
229 // (This is really a check on HostDataSourceFactory, but is convenient to
230 // perform here.)
231 EXPECT_THROW(HostMgr::addBackend(connectionString(
232 NULL, VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)),
233 InvalidParameter);
234 EXPECT_THROW(HostMgr::addBackend(connectionString(
235 INVALID_TYPE, VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)),
236 InvalidType);
237
238 // Check that invalid login data does not cause an exception, CQL should use
239 // default values.
240 EXPECT_NO_THROW(HostMgr::addBackend(connectionString(CQL_VALID_TYPE,
241 INVALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD)));
242 EXPECT_NO_THROW(HostMgr::addBackend(connectionString(CQL_VALID_TYPE,
243 VALID_NAME, INVALID_HOST, VALID_USER, VALID_PASSWORD)));
244 EXPECT_NO_THROW(HostMgr::addBackend(connectionString(CQL_VALID_TYPE,
245 VALID_NAME, VALID_HOST, INVALID_USER, VALID_PASSWORD)));
246 EXPECT_NO_THROW(HostMgr::addBackend(connectionString(CQL_VALID_TYPE,
247 VALID_NAME, VALID_HOST, VALID_USER, INVALID_PASSWORD)));
248
249 // Check that invalid timeouts throw DbOperationError.
250 EXPECT_THROW(HostMgr::addBackend(connectionString(CQL_VALID_TYPE,
251 VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, INVALID_TIMEOUT_1)),
252 DbOperationError);
253 EXPECT_THROW(HostMgr::addBackend(connectionString(CQL_VALID_TYPE,
254 VALID_NAME, VALID_HOST, VALID_USER, VALID_PASSWORD, INVALID_TIMEOUT_2)),
255 DbOperationError);
256
257 // Check that CQL allows the hostname to not be specified.
258 EXPECT_NO_THROW(HostMgr::addBackend(connectionString(CQL_VALID_TYPE,
259 NULL, VALID_HOST, INVALID_USER, VALID_PASSWORD)));
260
261 // Tidy up after the test
262 destroyCqlSchema();
263 }
264
265 /// @brief Check conversion functions
266 ///
267 /// The server works using cltt and valid_filetime. In the database, the
268 /// information is stored as expire_time and valid-lifetime, which are
269 /// related by
270 ///
271 /// expire_time = cltt + valid_lifetime
272 ///
273 /// This test checks that the conversion is correct. It does not check that the
274 /// data is entered into the database correctly, only that the CQL_TIME
275 /// structure used for the entry is correctly set up.
TEST(CqlConnection,checkTimeConversion)276 TEST(CqlConnection, checkTimeConversion) {
277 const time_t cltt = time(NULL);
278 const uint32_t valid_lft = 86400; // 1 day
279 cass_int64_t cql_expire;
280
281 // Convert to the database time
282 CqlExchange::convertToDatabaseTime(cltt, valid_lft, cql_expire);
283
284 // Convert back
285 time_t converted_cltt = 0;
286 CqlExchange::convertFromDatabaseTime(cql_expire, valid_lft, converted_cltt);
287 EXPECT_EQ(cltt, converted_cltt);
288 }
289
290 // This test verifies that database backend can operate in Read-Only mode.
291 // We currently don't test Cassandra in read-only mode.
TEST_F(CqlHostDataSourceTest,DISABLED_testReadOnlyDatabase)292 TEST_F(CqlHostDataSourceTest, DISABLED_testReadOnlyDatabase) {
293 testReadOnlyDatabase(CQL_VALID_TYPE);
294 }
295
296 // Test verifies if a host reservation can be added and later retrieved by IPv4
297 // address. Host uses hw address as identifier.
TEST_F(CqlHostDataSourceTest,basic4HWAddr)298 TEST_F(CqlHostDataSourceTest, basic4HWAddr) {
299 testBasic4(Host::IDENT_HWADDR);
300 }
301
302 // Verifies that IPv4 host reservation with options can have the global
303 // subnet id value
TEST_F(CqlHostDataSourceTest,globalSubnetId4)304 TEST_F(CqlHostDataSourceTest, globalSubnetId4) {
305 testGlobalSubnetId4();
306 }
307
308 // Verifies that IPv6 host reservation with options can have the global
309 // subnet id value
TEST_F(CqlHostDataSourceTest,globalSubnetId6)310 TEST_F(CqlHostDataSourceTest, globalSubnetId6) {
311 testGlobalSubnetId6();
312 }
313
314 // Verifies that IPv4 host reservation with options can have a max value
315 // for dhcp4_subnet id
TEST_F(CqlHostDataSourceTest,maxSubnetId4)316 TEST_F(CqlHostDataSourceTest, maxSubnetId4) {
317 testMaxSubnetId4();
318 }
319
320 // Verifies that IPv6 host reservation with options can have a max value
321 // for dhcp6_subnet id
TEST_F(CqlHostDataSourceTest,maxSubnetId6)322 TEST_F(CqlHostDataSourceTest, maxSubnetId6) {
323 testMaxSubnetId6();
324 }
325
326 // Verifies that IPv4 host reservations in the same subnet can be retrieved
TEST_F(CqlHostDataSourceTest,getAll4BySubnet)327 TEST_F(CqlHostDataSourceTest, getAll4BySubnet) {
328 testGetAll4();
329 }
330
331 // Verifies that IPv6 host reservations in the same subnet can be retrieved
TEST_F(CqlHostDataSourceTest,getAll6BySubnet)332 TEST_F(CqlHostDataSourceTest, getAll6BySubnet) {
333 testGetAll6();
334 }
335
336 // Verifies that host reservations with the same hostname can be retrieved
TEST_F(CqlHostDataSourceTest,getAllbyHostname)337 TEST_F(CqlHostDataSourceTest, getAllbyHostname) {
338 testGetAllbyHostname();
339 }
340
341 // Verifies that IPv4 host reservations with the same hostname and in
342 // the same subnet can be retrieved
TEST_F(CqlHostDataSourceTest,getAllbyHostnameSubnet4)343 TEST_F(CqlHostDataSourceTest, getAllbyHostnameSubnet4) {
344 testGetAllbyHostnameSubnet4();
345 }
346
347 // Verifies that IPv6 host reservations with the same hostname and in
348 // the same subnet can be retrieved
TEST_F(CqlHostDataSourceTest,getAllbyHostnameSubnet6)349 TEST_F(CqlHostDataSourceTest, getAllbyHostnameSubnet6) {
350 testGetAllbyHostnameSubnet6();
351 }
352
353 // Verifies that IPv4 host reservations in the same subnet can be retrieved
354 // by pages.
TEST_F(CqlHostDataSourceTest,getPage4)355 TEST_F(CqlHostDataSourceTest, getPage4) {
356 testGetPage4();
357 }
358
359 // Verifies that IPv6 host reservations in the same subnet can be retrieved
360 // by pages.
TEST_F(CqlHostDataSourceTest,getPage6)361 TEST_F(CqlHostDataSourceTest, getPage6) {
362 testGetPage6();
363 }
364
365 // Verifies that IPv4 host reservations in the same subnet can be retrieved
366 // by pages without truncation from the limit.
TEST_F(CqlHostDataSourceTest,getPageLimit4)367 TEST_F(CqlHostDataSourceTest, getPageLimit4) {
368 testGetPageLimit4(Host::IDENT_DUID);
369 }
370
371 // Verifies that IPv6 host reservations in the same subnet can be retrieved
372 // by pages without truncation from the limit.
TEST_F(CqlHostDataSourceTest,getPageLimit6)373 TEST_F(CqlHostDataSourceTest, getPageLimit6) {
374 testGetPageLimit6(Host::IDENT_HWADDR);
375 }
376
377 // Verifies that IPv4 host reservations in the same subnet can be retrieved
378 // by pages even with multiple subnets.
TEST_F(CqlHostDataSourceTest,getPage4Subnets)379 TEST_F(CqlHostDataSourceTest, getPage4Subnets) {
380 testGetPage4Subnets();
381 }
382
383 // Verifies that IPv6 host reservations in the same subnet can be retrieved
384 // by pages even with multiple subnets.
TEST_F(CqlHostDataSourceTest,getPage6Subnets)385 TEST_F(CqlHostDataSourceTest, getPage6Subnets) {
386 testGetPage6Subnets();
387 }
388
389 // Verifies that all IPv4 host reservations can be retrieved by pages.
TEST_F(CqlHostDataSourceTest,getPage4All)390 TEST_F(CqlHostDataSourceTest, getPage4All) {
391 testGetPage4All();
392 }
393
394 // Verifies that all IPv6 host reservations can be retrieved by pages.
TEST_F(CqlHostDataSourceTest,getPage6All)395 TEST_F(CqlHostDataSourceTest, getPage6All) {
396 testGetPage6All();
397 }
398
399 // Test verifies if a host reservation can be added and later retrieved by IPv4
400 // address. Host uses client-id (DUID) as identifier.
TEST_F(CqlHostDataSourceTest,basic4ClientId)401 TEST_F(CqlHostDataSourceTest, basic4ClientId) {
402 testBasic4(Host::IDENT_DUID);
403 }
404
405 // Test verifies that multiple hosts can be added and later retrieved by their
406 // reserved IPv4 address. This test uses HW addresses as identifiers.
TEST_F(CqlHostDataSourceTest,getByIPv4HWaddr)407 TEST_F(CqlHostDataSourceTest, getByIPv4HWaddr) {
408 testGetByIPv4(Host::IDENT_HWADDR);
409 }
410
411 // Test verifies that multiple hosts can be added and later retrieved by their
412 // reserved IPv4 address. This test uses client-id (DUID) as identifiers.
TEST_F(CqlHostDataSourceTest,getByIPv4ClientId)413 TEST_F(CqlHostDataSourceTest, getByIPv4ClientId) {
414 testGetByIPv4(Host::IDENT_DUID);
415 }
416
417 // Test verifies if a host reservation can be added and later retrieved by
418 // hardware address.
TEST_F(CqlHostDataSourceTest,get4ByHWaddr)419 TEST_F(CqlHostDataSourceTest, get4ByHWaddr) {
420 testGet4ByIdentifier(Host::IDENT_HWADDR);
421 }
422
423 // Test verifies if a host reservation can be added and later retrieved by
424 // DUID.
TEST_F(CqlHostDataSourceTest,get4ByDUID)425 TEST_F(CqlHostDataSourceTest, get4ByDUID) {
426 testGet4ByIdentifier(Host::IDENT_DUID);
427 }
428
429 // Test verifies if a host reservation can be added and later retrieved by
430 // circuit id.
TEST_F(CqlHostDataSourceTest,get4ByCircuitId)431 TEST_F(CqlHostDataSourceTest, get4ByCircuitId) {
432 testGet4ByIdentifier(Host::IDENT_CIRCUIT_ID);
433 }
434
435 // Test verifies if a host reservation can be added and later retrieved by
436 // client-id.
TEST_F(CqlHostDataSourceTest,get4ByClientId)437 TEST_F(CqlHostDataSourceTest, get4ByClientId) {
438 testGet4ByIdentifier(Host::IDENT_CLIENT_ID);
439 }
440
441 // Test verifies if hardware address and client identifier are not confused.
TEST_F(CqlHostDataSourceTest,hwaddrNotClientId1)442 TEST_F(CqlHostDataSourceTest, hwaddrNotClientId1) {
443 testHWAddrNotClientId();
444 }
445
446 // Test verifies if hardware address and client identifier are not confused.
TEST_F(CqlHostDataSourceTest,hwaddrNotClientId2)447 TEST_F(CqlHostDataSourceTest, hwaddrNotClientId2) {
448 testClientIdNotHWAddr();
449 }
450
451 // Test verifies if a host with FQDN hostname can be stored and later retrieved.
TEST_F(CqlHostDataSourceTest,hostnameFQDN)452 TEST_F(CqlHostDataSourceTest, hostnameFQDN) {
453 testHostname("foo.example.org", 1);
454 }
455
456 // Test verifies if 100 hosts with unique FQDN hostnames can be stored and later
457 // retrieved.
TEST_F(CqlHostDataSourceTest,hostnameFQDN100)458 TEST_F(CqlHostDataSourceTest, hostnameFQDN100) {
459 testHostname("foo.example.org", 100);
460 }
461
462 // Test verifies if a host without any hostname specified can be stored and later
463 // retrieved.
TEST_F(CqlHostDataSourceTest,noHostname)464 TEST_F(CqlHostDataSourceTest, noHostname) {
465 testHostname("", 1);
466 }
467
468 // Test verifies if a host with user context can be stored and later retrieved.
TEST_F(CqlHostDataSourceTest,usercontext)469 TEST_F(CqlHostDataSourceTest, usercontext) {
470 string comment = "{ \"comment\": \"a host reservation\" }";
471 testUserContext(Element::fromJSON(comment));
472 }
473
474 // Test verifies if the hardware or client-id query can match hardware address.
TEST_F(CqlHostDataSourceTest,DISABLED_hwaddrOrClientId1)475 TEST_F(CqlHostDataSourceTest, DISABLED_hwaddrOrClientId1) {
476 /// @todo: The logic behind ::get4(subnet_id, hwaddr, duid) call needs to
477 /// be discussed.
478 ///
479 /// @todo: Add host reservation with hardware address X, try to retrieve
480 /// host for hardware address X or client identifier Y, verify that the
481 /// reservation is returned.
482 }
483
484 // Test verifies if the hardware or client-id query can match client-id.
TEST_F(CqlHostDataSourceTest,DISABLED_hwaddrOrClientId2)485 TEST_F(CqlHostDataSourceTest, DISABLED_hwaddrOrClientId2) {
486 /// @todo: The logic behind ::get4(subnet_id, hwaddr, duid) call needs to
487 /// be discussed.
488 ///
489 /// @todo: Add host reservation with client identifier Y, try to retrieve
490 /// host for hardware address X or client identifier Y, verify that the
491 /// reservation is returned.
492 }
493
494 // Test verifies that host with IPv6 address and DUID can be added and
495 // later retrieved by IPv6 address.
TEST_F(CqlHostDataSourceTest,get6AddrWithDuid)496 TEST_F(CqlHostDataSourceTest, get6AddrWithDuid) {
497 testGetByIPv6(Host::IDENT_DUID, false);
498 }
499
500 // Test verifies that host with IPv6 address and HWAddr can be added and
501 // later retrieved by IPv6 address.
TEST_F(CqlHostDataSourceTest,get6AddrWithHWAddr)502 TEST_F(CqlHostDataSourceTest, get6AddrWithHWAddr) {
503 testGetByIPv6(Host::IDENT_HWADDR, false);
504 }
505
506 // Test verifies that host with IPv6 prefix and DUID can be added and
507 // later retrieved by IPv6 prefix.
TEST_F(CqlHostDataSourceTest,get6PrefixWithDuid)508 TEST_F(CqlHostDataSourceTest, get6PrefixWithDuid) {
509 testGetByIPv6(Host::IDENT_DUID, true);
510 }
511
512 // Test verifies that host with IPv6 prefix and HWAddr can be added and
513 // later retrieved by IPv6 prefix.
TEST_F(CqlHostDataSourceTest,get6PrefixWithHWaddr)514 TEST_F(CqlHostDataSourceTest, get6PrefixWithHWaddr) {
515 testGetByIPv6(Host::IDENT_HWADDR, true);
516 }
517
518 // Test verifies that host with IPv6 prefix reservation can be retrieved
519 // by subnet id and prefix value.
TEST_F(CqlHostDataSourceTest,get6SubnetPrefix)520 TEST_F(CqlHostDataSourceTest, get6SubnetPrefix) {
521 testGetBySubnetIPv6();
522 }
523
524 // Test verifies if a host reservation can be added and later retrieved by
525 // hardware address.
TEST_F(CqlHostDataSourceTest,get6ByHWaddr)526 TEST_F(CqlHostDataSourceTest, get6ByHWaddr) {
527 testGet6ByHWAddr();
528 }
529
530 // Test verifies if a host reservation can be added and later retrieved by
531 // client identifier.
TEST_F(CqlHostDataSourceTest,get6ByClientId)532 TEST_F(CqlHostDataSourceTest, get6ByClientId) {
533 testGet6ByClientId();
534 }
535
536 // Test verifies if a host reservation can be stored with both IPv6 address and
537 // prefix.
TEST_F(CqlHostDataSourceTest,addr6AndPrefix)538 TEST_F(CqlHostDataSourceTest, addr6AndPrefix) {
539 testAddr6AndPrefix();
540 }
541
542 // Tests if host with multiple IPv6 reservations can be added and then
543 // retrieved correctly. Test checks reservations comparing.
TEST_F(CqlHostDataSourceTest,multipleReservations)544 TEST_F(CqlHostDataSourceTest, multipleReservations) {
545 testMultipleReservations();
546 }
547
548 // Tests if compareIPv6Reservations() method treats same pool of reservations
549 // but added in different order as equal.
TEST_F(CqlHostDataSourceTest,multipleReservationsDifferentOrder)550 TEST_F(CqlHostDataSourceTest, multipleReservationsDifferentOrder) {
551 testMultipleReservationsDifferentOrder();
552 }
553
554 // Test that multiple client classes for IPv4 can be inserted and
555 // retrieved for a given host reservation.
TEST_F(CqlHostDataSourceTest,multipleClientClasses4)556 TEST_F(CqlHostDataSourceTest, multipleClientClasses4) {
557 testMultipleClientClasses4();
558 }
559
560 // Test that multiple client classes for IPv6 can be inserted and
561 // retrieved for a given host reservation.
TEST_F(CqlHostDataSourceTest,multipleClientClasses6)562 TEST_F(CqlHostDataSourceTest, multipleClientClasses6) {
563 testMultipleClientClasses6();
564 }
565
566 // Test that multiple client classes for both IPv4 and IPv6 can
567 // be inserted and retrieved for a given host reservation.
TEST_F(CqlHostDataSourceTest,multipleClientClassesBoth)568 TEST_F(CqlHostDataSourceTest, multipleClientClassesBoth) {
569 testMultipleClientClassesBoth();
570 }
571
572 // Test if the same host can have reservations in different subnets (with the
573 // same hardware address). The test logic is as follows:
574 // Insert 10 host reservations for a given physical host (the same
575 // hardware address), but for different subnets (different subnet-ids).
576 // Make sure that getAll() returns them all correctly.
TEST_F(CqlHostDataSourceTest,multipleSubnetsHWAddr)577 TEST_F(CqlHostDataSourceTest, multipleSubnetsHWAddr) {
578 testMultipleSubnets(10, Host::IDENT_HWADDR);
579 }
580
581 // Test if the same host can have reservations in different subnets (with the
582 // same client identifier). The test logic is as follows:
583 //
584 // Insert 10 host reservations for a given physical host (the same
585 // client-identifier), but for different subnets (different subnet-ids).
586 // Make sure that getAll() returns them correctly.
TEST_F(CqlHostDataSourceTest,multipleSubnetsClientId)587 TEST_F(CqlHostDataSourceTest, multipleSubnetsClientId) {
588 testMultipleSubnets(10, Host::IDENT_DUID);
589 }
590
591 // Test if host reservations made for different IPv6 subnets are handled correctly.
592 // The test logic is as follows:
593 //
594 // Insert 10 host reservations for different subnets. Make sure that
595 // get6(subnet-id, ...) calls return correct reservation.
TEST_F(CqlHostDataSourceTest,subnetId6)596 TEST_F(CqlHostDataSourceTest, subnetId6) {
597 testSubnetId6(10, Host::IDENT_HWADDR);
598 }
599
600 // Test if the duplicate host instances can't be inserted. The test logic is as
601 // follows: try to add multiple instances of the same host reservation and
602 // verify that the second and following attempts will throw exceptions.
603 // Hosts with same DUID.
TEST_F(CqlHostDataSourceTest,addDuplicate6WithDUID)604 TEST_F(CqlHostDataSourceTest, addDuplicate6WithDUID) {
605 testAddDuplicate6WithSameDUID();
606 }
607
608 // Test if the duplicate host instances can't be inserted. The test logic is as
609 // follows: try to add multiple instances of the same host reservation and
610 // verify that the second and following attempts will throw exceptions.
611 // Hosts with same HWAddr.
TEST_F(CqlHostDataSourceTest,addDuplicate6WithHWAddr)612 TEST_F(CqlHostDataSourceTest, addDuplicate6WithHWAddr) {
613 testAddDuplicate6WithSameHWAddr();
614 }
615
616 // Test if the duplicate IPv4 host instances can't be inserted. The test logic is as
617 // follows: try to add multiple instances of the same host reservation and
618 // verify that the second and following attempts will throw exceptions.
TEST_F(CqlHostDataSourceTest,addDuplicateIPv4)619 TEST_F(CqlHostDataSourceTest, addDuplicateIPv4) {
620 testAddDuplicateIPv4();
621 }
622
623 /// @brief Test that the CQL backend does not support using non-unique
624 /// IP addresses between multiple reservations.
TEST_F(CqlHostDataSourceTest,disallowDuplicateIP)625 TEST_F(CqlHostDataSourceTest, disallowDuplicateIP) {
626 testDisallowDuplicateIP();
627 }
628
629 // This test verifies that DHCPv4 options can be inserted in a binary format
630 /// and retrieved from the CQL host database.
TEST_F(CqlHostDataSourceTest,optionsReservations4)631 TEST_F(CqlHostDataSourceTest, optionsReservations4) {
632 string comment = "{ \"comment\": \"a host reservation\" }";
633 testOptionsReservations4(false, Element::fromJSON(comment));
634 }
635
636 // This test verifies that DHCPv6 options can be inserted in a binary format
637 /// and retrieved from the CQL host database.
TEST_F(CqlHostDataSourceTest,optionsReservations6)638 TEST_F(CqlHostDataSourceTest, optionsReservations6) {
639 string comment = "{ \"comment\": \"a host reservation\" }";
640 testOptionsReservations6(false, Element::fromJSON(comment));
641 }
642
643 // This test verifies that DHCPv4 and DHCPv6 options can be inserted in a
644 /// binary format and retrieved with a single query to the database.
TEST_F(CqlHostDataSourceTest,optionsReservations46)645 TEST_F(CqlHostDataSourceTest, optionsReservations46) {
646 testOptionsReservations46(false);
647 }
648
649 // This test verifies that DHCPv4 options can be inserted in a textual format
650 /// and retrieved from the CQL host database.
TEST_F(CqlHostDataSourceTest,formattedOptionsReservations4)651 TEST_F(CqlHostDataSourceTest, formattedOptionsReservations4) {
652 string comment = "{ \"comment\": \"a host reservation\" }";
653 testOptionsReservations4(true, Element::fromJSON(comment));
654 }
655
656 // This test verifies that DHCPv6 options can be inserted in a textual format
657 /// and retrieved from the CQL host database.
TEST_F(CqlHostDataSourceTest,formattedOptionsReservations6)658 TEST_F(CqlHostDataSourceTest, formattedOptionsReservations6) {
659 string comment = "{ \"comment\": \"a host reservation\" }";
660 testOptionsReservations6(true, Element::fromJSON(comment));
661 }
662
663 // This test verifies that DHCPv4 and DHCPv6 options can be inserted in a
664 // textual format and retrieved with a single query to the database.
TEST_F(CqlHostDataSourceTest,formattedOptionsReservations46)665 TEST_F(CqlHostDataSourceTest, formattedOptionsReservations46) {
666 testOptionsReservations46(true);
667 }
668
669 // This test checks transactional insertion of the host information
670 // into the database. The failure to insert host information at
671 // any stage should cause the whole transaction to be rolled back.
TEST_F(CqlHostDataSourceTest,testAddRollback)672 TEST_F(CqlHostDataSourceTest, testAddRollback) {
673 // Make sure we have the pointer to the host data source.
674 ASSERT_TRUE(hdsptr_);
675
676 // To test the transaction rollback mechanism we need to cause the
677 // insertion of host information to fail at some stage. The 'hosts'
678 // table should be updated correctly but the failure should occur
679 // when inserting reservations or options. The simplest way to
680 // achieve that is to simply drop one of the tables. To do so, we
681 // connect to the database and issue a DROP query.
682 CqlConnection::ParameterMap params;
683 params["name"] = "keatest";
684 params["user"] = "keatest";
685 params["password"] = "keatest";
686 CqlConnection conn(params);
687 ASSERT_NO_THROW(conn.openDatabase());
688
689 // Drop every table so we make sure hosts doesn't exist anymore.
690 destroyCqlSchema(false, true);
691
692 // Create a host with a reservation.
693 HostPtr host = HostDataSourceUtils::initializeHost6("2001:db8:1::1",
694 Host::IDENT_HWADDR, false, "randomKey");
695 // Let's assign some DHCPv4 subnet to the host, because we will use the
696 // DHCPv4 subnet to try to retrieve the host after failed insertion.
697 host->setIPv4SubnetID(SubnetID(4));
698
699 // There is no ipv6_reservations table, so the insertion should fail.
700 ASSERT_THROW(hdsptr_->add(host), DbOperationError);
701
702 // Even though we have created a DHCPv6 host, we can't use get6()
703 // method to retrieve the host from the database, because the
704 // query would expect that the ipv6_reservations table is present.
705 // Therefore, the query would fail. Instead, we use the get4 method
706 // which uses the same client identifier, but doesn't attempt to
707 // retrieve the data from ipv6_reservations table. The query should
708 // pass but return no host because the (insert) transaction is expected
709 // to be rolled back.
710 ASSERT_THROW(hdsptr_->get4(host->getIPv4SubnetID(),
711 host->getIdentifierType(),
712 &host->getIdentifier()[0],
713 host->getIdentifier().size()),
714 DbOperationError);
715 }
716
TEST_F(CqlHostDataSourceTest,DISABLED_stressTest)717 TEST_F(CqlHostDataSourceTest, DISABLED_stressTest) {
718 // Run with 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4092, 8192,
719 // 16384 & 32768 hosts.
720 for (unsigned int i = 0X0001U; i < 0xfffdU; i <<= 1) {
721 initializeTest();
722 stressTest(i);
723 destroyTest();
724 }
725 }
726
727 // This test checks that siaddr, sname, file fields can be retrieved
728 /// from a database for a host.
TEST_F(CqlHostDataSourceTest,messageFields)729 TEST_F(CqlHostDataSourceTest, messageFields) {
730 testMessageFields4();
731 }
732
733 // Check that delete(subnet-id, addr4) works.
TEST_F(CqlHostDataSourceTest,deleteByAddr4)734 TEST_F(CqlHostDataSourceTest, deleteByAddr4) {
735 testDeleteByAddr4();
736 }
737
738 // Check that delete(subnet4-id, identifier-type, identifier) works.
TEST_F(CqlHostDataSourceTest,deleteById4)739 TEST_F(CqlHostDataSourceTest, deleteById4) {
740 testDeleteById4();
741 }
742
743 // Check that delete(subnet4-id, identifier-type, identifier) works,
744 // even when options are present.
TEST_F(CqlHostDataSourceTest,deleteById4Options)745 TEST_F(CqlHostDataSourceTest, deleteById4Options) {
746 testDeleteById4Options();
747 }
748
749 // Check that delete(subnet6-id, identifier-type, identifier) works.
TEST_F(CqlHostDataSourceTest,deleteById6)750 TEST_F(CqlHostDataSourceTest, deleteById6) {
751 testDeleteById6();
752 }
753
754 // Check that delete(subnet6-id, identifier-type, identifier) works,
755 // even when options are present.
TEST_F(CqlHostDataSourceTest,deleteById6Options)756 TEST_F(CqlHostDataSourceTest, deleteById6Options) {
757 testDeleteById6Options();
758 }
759
760 // Tests that multiple reservations without IPv4 addresses can be
761 // specified within a subnet.
TEST_F(CqlHostDataSourceTest,testMultipleHostsNoAddress4)762 TEST_F(CqlHostDataSourceTest, testMultipleHostsNoAddress4) {
763 testMultipleHostsNoAddress4();
764 }
765
766 // Tests that multiple hosts can be specified within an IPv6 subnet.
TEST_F(CqlHostDataSourceTest,testMultipleHosts6)767 TEST_F(CqlHostDataSourceTest, testMultipleHosts6) {
768 testMultipleHosts6();
769 }
770
771 /// @brief Test fixture class for validating @c HostMgr using
772 /// CQL as alternate host data source.
773 class CQLHostMgrTest : public HostMgrTest {
774 protected:
775
776 /// @brief Build CQL schema for a test.
777 virtual void SetUp();
778
779 /// @brief Rollback and drop CQL schema after the test.
780 virtual void TearDown();
781 };
782
783 void
SetUp()784 CQLHostMgrTest::SetUp() {
785 HostMgrTest::SetUp();
786
787 // Ensure we have the proper schema with no transient data.
788 db::test::createCqlSchema();
789
790 // Connect to the database
791 try {
792 HostMgr::addBackend(db::test::validCqlConnectionString());
793 } catch (...) {
794 std::cerr << "*** ERROR: unable to open database. The test\n"
795 "*** environment is broken and must be fixed before\n"
796 "*** the CQL tests will run correctly.\n"
797 "*** The reason for the problem is described in the\n"
798 "*** accompanying exception output.\n";
799 throw;
800 }
801 }
802
803 void
TearDown()804 CQLHostMgrTest::TearDown() {
805 HostMgr::instance().getHostDataSource()->rollback();
806 HostMgr::delBackend("cql");
807
808 // If data wipe enabled, delete transient data otherwise destroy the schema
809 db::test::destroyCqlSchema();
810 }
811
812 // This test verifies that reservations for a particular client can
813 // be retrieved from the configuration file and a database simultaneously.
TEST_F(CQLHostMgrTest,getAll)814 TEST_F(CQLHostMgrTest, getAll) {
815 testGetAll(*getCfgHosts(), HostMgr::instance());
816 }
817
818 // This test verifies that reservations for a particular subnet can
819 // be retrieved from the configuration file and a database simultaneously.
TEST_F(CQLHostMgrTest,getAll4BySubnet)820 TEST_F(CQLHostMgrTest, getAll4BySubnet) {
821 testGetAll4BySubnet(*getCfgHosts(), HostMgr::instance());
822 }
823
824 // This test verifies that reservations for a particular subnet can
825 // be retrieved from the configuration file and a database simultaneously.
TEST_F(CQLHostMgrTest,getAll6BySubnet)826 TEST_F(CQLHostMgrTest, getAll6BySubnet) {
827 testGetAll6BySubnet(*getCfgHosts(), HostMgr::instance());
828 }
829
830 // This test verifies that reservations for a particular hostname can be
831 // retrieved from the configuration file and a database simultaneously.
TEST_F(CQLHostMgrTest,getAllbyHostname)832 TEST_F(CQLHostMgrTest, getAllbyHostname) {
833 testGetAllbyHostname(*getCfgHosts(), HostMgr::instance());
834 }
835
836 // This test verifies that reservations for a particular hostname and
837 // DHCPv4 subnet can be retrieved from the configuration file and a
838 // database simultaneously.
TEST_F(CQLHostMgrTest,getAllbyHostnameSubnet4)839 TEST_F(CQLHostMgrTest, getAllbyHostnameSubnet4) {
840 testGetAllbyHostnameSubnet4(*getCfgHosts(), HostMgr::instance());
841 }
842
843 // This test verifies that reservations for a particular hostname and
844 // DHCPv6 subnet can be retrieved from the configuration file and a
845 // database simultaneously.
TEST_F(CQLHostMgrTest,getAllbyHostnameSubnet6)846 TEST_F(CQLHostMgrTest, getAllbyHostnameSubnet6) {
847 testGetAllbyHostnameSubnet6(*getCfgHosts(), HostMgr::instance());
848 }
849
850 // This test verifies that reservations for a particular subnet can
851 // be retrieved by pages from the configuration file and a database
852 // simultaneously.
TEST_F(CQLHostMgrTest,getPage4)853 TEST_F(CQLHostMgrTest, getPage4) {
854 testGetPage4(true);
855 }
856
857 // This test verifies that all v4 reservations be retrieved by pages
858 // from the configuration file and a database simultaneously.
TEST_F(CQLHostMgrTest,getPage4All)859 TEST_F(CQLHostMgrTest, getPage4All) {
860 testGetPage4All(true);
861 }
862
863 // This test verifies that reservations for a particular subnet can
864 // be retrieved by pages from the configuration file and a database
865 // simultaneously.
TEST_F(CQLHostMgrTest,getPage6)866 TEST_F(CQLHostMgrTest, getPage6) {
867 testGetPage6(true);
868 }
869
870 // This test verifies that all v6 reservations be retrieved by pages
871 // from the configuration file and a database simultaneously.
TEST_F(CQLHostMgrTest,getPage6All)872 TEST_F(CQLHostMgrTest, getPage6All) {
873 testGetPage6All(true);
874 }
875
876 // This test verifies that IPv4 reservations for a particular client can
877 // be retrieved from the configuration file and a database simultaneously.
TEST_F(CQLHostMgrTest,getAll4)878 TEST_F(CQLHostMgrTest, getAll4) {
879 testGetAll4(*getCfgHosts(), HostMgr::instance());
880 }
881
882 // This test verifies that the IPv4 reservation can be retrieved from a
883 // database.
TEST_F(CQLHostMgrTest,get4)884 TEST_F(CQLHostMgrTest, get4) {
885 testGet4(HostMgr::instance());
886 }
887
888 // This test verifies that the IPv6 reservation can be retrieved from a
889 // database.
TEST_F(CQLHostMgrTest,get6)890 TEST_F(CQLHostMgrTest, get6) {
891 testGet6(HostMgr::instance());
892 }
893
894 // This test verifies that the IPv6 prefix reservation can be retrieved
895 // from a configuration file and a database.
TEST_F(CQLHostMgrTest,get6ByPrefix)896 TEST_F(CQLHostMgrTest, get6ByPrefix) {
897 testGet6ByPrefix(*getCfgHosts(), HostMgr::instance());
898 }
899
900 // This test verifies that it is possible to control whether the reserved
901 // IP addresses are unique or non unique via the HostMgr.
TEST_F(CQLHostMgrTest,setIPReservationsUnique)902 TEST_F(CQLHostMgrTest, setIPReservationsUnique) {
903 EXPECT_TRUE(HostMgr::instance().setIPReservationsUnique(true));
904 // This is currently not supported for Cassandra.
905 EXPECT_FALSE(HostMgr::instance().setIPReservationsUnique(false));
906 }
907
908 } // namespace
909