1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
// Copyright (C) 2019-2024 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>

#include <config_backend/base_config_backend_mgr.h>
#include <config_backend/base_config_backend_pool.h>
#include <process/cb_ctl_base.h>
#include <boost/date_time/posix_time/posix_time.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <boost/date_time/gregorian/gregorian.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <boost/shared_ptr.hpp><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <gtest/gtest.h><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <map><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <string><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

using namespace isc;
using namespace isc::cb;
using namespace isc::db;
using namespace isc::process;

namespace {

/// @brief Implementation of the config backend for testing the
/// @c CBControlBase template class.
///
/// This simple class allows for adding, retrieving and clearing audit
/// entries. The @c CBControlBase unit tests use it to control the
/// behavior of the @c CBControlBase class under test.
class CBControlBackend : BaseConfigBackend {
public:

    /// @brief Constructor.
    CBControlBackend(const db::DatabaseConnection::ParameterMap&) {
    }

    /// @brief Retrieves the audit entries later than specified time.
    ///
    /// @param modification_time The lower bound time for which audit
    /// entries should be returned.
    /// @param modification_id The lower bound id for which audit
    /// entries should be returned.
    ///
    /// @return Collection of audit entries later than specified time.
    virtual db::AuditEntryCollection
    getRecentAuditEntries(const db::ServerSelector&,
                          const boost::posix_time::ptime& modification_time,
                          const uint64_t modification_id) const {
        db::AuditEntryCollection filtered_entries;

        // Use the index which orders the audit entries by timestamps.
        auto const& index = audit_entries_.get<AuditEntryModificationTimeIdTag>();

        // Locate the first audit entry after the last one having the
        // specified modification time and id.
        auto modification = boost::make_tuple(modification_time, modification_id);
        auto first_entry = index.upper_bound(modification);

        // If there are any entries found return them.
        if (first_entry != index.end()) {
            filtered_entries.insert(first_entry, index.end());
        }

        return (filtered_entries);
    }

    /// @brief Add audit entry to the backend.
    ///
    /// @param object_type Object type to be stored in the audit entry.
    /// @param object_id Object id to be stored in the audit entry.
    /// @param modification_time Audit entry modification time to be set.
    /// @param modification_id Audit entry modification id to be set.
    void 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) {
        // Create new audit entry from the specified parameters.
        AuditEntryPtr audit_entry(new AuditEntry(object_type,
                                                 object_id,
                                                 AuditEntry::ModificationType::CREATE,
                                                 modification_time,
                                                 modification_id,
                                                 "added audit entry"));

        // The audit entries are held in the static variable so as they
        // don't disappear when we diconnect from the backend. The
        // audit entries are explicitly cleared during the unit tests
        // setup.
        audit_entries_.insert(audit_entry);
    }

    /// @brief Returns backend type in the textual format.
    ///
    /// @return Name of the storage for configurations, e.g. "mysql",
    /// "pgsql" and so forth.
    virtual std::string getType() const {
        return ("memfile");
    }

    /// @brief Returns backend host
    ///
    /// This is used by the @c BaseConfigBackendPool to select backend
    /// when @c BackendSelector is specified.
    ///
    /// @return host on which the database is located.
    virtual std::string getHost() const {
        return ("");
    }

    /// @brief Returns backend port number.
    ///
    /// This is used by the @c BaseConfigBackendPool to select backend
    /// when @c BackendSelector is specified.
    ///
    /// @return Port number on which database service is available.
    virtual uint16_t getPort() const {
        return (0);
    }

    /// @brief Removes audit entries.
    static void clearAuditEntries() {
        audit_entries_.clear();
    }

private:

    /// @brief Static collection of audit entries.
    ///
    /// Thanks to storing them in the static member they are preserved
    /// when the unit tests "disconnect" from the backend.
    static AuditEntryCollection audit_entries_;
};

/// @brief Pointer to the @c CBControlBackend object.
typedef boost::shared_ptr<CBControlBackend> CBControlBackendPtr;

AuditEntryCollection CBControlBackend::audit_entries_;

/// @brief Implementation of the backends pool used in the
/// @c CBControlBase template class unit tests.
class CBControlBackendPool : public BaseConfigBackendPool<CBControlBackend> {
public:

