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