database_unittest.cc 145 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

15
16
#include <stdlib.h>

17
#include <boost/shared_ptr.hpp>
18
#include <boost/lexical_cast.hpp>
19

20
21
#include <gtest/gtest.h>

22
23
#include <exceptions/exceptions.h>

24
#include <dns/name.h>
25
#include <dns/rrttl.h>
26
#include <dns/rrset.h>
27
#include <exceptions/exceptions.h>
28
29

#include <datasrc/database.h>
30
31
#include <datasrc/zone.h>
#include <datasrc/data_source.h>
32
#include <datasrc/iterator.h>
33
#include <datasrc/sqlite3_accessor.h>
34

35
36
#include <testutils/dnsmessage_test.h>

37
#include <map>
38
#include <vector>
39
40
41

using namespace isc::datasrc;
using namespace std;
42
43
44
45
// don't import the entire boost namespace.  It will unexpectedly hide uint32_t
// for some systems.
using boost::shared_ptr;
using boost::dynamic_pointer_cast;
46
using boost::lexical_cast;
47
using namespace isc::dns;
48
49
50

namespace {

51
52
53
54
// Imaginary zone IDs used in the mock accessor below.
const int READONLY_ZONE_ID = 42;
const int WRITABLE_ZONE_ID = 4200;

55
56
57
58
59
60
// Commonly used test data
const char* const TEST_RECORDS[][5] = {
    // some plain data
    {"www.example.org.", "A", "3600", "", "192.0.2.1"},
    {"www.example.org.", "AAAA", "3600", "", "2001:db8::1"},
    {"www.example.org.", "AAAA", "3600", "", "2001:db8::2"},
61
62
    {"www.example.org.", "NSEC", "3600", "", "www2.example.org. A AAAA NSEC RRSIG"},
    {"www.example.org.", "RRSIG", "3600", "", "NSEC 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
63
64

    {"www2.example.org.", "A", "3600", "", "192.0.2.1"},
65
    {"www2.example.org.", "AAAA", "3600", "", "2001:db8::1"},
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
    {"www2.example.org.", "A", "3600", "", "192.0.2.2"},

    {"cname.example.org.", "CNAME", "3600", "", "www.example.org."},

    // some DNSSEC-'signed' data
    {"signed1.example.org.", "A", "3600", "", "192.0.2.1"},
    {"signed1.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},

    {"signed1.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE"},
    {"signed1.example.org.", "AAAA", "3600", "", "2001:db8::1"},
    {"signed1.example.org.", "AAAA", "3600", "", "2001:db8::2"},
    {"signed1.example.org.", "RRSIG", "3600", "", "AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},

    {"signedcname1.example.org.", "CNAME", "3600", "", "www.example.org."},
    {"signedcname1.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},

    // special case might fail; sig is for cname, which isn't there (should be ignored)
    // (ignoring of 'normal' other type is done above by www.)
    {"acnamesig1.example.org.", "A", "3600", "", "192.0.2.1"},
    {"acnamesig1.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
    {"acnamesig1.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},

    // let's pretend we have a database that is not careful
    // about the order in which it returns data
    {"signed2.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
    {"signed2.example.org.", "AAAA", "3600", "", "2001:db8::2"},
    {"signed2.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12346 example.org. FAKEFAKEFAKE"},
    {"signed2.example.org.", "A", "3600", "", "192.0.2.1"},
    {"signed2.example.org.", "RRSIG", "3600", "", "AAAA 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
    {"signed2.example.org.", "AAAA", "3600", "", "2001:db8::1"},

    {"signedcname2.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
    {"signedcname2.example.org.", "CNAME", "3600", "", "www.example.org."},

    {"acnamesig2.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
    {"acnamesig2.example.org.", "A", "3600", "", "192.0.2.1"},
    {"acnamesig2.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},

    {"acnamesig3.example.org.", "RRSIG", "3600", "", "CNAME 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
    {"acnamesig3.example.org.", "RRSIG", "3600", "", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
    {"acnamesig3.example.org.", "A", "3600", "", "192.0.2.1"},

    {"ttldiff1.example.org.", "A", "3600", "", "192.0.2.1"},
    {"ttldiff1.example.org.", "A", "360", "", "192.0.2.2"},

    {"ttldiff2.example.org.", "A", "360", "", "192.0.2.1"},
    {"ttldiff2.example.org.", "A", "3600", "", "192.0.2.2"},

    // also add some intentionally bad data
    {"badcname1.example.org.", "A", "3600", "", "192.0.2.1"},
    {"badcname1.example.org.", "CNAME", "3600", "", "www.example.org."},

    {"badcname2.example.org.", "CNAME", "3600", "", "www.example.org."},
    {"badcname2.example.org.", "A", "3600", "", "192.0.2.1"},

    {"badcname3.example.org.", "CNAME", "3600", "", "www.example.org."},
    {"badcname3.example.org.", "CNAME", "3600", "", "www.example2.org."},

    {"badrdata.example.org.", "A", "3600", "", "bad"},

    {"badtype.example.org.", "BAD_TYPE", "3600", "", "192.0.2.1"},

    {"badttl.example.org.", "A", "badttl", "", "192.0.2.1"},

    {"badsig.example.org.", "A", "badttl", "", "192.0.2.1"},
    {"badsig.example.org.", "RRSIG", "3600", "", "A 5 3 3600 somebaddata 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},

    {"badsigtype.example.org.", "A", "3600", "", "192.0.2.1"},
    {"badsigtype.example.org.", "RRSIG", "3600", "TXT", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},

    // Data for testing delegation (with NS and DNAME)
    {"delegation.example.org.", "NS", "3600", "", "ns.example.com."},
    {"delegation.example.org.", "NS", "3600", "",
     "ns.delegation.example.org."},
140
    {"delegation.example.org.", "DS", "3600", "", "1 RSAMD5 2 abcd"},
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
    {"delegation.example.org.", "RRSIG", "3600", "", "NS 5 3 3600 "
     "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
    {"ns.delegation.example.org.", "A", "3600", "", "192.0.2.1"},
    {"deep.below.delegation.example.org.", "A", "3600", "", "192.0.2.1"},

    {"dname.example.org.", "A", "3600", "", "192.0.2.1"},
    {"dname.example.org.", "DNAME", "3600", "", "dname.example.com."},
    {"dname.example.org.", "RRSIG", "3600", "",
     "DNAME 5 3 3600 20000101000000 20000201000000 12345 "
     "example.org. FAKEFAKEFAKE"},

    {"below.dname.example.org.", "A", "3600", "", "192.0.2.1"},

    // Broken NS
    {"brokenns1.example.org.", "A", "3600", "", "192.0.2.1"},
    {"brokenns1.example.org.", "NS", "3600", "", "ns.example.com."},

    {"brokenns2.example.org.", "NS", "3600", "", "ns.example.com."},
    {"brokenns2.example.org.", "A", "3600", "", "192.0.2.1"},

    // Now double DNAME, to test failure mode
    {"baddname.example.org.", "DNAME", "3600", "", "dname1.example.com."},
    {"baddname.example.org.", "DNAME", "3600", "", "dname2.example.com."},

    // Put some data into apex (including NS) so we can check our NS
    // doesn't break anything
167
168
    {"example.org.", "SOA", "3600", "", "ns1.example.org. admin.example.org. "
     "1234 3600 1800 2419200 7200" },
169
170
    {"example.org.", "NS", "3600", "", "ns.example.com."},
    {"example.org.", "A", "3600", "", "192.0.2.1"},
171
    {"example.org.", "NSEC", "3600", "", "acnamesig1.example.org. NS A NSEC RRSIG"},
172
173
    {"example.org.", "RRSIG", "3600", "", "SOA 5 3 3600 20000101000000 "
              "20000201000000 12345 example.org. FAKEFAKEFAKE"},
174
175
    {"example.org.", "RRSIG", "3600", "", "NSEC 5 3 3600 20000101000000 "
              "20000201000000 12345 example.org. FAKEFAKEFAKE"},
176
177
178
179
180
181
182
183
    {"example.org.", "RRSIG", "3600", "", "NS 5 3 3600 20000101000000 "
              "20000201000000 12345 example.org. FAKEFAKEFAKE"},

    // This is because of empty domain test
    {"a.b.example.org.", "A", "3600", "", "192.0.2.1"},

    // Something for wildcards
    {"*.wild.example.org.", "A", "3600", "", "192.0.2.5"},
184
    {"*.wild.example.org.", "RRSIG", "3600", "A", "A 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
185
186
    {"*.wild.example.org.", "NSEC", "3600", "", "cancel.here.wild.example.org. A NSEC RRSIG"},
    {"*.wild.example.org.", "RRSIG", "3600", "", "NSEC 5 3 3600 20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE"},
187
188
189
190
191
    {"cancel.here.wild.example.org.", "AAAA", "3600", "", "2001:db8::5"},
    {"delegatedwild.example.org.", "NS", "3600", "", "ns.example.com."},
    {"*.delegatedwild.example.org.", "A", "3600", "", "192.0.2.5"},
    {"wild.*.foo.example.org.", "A", "3600", "", "192.0.2.5"},
    {"wild.*.foo.*.bar.example.org.", "A", "3600", "", "192.0.2.5"},
192
193
    {"wild.*.foo.*.bar.example.org.", "NSEC", "3600", "",
     "brokenns1.example.org. A NSEC"},
194
    {"bao.example.org.", "NSEC", "3600", "", "wild.*.foo.*.bar.example.org. NSEC"},
195
    {"*.cnamewild.example.org.", "CNAME", "3600", "", "www.example.org."},
196
    {"*.dnamewild.example.org.", "DNAME", "3600", "", "dname.example.com."},
197
    {"*.nswild.example.org.", "NS", "3600", "", "ns.example.com."},
198
199
200
    // For NSEC empty non-terminal
    {"l.example.org.", "NSEC", "3600", "", "empty.nonterminal.example.org. NSEC"},
    {"empty.nonterminal.example.org.", "A", "3600", "", "192.0.2.1"},
201
202
203
204
    // Invalid rdata
    {"invalidrdata.example.org.", "A", "3600", "", "Bunch of nonsense"},
    {"invalidrdata2.example.org.", "A", "3600", "", "192.0.2.1"},
    {"invalidrdata2.example.org.", "RRSIG", "3600", "", "Nonsense"},
205
206
207
208

    {NULL, NULL, NULL, NULL, NULL},
};

209
/*
210
 * An accessor with minimum implementation, keeping the original
211
 * "NotImplemented" methods.
212
 */
213
class NopAccessor : public DatabaseAccessor {
214
public:
215
216
    NopAccessor() : database_name_("mock_database")
    { }
217

218
219
    virtual std::pair<bool, int> getZone(const std::string& name) const {
        if (name == "example.org.") {
220
            return (std::pair<bool, int>(true, READONLY_ZONE_ID));
221
        } else if (name == "null.example.org.") {
222
            return (std::pair<bool, int>(true, 13));
223
        } else if (name == "empty.example.org.") {
224
            return (std::pair<bool, int>(true, 0));
225
        } else if (name == "bad.example.org.") {
226
            return (std::pair<bool, int>(true, -1));
227
228
229
230
        } else {
            return (std::pair<bool, int>(false, 0));
        }
    }
231

232
    virtual shared_ptr<DatabaseAccessor> clone() {
233
234
        // This accessor is stateless, so we can simply return a new instance.
        return (shared_ptr<DatabaseAccessor>(new NopAccessor));
235
    }
236

JINMEI Tatuya's avatar
JINMEI Tatuya committed
237
238
239
240
    virtual std::pair<bool, int> startUpdateZone(const std::string&, bool) {
        // return dummy value.  unused anyway.
        return (pair<bool, int>(true, 0));
    }
241
    virtual void startTransaction() {}
242
243
    virtual void commit() {}
    virtual void rollback() {}
244
245
    virtual void addRecordToZone(const string (&)[ADD_COLUMN_COUNT]) {}
    virtual void deleteRecordInZone(const string (&)[DEL_PARAM_COUNT]) {}
246
247
    virtual void addRecordDiff(int, uint32_t, DiffOperation,
                               const std::string (&)[DIFF_PARAM_COUNT]) {}
JINMEI Tatuya's avatar
JINMEI Tatuya committed
248

249
250
251
252
    virtual const std::string& getDBName() const {
        return (database_name_);
    }

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
253
254
255
    virtual IteratorContextPtr getRecords(const std::string&, int, bool)
        const
        {
256
257
        isc_throw(isc::NotImplemented,
                  "This database datasource can't be iterated");
258
    }
259
260
261
262

    virtual IteratorContextPtr getAllRecords(int) const {
        isc_throw(isc::NotImplemented,
                  "This database datasource can't be iterated");
263
264
    }

265
    virtual IteratorContextPtr getDiffs(int, uint32_t, uint32_t) const {
266
        isc_throw(isc::NotImplemented,
267
                  "This database datasource doesn't support diffs");
268
269
    }

270
271
272
273
    virtual std::string findPreviousName(int, const std::string&) const {
        isc_throw(isc::NotImplemented,
                  "This data source doesn't support DNSSEC");
    }
274
275
276
private:
    const std::string database_name_;

277
278
};

279
280
281
282
283
284
285
286
287
288
289
/**
 * Single journal entry in the mock database.
 *
 * All the members there are public for simplicity, as it only stores data.
 * We use the implicit constructor and operator. The members can't be const
 * because of the assignment operator (used in the vectors).
 */
struct JournalEntry {
    JournalEntry(int id, uint32_t serial,
                 DatabaseAccessor::DiffOperation operation,
                 const std::string (&data)[DatabaseAccessor::DIFF_PARAM_COUNT])
290
291
292
293
294
295
296
297
        : id_(id), serial_(serial), operation_(operation)
    {
        data_[DatabaseAccessor::DIFF_NAME] = data[DatabaseAccessor::DIFF_NAME];
        data_[DatabaseAccessor::DIFF_TYPE] = data[DatabaseAccessor::DIFF_TYPE];
        data_[DatabaseAccessor::DIFF_TTL] = data[DatabaseAccessor::DIFF_TTL];
        data_[DatabaseAccessor::DIFF_RDATA] =
            data[DatabaseAccessor::DIFF_RDATA];
    }
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
    JournalEntry(int id, uint32_t serial,
                 DatabaseAccessor::DiffOperation operation,
                 const std::string& name, const std::string& type,
                 const std::string& ttl, const std::string& rdata):
        id_(id), serial_(serial), operation_(operation)
    {
        data_[DatabaseAccessor::DIFF_NAME] = name;
        data_[DatabaseAccessor::DIFF_TYPE] = type;
        data_[DatabaseAccessor::DIFF_TTL] = ttl;
        data_[DatabaseAccessor::DIFF_RDATA] = rdata;
    }
    int id_;
    uint32_t serial_;
    DatabaseAccessor::DiffOperation operation_;
    std::string data_[DatabaseAccessor::DIFF_PARAM_COUNT];
    bool operator==(const JournalEntry& other) const {
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
314
315
316
317
        bool data_equal(true);
        for (size_t i(0); i < DatabaseAccessor::DIFF_PARAM_COUNT; ++ i) {
            data_equal = data_equal && (data_[i] == other.data_[i]);
        }
318
        return (id_ == other.id_ && serial_ == other.serial_ &&
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
319
                operation_ == other.operation_ && data_equal);
320
321
322
    }
};

323
/*
324
 * A virtual database accessor that pretends it contains single zone --
325
326
327
328
329
 * example.org.
 *
 * It has the same getZone method as NopConnection, but it provides
 * implementation of the optional functionality.
 */
330
class MockAccessor : public NopAccessor {
331
332
333
    // Type of mock database "row"s.  This is a map whose keys are the
    // own names.  We internally sort them by the name comparison order.
    struct NameCompare : public binary_function<string, string, bool> {
334
        bool operator()(const string& n1, const string& n2) const {
335
336
337
338
339
340
            return (Name(n1).compare(Name(n2)).getOrder() < 0);
        }
    };
    typedef std::map<std::string,
                     std::vector< std::vector<std::string> >,
                     NameCompare > Domains;
341

342
public:
343
    MockAccessor() : rollbacked_(false), did_transaction_(false) {
344
345
346
        readonly_records_ = &readonly_records_master_;
        update_records_ = &update_records_master_;
        empty_records_ = &empty_records_master_;
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
347
        journal_entries_ = &journal_entries_master_;
348
349
        fillData();
    }
350
351
352
353
354
355

    virtual shared_ptr<DatabaseAccessor> clone() {
        shared_ptr<MockAccessor> cloned_accessor(new MockAccessor());
        cloned_accessor->readonly_records_ = &readonly_records_master_;
        cloned_accessor->update_records_ = &update_records_master_;
        cloned_accessor->empty_records_ = &empty_records_master_;
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
356
        cloned_accessor->journal_entries_ = &journal_entries_master_;
357
358
359
360
        latest_clone_ = cloned_accessor;
        return (cloned_accessor);
    }

361
362
363
364
    virtual void startTransaction() {
        // Currently we only use this transaction for simple read-only
        // operations.  So we just make a local copy of the data (we don't
        // care about what happens after commit() or rollback()).
365
366
367
368
369
370
371
372
373
        // Obviously as a consequence, if a test case tries to make multiple
        // transactions on a single mock accessor it will fail.

        // Check any attempt of multiple transactions
        if (did_transaction_) {
            isc_throw(isc::Unexpected, "MockAccessor::startTransaction() "
                      "called multiple times - likely a bug in the test");
        }

374
        readonly_records_copy_ = *readonly_records_;
375
        readonly_records_ = &readonly_records_copy_;
376
        did_transaction_ = true;
377
378
    }

379
private:
380
381
382
    class MockNameIteratorContext : public IteratorContext {
    public:
        MockNameIteratorContext(const MockAccessor& mock_accessor, int zone_id,
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
383
                                const std::string& name, bool subdomains) :
384
            searched_name_(name), cur_record_(0)
385
        {
386
            // 'hardcoded' names to trigger exceptions
387
            // On these names some exceptions are thrown, to test the robustness
388
            // of the find() method.
389
390
391
392
393
394
395
396
            if (searched_name_ == "dsexception.in.search.") {
                isc_throw(DataSourceError, "datasource exception on search");
            } else if (searched_name_ == "iscexception.in.search.") {
                isc_throw(isc::Exception, "isc exception on search");
            } else if (searched_name_ == "basicexception.in.search.") {
                throw std::exception();
            }

397
398
399
            cur_record_ = 0;
            const Domains& cur_records = mock_accessor.getMockRecords(zone_id);
            if (cur_records.count(name) > 0) {
400
401
                    // we're not aiming for efficiency in this test, simply
                    // copy the relevant vector from records
402
403
404
405
406
407
408
409
410
411
412
413
                    cur_name = cur_records.find(name)->second;
            } else if (subdomains) {
                cur_name.clear();
                // Just walk everything and check if it is a subdomain.
                // If it is, just copy all data from there.
                for (Domains::const_iterator i(cur_records.begin());
                     i != cur_records.end(); ++i) {
                    const Name local(i->first);
                    if (local.compare(Name(name)).getRelation() ==
                        isc::dns::NameComparisonResult::SUBDOMAIN) {
                        cur_name.insert(cur_name.end(), i->second.begin(),
                                        i->second.end());
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
414
                    }
415
                }
416
417
            } else {
                cur_name.clear();
418
419
420
            }
        }

421
        virtual bool getNext(std::string (&columns)[COLUMN_COUNT]) {
422
423
424
425
426
427
428
429
430
            if (searched_name_ == "dsexception.in.getnext.") {
                isc_throw(DataSourceError, "datasource exception on getnextrecord");
            } else if (searched_name_ == "iscexception.in.getnext.") {
                isc_throw(isc::Exception, "isc exception on getnextrecord");
            } else if (searched_name_ == "basicexception.in.getnext.") {
                throw std::exception();
            }

            if (cur_record_ < cur_name.size()) {
431
                for (size_t i = 0; i < COLUMN_COUNT; ++i) {
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
                    columns[i] = cur_name[cur_record_][i];
                }
                cur_record_++;
                return (true);
            } else {
                return (false);
            }
        }

    private:
        const std::string searched_name_;
        int cur_record_;
        std::vector< std::vector<std::string> > cur_name;
    };

447
448
449
    class MockIteratorContext : public IteratorContext {
    private:
        int step;
450
        const Domains& domains_;
451
    public:
452
453
        MockIteratorContext(const Domains& domains) :
            step(0), domains_(domains)
454
        { }
455
        virtual bool getNext(string (&data)[COLUMN_COUNT]) {
456
457
458
459
460
461
462
            // A special case: if the given set of domains is already empty,
            // we always return false.
            if (domains_.empty()) {
                return (false);
            }

            // Return faked data for tests
463
464
            switch (step ++) {
                case 0:
465
466
467
468
469
470
                    data[DatabaseAccessor::NAME_COLUMN] = "example.org";
                    data[DatabaseAccessor::TYPE_COLUMN] = "A";
                    data[DatabaseAccessor::TTL_COLUMN] = "3600";
                    data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.1";
                    return (true);
                case 1:
471
472
                    data[DatabaseAccessor::NAME_COLUMN] = "example.org";
                    data[DatabaseAccessor::TYPE_COLUMN] = "SOA";
473
                    data[DatabaseAccessor::TTL_COLUMN] = "3600";
474
                    data[DatabaseAccessor::RDATA_COLUMN] = "ns1.example.org. admin.example.org. "
475
476
                        "1234 3600 1800 2419200 7200";
                    return (true);
477
                case 2:
478
479
480
481
                    data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
                    data[DatabaseAccessor::TYPE_COLUMN] = "A";
                    data[DatabaseAccessor::TTL_COLUMN] = "300";
                    data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.1";
482
                    return (true);
483
                case 3:
484
485
486
487
                    data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
                    data[DatabaseAccessor::TYPE_COLUMN] = "A";
                    data[DatabaseAccessor::TTL_COLUMN] = "300";
                    data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.2";
488
                    return (true);
489
                case 4:
490
491
492
493
                    data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
                    data[DatabaseAccessor::TYPE_COLUMN] = "AAAA";
                    data[DatabaseAccessor::TTL_COLUMN] = "300";
                    data[DatabaseAccessor::RDATA_COLUMN] = "2001:db8::1";
494
                    return (true);
495
                case 5:
496
497
498
499
                    data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
                    data[DatabaseAccessor::TYPE_COLUMN] = "AAAA";
                    data[DatabaseAccessor::TTL_COLUMN] = "300";
                    data[DatabaseAccessor::RDATA_COLUMN] = "2001:db8::2";
500
                    return (true);
501
                case 6:
502
503
504
505
506
                    data[DatabaseAccessor::NAME_COLUMN] = "ttldiff.example.org";
                    data[DatabaseAccessor::TYPE_COLUMN] = "A";
                    data[DatabaseAccessor::TTL_COLUMN] = "300";
                    data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.1";
                    return (true);
507
                case 7:
508
509
510
511
512
                    data[DatabaseAccessor::NAME_COLUMN] = "ttldiff.example.org";
                    data[DatabaseAccessor::TYPE_COLUMN] = "A";
                    data[DatabaseAccessor::TTL_COLUMN] = "600";
                    data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.2";
                    return (true);
513
514
515
                default:
                    ADD_FAILURE() <<
                        "Request past the end of iterator context";
516
                case 8:
517
518
519
520
521
522
                    return (false);
            }
        }
    };
    class EmptyIteratorContext : public IteratorContext {
    public:
523
        virtual bool getNext(string(&)[COLUMN_COUNT]) {
524
525
526
527
528
529
530
531
532
533
            return (false);
        }
    };
    class BadIteratorContext : public IteratorContext {
    private:
        int step;
    public:
        BadIteratorContext() :
            step(0)
        { }
534
        virtual bool getNext(string (&data)[COLUMN_COUNT]) {
535
536
            switch (step ++) {
                case 0:
537
538
539
540
                    data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
                    data[DatabaseAccessor::TYPE_COLUMN] = "A";
                    data[DatabaseAccessor::TTL_COLUMN] = "300";
                    data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.1";
541
542
                    return (true);
                case 1:
543
544
545
546
                    data[DatabaseAccessor::NAME_COLUMN] = "x.example.org";
                    data[DatabaseAccessor::TYPE_COLUMN] = "A";
                    data[DatabaseAccessor::TTL_COLUMN] = "301";
                    data[DatabaseAccessor::RDATA_COLUMN] = "192.0.2.2";
547
548
549
550
551
552
553
554
555
                    return (true);
                default:
                    ADD_FAILURE() <<
                        "Request past the end of iterator context";
                case 2:
                    return (false);
            }
        }
    };
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
    class MockDiffIteratorContext : public IteratorContext {
        const vector<JournalEntry> diffs_;
        vector<JournalEntry>::const_iterator it_;
    public:
        MockDiffIteratorContext(const vector<JournalEntry>& diffs) :
            diffs_(diffs), it_(diffs_.begin())
        {}
        virtual bool getNext(string (&data)[COLUMN_COUNT]) {
            if (it_ == diffs_.end()) {
                return (false);
            }
            data[DatabaseAccessor::NAME_COLUMN] =
                (*it_).data_[DatabaseAccessor::DIFF_NAME];
            data[DatabaseAccessor::TYPE_COLUMN] =
                (*it_).data_[DatabaseAccessor::DIFF_TYPE];
            data[DatabaseAccessor::TTL_COLUMN] =
                (*it_).data_[DatabaseAccessor::DIFF_TTL];
            data[DatabaseAccessor::RDATA_COLUMN] =
                (*it_).data_[DatabaseAccessor::DIFF_RDATA];
            ++it_;
            return (true);
        }
    };
579
public:
580
    virtual IteratorContextPtr getAllRecords(int id) const {
581
        if (id == READONLY_ZONE_ID) {
582
583
            return (IteratorContextPtr(new MockIteratorContext(
                                           *readonly_records_)));
584
585
586
587
588
589
590
591
592
593
        } else if (id == 13) {
            return (IteratorContextPtr());
        } else if (id == 0) {
            return (IteratorContextPtr(new EmptyIteratorContext()));
        } else if (id == -1) {
            return (IteratorContextPtr(new BadIteratorContext()));
        } else {
            isc_throw(isc::Unexpected, "Unknown zone ID");
        }
    }
594

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
595
596
597
    virtual IteratorContextPtr getRecords(const std::string& name, int id,
                                          bool subdomains) const
    {
598
599
600
601
        if (id == READONLY_ZONE_ID || id == WRITABLE_ZONE_ID) {
            return (IteratorContextPtr(
                        new MockNameIteratorContext(*this, id, name,
                                                    subdomains)));
602
        } else {
603
604
605
606
607
            // This iterator is bogus, but for the cases tested below that's
            // sufficient.
            return (IteratorContextPtr(
                        new MockNameIteratorContext(*this, READONLY_ZONE_ID,
                                                    name, subdomains)));
608
609
610
        }
    }

611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
    virtual pair<bool, int> startUpdateZone(const std::string& zone_name,
                                            bool replace)
    {
        const pair<bool, int> zone_info = getZone(zone_name);
        if (!zone_info.first) {
            return (pair<bool, int>(false, 0));
        }

        // Prepare the record set for update.  If replacing the existing one,
        // we use an empty set; otherwise we use a writable copy of the
        // original.
        if (replace) {
            update_records_->clear();
        } else {
            *update_records_ = *readonly_records_;
        }

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
628
629
630
631
632
633
634
        if (zone_name == "bad.example.org.") {
            return (pair<bool, int>(true, -1));
        } else if (zone_name == "null.example.org.") {
            return (pair<bool, int>(true, 13));
        } else {
            return (pair<bool, int>(true, WRITABLE_ZONE_ID));
        }
635
    }
636
    virtual void commit() {
637
638
        *readonly_records_ = *update_records_;
    }
639
    virtual void rollback() {
640
641
642
643
644
645
646
        // Special hook: if something with a name of "throw.example.org"
        // has been added, trigger an imaginary unexpected event with an
        // exception.
        if (update_records_->count("throw.example.org.") > 0) {
            isc_throw(DataSourceError, "unexpected failure in rollback");
        }

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
        rollbacked_ = true;
    }
    virtual void addRecordToZone(const string (&columns)[ADD_COLUMN_COUNT]) {
        // Copy the current value to cur_name.  If it doesn't exist,
        // operator[] will create a new one.
        cur_name_ = (*update_records_)[columns[DatabaseAccessor::ADD_NAME]];

        vector<string> record_columns;
        record_columns.push_back(columns[DatabaseAccessor::ADD_TYPE]);
        record_columns.push_back(columns[DatabaseAccessor::ADD_TTL]);
        record_columns.push_back(columns[DatabaseAccessor::ADD_SIGTYPE]);
        record_columns.push_back(columns[DatabaseAccessor::ADD_RDATA]);
        record_columns.push_back(columns[DatabaseAccessor::ADD_NAME]);

        // copy back the added entry
        cur_name_.push_back(record_columns);
        (*update_records_)[columns[DatabaseAccessor::ADD_NAME]] = cur_name_;

        // remember this one so that test cases can check it.
        copy(columns, columns + DatabaseAccessor::ADD_COLUMN_COUNT,
             columns_lastadded_);
    }

    // Helper predicate class used in deleteRecordInZone().
    struct deleteMatch {
        deleteMatch(const string& type, const string& rdata) :
            type_(type), rdata_(rdata)
        {}
        bool operator()(const vector<string>& row) const {
            return (row[0] == type_ && row[3] == rdata_);
        }
        const string& type_;
        const string& rdata_;
    };

    virtual void deleteRecordInZone(const string (&params)[DEL_PARAM_COUNT]) {
        vector<vector<string> >& records =
            (*update_records_)[params[DatabaseAccessor::DEL_NAME]];
        records.erase(remove_if(records.begin(), records.end(),
                                deleteMatch(
                                    params[DatabaseAccessor::DEL_TYPE],
                                    params[DatabaseAccessor::DEL_RDATA])),
                      records.end());
        if (records.empty()) {
            (*update_records_).erase(params[DatabaseAccessor::DEL_NAME]);
        }
    }

695
696
697
698
699
700
701
    //
    // Helper methods to keep track of some update related activities
    //
    bool isRollbacked() const {
        return (rollbacked_);
    }

702
    const string* getLastAdded() const {
703
704
705
706
707
708
709
710
        return (columns_lastadded_);
    }

    // This allows the test code to get the accessor used in an update context
    shared_ptr<const MockAccessor> getLatestClone() const {
        return (latest_clone_);
    }

711
    virtual std::string findPreviousName(int id, const std::string& rname)
712
713
714
715
        const
    {
        if (id == -1) {
            isc_throw(isc::NotImplemented, "Test not implemented behaviour");
716
717
718
719
        } else if (id == READONLY_ZONE_ID) {
            // For some specific names we intentionally return broken or
            // unexpected result.
            if (rname == "org.example.badnsec2.") {
720
                return ("badnsec1.example.org.");
721
722
            } else if (rname == "org.example.brokenname.") {
                return ("brokenname...example.org.");
723
724
            } else if (rname == "org.example.notimplnsec." ||
                       rname == "org.example.wild.here.") {
725
                isc_throw(isc::NotImplemented, "Not implemented in this test");
726
727
728
729
730
731
732
733
734
735
736
737
            }

            // For the general case, we search for the first name N in the
            // domains that meets N >= reverse(rname) using lower_bound.
            // The "previous name" is the name of the previous entry of N.
            // Note that Domains are internally sorted by the Name comparison
            // order.  Due to the API requirement we are given a reversed
            // name (rname), so we need to reverse it again to convert it
            // to the original name.
            Domains::const_iterator it(readonly_records_->lower_bound(
                                           Name(rname).reverse().toText()));
            if (it == readonly_records_->begin()) {
738
739
                isc_throw(isc::Unexpected, "Unexpected name");
            }
740
741
742
743
            if (it == readonly_records_->end()) {
                return ((*readonly_records_->rbegin()).first);
            }
            return ((*(--it)).first);
744
745
746
747
        } else {
            isc_throw(isc::Unexpected, "Unknown zone ID");
        }
    }
748
749
750
751
752
753
754
755
756
    virtual void addRecordDiff(int id, uint32_t serial,
                               DiffOperation operation,
                               const std::string (&data)[DIFF_PARAM_COUNT])
    {
        if (id == 13) { // The null zone doesn't support journaling
            isc_throw(isc::NotImplemented, "Test not implemented behaviour");
        } else if (id == -1) { // Bad zone throws
            isc_throw(DataSourceError, "Test error");
        } else {
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
757
            journal_entries_->push_back(JournalEntry(id, serial, operation,
758
                                                     data));
759
760
761
        }
    }

762
763
764
765
766
767
768
769
770
    virtual IteratorContextPtr getDiffs(int id, uint32_t start,
                                        uint32_t end) const
    {
        vector<JournalEntry> selected_jnl;

        for (vector<JournalEntry>::const_iterator it =
                 journal_entries_->begin();
             it != journal_entries_->end(); ++it)
        {
771
772
773
774
775
776
777
778
779
780
781
            // For simplicity we assume this method is called for the
            // "readonly" zone possibly after making updates on the "writable"
            // copy and committing them.
            if (id != READONLY_ZONE_ID) {
                continue;
            }

            // Note: the following logic is not 100% accurate in terms of
            // serial number arithmetic; we prefer brevity for testing.
            // Skip until we see the starting serial.  Once we started
            // recording this condition is ignored (to support wrap-around
JINMEI Tatuya's avatar
JINMEI Tatuya committed
782
783
            // case).  Also, it ignores the RR type; it only checks the
            // versions.
784
            if ((*it).serial_ < start && selected_jnl.empty()) {
785
786
                continue;
            }
787
788
789
            if ((*it).serial_ > end) { // gone over the end serial. we're done.
                break;
            }
790
            selected_jnl.push_back(*it);
791
        }
792
793
794
795
796
797
798

        // Check if we've found the requested range.  If not, throw.
        if (selected_jnl.empty() || selected_jnl.front().serial_ != start ||
            selected_jnl.back().serial_ != end) {
            isc_throw(NoSuchSerial, "requested diff range is not found");
        }

799
800
801
        return (IteratorContextPtr(new MockDiffIteratorContext(selected_jnl)));
    }

802
    // Check the journal is as expected and clear the journal
803
    void checkJournal(const std::vector<JournalEntry> &expected) const {
804
805
        std::vector<JournalEntry> journal;
        // Clean the journal, but keep local copy to check
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
806
        journal.swap(*journal_entries_);
807
808
809
810
811
        ASSERT_EQ(expected.size(), journal.size());
        for (size_t i(0); i < expected.size(); ++ i) {
            EXPECT_TRUE(expected[i] == journal[i]);
        }
    }
812

813
private:
814
815
    // The following member variables are storage and/or update work space
    // of the test zone.  The "master"s are the real objects that contain
816
    // the data, and they are shared among all accessors cloned from
817
818
    // an initially created one.  The "copy" data will be used for read-only
    // transaction.  The pointer members allow the sharing.
819
820
821
822
823
    // "readonly" is for normal lookups.  "update" is the workspace for
    // updates.  When update starts it will be initialized either as an
    // empty set (when replacing the entire zone) or as a copy of the
    // "readonly" one.  "empty" is a sentinel to produce negative results.
    Domains readonly_records_master_;
824
    Domains readonly_records_copy_;
825
826
827
828
829
830
    Domains* readonly_records_;
    Domains update_records_master_;
    Domains* update_records_;
    const Domains empty_records_master_;
    const Domains* empty_records_;

831
    // The journal data
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
832
833
    std::vector<JournalEntry> journal_entries_master_;
    std::vector<JournalEntry>* journal_entries_;
834

835
836
837
    // used as temporary storage after searchForRecord() and during
    // getNextRecord() calls, as well as during the building of the
    // fake data
838
839
840
    std::vector< std::vector<std::string> > cur_name_;

    // The columns that were most recently added via addRecordToZone()
841
    string columns_lastadded_[ADD_COLUMN_COUNT];
842
843
844
845
846
847
848
849

    // Whether rollback operation has been performed for the database.
    // Not useful except for purely testing purpose.
    bool rollbacked_;

    // Remember the mock accessor that was last cloned
    boost::shared_ptr<MockAccessor> latest_clone_;

850
851
852
    // Internal flag for duplicate check
    bool did_transaction_;

853
854
855
856
857
858
859
860
    const Domains& getMockRecords(int zone_id) const {
        if (zone_id == READONLY_ZONE_ID) {
            return (*readonly_records_);
        } else if (zone_id == WRITABLE_ZONE_ID) {
            return (*update_records_);
        }
        return (*empty_records_);
    }
861

862
863
864
    // Adds one record to the current name in the database
    // The actual data will not be added to 'records' until
    // addCurName() is called
865
866
    void addRecord(const std::string& type,
                   const std::string& ttl,
867
868
869
870
                   const std::string& sigtype,
                   const std::string& rdata) {
        std::vector<std::string> columns;
        columns.push_back(type);
871
        columns.push_back(ttl);
872
873
        columns.push_back(sigtype);
        columns.push_back(rdata);
874
        cur_name_.push_back(columns);
875
876
    }

877
    // Adds all records we just built with calls to addRecords
878
    // to the actual fake database. This will clear cur_name_,
879
    // so we can immediately start adding new records.
880
    void addCurName(const std::string& name) {
881
        ASSERT_EQ(0, readonly_records_->count(name));
882
883
        // Append the name to all of them
        for (std::vector<std::vector<std::string> >::iterator
884
             i(cur_name_.begin()); i != cur_name_.end(); ++ i) {
885
886
            i->push_back(name);
        }
887
888
        (*readonly_records_)[name] = cur_name_;
        cur_name_.clear();
889
890
    }

891
892
893
894
895
896
897
898
899
900
    // Fills the database with zone data.
    // This method constructs a number of resource records (with addRecord),
    // which will all be added for one domain name to the fake database
    // (with addCurName). So for instance the first set of calls create
    // data for the name 'www.example.org', which will consist of one A RRset
    // of one record, and one AAAA RRset of two records.
    // The order in which they are added is the order in which getNextRecord()
    // will return them (so we can test whether find() etc. support data that
    // might not come in 'normal' order)
    // It shall immediately fail if you try to add the same name twice.
901
    void fillData() {
902
903
904
905
906
907
908
909
910
911
912
        const char* prev_name = NULL;
        for (int i = 0; TEST_RECORDS[i][0] != NULL; ++i) {
            if (prev_name != NULL &&
                strcmp(prev_name, TEST_RECORDS[i][0]) != 0) {
                addCurName(prev_name);
            }
            prev_name = TEST_RECORDS[i][0];
            addRecord(TEST_RECORDS[i][1], TEST_RECORDS[i][2],
                      TEST_RECORDS[i][3], TEST_RECORDS[i][4]);
        }
        addCurName(prev_name);
913
    }
914
915
};

916
917
// This tests the default getRecords behaviour, throwing NotImplemented
TEST(DatabaseConnectionTest, getRecords) {
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
918
    EXPECT_THROW(NopAccessor().getRecords(".", 1, false),
919
920
921
                 isc::NotImplemented);
}

922
923
// This tests the default getAllRecords behaviour, throwing NotImplemented
TEST(DatabaseConnectionTest, getAllRecords) {
924
    // The parameters don't matter
925
    EXPECT_THROW(NopAccessor().getAllRecords(1),
926
927
928
                 isc::NotImplemented);
}

929
930
931
932
// This test fixture is templated so that we can share (most of) the test
// cases with different types of data sources.  Note that in test cases
// we need to use 'this' to refer to member variables of the test class.
template <typename ACCESSOR_TYPE>
933
934
class DatabaseClientTest : public ::testing::Test {
public:
935
936
937
938
    DatabaseClientTest() : zname_("example.org"), qname_("www.example.org"),
                           qclass_(RRClass::IN()), qtype_(RRType::A()),
                           rrttl_(3600)
    {
939
        createClient();
940
941
942
943
944
945
946
947
948
949
950
951

        // set up the commonly used finder.
        DataSourceClient::FindResult zone(client_->findZone(zname_));
        assert(zone.code == result::SUCCESS);
        finder_ = dynamic_pointer_cast<DatabaseClient::Finder>(
            zone.zone_finder);

        // Test IN/A RDATA to be added in update tests.  Intentionally using
        // different data than the initial data configured in the MockAccessor.
        rrset_.reset(new RRset(qname_, qclass_, qtype_, rrttl_));
        rrset_->addRdata(rdata::createRdata(rrset_->getType(),
                                            rrset_->getClass(), "192.0.2.2"));
952
953
954
955
        soa_.reset(new RRset(zname_, qclass_, RRType::SOA(), rrttl_));
        soa_->addRdata(rdata::createRdata(soa_->getType(), soa_->getClass(),
                                         "ns1.example.org. admin.example.org. "
                                         "1234 3600 1800 2419200 7200"));
956
957
958
959
960
961
962
963
964

        // And its RRSIG.  Also different from the configured one.
        rrsigset_.reset(new RRset(qname_, qclass_, RRType::RRSIG(),
                                  rrttl_));
        rrsigset_->addRdata(rdata::createRdata(rrsigset_->getType(),
                                               rrsigset_->getClass(),
                                               "A 5 3 0 20000101000000 "
                                               "20000201000000 0 example.org. "
                                               "FAKEFAKEFAKE"));
965
    }
966

967
968
969
970
971
    /*
     * We initialize the client from a function, so we can call it multiple
     * times per test.
     */
    void createClient() {
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
        // To make sure we always have empty diffs table at the beginning of
        // each test, we re-install the writable data source here.
        // Note: this is SQLite3 specific and a waste (though otherwise
        // harmless) for other types of data sources.  If and when we support
        // more types of data sources in this test framework, we should
        // probably move this to some specialized templated method specific
        // to SQLite3 (or for even a longer term we should add an API to
        // purge the diffs table).
        const char* const install_cmd = INSTALL_PROG " " TEST_DATA_DIR
            "/rwtest.sqlite3 " TEST_DATA_BUILDDIR
            "/rwtest.sqlite3.copied";
        if (system(install_cmd) != 0) {
            // any exception will do, this is failure in test setup, but nice
            // to show the command that fails, and shouldn't be caught
            isc_throw(isc::Exception,
                      "Error setting up; command failed: " << install_cmd);
        }

990
991
        current_accessor_ = new ACCESSOR_TYPE();
        is_mock_ = (dynamic_cast<MockAccessor*>(current_accessor_) != NULL);
992
        client_.reset(new DatabaseClient(qclass_,
993
                                         shared_ptr<ACCESSOR_TYPE>(
994
                                             current_accessor_)));
995
    }
996

997
998
    /**
     * Check the zone finder is a valid one and references the zone ID and
999
     * database available here.
1000
     */