    /// @brief Add audit entry to the backend.
    ///
    /// @param backend_selector Backend selector.
    /// @param server_selector Server selector.
    /// @param object_type Object type to be stored in the audit entry.
    /// @param object_id Object id to be stored in the audit entry.
    /// @param modification_time Audit entry modification time to be set.
    /// @param modification_id Audit entry modification id to be set.
    void 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) {
        createUpdateDeleteProperty<void, const std::string&, uint64_t,
                                   const boost::posix_time::ptime&, uint64_t>
            (&CBControlBackend::addAuditEntry, backend_selector, server_selector,
             object_type, object_id, modification_time, modification_id);
    }

    /// @brief Retrieves the audit entries later than specified time.
    ///
    /// @param backend_selector Backend selector.
    /// @param server_selector Server selector.
    /// @param modification_time The lower bound time for which audit
    /// entries should be returned.
    /// @param modification_id The lower bound id for which audit
    /// entries should be returned.
    ///
    /// @return Collection of audit entries later than specified time.
    virtual db::AuditEntryCollection
    getRecentAuditEntries(const BackendSelector& backend_selector,
                          const ServerSelector& server_selector,
                          const boost::posix_time::ptime& modification_time,
                          const uint64_t modification_id) const {
        AuditEntryCollection audit_entries;
        getMultiplePropertiesConst<AuditEntryCollection, const boost::posix_time::ptime&>
            (&CBControlBackend::getRecentAuditEntries, backend_selector,
             server_selector, audit_entries, modification_time,
             modification_id);
        return (audit_entries);
    }
};

/// @brief Implementation of the config backends manager used
/// in the @c CBControlBase template class unit tests.
class CBControlBackendMgr : public BaseConfigBackendMgr<CBControlBackendPool> {
public:

    /// @brief Constructor.
    CBControlBackendMgr()
        : instance_id_(0) {
    }

    /// @brief Returns instance of the @c CBControlBackendMgr.
    ///
    /// @return Reference to the instance of the @c CBControlBackendMgr.
    static CBControlBackendMgr& instance() {
        static CBControlBackendMgr mgr;
        return (mgr);
    }

    /// @brief Returns instance id.
    ///
    /// This value is used in tests which verify that the @c CBControlBase::getMgr
    /// returns the right instance of the CB manager.
    ///
    /// @return Instance id.
    uint32_t getInstanceId() const {
        return (instance_id_);
    }

    /// @brief Sets new instance id.
    ///
    /// @param instance_id New instance id.
    void setInstanceId(const uint32_t instance_id) {
        instance_id_ = instance_id;
    }

    /// @brief Instance id.
    uint32_t instance_id_;
};

/// @brief Implementation of the @c CBControlBase class used in
/// the unit tests.
///
/// It makes some of the protected methods public. It also provides
/// means to test the behavior of the @c CBControlBase template.
class CBControl : public CBControlBase<CBControlBackendMgr> {
public:

    using CBControlBase<CBControlBackendMgr>::fetchConfigElement;
    using CBControlBase<CBControlBackendMgr>::getMgr;
    using CBControlBase<CBControlBackendMgr>::getInitialAuditRevisionTime;

    /// @brief Constructor.
    CBControl()
        : CBControlBase<CBControlBackendMgr>(),
        merges_num_(0),
        backend_selector_(BackendSelector::Type::MYSQL),
        server_selector_(ServerSelector::UNASSIGNED()),
        audit_entries_num_(-1),
        enable_throw_(false) {
    }

    /// @brief Implementation of the method called to fetch and apply
    /// configuration from the database into the local configuration.
    ///
    /// This stub implementation doesn't attempt to merge any configurations
    /// but merely records the values of the parameters called.
    ///
    /// @param backend_selector Backend selector.
    /// @param server_selector Server selector.
    /// @param audit_entries Collection of audit entries.
    virtual void databaseConfigApply(const BackendSelector& backend_selector,
                                     const ServerSelector& server_selector,
                                     const boost::posix_time::ptime&,
                                     const AuditEntryCollection& audit_entries) {
        ++merges_num_;
        backend_selector_ = backend_selector;
        server_selector_ = server_selector;
        audit_entries_num_ = static_cast<int>(audit_entries.size());

        if (enable_throw_) {
            isc_throw(Unexpected, "throwing from databaseConfigApply");
        }
    }

