1 // Copyright (C) 2019-2020 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #include <config.h>
8 
9 #include <config_backend/base_config_backend_mgr.h>
10 #include <config_backend/base_config_backend_pool.h>
11 #include <process/cb_ctl_base.h>
12 #include <boost/date_time/posix_time/posix_time.hpp>
13 #include <boost/date_time/gregorian/gregorian.hpp>
14 #include <boost/shared_ptr.hpp>
15 #include <gtest/gtest.h>
16 #include <map>
17 #include <string>
18 
19 using namespace isc;
20 using namespace isc::cb;
21 using namespace isc::db;
22 using namespace isc::process;
23 
24 namespace {
25 
26 /// @brief Implementation of the config backend for testing the
27 /// @c CBControlBase template class.
28 ///
29 /// This simple class allows for adding, retrieving and clearing audit
30 /// entries. The @c CBControlBase unit tests use it to control the
31 /// behavior of the @c CBControlBase class under test.
32 class CBControlBackend : BaseConfigBackend {
33 public:
34 
35     /// @brief Constructor.
CBControlBackend(const db::DatabaseConnection::ParameterMap &)36     CBControlBackend(const db::DatabaseConnection::ParameterMap&) {
37     }
38 
39     /// @brief Retrieves the audit entries later than specified time.
40     ///
41     /// @param modification_time The lower bound time for which audit
42     /// entries should be returned.
43     /// @param modification_id The lower bound id for which audit
44     /// entries should be returned.
45     ///
46     /// @return Collection of audit entries later than specified time.
47     virtual db::AuditEntryCollection
getRecentAuditEntries(const db::ServerSelector &,const boost::posix_time::ptime & modification_time,const uint64_t modification_id) const48     getRecentAuditEntries(const db::ServerSelector&,
49                           const boost::posix_time::ptime& modification_time,
50                           const uint64_t modification_id) const {
51         db::AuditEntryCollection filtered_entries;
52 
53         // Use the index which orders the audit entries by timestamps.
54         const auto& index = audit_entries_.get<AuditEntryModificationTimeIdTag>();
55 
56         // Locate the first audit entry after the last one having the
57         // specified modification time and id.
58         auto modification = boost::make_tuple(modification_time, modification_id);
59         auto first_entry = index.upper_bound(modification);
60 
61         // If there are any entries found return them.
62         if (first_entry != index.end()) {
63             filtered_entries.insert(first_entry, index.end());
64         }
65 
66         return (filtered_entries);
67     }
68 
69     /// @brief Add audit entry to the backend.
70     ///
71     /// @param object_type Object type to be stored in the audit entry.
72     /// @param object_id Object id to be stored in the audit entry.
73     /// @param modification_time Audit entry modification time to be set.
74     /// @param modification_id Audit entry modification id to be set.
addAuditEntry(const ServerSelector &,const std::string & object_type,const uint64_t object_id,const boost::posix_time::ptime & modification_time,const uint64_t modification_id)75     void addAuditEntry(const ServerSelector&,
76                        const std::string& object_type,
77                        const uint64_t object_id,
78                        const boost::posix_time::ptime& modification_time,
79                        const uint64_t modification_id) {
80         // Create new audit entry from the specified parameters.
81         AuditEntryPtr audit_entry(new AuditEntry(object_type,
82                                                  object_id,
83                                                  AuditEntry::ModificationType::CREATE,
84                                                  modification_time,
85                                                  modification_id,
86                                                  "added audit entry"));
87 
88         // The audit entries are held in the static variable so as they
89         // don't disappear when we diconnect from the backend. The
90         // audit entries are explicitly cleared during the unit tests
91         // setup.
92         audit_entries_.insert(audit_entry);
93     }
94 
95     /// @brief Returns backend type in the textual format.
96     ///
97     /// @return Name of the storage for configurations, e.g. "mysql",
98     /// "pgsql" and so forth.
getType() const99     virtual std::string getType() const {
100         return ("memfile");
101     }
102 
103     /// @brief Returns backend host
104     ///
105     /// This is used by the @c BaseConfigBackendPool to select backend
106     /// when @c BackendSelector is specified.
107     ///
108     /// @return host on which the database is located.
getHost() const109     virtual std::string getHost() const {
110         return ("");
111     }
112 
113     /// @brief Returns backend port number.
114     ///
115     /// This is used by the @c BaseConfigBackendPool to select backend
116     /// when @c BackendSelector is specified.
117     ///
118     /// @return Port number on which database service is available.
getPort() const119     virtual uint16_t getPort() const {
120         return (0);
121     }
122 
123     /// @brief Removes audit entries.
clearAuditEntries()124     static void clearAuditEntries() {
125         audit_entries_.clear();
126     }
127 
128 private:
129 
130     /// @brief Static collection of audit entries.
131     ///
132     /// Thanks to storing them in the static member they are preserved
133     /// when the unit tests "disconnect" from the backend.
134     static AuditEntryCollection audit_entries_;
135 };
136 
137 /// @brief Pointer to the @c CBControlBackend object.
138 typedef boost::shared_ptr<CBControlBackend> CBControlBackendPtr;
139 
140 AuditEntryCollection CBControlBackend::audit_entries_;
141 
142 /// @brief Implementation of the backends pool used in the
143 /// @c CBControlBase template class unit tests.
144 class CBControlBackendPool : public BaseConfigBackendPool<CBControlBackend> {
145 public:
146 
147     /// @brief Add audit entry to the backend.
148     ///
149     /// @param backend_selector Backend selector.
150     /// @param server_selector Server selector.
151     /// @param object_type Object type to be stored in the audit entry.
152     /// @param object_id Object id to be stored in the audit entry.
153     /// @param modification_time Audit entry modification time to be set.
154     /// @param modification_id Audit entry modification id to be set.
addAuditEntry(const BackendSelector & backend_selector,const ServerSelector & server_selector,const std::string & object_type,const uint64_t object_id,const boost::posix_time::ptime & modification_time,const uint64_t modification_id)155     void addAuditEntry(const BackendSelector& backend_selector,
156                        const ServerSelector& server_selector,
157                        const std::string& object_type,
158                        const uint64_t object_id,
159                        const boost::posix_time::ptime& modification_time,
160                        const uint64_t modification_id) {
161         createUpdateDeleteProperty<void, const std::string&, uint64_t,
162                                    const boost::posix_time::ptime&, uint64_t>
163             (&CBControlBackend::addAuditEntry, backend_selector, server_selector,
164              object_type, object_id, modification_time, modification_id);
165     }
166 
167     /// @brief Retrieves the audit entries later than specified time.
168     ///
169     /// @param backend_selector Backend selector.
170     /// @param server_selector Server selector.
171     /// @param modification_time The lower bound time for which audit
172     /// entries should be returned.
173     /// @param modification_id The lower bound id for which audit
174     /// entries should be returned.
175     ///
176     /// @return Collection of audit entries later than specified time.
177     virtual db::AuditEntryCollection
getRecentAuditEntries(const BackendSelector & backend_selector,const ServerSelector & server_selector,const boost::posix_time::ptime & modification_time,const uint64_t modification_id) const178     getRecentAuditEntries(const BackendSelector& backend_selector,
179                           const ServerSelector& server_selector,
180                           const boost::posix_time::ptime& modification_time,
181                           const uint64_t modification_id) const {
182         AuditEntryCollection audit_entries;
183         getMultiplePropertiesConst<AuditEntryCollection, const boost::posix_time::ptime&>
184             (&CBControlBackend::getRecentAuditEntries, backend_selector,
185              server_selector, audit_entries, modification_time,
186              modification_id);
187         return (audit_entries);
188     }
189 };
190 
191 /// @brief Implementation of the config backends manager used
192 /// in the @c CBControlBase template class unit tests.
193 class CBControlBackendMgr : public BaseConfigBackendMgr<CBControlBackendPool> {
194 public:
195 
196     /// @brief Constructor.
CBControlBackendMgr()197     CBControlBackendMgr()
198         : instance_id_(0) {
199     }
200 
201     /// @brief Returns instance of the @c CBControlBackendMgr.
202     ///
203     /// @return Reference to the instance of the @c CBControlBackendMgr.
instance()204     static CBControlBackendMgr& instance() {
205         static CBControlBackendMgr mgr;
206         return (mgr);
207     }
208 
209     /// @brief Returns instance id.
210     ///
211     /// This value is used in tests which verify that the @c CBControlBase::getMgr
212     /// returns the right instance of the CB manager.
213     ///
214     /// @return Instance id.
getInstanceId() const215     uint32_t getInstanceId() const {
216         return (instance_id_);
217     }
218 
219     /// @brief Sets new instance id.
220     ///
221     /// @param instance_id New instance id.
setInstanceId(const uint32_t instance_id)222     void setInstanceId(const uint32_t instance_id) {
223         instance_id_ = instance_id;
224     }
225 
226     /// @brief Instance id.
227     uint32_t instance_id_;
228 };
229 
230 /// @brief Implementation of the @c CBControlBase class used in
231 /// the unit tests.
232 ///
233 /// It makes some of the protected methods public. It also provides
234 /// means to test the behavior of the @c CBControlBase template.
235 class CBControl : public CBControlBase<CBControlBackendMgr> {
236 public:
237 
238     using CBControlBase<CBControlBackendMgr>::fetchConfigElement;
239     using CBControlBase<CBControlBackendMgr>::getMgr;
240     using CBControlBase<CBControlBackendMgr>::getInitialAuditRevisionTime;
241 
242     /// @brief Constructor.
CBControl()243     CBControl()
244         : CBControlBase<CBControlBackendMgr>(),
245         merges_num_(0),
246         backend_selector_(BackendSelector::Type::MYSQL),
247         server_selector_(ServerSelector::UNASSIGNED()),
248         audit_entries_num_(-1),
249         enable_throw_(false) {
250     }
251 
252     /// @brief Implementation of the method called to fetch and apply
253     /// configuration from the database into the local configuration.
254     ///
255     /// This stub implementation doesn't attempt to merge any configurations
256     /// but merely records the values of the parameters called.
257     ///
258     /// @param backend_selector Backend selector.
259     /// @param server_selector Server selector.
260     /// @param audit_entries Collection of audit entries.
databaseConfigApply(const BackendSelector & backend_selector,const ServerSelector & server_selector,const boost::posix_time::ptime &,const AuditEntryCollection & audit_entries)261     virtual void databaseConfigApply(const BackendSelector& backend_selector,
262                                      const ServerSelector& server_selector,
263                                      const boost::posix_time::ptime&,
264                                      const AuditEntryCollection& audit_entries) {
265         ++merges_num_;
266         backend_selector_ = backend_selector;
267         server_selector_ = server_selector;
268         audit_entries_num_ = static_cast<int>(audit_entries.size());
269 
270         if (enable_throw_) {
271             isc_throw(Unexpected, "throwing from databaseConfigApply");
272         }
273     }
274 
275     /// @brief Returns the number of times the @c databaseConfigApply was called.
getMergesNum() const276     size_t getMergesNum() const {
277         return (merges_num_);
278     }
279 
280     /// @brief Returns backend selector used as an argument in a call to
281     /// @c databaseConfigApply.
getBackendSelector() const282     const BackendSelector& getBackendSelector() const {
283         return (backend_selector_);
284     }
285 
286     /// @brief Returns server selector used as an argument in a call to
287     /// @c databaseConfigApply.
getServerSelector() const288     const ServerSelector& getServerSelector() const {
289         return (server_selector_);
290     }
291 
292     /// @brief Returns the number of audit entries in the collection passed
293     /// to @c databaseConfigApply
getAuditEntriesNum() const294     int getAuditEntriesNum() const {
295         return (audit_entries_num_);
296     }
297 
298     /// @brief Returns the recorded time of last audit entry.
getLastAuditRevisionTime() const299     boost::posix_time::ptime getLastAuditRevisionTime() const {
300         return (last_audit_revision_time_);
301     }
302 
303     /// @brief Returns the recorded id of last audit entry.
getLastAuditRevisionId() const304     uint64_t getLastAuditRevisionId() const {
305         return (last_audit_revision_id_);
306     }
307 
308     /// @brief Overwrites the last audit entry time.
309     ///
310     /// @param last_audit_revision_time New time to be set.
setLastAuditRevisionTime(const boost::posix_time::ptime & last_audit_revision_time)311     void setLastAuditRevisionTime(const boost::posix_time::ptime& last_audit_revision_time) {
312         last_audit_revision_time_ = last_audit_revision_time;
313     }
314 
315     /// @brief Overwrites the last audit revision id.
316     ///
317     /// @param last_audit_revision_id New id to be set.
setLastAuditRevisionId(const uint64_t & last_audit_revision_id)318     void setLastAuditRevisionId(const uint64_t& last_audit_revision_id) {
319         last_audit_revision_id_ = last_audit_revision_id;
320     }
321 
322     /// @brief Enables the @c databaseConfigApply function to throw.
323     ///
324     /// This is useful to test scenarios when configuration merge fails.
enableThrow()325     void enableThrow() {
326         enable_throw_ = true;
327     }
328 
329 private:
330 
331     /// @brief Recorded number of calls to @c databaseConfigApply.
332     size_t merges_num_;
333 
334     /// @brief Recorded backend selector value.
335     BackendSelector backend_selector_;
336 
337     /// @brief Recorded server selector value.
338     ServerSelector server_selector_;
339 
340     /// @brief Recorded number of audit entries.
341     int audit_entries_num_;
342 
343     /// @brief Boolean value indicating if the @c databaseConfigApply should throw.
344     bool enable_throw_;
345 };
346 
347 /// @brief Out of the blue instance id used in tests.
348 constexpr uint32_t TEST_INSTANCE_ID = 123;
349 
350 /// @brief Test fixture class for @c CBControlBase template class.
351 class CBControlBaseTest : public ::testing::Test {
352 public:
353 
354     /// @brief Constructor.
CBControlBaseTest()355     CBControlBaseTest()
356         : cb_ctl_(), mgr_(CBControlBackendMgr::instance()),
357           timestamps_() {
358         mgr_.registerBackendFactory("db1",
359             [](const DatabaseConnection::ParameterMap& params)
360                 -> CBControlBackendPtr {
361             return (CBControlBackendPtr(new CBControlBackend(params)));
362         });
363         mgr_.setInstanceId(TEST_INSTANCE_ID);
364         initTimestamps();
365         CBControlBackend::clearAuditEntries();
366     }
367 
368     /// @brief Destructor.
369     ///
370     /// Removes audit entries created in the test.
~CBControlBaseTest()371     ~CBControlBaseTest() {
372         CBControlBackend::clearAuditEntries();
373     }
374 
375     /// @brief Initialize posix time values used in tests.
initTimestamps()376     void initTimestamps() {
377         // Current time minus 1 hour to make sure it is in the past.
378         timestamps_["today"] = boost::posix_time::second_clock::local_time()
379             - boost::posix_time::hours(1);
380         // Yesterday.
381         timestamps_["yesterday"] = timestamps_["today"] - boost::posix_time::hours(24);
382         // Two days ago.
383         timestamps_["two days ago"] = timestamps_["today"] - boost::posix_time::hours(48);
384         // Tomorrow.
385         timestamps_["tomorrow"] = timestamps_["today"] + boost::posix_time::hours(24);
386     }
387 
388     /// @brief Creates an instance of the configuration object.
389     ///
390     /// @param db1_access Database access string to be used to connect to
391     /// the test configuration backend. It doesn't connect if the string
392     /// is empty.
makeConfigBase(const std::string & db1_access="") const393     ConfigPtr makeConfigBase(const std::string& db1_access = "") const {
394         ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo());
395 
396         if (!db1_access.empty()) {
397             config_ctl_info->addConfigDatabase(db1_access);
398         }
399 
400         ConfigPtr config_base(new ConfigBase());
401 
402         config_base->setConfigControlInfo(config_ctl_info);
403         return (config_base);
404     }
405 
406     /// @brief Instance of the @c CBControl used in tests.
407     CBControl cb_ctl_;
408 
409     /// @brief Instance of the Config Backend Manager.
410     CBControlBackendMgr& mgr_;
411 
412     /// @brief Holds timestamp values used in tests.
413     std::map<std::string, boost::posix_time::ptime> timestamps_;
414 };
415 
416 // This test verifies that the same instance of the Config
417 // Backend Manager is returned all the time.
TEST_F(CBControlBaseTest,getMgr)418 TEST_F(CBControlBaseTest, getMgr) {
419     auto mgr = cb_ctl_.getMgr();
420     EXPECT_EQ(TEST_INSTANCE_ID, mgr.getInstanceId());
421 }
422 
423 // This test verifies that the initial audit revision time is set to
424 // local time of 2000-01-01.
TEST_F(CBControlBaseTest,getInitialAuditRevisionTime)425 TEST_F(CBControlBaseTest, getInitialAuditRevisionTime) {
426     auto initial_time = cb_ctl_.getInitialAuditRevisionTime();
427     ASSERT_FALSE(initial_time.is_not_a_date_time());
428     auto tm = boost::posix_time::to_tm(initial_time);
429     EXPECT_EQ(100, tm.tm_year);
430     EXPECT_EQ(0, tm.tm_mon);
431     EXPECT_EQ(0, tm.tm_yday);
432     EXPECT_EQ(0, tm.tm_hour);
433     EXPECT_EQ(0, tm.tm_min);
434     EXPECT_EQ(0, tm.tm_sec);
435 }
436 
437 // This test verifies that last audit entry time is reset upon the
438 // call to CBControlBase::reset().
TEST_F(CBControlBaseTest,reset)439 TEST_F(CBControlBaseTest, reset) {
440     cb_ctl_.setLastAuditRevisionTime(timestamps_["tomorrow"]);
441     cb_ctl_.reset();
442     EXPECT_EQ(cb_ctl_.getInitialAuditRevisionTime(), cb_ctl_.getLastAuditRevisionTime());
443     EXPECT_EQ(0, cb_ctl_.getLastAuditRevisionId());
444 }
445 
446 // This test verifies that it is correctly determined what entries the
447 // server should fetch for the particular configuration element.
TEST_F(CBControlBaseTest,fetchConfigElement)448 TEST_F(CBControlBaseTest, fetchConfigElement) {
449     db::AuditEntryCollection audit_entries;
450     db::AuditEntryCollection updated;
451     // When audit entries collection is empty any subset is empty too.
452     updated = cb_ctl_.fetchConfigElement(audit_entries, "my_object_type");
453     EXPECT_TRUE(updated.empty());
454 
455     // Now test the case that there is a DELETE audit entry. In this case
456     // our function should indicate that the configuration should not be
457     // fetched for the given object type. Note that when the configuration
458     // element is deleted, it no longer exists in database so there is
459     // no reason to fetch the data from the database.
460     AuditEntryPtr audit_entry(new AuditEntry("dhcp4_subnet", 1234 ,
461                                              AuditEntry::ModificationType::DELETE,
462                                              2345, "added audit entry"));
463     audit_entries.insert(audit_entry);
464     updated = cb_ctl_.fetchConfigElement(audit_entries, "my_object_type");
465     EXPECT_TRUE(updated.empty());
466     EXPECT_TRUE(hasObjectId(audit_entries, 1234));
467     EXPECT_FALSE(hasObjectId(audit_entries, 5678));
468     EXPECT_FALSE(hasObjectId(updated, 1234));
469 
470     // Add another audit entry which indicates creation of the configuration element.
471     // This time we should get it.
472     audit_entry.reset(new AuditEntry("my_object_type", 5678,
473                                      AuditEntry::ModificationType::CREATE,
474                                      6789, "added audit entry"));
475     audit_entries.insert(audit_entry);
476     updated = cb_ctl_.fetchConfigElement(audit_entries, "my_object_type");
477     ASSERT_EQ(1, updated.size());
478     AuditEntryPtr updated_entry = (*updated.begin());
479     ASSERT_TRUE(updated_entry);
480     EXPECT_EQ("my_object_type", updated_entry->getObjectType());
481     EXPECT_EQ(5678, updated_entry->getObjectId());
482     EXPECT_EQ(AuditEntry::ModificationType::CREATE, updated_entry->getModificationType());
483     EXPECT_TRUE(hasObjectId(audit_entries, 5678));
484     EXPECT_TRUE(hasObjectId(updated, 5678));
485     EXPECT_FALSE(hasObjectId(updated, 1234));
486 
487     // Also we should get 'true' for the UPDATE case.
488     audit_entry.reset(new AuditEntry("my_object_type",
489                                      5678, AuditEntry::ModificationType::UPDATE,
490                                      6790, "added audit entry"));
491     audit_entries.insert(audit_entry);
492     updated = cb_ctl_.fetchConfigElement(audit_entries, "my_object_type");
493     EXPECT_EQ(2, updated.size());
494     bool saw_create = false;
495     bool saw_update =  false;
496     for (auto entry : updated) {
497         EXPECT_EQ("my_object_type", entry->getObjectType());
498         EXPECT_EQ(5678, entry->getObjectId());
499         if (AuditEntry::ModificationType::CREATE == entry->getModificationType()) {
500             EXPECT_FALSE(saw_create);
501             saw_create = true;
502         } else if (AuditEntry::ModificationType::UPDATE == entry->getModificationType()) {
503             EXPECT_FALSE(saw_update);
504             saw_update = true;
505         }
506     }
507     EXPECT_TRUE(saw_create);
508     EXPECT_TRUE(saw_update);
509     EXPECT_TRUE(hasObjectId(updated, 5678));
510     EXPECT_FALSE(hasObjectId(updated, 1234));
511 }
512 
513 // This test verifies that true is return when the server successfully
514 // connects to the backend and false if there are no backends to connect
515 // to.
TEST_F(CBControlBaseTest,connect)516 TEST_F(CBControlBaseTest, connect) {
517     EXPECT_TRUE(cb_ctl_.databaseConfigConnect(makeConfigBase("type=db1")));
518     EXPECT_FALSE(cb_ctl_.databaseConfigConnect(makeConfigBase()));
519 }
520 
521 // This test verifies the scenario when the server fetches the entire
522 // configuration from the database upon startup.
TEST_F(CBControlBaseTest,fetchAll)523 TEST_F(CBControlBaseTest, fetchAll) {
524     auto config_base = makeConfigBase("type=db1");
525 
526     // Add two audit entries to the database. The server should load
527     // the entire configuration from the database regardless of the
528     // existing audit entries. However, the last audit entry timestamp
529     // should be set to the most recent audit entry in the
530     // @c CBControlBase.
531     ASSERT_TRUE(cb_ctl_.databaseConfigConnect(config_base));
532 
533     cb_ctl_.getMgr().getPool()->addAuditEntry(BackendSelector::UNSPEC(),
534                                               ServerSelector::ALL(),
535                                               "sql_table_2",
536                                               1234,
537                                               timestamps_["yesterday"],
538                                               2345);
539 
540     cb_ctl_.getMgr().getPool()->addAuditEntry(BackendSelector::UNSPEC(),
541                                               ServerSelector::ALL(),
542                                               "sql_table_1",
543                                               3456,
544                                               timestamps_["today"],
545                                               4567);
546 
547     // Disconnect from the database in order to check that the
548     // databaseConfigFetch reconnects.
549     ASSERT_NO_THROW(cb_ctl_.databaseConfigDisconnect());
550 
551     // Verify that various indicators are set to their initial values.
552     ASSERT_EQ(0, cb_ctl_.getMergesNum());
553     ASSERT_EQ(BackendSelector::Type::MYSQL, cb_ctl_.getBackendSelector().getBackendType());
554     ASSERT_EQ(ServerSelector::Type::UNASSIGNED, cb_ctl_.getServerSelector().getType());
555     ASSERT_EQ(-1, cb_ctl_.getAuditEntriesNum());
556     EXPECT_EQ(cb_ctl_.getInitialAuditRevisionTime(), cb_ctl_.getLastAuditRevisionTime());
557     EXPECT_EQ(0, cb_ctl_.getLastAuditRevisionId());
558 
559     // Connect to the database and fetch the configuration.
560     ASSERT_NO_THROW(cb_ctl_.databaseConfigFetch(config_base));
561 
562     // There should be one invocation of the databaseConfigApply.
563     ASSERT_EQ(1, cb_ctl_.getMergesNum());
564     // Since this is full reconfiguration the audit entry collection
565     // passed to the databaseConfigApply should be empty.
566     EXPECT_EQ(0, cb_ctl_.getAuditEntriesNum());
567     EXPECT_EQ(BackendSelector::Type::UNSPEC, cb_ctl_.getBackendSelector().getBackendType());
568     EXPECT_EQ(ServerSelector::Type::ALL, cb_ctl_.getServerSelector().getType());
569     // Make sure that the internal timestamp is set to the most recent
570     // audit entry, so as the server will only later fetch config
571     // updates after this timestamp.
572     EXPECT_EQ(timestamps_["today"], cb_ctl_.getLastAuditRevisionTime());
573     EXPECT_EQ(4567, cb_ctl_.getLastAuditRevisionId());
574 }
575 
576 // This test verifies that the configuration can be fetched for a
577 // specified server tag.
TEST_F(CBControlBaseTest,fetchFromServer)578 TEST_F(CBControlBaseTest, fetchFromServer) {
579     auto config_base = makeConfigBase("type=db1");
580     // Set a server tag.
581     config_base->setServerTag("a-tag");
582 
583     // Verify that various indicators are set to their initial values.
584     ASSERT_EQ(0, cb_ctl_.getMergesNum());
585     ASSERT_EQ(BackendSelector::Type::MYSQL, cb_ctl_.getBackendSelector().getBackendType());
586     ASSERT_EQ(ServerSelector::Type::UNASSIGNED, cb_ctl_.getServerSelector().getType());
587     ASSERT_EQ(-1, cb_ctl_.getAuditEntriesNum());
588     EXPECT_EQ(cb_ctl_.getInitialAuditRevisionTime(), cb_ctl_.getLastAuditRevisionTime());
589     EXPECT_EQ(0, cb_ctl_.getLastAuditRevisionId());
590 
591     ASSERT_NO_THROW(cb_ctl_.databaseConfigFetch(config_base));
592 
593     ASSERT_EQ(1, cb_ctl_.getMergesNum());
594     EXPECT_EQ(0, cb_ctl_.getAuditEntriesNum());
595     EXPECT_EQ(BackendSelector::Type::UNSPEC, cb_ctl_.getBackendSelector().getBackendType());
596     // An explicit server selector should have been used this time.
597     ASSERT_EQ(ServerSelector::Type::SUBSET, cb_ctl_.getServerSelector().getType());
598     EXPECT_EQ(cb_ctl_.getInitialAuditRevisionTime(), cb_ctl_.getLastAuditRevisionTime());
599 
600     // Make sure that the server selector used in databaseConfigFetch is
601     // correct.
602     auto tags = cb_ctl_.getServerSelector().getTags();
603     ASSERT_EQ(1, tags.size());
604     EXPECT_EQ("a-tag", tags.begin()->get());
605 }
606 
607 // This test verifies that incremental configuration changes can be
608 // fetched.
TEST_F(CBControlBaseTest,fetchUpdates)609 TEST_F(CBControlBaseTest, fetchUpdates) {
610     auto config_base = makeConfigBase("type=db1");
611 
612     // Connect to the database and store an audit entry. Do not close
613     // the database connection to simulate the case when the server
614     // uses existing connection to fetch configuration updates.
615     ASSERT_TRUE(cb_ctl_.databaseConfigConnect(config_base));
616     cb_ctl_.getMgr().getPool()->addAuditEntry(BackendSelector::UNSPEC(),
617                                               ServerSelector::ALL(),
618                                               "sql_table_1",
619                                               3456,
620                                               timestamps_["today"],
621                                               4567);
622 
623     // Verify that various indicators are set to their initial values.
624     ASSERT_EQ(0, cb_ctl_.getMergesNum());
625     ASSERT_EQ(BackendSelector::Type::MYSQL, cb_ctl_.getBackendSelector().getBackendType());
626     ASSERT_EQ(ServerSelector::Type::UNASSIGNED, cb_ctl_.getServerSelector().getType());
627     ASSERT_EQ(-1, cb_ctl_.getAuditEntriesNum());
628     EXPECT_EQ(cb_ctl_.getInitialAuditRevisionTime(), cb_ctl_.getLastAuditRevisionTime());
629     EXPECT_EQ(0, cb_ctl_.getLastAuditRevisionId());
630 
631     ASSERT_NO_THROW(cb_ctl_.databaseConfigFetch(config_base,
632                                                 CBControl::FetchMode::FETCH_UPDATE));
633 
634     // There should be one invocation to databaseConfigApply recorded.
635     ASSERT_EQ(1, cb_ctl_.getMergesNum());
636     // The number of audit entries passed to this function should be 1.
637     EXPECT_EQ(1, cb_ctl_.getAuditEntriesNum());
638     EXPECT_EQ(BackendSelector::Type::UNSPEC, cb_ctl_.getBackendSelector().getBackendType());
639     EXPECT_EQ(ServerSelector::Type::ALL, cb_ctl_.getServerSelector().getType());
640     // The last audit entry time should be set to the latest audit entry.
641     EXPECT_EQ(timestamps_["today"], cb_ctl_.getLastAuditRevisionTime());
642     EXPECT_EQ(4567, cb_ctl_.getLastAuditRevisionId());
643 }
644 
645 // Check that the databaseConfigApply function is not called when there
646 // are no more unprocessed audit entries.
TEST_F(CBControlBaseTest,fetchNoUpdates)647 TEST_F(CBControlBaseTest, fetchNoUpdates) {
648     auto config_base = makeConfigBase("type=db1");
649 
650     // Set last audit entry time to the timestamp of the audit
651     // entry we are going to add. That means that there will be
652     // no new audit entries to fetch.
653     cb_ctl_.setLastAuditRevisionTime(timestamps_["yesterday"]);
654     cb_ctl_.setLastAuditRevisionId(4567);
655 
656     ASSERT_TRUE(cb_ctl_.databaseConfigConnect(config_base));
657 
658     cb_ctl_.getMgr().getPool()->addAuditEntry(BackendSelector::UNSPEC(),
659                                               ServerSelector::ALL(),
660                                               "sql_table_1",
661                                               3456,
662                                               timestamps_["yesterday"],
663                                               4567);
664 
665     ASSERT_EQ(0, cb_ctl_.getMergesNum());
666 
667     ASSERT_NO_THROW(cb_ctl_.databaseConfigFetch(config_base,
668                                                 CBControl::FetchMode::FETCH_UPDATE));
669 
670     // The databaseConfigApply should not be called because there are
671     // no new audit entires to process.
672     ASSERT_EQ(0, cb_ctl_.getMergesNum());
673 }
674 
675 // This test verifies that database config fetch failures are handled
676 // gracefully.
TEST_F(CBControlBaseTest,fetchFailure)677 TEST_F(CBControlBaseTest, fetchFailure) {
678     auto config_base = makeConfigBase("type=db1");
679 
680     // Connect to the database and store an audit entry. Do not close
681     // the database connection to simulate the case when the server
682     // uses existing connection to fetch configuration updates.
683     ASSERT_TRUE(cb_ctl_.databaseConfigConnect(config_base));
684     cb_ctl_.getMgr().getPool()->addAuditEntry(BackendSelector::UNSPEC(),
685                                               ServerSelector::ALL(),
686                                               "sql_table_1",
687                                               3456,
688                                               timestamps_["today"],
689                                               4567);
690 
691     // Configure the CBControl to always throw simulating the failure
692     // during configuration merge.
693     cb_ctl_.enableThrow();
694 
695     // Verify that various indicators are set to their initial values.
696     ASSERT_EQ(0, cb_ctl_.getMergesNum());
697     ASSERT_EQ(BackendSelector::Type::MYSQL, cb_ctl_.getBackendSelector().getBackendType());
698     ASSERT_EQ(ServerSelector::Type::UNASSIGNED, cb_ctl_.getServerSelector().getType());
699     ASSERT_EQ(-1, cb_ctl_.getAuditEntriesNum());
700     EXPECT_EQ(cb_ctl_.getInitialAuditRevisionTime(), cb_ctl_.getLastAuditRevisionTime());
701     EXPECT_EQ(0, cb_ctl_.getLastAuditRevisionId());
702 
703     ASSERT_THROW(cb_ctl_.databaseConfigFetch(config_base, CBControl::FetchMode::FETCH_UPDATE),
704                  isc::Unexpected);
705 
706     // There should be one invocation to databaseConfigApply recorded.
707     ASSERT_EQ(1, cb_ctl_.getMergesNum());
708     // The number of audit entries passed to this function should be 1.
709     EXPECT_EQ(1, cb_ctl_.getAuditEntriesNum());
710     EXPECT_EQ(BackendSelector::Type::UNSPEC, cb_ctl_.getBackendSelector().getBackendType());
711     EXPECT_EQ(ServerSelector::Type::ALL, cb_ctl_.getServerSelector().getType());
712     // The last audit entry time should not be modified because there was a merge
713     // error.
714     EXPECT_EQ(cb_ctl_.getInitialAuditRevisionTime(), cb_ctl_.getLastAuditRevisionTime());
715     EXPECT_EQ(0, cb_ctl_.getLastAuditRevisionId());
716 }
717 
718 }
719