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