    /// @brief Returns the number of times the @c databaseConfigApply was called.
    size_t getMergesNum() const {
        return (merges_num_);
    }

    /// @brief Returns backend selector used as an argument in a call to
    /// @c databaseConfigApply.
    const BackendSelector& getBackendSelector() const {
        return (backend_selector_);
    }

    /// @brief Returns server selector used as an argument in a call to
    /// @c databaseConfigApply.
    const ServerSelector& getServerSelector() const {
        return (server_selector_);
    }

    /// @brief Returns the number of audit entries in the collection passed
    /// to @c databaseConfigApply
    int getAuditEntriesNum() const {
        return (audit_entries_num_);
    }

    /// @brief Returns the recorded time of last audit entry.
    boost::posix_time::ptime getLastAuditRevisionTime() const {
        return (last_audit_revision_time_);
    }

    /// @brief Returns the recorded id of last audit entry.
    uint64_t getLastAuditRevisionId() const {
        return (last_audit_revision_id_);
    }

    /// @brief Overwrites the last audit entry time.
    ///
    /// @param last_audit_revision_time New time to be set.
    void setLastAuditRevisionTime(const boost::posix_time::ptime& last_audit_revision_time) {
        last_audit_revision_time_ = last_audit_revision_time;
    }

    /// @brief Overwrites the last audit revision id.
    ///
    /// @param last_audit_revision_id New id to be set.
    void setLastAuditRevisionId(const uint64_t& last_audit_revision_id) {
        last_audit_revision_id_ = last_audit_revision_id;
    }

    /// @brief Enables the @c databaseConfigApply function to throw.
    ///
    /// This is useful to test scenarios when configuration merge fails.
    void enableThrow() {
        enable_throw_ = true;
    }

private:

    /// @brief Recorded number of calls to @c databaseConfigApply.
    size_t merges_num_;

    /// @brief Recorded backend selector value.
    BackendSelector backend_selector_;

    /// @brief Recorded server selector value.
    ServerSelector server_selector_;

    /// @brief Recorded number of audit entries.
    int audit_entries_num_;

    /// @brief Boolean value indicating if the @c databaseConfigApply should throw.
    bool enable_throw_;
};

/// @brief Out of the blue instance id used in tests.
constexpr uint32_t TEST_INSTANCE_ID = 123;

/// @brief Test fixture class for @c CBControlBase template class.
class CBControlBaseTest : public ::testing::Test {
public:

    /// @brief Constructor.
    CBControlBaseTest()
        : cb_ctl_(), mgr_(CBControlBackendMgr::instance()),
          timestamps_() {
        mgr_.registerBackendFactory("db1",
            [](const DatabaseConnection::ParameterMap& params)
                -> CBControlBackendPtr {
            return (CBControlBackendPtr(new CBControlBackend(params)));
        });
        mgr_.setInstanceId(TEST_INSTANCE_ID);
        initTimestamps();
        CBControlBackend::clearAuditEntries();
    }

    /// @brief Destructor.
    ///
    /// Removes audit entries created in the test.
    ~CBControlBaseTest() {
        CBControlBackend::clearAuditEntries();
    }

    /// @brief Initialize posix time values used in tests.
    void initTimestamps() {
        // Current time minus 1 hour to make sure it is in the past.
        timestamps_["today"] = boost::posix_time::second_clock::local_time()
            - boost::posix_time::hours(1);
        // Yesterday.
        timestamps_["yesterday"] = timestamps_["today"] - boost::posix_time::hours(24);
        // Two days ago.
        timestamps_["two days ago"] = timestamps_["today"] - boost::posix_time::hours(48);
        // Tomorrow.
        timestamps_["tomorrow"] = timestamps_["today"] + boost::posix_time::hours(24);
    }

    /// @brief Creates an instance of the configuration object.
    ///
    /// @param db1_access Database access string to be used to connect to
    /// the test configuration backend. It doesn't connect if the string
    /// is empty.
    ConfigPtr makeConfigBase(const std::string& db1_access = "") const {
        ConfigControlInfoPtr config_ctl_info(new ConfigControlInfo());

        if (!db1_access.empty()) {
            config_ctl_info->addConfigDatabase(db1_access);
        }

        ConfigPtr config_base(new ConfigBase());

        config_base->setConfigControlInfo(config_ctl_info);
        return (config_base);
    }

    /// @brief Instance of the @c CBControl used in tests.
    CBControl cb_ctl_;

    /// @brief Instance of the Config Backend Manager.
    CBControlBackendMgr& mgr_;

    /// @brief Holds timestamp values used in tests.
    std::map<std::string, boost::posix_time::ptime> timestamps_;
};

// This test verifies that the same instance of the Config
// Backend Manager is returned all the time.
TEST_F(CBControlBaseTest, getMgr) {<--- syntax error
    auto mgr = cb_ctl_.getMgr();
    EXPECT_EQ(TEST_INSTANCE_ID, mgr.getInstanceId());
}

// This test verifies that the initial audit revision time is set to
// local time of 2000-01-01.
TEST_F(CBControlBaseTest, getInitialAuditRevisionTime) {
    auto initial_time = cb_ctl_.getInitialAuditRevisionTime();
    ASSERT_FALSE(initial_time.is_not_a_date_time());
    auto tm = boost::posix_time::to_tm(initial_time);
    EXPECT_EQ(100, tm.tm_year);
    EXPECT_EQ(0, tm.tm_mon);
    EXPECT_EQ(0, tm.tm_yday);
    EXPECT_EQ(0, tm.tm_hour);
    EXPECT_EQ(0, tm.tm_min);
    EXPECT_EQ(0, tm.tm_sec);
}

// This test verifies that last audit entry time is reset upon the
// call to CBControlBase::reset().
TEST_F(CBControlBaseTest, reset) {
    cb_ctl_.setLastAuditRevisionTime(timestamps_["tomorrow"]);
    cb_ctl_.reset();
    EXPECT_EQ(cb_ctl_.getInitialAuditRevisionTime(), cb_ctl_.getLastAuditRevisionTime());
    EXPECT_EQ(0, cb_ctl_.getLastAuditRevisionId());
}

// This test verifies that it is correctly determined what entries the
// server should fetch for the particular configuration element.
TEST_F(CBControlBaseTest, fetchConfigElement) {
    db::AuditEntryCollection audit_entries;
    db::AuditEntryCollection updated;
    // When audit entries collection is empty any subset is empty too.
    updated = cb_ctl_.fetchConfigElement(audit_entries, "my_object_type");
    EXPECT_TRUE(updated.empty());

    // Now test the case that there is a DELETE audit entry. In this case
    // our function should indicate that the configuration should not be
    // fetched for the given object type. Note that when the configuration
    // element is deleted, it no longer exists in database so there is
    // no reason to fetch the data from the database.
    AuditEntryPtr audit_entry(new AuditEntry("dhcp4_subnet", 1234 ,
                                             AuditEntry::ModificationType::DELETE,
                                             2345, "added audit entry"));
    audit_entries.insert(audit_entry);
    updated = cb_ctl_.fetchConfigElement(audit_entries, "my_object_type");
    EXPECT_TRUE(updated.empty());
    EXPECT_TRUE(hasObjectId(audit_entries, 1234));
    EXPECT_FALSE(hasObjectId(audit_entries, 5678));
    EXPECT_FALSE(hasObjectId(updated, 1234));

    // Add another audit entry which indicates creation of the configuration element.
    // This time we should get it.
    audit_entry.reset(new AuditEntry("my_object_type", 5678,
                                     AuditEntry::ModificationType::CREATE,
                                     6789, "added audit entry"));
    audit_entries.insert(audit_entry);
    updated = cb_ctl_.fetchConfigElement(audit_entries, "my_object_type");
    ASSERT_EQ(1, updated.size());
    AuditEntryPtr updated_entry = (*updated.begin());
    ASSERT_TRUE(updated_entry);
    EXPECT_EQ("my_object_type", updated_entry->getObjectType());
    EXPECT_EQ(5678, updated_entry->getObjectId());
    EXPECT_EQ(AuditEntry::ModificationType::CREATE, updated_entry->getModificationType());
    EXPECT_TRUE(hasObjectId(audit_entries, 5678));
    EXPECT_TRUE(hasObjectId(updated, 5678));
    EXPECT_FALSE(hasObjectId(updated, 1234));

    // Also we should get 'true' for the UPDATE case.
    audit_entry.reset(new AuditEntry("my_object_type",
                                     5678, AuditEntry::ModificationType::UPDATE,
                                     6790, "added audit entry"));
    audit_entries.insert(audit_entry);
    updated = cb_ctl_.fetchConfigElement(audit_entries, "my_object_type");
    EXPECT_EQ(2, updated.size());
    bool saw_create = false;
    bool saw_update =  false;
    for (auto const& entry : updated) {
        EXPECT_EQ("my_object_type", entry->getObjectType());
        EXPECT_EQ(5678, entry->getObjectId());
        if (AuditEntry::ModificationType::CREATE == entry->getModificationType()) {
            EXPECT_FALSE(saw_create);
            saw_create = true;
        } else if (AuditEntry::ModificationType::UPDATE == entry->getModificationType()) {
            EXPECT_FALSE(saw_update);
            saw_update = true;
        }
    }
    EXPECT_TRUE(saw_create);
    EXPECT_TRUE(saw_update);
    EXPECT_TRUE(hasObjectId(updated, 5678));
    EXPECT_FALSE(hasObjectId(updated, 1234));
}

// This test verifies that true is return when the server successfully
// connects to the backend and false if there are no backends to connect
// to.
TEST_F(CBControlBaseTest, connect) {
    EXPECT_TRUE(cb_ctl_.databaseConfigConnect(makeConfigBase("type=db1")));
    EXPECT_FALSE(cb_ctl_.databaseConfigConnect(makeConfigBase()));
}

// This test verifies the scenario when the server fetches the entire
// configuration from the database upon startup.
TEST_F(CBControlBaseTest, fetchAll) {
    auto config_base = makeConfigBase("type=db1");

    // Add two audit entries to the database. The server should load
    // the entire configuration from the database regardless of the
    // existing audit entries. However, the last audit entry timestamp
    // should be set to the most recent audit entry in the
    // @c CBControlBase.
    ASSERT_TRUE(cb_ctl_.databaseConfigConnect(config_base));

    cb_ctl_.getMgr().getPool()->addAuditEntry(BackendSelector::UNSPEC(),
                                              ServerSelector::ALL(),
                                              "sql_table_2",
                                              1234,
                                              timestamps_["yesterday"],
                                              2345);

    cb_ctl_.getMgr().getPool()->addAuditEntry(BackendSelector::UNSPEC(),
                                              ServerSelector::ALL(),
                                              "sql_table_1",
                                              3456,
                                              timestamps_["today"],
                                              4567);

    // Disconnect from the database in order to check that the
    // databaseConfigFetch reconnects.
    ASSERT_NO_THROW(cb_ctl_.databaseConfigDisconnect());

    // Verify that various indicators are set to their initial values.
    ASSERT_EQ(0, cb_ctl_.getMergesNum());
    ASSERT_EQ(BackendSelector::Type::MYSQL, cb_ctl_.getBackendSelector().getBackendType());
    ASSERT_EQ(ServerSelector::Type::UNASSIGNED, cb_ctl_.getServerSelector().getType());
    ASSERT_EQ(-1, cb_ctl_.getAuditEntriesNum());
    EXPECT_EQ(cb_ctl_.getInitialAuditRevisionTime(), cb_ctl_.getLastAuditRevisionTime());
    EXPECT_EQ(0, cb_ctl_.getLastAuditRevisionId());

    // Connect to the database and fetch the configuration.
    ASSERT_NO_THROW(cb_ctl_.databaseConfigFetch(config_base));

    // There should be one invocation of the databaseConfigApply.
    ASSERT_EQ(1, cb_ctl_.getMergesNum());
    // Since this is full reconfiguration the audit entry collection
    // passed to the databaseConfigApply should be empty.
    EXPECT_EQ(0, cb_ctl_.getAuditEntriesNum());
    EXPECT_EQ(BackendSelector::Type::UNSPEC, cb_ctl_.getBackendSelector().getBackendType());
    EXPECT_EQ(ServerSelector::Type::ALL, cb_ctl_.getServerSelector().getType());
    // Make sure that the internal timestamp is set to the most recent
    // audit entry, so as the server will only later fetch config
    // updates after this timestamp.
    EXPECT_EQ(timestamps_["today"], cb_ctl_.getLastAuditRevisionTime());
    EXPECT_EQ(4567, cb_ctl_.getLastAuditRevisionId());
}

// This test verifies that the configuration can be fetched for a
// specified server tag.
TEST_F(CBControlBaseTest, fetchFromServer) {
    auto config_base = makeConfigBase("type=db1");
    // Set a server tag.
    config_base->setServerTag("a-tag");

    // Verify that various indicators are set to their initial values.
    ASSERT_EQ(0, cb_ctl_.getMergesNum());
    ASSERT_EQ(BackendSelector::Type::MYSQL, cb_ctl_.getBackendSelector().getBackendType());
    ASSERT_EQ(ServerSelector::Type::UNASSIGNED, cb_ctl_.getServerSelector().getType());
    ASSERT_EQ(-1, cb_ctl_.getAuditEntriesNum());
    EXPECT_EQ(cb_ctl_.getInitialAuditRevisionTime(), cb_ctl_.getLastAuditRevisionTime());
    EXPECT_EQ(0, cb_ctl_.getLastAuditRevisionId());

    ASSERT_NO_THROW(cb_ctl_.databaseConfigFetch(config_base));

    ASSERT_EQ(1, cb_ctl_.getMergesNum());
    EXPECT_EQ(0, cb_ctl_.getAuditEntriesNum());
    EXPECT_EQ(BackendSelector::Type::UNSPEC, cb_ctl_.getBackendSelector().getBackendType());
    // An explicit server selector should have been used this time.
    ASSERT_EQ(ServerSelector::Type::SUBSET, cb_ctl_.getServerSelector().getType());
    EXPECT_EQ(cb_ctl_.getInitialAuditRevisionTime(), cb_ctl_.getLastAuditRevisionTime());

    // Make sure that the server selector used in databaseConfigFetch is
    // correct.
    auto tags = cb_ctl_.getServerSelector().getTags();
    ASSERT_EQ(1, tags.size());
    EXPECT_EQ("a-tag", tags.begin()->get());
}

// This test verifies that incremental configuration changes can be
// fetched.
TEST_F(CBControlBaseTest, fetchUpdates) {
    auto config_base = makeConfigBase("type=db1");

    // Connect to the database and store an audit entry. Do not close
    // the database connection to simulate the case when the server
    // uses existing connection to fetch configuration updates.
    ASSERT_TRUE(cb_ctl_.databaseConfigConnect(config_base));
    cb_ctl_.getMgr().getPool()->addAuditEntry(BackendSelector::UNSPEC(),
                                              ServerSelector::ALL(),
                                              "sql_table_1",
                                              3456,
                                              timestamps_["today"],
                                              4567);

    // Verify that various indicators are set to their initial values.
    ASSERT_EQ(0, cb_ctl_.getMergesNum());
    ASSERT_EQ(BackendSelector::Type::MYSQL, cb_ctl_.getBackendSelector().getBackendType());
    ASSERT_EQ(ServerSelector::Type::UNASSIGNED, cb_ctl_.getServerSelector().getType());
    ASSERT_EQ(-1, cb_ctl_.getAuditEntriesNum());
    EXPECT_EQ(cb_ctl_.getInitialAuditRevisionTime(), cb_ctl_.getLastAuditRevisionTime());
    EXPECT_EQ(0, cb_ctl_.getLastAuditRevisionId());

    ASSERT_NO_THROW(cb_ctl_.databaseConfigFetch(config_base,
                                                CBControl::FetchMode::FETCH_UPDATE));

    // There should be one invocation to databaseConfigApply recorded.
    ASSERT_EQ(1, cb_ctl_.getMergesNum());
    // The number of audit entries passed to this function should be 1.
    EXPECT_EQ(1, cb_ctl_.getAuditEntriesNum());
    EXPECT_EQ(BackendSelector::Type::UNSPEC, cb_ctl_.getBackendSelector().getBackendType());
    EXPECT_EQ(ServerSelector::Type::ALL, cb_ctl_.getServerSelector().getType());
    // The last audit entry time should be set to the latest audit entry.
    EXPECT_EQ(timestamps_["today"], cb_ctl_.getLastAuditRevisionTime());
    EXPECT_EQ(4567, cb_ctl_.getLastAuditRevisionId());
}

// Check that the databaseConfigApply function is not called when there
// are no more unprocessed audit entries.
TEST_F(CBControlBaseTest, fetchNoUpdates) {
    auto config_base = makeConfigBase("type=db1");

    // Set last audit entry time to the timestamp of the audit
    // entry we are going to add. That means that there will be
    // no new audit entries to fetch.
    cb_ctl_.setLastAuditRevisionTime(timestamps_["yesterday"]);
    cb_ctl_.setLastAuditRevisionId(4567);

    ASSERT_TRUE(cb_ctl_.databaseConfigConnect(config_base));

    cb_ctl_.getMgr().getPool()->addAuditEntry(BackendSelector::UNSPEC(),
                                              ServerSelector::ALL(),
                                              "sql_table_1",
                                              3456,
                                              timestamps_["yesterday"],
                                              4567);

    ASSERT_EQ(0, cb_ctl_.getMergesNum());

    ASSERT_NO_THROW(cb_ctl_.databaseConfigFetch(config_base,
                                                CBControl::FetchMode::FETCH_UPDATE));

    // The databaseConfigApply should not be called because there are
    // no new audit entires to process.
    ASSERT_EQ(0, cb_ctl_.getMergesNum());
}

// This test verifies that database config fetch failures are handled
// gracefully.
TEST_F(CBControlBaseTest, fetchFailure) {
    auto config_base = makeConfigBase("type=db1");

    // Connect to the database and store an audit entry. Do not close
    // the database connection to simulate the case when the server
    // uses existing connection to fetch configuration updates.
    ASSERT_TRUE(cb_ctl_.databaseConfigConnect(config_base));
    cb_ctl_.getMgr().getPool()->addAuditEntry(BackendSelector::UNSPEC(),
                                              ServerSelector::ALL(),
                                              "sql_table_1",
                                              3456,
                                              timestamps_["today"],
                                              4567);

    // Configure the CBControl to always throw simulating the failure
    // during configuration merge.
    cb_ctl_.enableThrow();

    // Verify that various indicators are set to their initial values.
    ASSERT_EQ(0, cb_ctl_.getMergesNum());
    ASSERT_EQ(BackendSelector::Type::MYSQL, cb_ctl_.getBackendSelector().getBackendType());
    ASSERT_EQ(ServerSelector::Type::UNASSIGNED, cb_ctl_.getServerSelector().getType());
    ASSERT_EQ(-1, cb_ctl_.getAuditEntriesNum());
    EXPECT_EQ(cb_ctl_.getInitialAuditRevisionTime(), cb_ctl_.getLastAuditRevisionTime());
    EXPECT_EQ(0, cb_ctl_.getLastAuditRevisionId());

    ASSERT_THROW(cb_ctl_.databaseConfigFetch(config_base, CBControl::FetchMode::FETCH_UPDATE),
                 isc::Unexpected);

    // There should be one invocation to databaseConfigApply recorded.
    ASSERT_EQ(1, cb_ctl_.getMergesNum());
    // The number of audit entries passed to this function should be 1.
    EXPECT_EQ(1, cb_ctl_.getAuditEntriesNum());
    EXPECT_EQ(BackendSelector::Type::UNSPEC, cb_ctl_.getBackendSelector().getBackendType());
    EXPECT_EQ(ServerSelector::Type::ALL, cb_ctl_.getServerSelector().getType());
    // The last audit entry time should not be modified because there was a merge
    // error.
    EXPECT_EQ(cb_ctl_.getInitialAuditRevisionTime(), cb_ctl_.getLastAuditRevisionTime());
    EXPECT_EQ(0, cb_ctl_.getLastAuditRevisionId());
}

}