client_list_unittest.cc 42.1 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Copyright (C) 2012  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
#include <datasrc/client_list.h>
16
#include <datasrc/client.h>
17
#include <datasrc/iterator.h>
18
#include <datasrc/data_source.h>
19
#include <datasrc/memory/memory_client.h>
20
#include <datasrc/memory/zone_table_segment.h>
21
#include <datasrc/memory/zone_finder.h>
22
#include <datasrc/memory/zone_writer.h>
23

24
#include <dns/rrclass.h>
25
26
#include <dns/rrttl.h>
#include <dns/rdataclass.h>
27

28
29
#include <gtest/gtest.h>

30
31
#include <boost/shared_ptr.hpp>

32
#include <set>
33
#include <fstream>
34

35
using namespace isc::datasrc;
36
using isc::datasrc::memory::InMemoryClient;
37
using isc::datasrc::memory::ZoneTableSegment;
38
using isc::datasrc::memory::InMemoryZoneFinder;
39
40
using namespace isc::data;
using namespace isc::dns;
41
42
43
// don't import the entire boost namespace.  It will unexpectedly hide uintXX_t
// for some systems.
using boost::shared_ptr;
44
using namespace std;
45
46
47

namespace {

48
// A test data source. It pretends it has some zones.
49
class MockDataSourceClient : public DataSourceClient {
50
public:
51
52
53
54
    class Finder : public ZoneFinder {
    public:
        Finder(const Name& origin) :
            origin_(origin)
55
        {}
56
57
58
        Name getOrigin() const { return (origin_); }
        // The rest is not to be called, so just have them
        RRClass getClass() const {
59
            isc_throw(isc::NotImplemented, "Not implemented");
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
        }
        shared_ptr<Context> find(const Name&, const RRType&,
                                 const FindOptions)
        {
            isc_throw(isc::NotImplemented, "Not implemented");
        }
        shared_ptr<Context> findAll(const Name&,
                                    vector<ConstRRsetPtr>&,
                                    const FindOptions)
        {
            isc_throw(isc::NotImplemented, "Not implemented");
        }
        FindNSEC3Result findNSEC3(const Name&, bool) {
            isc_throw(isc::NotImplemented, "Not implemented");
        }
    private:
        Name origin_;
    };
78
79
    class Iterator : public ZoneIterator {
    public:
80
        Iterator(const Name& origin, bool include_ns) :
81
            origin_(origin),
82
83
            soa_(new RRset(origin_, RRClass::IN(), RRType::SOA(),
                           RRTTL(3600)))
84
85
86
87
88
89
        {
            // The RData here is bogus, but it is not used to anything. There
            // just needs to be some.
            soa_->addRdata(rdata::generic::SOA(Name::ROOT_NAME(),
                                               Name::ROOT_NAME(),
                                               0, 0, 0, 0, 0));
90
91
92
93
94
95
96
97
98
99
100
            rrsets_.push_back(soa_);

            if (include_ns) {
                ns_.reset(new RRset(origin_, RRClass::IN(), RRType::NS(),
                                    RRTTL(3600)));
                ns_->addRdata(rdata::generic::NS(Name::ROOT_NAME()));
                rrsets_.push_back(ns_);
            }
            rrsets_.push_back(ConstRRsetPtr());

            it_ = rrsets_.begin();
101
102
        }
        virtual isc::dns::ConstRRsetPtr getNextRRset() {
103
104
105
            ConstRRsetPtr result = *it_;
            ++it_;
            return (result);
106
107
108
109
110
111
        }
        virtual isc::dns::ConstRRsetPtr getSOA() const {
            return (soa_);
        }
    private:
        const Name origin_;
112
113
114
115
        const RRsetPtr soa_;
        RRsetPtr ns_;
        std::vector<ConstRRsetPtr> rrsets_;
        std::vector<ConstRRsetPtr>::const_iterator it_;
116
    };
117
    // Constructor from a list of zones.
118
119
120
    MockDataSourceClient(const char* zone_names[]) :
        have_ns_(true), use_baditerator_(true)
    {
121
        for (const char** zone(zone_names); *zone; ++zone) {
122
123
124
            zones.insert(Name(*zone));
        }
    }
125
126
    // Constructor from configuration. The list of zones will be empty, but
    // it will keep the configuration inside for further inspection.
127
128
    MockDataSourceClient(const string& type,
                         const ConstElementPtr& configuration) :
129
        type_(type),
130
131
        configuration_(configuration),
        have_ns_(true), use_baditerator_(true)
132
    {
133
134
        EXPECT_NE("MasterFiles", type) << "MasterFiles is a special case "
            "and it never should be created as a data source client";
135
136
137
138
139
140
        if (configuration_->getType() == Element::list) {
            for (size_t i(0); i < configuration_->size(); ++i) {
                zones.insert(Name(configuration_->get(i)->stringValue()));
            }
        }
    }
141
    virtual FindResult findZone(const Name& name) const {
142
        if (zones.empty()) {
143
            return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
144
145
        }
        set<Name>::const_iterator it(zones.upper_bound(name));
146
147
148
        if (it == zones.begin()) {
            return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
        }
149
        --it;
150
151
152
153
154
155
156
157
158
        NameComparisonResult compar(it->compare(name));
        const ZoneFinderPtr finder(new Finder(*it));
        switch (compar.getRelation()) {
            case NameComparisonResult::EQUAL:
                return (FindResult(result::SUCCESS, finder));
            case NameComparisonResult::SUPERDOMAIN:
                return (FindResult(result::PARTIALMATCH, finder));
            default:
                return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
159
        }
160
161
162
163
164
165
166
167
168
169
170
    }
    // These methods are not used. They just need to be there to have
    // complete vtable.
    virtual ZoneUpdaterPtr getUpdater(const Name&, bool, bool) const {
        isc_throw(isc::NotImplemented, "Not implemented");
    }
    virtual pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
        getJournalReader(const Name&, uint32_t, uint32_t) const
    {
        isc_throw(isc::NotImplemented, "Not implemented");
    }
171
    virtual ZoneIteratorPtr getIterator(const Name& name, bool) const {
172
        if (use_baditerator_ && name == Name("noiter.org")) {
173
            isc_throw(isc::NotImplemented, "Asked not to be implemented");
174
        } else if (use_baditerator_ && name == Name("null.org")) {
175
176
            return (ZoneIteratorPtr());
        } else {
177
178
            FindResult result(findZone(name));
            if (result.code == isc::datasrc::result::SUCCESS) {
179
                return (ZoneIteratorPtr(new Iterator(name, have_ns_)));
180
181
182
            } else {
                isc_throw(DataSourceError, "No such zone");
            }
183
        }
184
    }
185
186
    void disableNS() { have_ns_ = false; }
    void disableBadIterator() { use_baditerator_ = false; }
187
188
    const string type_;
    const ConstElementPtr configuration_;
189
190
private:
    set<Name> zones;
191
192
    bool have_ns_; // control the iterator behavior wrt whether to include NS
    bool use_baditerator_; // whether to use bogus zone iterators for tests
193
194
};

195
196
197

// The test version is the same as the normal version. We, however, add
// some methods to dig directly in the internals, for the tests.
198
class TestedList : public ConfigurableClientList {
199
public:
200
201
202
    TestedList(const RRClass& rrclass) :
        ConfigurableClientList(rrclass)
    {}
203
    DataSources& getDataSources() { return (data_sources_); }
204
    // Overwrite the list's method to get a data source with given type
205
206
    // and configuration. We mock the data source and don't create the
    // container. This is just to avoid some complexity in the tests.
207
208
209
    virtual DataSourcePair getDataSourceClient(const string& type,
                                               const ConstElementPtr&
                                               configuration)
210
    {
211
212
213
        if (type == "error") {
            isc_throw(DataSourceError, "The error data source type");
        }
214
215
        shared_ptr<MockDataSourceClient>
            ds(new MockDataSourceClient(type, configuration));
216
        // Make sure it is deleted when the test list is deleted.
217
218
219
220
221
222
        to_delete_.push_back(ds);
        return (DataSourcePair(ds.get(), DataSourceClientContainerPtr()));
    }
private:
    // Hold list of data sources created internally, so they are preserved
    // until the end of the test and then deleted.
223
    vector<shared_ptr<MockDataSourceClient> > to_delete_;
224
};
225

226
const char* ds_zones[][3] = {
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
    {
        "example.org.",
        "example.com.",
        NULL
    },
    {
        "sub.example.org.",
        NULL, NULL
    },
    {
        NULL, NULL, NULL
    },
    {
        "sub.example.org.",
        NULL, NULL
    }
243
244
};

245
const size_t ds_count = (sizeof(ds_zones) / sizeof(*ds_zones));
246

247
class ListTest : public ::testing::Test {
248
public:
249
    ListTest() :
250
        rrclass_(RRClass::IN()),
251
        // The empty list corresponds to a list with no elements inside
252
        list_(new TestedList(rrclass_)),
253
254
255
256
        config_elem_(Element::fromJSON("["
            "{"
            "   \"type\": \"test_type\","
            "   \"params\": {}"
257
258
259
260
261
262
            "}]")),
        config_elem_zones_(Element::fromJSON("["
            "{"
            "   \"type\": \"test_type\","
            "   \"params\": [\"example.org\", \"example.com\", "
            "                \"noiter.org\", \"null.org\"]"
263
264
            "}]")),
        config_(Element::fromJSON("{}")),
265
        ztable_segment_(ZoneTableSegment::create(*config_, rrclass_))
266
    {
267
        for (size_t i(0); i < ds_count; ++ i) {
268
269
            shared_ptr<MockDataSourceClient>
                ds(new MockDataSourceClient(ds_zones[i]));
270
            ds_.push_back(ds);
271
272
            ds_info_.push_back(ConfigurableClientList::DataSourceInfo(
                                   ds.get(), DataSourceClientContainerPtr(),
273
                                   false, rrclass_, ztable_segment_));
274
        }
275
    }
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292

    // Install a "fake" cached zone using a temporary underlying data source
    // client.
    void prepareCache(size_t index, const Name& zone) {
        // Prepare the temporary data source client
        const char* zones[2];
        const std::string zonename_txt = zone.toText();
        zones[0] = zonename_txt.c_str();
        zones[1] = NULL;
        MockDataSourceClient mock_client(zones);
        // Disable some default features of the mock to distinguish the
        // temporary case from normal case.
        mock_client.disableNS();
        mock_client.disableBadIterator();

        // Create cache from the temporary data source, and push it to the
        // client list.
293
294
        const shared_ptr<InMemoryClient> cache(
            new InMemoryClient(ztable_segment_, rrclass_));
295
296
297
298
299
        cache->load(zone, *mock_client.getIterator(zone, false));

        ConfigurableClientList::DataSourceInfo& dsrc_info =
                list_->getDataSources()[index];
        dsrc_info.cache_ = cache;
300
        dsrc_info.ztable_segment_ = ztable_segment_;
301
    }
302
    // Check the positive result is as we expect it.
303
    void positiveResult(const ClientList::FindResult& result,
304
                        const shared_ptr<MockDataSourceClient>& dsrc,
305
                        const Name& name, bool exact,
306
                        const char* test, bool from_cache = false)
307
308
309
310
311
    {
        SCOPED_TRACE(test);
        ASSERT_NE(ZoneFinderPtr(), result.finder_);
        EXPECT_EQ(name, result.finder_->getOrigin());
        EXPECT_EQ(exact, result.exact_match_);
312
313
314
315
316
317
        // If it is a positive result, there's something to keep
        // alive, even when we don't know what it is.
        // Any better idea how to test it actually keeps the thing
        // alive?
        EXPECT_NE(shared_ptr<ClientList::FindResult::LifeKeeper>(),
                  result.life_keeper_);
318
319
        if (from_cache) {
            EXPECT_NE(shared_ptr<InMemoryZoneFinder>(),
320
                      boost::dynamic_pointer_cast<InMemoryZoneFinder>(
321
322
323
324
325
326
                          result.finder_)) << "Finder is not from cache";
            EXPECT_TRUE(NULL !=
                        dynamic_cast<InMemoryClient*>(result.dsrc_client_));
        } else {
            EXPECT_EQ(dsrc.get(), result.dsrc_client_);
        }
327
    }
328
    // Configure the list with multiple data sources, according to
329
330
331
    // some configuration. It uses the index as parameter, to be able to
    // loop through the configurations.
    void multiConfiguration(size_t index) {
332
        list_->getDataSources().clear();
333
334
        switch (index) {
            case 2:
335
                list_->getDataSources().push_back(ds_info_[2]);
336
                // The ds_[2] is empty. We just check that it doesn't confuse
337
338
                // us. Fall through to the case 0.
            case 0:
339
340
                list_->getDataSources().push_back(ds_info_[0]);
                list_->getDataSources().push_back(ds_info_[1]);
341
342
343
                break;
            case 1:
                // The other order
344
345
                list_->getDataSources().push_back(ds_info_[1]);
                list_->getDataSources().push_back(ds_info_[0]);
346
347
                break;
            case 3:
348
349
                list_->getDataSources().push_back(ds_info_[1]);
                list_->getDataSources().push_back(ds_info_[0]);
350
                // It is the same as ds_[1], but we take from the first one.
351
                // The first one to match is the correct one.
352
                list_->getDataSources().push_back(ds_info_[3]);
353
354
355
356
357
                break;
            default:
                FAIL() << "Unknown configuration index " << index;
        }
    }
358
359
    void checkDS(size_t index, const string& type, const string& params,
                 bool cache) const
360
    {
361
        ASSERT_GT(list_->getDataSources().size(), index);
362
        MockDataSourceClient* ds(dynamic_cast<MockDataSourceClient*>(
363
            list_->getDataSources()[index].data_src_client_));
364

365
        // Comparing with NULL does not work
366
        ASSERT_NE(ds, static_cast<const MockDataSourceClient*>(NULL));
367
368
        EXPECT_EQ(type, ds->type_);
        EXPECT_TRUE(Element::fromJSON(params)->equals(*ds->configuration_));
369
370
        EXPECT_EQ(cache, list_->getDataSources()[index].cache_ !=
                  shared_ptr<InMemoryClient>());
371
    }
372
    const RRClass rrclass_;
373
    shared_ptr<TestedList> list_;
374
    const ClientList::FindResult negative_result_;
375
    vector<shared_ptr<MockDataSourceClient> > ds_;
376
    vector<ConfigurableClientList::DataSourceInfo> ds_info_;
377
    const ConstElementPtr config_elem_, config_elem_zones_, config_;
378
    shared_ptr<ZoneTableSegment> ztable_segment_;
379
380
};

381
// Test the test itself
382
TEST_F(ListTest, selfTest) {
383
384
385
386
387
    EXPECT_EQ(result::SUCCESS, ds_[0]->findZone(Name("example.org")).code);
    EXPECT_EQ(result::PARTIALMATCH,
              ds_[0]->findZone(Name("sub.example.org")).code);
    EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("org")).code);
    EXPECT_EQ(result::NOTFOUND, ds_[1]->findZone(Name("example.org")).code);
388
389
    EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("aaa")).code);
    EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("zzz")).code);
390
391
    // Nothing to keep alive here.
    EXPECT_EQ(shared_ptr<ClientList::FindResult::LifeKeeper>(),
392
                  negative_result_.life_keeper_);
393
394
}

395
396
397
// Test the list we create with empty configuration is, in fact, empty
TEST_F(ListTest, emptyList) {
    EXPECT_TRUE(list_->getDataSources().empty());
398
399
}

400
// Check the values returned by a find on an empty list. It should be
401
402
// a negative answer (nothing found) no matter if we want an exact or inexact
// match.
403
TEST_F(ListTest, emptySearch) {
404
    // No matter what we try, we don't get an answer.
405
406
407

    // Note: we don't have operator<< for the result class, so we cannot use
    // EXPECT_EQ.  Same for other similar cases.
408
409
410
411
412
413
414
415
    EXPECT_TRUE(negative_result_ == list_->find(Name("example.org"), false,
                                                false));
    EXPECT_TRUE(negative_result_ == list_->find(Name("example.org"), false,
                                                true));
    EXPECT_TRUE(negative_result_ == list_->find(Name("example.org"), true,
                                                false));
    EXPECT_TRUE(negative_result_ == list_->find(Name("example.org"), true,
                                                true));
416
417
}

418
// Put a single data source inside the list and check it can find an
419
// exact match if there's one.
420
421
TEST_F(ListTest, singleDSExactMatch) {
    list_->getDataSources().push_back(ds_info_[0]);
422
    // This zone is not there
423
    EXPECT_TRUE(negative_result_ == list_->find(Name("org."), true));
424
    // But this one is, so check it.
425
426
    positiveResult(list_->find(Name("example.org"), true), ds_[0],
                   Name("example.org"), true, "Exact match");
427
428
    // When asking for a sub zone of a zone there, we get nothing
    // (we want exact match, this would be partial one)
429
430
    EXPECT_TRUE(negative_result_ == list_->find(Name("sub.example.org."),
                                                true));
431
432
433
}

// When asking for a partial match, we get all that the exact one, but more.
434
435
TEST_F(ListTest, singleDSBestMatch) {
    list_->getDataSources().push_back(ds_info_[0]);
436
    // This zone is not there
437
    EXPECT_TRUE(negative_result_ == list_->find(Name("org.")));
438
    // But this one is, so check it.
439
440
    positiveResult(list_->find(Name("example.org")), ds_[0],
                   Name("example.org"), true, "Exact match");
441
442
    // When asking for a sub zone of a zone there, we get the parent
    // one.
443
444
    positiveResult(list_->find(Name("sub.example.org.")), ds_[0],
                   Name("example.org"), false, "Subdomain match");
445
446
}

447
const char* const test_names[] = {
448
449
450
451
452
453
    "Sub second",
    "Sub first",
    "With empty",
    "With a duplicity"
};

454
TEST_F(ListTest, multiExactMatch) {
455
    // Run through all the multi-configurations
456
    for (size_t i(0); i < sizeof(test_names) / sizeof(*test_names); ++i) {
457
458
459
        SCOPED_TRACE(test_names[i]);
        multiConfiguration(i);
        // Something that is nowhere there
460
        EXPECT_TRUE(negative_result_ == list_->find(Name("org."), true));
461
        // This one is there exactly.
462
463
        positiveResult(list_->find(Name("example.org"), true), ds_[0],
                       Name("example.org"), true, "Exact match");
464
        // This one too, but in a different data source.
465
466
        positiveResult(list_->find(Name("sub.example.org."), true), ds_[1],
                       Name("sub.example.org"), true, "Subdomain match");
467
        // But this one is in neither data source.
468
        EXPECT_TRUE(negative_result_ ==
469
                    list_->find(Name("sub.example.com."), true));
470
471
472
    }
}

473
TEST_F(ListTest, multiBestMatch) {
474
475
476
477
478
    // Run through all the multi-configurations
    for (size_t i(0); i < 4; ++ i) {
        SCOPED_TRACE(test_names[i]);
        multiConfiguration(i);
        // Something that is nowhere there
479
        EXPECT_TRUE(negative_result_ == list_->find(Name("org.")));
480
        // This one is there exactly.
481
482
        positiveResult(list_->find(Name("example.org")), ds_[0],
                       Name("example.org"), true, "Exact match");
483
        // This one too, but in a different data source.
484
485
        positiveResult(list_->find(Name("sub.example.org.")), ds_[1],
                       Name("sub.example.org"), true, "Subdomain match");
486
487
        // But this one is in neither data source. But it is a subdomain
        // of one of the zones in the first data source.
488
489
        positiveResult(list_->find(Name("sub.example.com.")), ds_[0],
                       Name("example.com."), false, "Subdomain in com");
490
    }
491
492
}

493
// Check the configuration is empty when the list is empty
494
TEST_F(ListTest, configureEmpty) {
495
    const ConstElementPtr elem(new ListElement);
496
    list_->configure(elem, true);
497
    EXPECT_TRUE(list_->getDataSources().empty());
498
499
    // Check the exact configuration is preserved
    EXPECT_EQ(elem, list_->getConfiguration());
500
501
502
}

// Check we can get multiple data sources and they are in the right order.
503
TEST_F(ListTest, configureMulti) {
504
    const ConstElementPtr elem(Element::fromJSON("["
505
506
507
508
509
510
511
512
513
514
515
        "{"
        "   \"type\": \"type1\","
        "   \"cache\": \"off\","
        "   \"params\": {}"
        "},"
        "{"
        "   \"type\": \"type2\","
        "   \"cache\": \"off\","
        "   \"params\": {}"
        "}]"
    ));
516
    list_->configure(elem, true);
517
    EXPECT_EQ(2, list_->getDataSources().size());
518
519
    checkDS(0, "type1", "{}", false);
    checkDS(1, "type2", "{}", false);
520
521
    // Check the exact configuration is preserved
    EXPECT_EQ(elem, list_->getConfiguration());
522
523
524
}

// Check we can pass whatever we want to the params
525
TEST_F(ListTest, configureParams) {
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
    const char* params[] = {
        "true",
        "false",
        "null",
        "\"hello\"",
        "42",
        "[]",
        "{}",
        NULL
    };
    for (const char** param(params); *param; ++param) {
        SCOPED_TRACE(*param);
        ConstElementPtr elem(Element::fromJSON(string("["
            "{"
            "   \"type\": \"t\","
            "   \"cache\": \"off\","
            "   \"params\": ") + *param +
            "}]"));
544
        list_->configure(elem, true);
545
        EXPECT_EQ(1, list_->getDataSources().size());
546
        checkDS(0, "t", *param, false);
547
548
549
    }
}

550
TEST_F(ListTest, wrongConfig) {
551
552
    const char* configs[] = {
        // A lot of stuff missing from there
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
553
        "[{\"type\": \"test_type\", \"params\": 13}, {}]",
554
555
556
557
558
        // Some bad types completely
        "{}",
        "true",
        "42",
        "null",
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
559
560
561
        "[{\"type\": \"test_type\", \"params\": 13}, true]",
        "[{\"type\": \"test_type\", \"params\": 13}, []]",
        "[{\"type\": \"test_type\", \"params\": 13}, 42]",
562
        // Bad type of type
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
563
564
565
566
567
        "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": 42}]",
        "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": true}]",
        "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": null}]",
        "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": []}]",
        "[{\"type\": \"test_type\", \"params\": 13}, {\"type\": {}}]",
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
        // Bad type of cache-enable
        "[{\"type\": \"test_type\", \"params\": 13}, "
         "{\"type\": \"x\", \"cache-enable\": 13, \"cache-zones\": []}]",
        "[{\"type\": \"test_type\", \"params\": 13}, "
         "{\"type\": \"x\", \"cache-enable\": \"xx\", \"cache-zones\": []}]",
        "[{\"type\": \"test_type\", \"params\": 13}, "
         "{\"type\": \"x\", \"cache-enable\": [], \"cache-zones\": []}]",
        "[{\"type\": \"test_type\", \"params\": 13}, "
         "{\"type\": \"x\", \"cache-enable\": {}, \"cache-zones\": []}]",
        "[{\"type\": \"test_type\", \"params\": 13}, "
         "{\"type\": \"x\", \"cache-enable\": null, \"cache-zones\": []}]",
        // Bad type of cache-zones
        "[{\"type\": \"test_type\", \"params\": 13}, "
         "{\"type\": \"x\", \"cache-enable\": true, \"cache-zones\": \"x\"}]",
        "[{\"type\": \"test_type\", \"params\": 13}, "
         "{\"type\": \"x\", \"cache-enable\": true, \"cache-zones\": true}]",
        "[{\"type\": \"test_type\", \"params\": 13}, "
         "{\"type\": \"x\", \"cache-enable\": true, \"cache-zones\": null}]",
        "[{\"type\": \"test_type\", \"params\": 13}, "
         "{\"type\": \"x\", \"cache-enable\": true, \"cache-zones\": 13}]",
        "[{\"type\": \"test_type\", \"params\": 13}, "
         "{\"type\": \"x\", \"cache-enable\": true, \"cache-zones\": {}}]",
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
        // Some bad inputs for MasterFiles special case

        // It must have the cache enabled
        "[{\"type\": \"test_type\", \"params\": 13}, "
         "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
         "\"params\": {}}]",
        // No cache-zones allowed here
        "[{\"type\": \"test_type\", \"params\": 13}, "
         "{\"type\": \"MasterFiles\", \"cache-enable\": true,"
         "\"param\": {}, \"cache-zones\": []}]",
        // Some bad types of params
        "[{\"type\": \"test_type\", \"params\": 13}, "
         "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
         "\"params\": []}]",
        "[{\"type\": \"test_type\", \"params\": 13}, "
         "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
         "\"params\": 13}]",
        "[{\"type\": \"test_type\", \"params\": 13}, "
         "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
         "\"params\": true}]",
        "[{\"type\": \"test_type\", \"params\": 13}, "
         "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
         "\"params\": null}]",
        "[{\"type\": \"test_type\", \"params\": 13}, "
         "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
         "\"params\": \"x\"}]",
        "[{\"type\": \"test_type\", \"params\": 13}, "
         "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
         "\"params\": {\".\": 13}}]",
        "[{\"type\": \"test_type\", \"params\": 13}, "
         "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
         "\"params\": {\".\": true}}]",
        "[{\"type\": \"test_type\", \"params\": 13}, "
         "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
         "\"params\": {\".\": null}}]",
        "[{\"type\": \"test_type\", \"params\": 13}, "
         "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
         "\"params\": {\".\": []}}]",
        "[{\"type\": \"test_type\", \"params\": 13}, "
         "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
         "\"params\": {\".\": {}}}]",
631
632
        NULL
    };
633
    // Put something inside to see it survives the exception
634
    list_->configure(config_elem_, true);
635
    checkDS(0, "test_type", "{}", false);
636
637
638
    for (const char** config(configs); *config; ++config) {
        SCOPED_TRACE(*config);
        ConstElementPtr elem(Element::fromJSON(*config));
639
        EXPECT_THROW(list_->configure(elem, true),
640
                     ConfigurableClientList::ConfigurationError);
641
        // Still untouched
642
        checkDS(0, "test_type", "{}", false);
643
        EXPECT_EQ(1, list_->getDataSources().size());
644
645
646
647
    }
}

// The param thing defaults to null. Cache is not used yet.
648
TEST_F(ListTest, defaults) {
649
    const ConstElementPtr elem(Element::fromJSON("["
650
651
652
        "{"
        "   \"type\": \"type1\""
        "}]"));
653
    list_->configure(elem, true);
654
    EXPECT_EQ(1, list_->getDataSources().size());
655
    checkDS(0, "type1", "null", false);
656
657
}

658
// Check we can call the configure multiple times, to change the configuration
659
TEST_F(ListTest, reconfigure) {
660
    const ConstElementPtr empty(new ListElement);
661
    list_->configure(config_elem_, true);
662
    checkDS(0, "test_type", "{}", false);
663
    list_->configure(empty, true);
664
    EXPECT_TRUE(list_->getDataSources().empty());
665
    list_->configure(config_elem_, true);
666
    checkDS(0, "test_type", "{}", false);
667
668
}

669
// Make sure the data source error exception from the factory is propagated
670
TEST_F(ListTest, dataSrcError) {
671
    const ConstElementPtr elem(Element::fromJSON("["
672
673
674
        "{"
        "   \"type\": \"error\""
        "}]"));
675
    list_->configure(config_elem_, true);
676
    checkDS(0, "test_type", "{}", false);
677
    EXPECT_THROW(list_->configure(elem, true), DataSourceError);
678
    checkDS(0, "test_type", "{}", false);
679
680
}

681
682
// Check we can get the cache
TEST_F(ListTest, configureCacheEmpty) {
683
    const ConstElementPtr elem(Element::fromJSON("["
684
685
686
687
688
689
690
691
692
693
694
695
696
        "{"
        "   \"type\": \"type1\","
        "   \"cache-enable\": true,"
        "   \"cache-zones\": [],"
        "   \"params\": {}"
        "},"
        "{"
        "   \"type\": \"type2\","
        "   \"cache-enable\": false,"
        "   \"cache-zones\": [],"
        "   \"params\": {}"
        "}]"
    ));
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
697
    list_->configure(elem, true);
698
699
700
701
702
703
704
    EXPECT_EQ(2, list_->getDataSources().size());
    checkDS(0, "type1", "{}", true);
    checkDS(1, "type2", "{}", false);
}

// But no cache if we disallow it globally
TEST_F(ListTest, configureCacheDisabled) {
705
    const ConstElementPtr elem(Element::fromJSON("["
706
707
708
709
710
711
712
713
714
715
716
717
718
        "{"
        "   \"type\": \"type1\","
        "   \"cache-enable\": true,"
        "   \"cache-zones\": [],"
        "   \"params\": {}"
        "},"
        "{"
        "   \"type\": \"type2\","
        "   \"cache-enable\": false,"
        "   \"cache-zones\": [],"
        "   \"params\": {}"
        "}]"
    ));
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
719
    list_->configure(elem, false);
720
721
722
723
724
    EXPECT_EQ(2, list_->getDataSources().size());
    checkDS(0, "type1", "{}", false);
    checkDS(1, "type2", "{}", false);
}

725
726
// Put some zones into the cache
TEST_F(ListTest, cacheZones) {
727
    const ConstElementPtr elem(Element::fromJSON("["
728
729
730
731
732
733
        "{"
        "   \"type\": \"type1\","
        "   \"cache-enable\": true,"
        "   \"cache-zones\": [\"example.org\", \"example.com\"],"
        "   \"params\": [\"example.org\", \"example.com\", \"exmaple.cz\"]"
        "}]"));
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
734
    list_->configure(elem, true);
735
736
737
738
739
740
741
742
743
    checkDS(0, "type1", "[\"example.org\", \"example.com\", \"exmaple.cz\"]",
            true);

    const shared_ptr<InMemoryClient> cache(list_->getDataSources()[0].cache_);
    EXPECT_EQ(2, cache->getZoneCount());

    EXPECT_EQ(result::SUCCESS, cache->findZone(Name("example.org")).code);
    EXPECT_EQ(result::SUCCESS, cache->findZone(Name("example.com")).code);
    EXPECT_EQ(result::NOTFOUND, cache->findZone(Name("example.cz")).code);
744
745
    EXPECT_EQ(RRClass::IN(),
              cache->findZone(Name("example.org")).zone_finder->getClass());
746
747
748
749
750
751
752
753
754

    // These are cached and answered from the cache
    positiveResult(list_->find(Name("example.com.")), ds_[0],
                   Name("example.com."), true, "com", true);
    positiveResult(list_->find(Name("example.org.")), ds_[0],
                   Name("example.org."), true, "org", true);
    positiveResult(list_->find(Name("sub.example.com.")), ds_[0],
                   Name("example.com."), false, "Subdomain of com", true);
    // For now, the ones not cached are ignored.
755
    EXPECT_TRUE(negative_result_ == list_->find(Name("example.cz.")));
756
757
}

758
759
760
// Check the caching handles misbehaviour from the data source and
// misconfiguration gracefully
TEST_F(ListTest, badCache) {
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
761
    list_->configure(config_elem_, true);
762
763
764
765
766
767
768
769
770
    checkDS(0, "test_type", "{}", false);
    // First, the zone is not in the data source
    const ConstElementPtr elem1(Element::fromJSON("["
        "{"
        "   \"type\": \"type1\","
        "   \"cache-enable\": true,"
        "   \"cache-zones\": [\"example.org\"],"
        "   \"params\": []"
        "}]"));
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
771
    EXPECT_THROW(list_->configure(elem1, true),
772
773
774
775
776
777
778
779
780
781
                 ConfigurableClientList::ConfigurationError);
    checkDS(0, "test_type", "{}", false);
    // Now, the zone doesn't give an iterator
    const ConstElementPtr elem2(Element::fromJSON("["
        "{"
        "   \"type\": \"type1\","
        "   \"cache-enable\": true,"
        "   \"cache-zones\": [\"noiter.org\"],"
        "   \"params\": [\"noiter.org\"]"
        "}]"));
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
782
    EXPECT_THROW(list_->configure(elem2, true), isc::NotImplemented);
783
784
785
786
787
788
789
790
791
    checkDS(0, "test_type", "{}", false);
    // Now, the zone returns NULL iterator
    const ConstElementPtr elem3(Element::fromJSON("["
        "{"
        "   \"type\": \"type1\","
        "   \"cache-enable\": true,"
        "   \"cache-zones\": [\"null.org\"],"
        "   \"params\": [\"null.org\"]"
        "}]"));
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
792
    EXPECT_THROW(list_->configure(elem3, true), isc::Unexpected);
793
794
795
796
797
798
799
800
    checkDS(0, "test_type", "{}", false);
    // The autodetection of zones is not enabled
    const ConstElementPtr elem4(Element::fromJSON("["
        "{"
        "   \"type\": \"type1\","
        "   \"cache-enable\": true,"
        "   \"params\": [\"example.org\"]"
        "}]"));
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
801
    EXPECT_THROW(list_->configure(elem4, true), isc::NotImplemented);
802
803
804
    checkDS(0, "test_type", "{}", false);
}

805
806
807
808
809
810
811
812
813
TEST_F(ListTest, masterFiles) {
    const ConstElementPtr elem(Element::fromJSON("["
        "{"
        "   \"type\": \"MasterFiles\","
        "   \"cache-enable\": true,"
        "   \"params\": {"
        "       \".\": \"" TEST_DATA_DIR "/root.zone\""
        "   }"
        "}]"));
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
814
    list_->configure(elem, true);
815
816

    // It has only the cache
817
818
    EXPECT_EQ(static_cast<const DataSourceClient*>(NULL),
              list_->getDataSources()[0].data_src_client_);
819
820
821
822
823
824

    // And it can search
    positiveResult(list_->find(Name(".")), ds_[0], Name("."), true, "com",
                   true);

    // If cache is not enabled, nothing is loaded
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
825
    list_->configure(elem, false);
826
827
828
    EXPECT_EQ(0, list_->getDataSources().size());
}

829
TEST_F(ListTest, BadMasterFile) {
830
831
    // Configuration should succeed, and the good zones in the list
    // below should be loaded. No bad zones should be loaded.
832
833
834
835
836
    const ConstElementPtr elem(Element::fromJSON("["
        "{"
        "   \"type\": \"MasterFiles\","
        "   \"cache-enable\": true,"
        "   \"params\": {"
837
838

        // good zone
839
        "       \"example.com.\": \"" TEST_DATA_DIR "/example.com.flattened\","
840
841

        // bad zone (empty file)
842
        "       \"example.net.\": \"" TEST_DATA_DIR "/example.net-empty\","
843
844

        // bad zone (data doesn't validate: see the file for details)
845
        "       \"example.edu.\": \"" TEST_DATA_DIR "/example.edu-broken\","
846
847

        // bad zone (file doesn't exist)
848
        "       \"example.info.\": \"" TEST_DATA_DIR "/example.info-nonexist\","
849
850

        // bad zone (data doesn't match the zone name)
851
        "       \"foo.bar.\": \"" TEST_DATA_DIR "/example.org.nsec3-signed\","
852
853

        // good zone
854
        "       \".\": \"" TEST_DATA_DIR "/root.zone\""
855

856
857
        "   }"
        "}]"));
858
859

    // this should not throw even if there are any zone loading errors.
860
861
    list_->configure(elem, true);

862
863
    positiveResult(list_->find(Name("example.com."), true), ds_[0],
                   Name("example.com."), true, "example.com", true);
864
865
    EXPECT_TRUE(negative_result_ == list_->find(Name("example.org."), true));
    EXPECT_TRUE(negative_result_ == list_->find(Name("foo.bar"), true));
866
867
868
    EXPECT_TRUE(negative_result_ == list_->find(Name("example.net."), true));
    EXPECT_TRUE(negative_result_ == list_->find(Name("example.edu."), true));
    EXPECT_TRUE(negative_result_ == list_->find(Name("example.info."), true));
869
    positiveResult(list_->find(Name(".")), ds_[0], Name("."), true, "root",
870
871
872
                   true);
}

873
874
// This allows us to test two versions of the reloading code
// (One by calling reload(), one by obtaining a ZoneWriter and
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
875
// playing with that). Once we deprecate reload(), we should revert this
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
// change and not use typed tests any more.
template<class UpdateType>
class ReloadTest : public ListTest {
public:
    ConfigurableClientList::ReloadResult doReload(const Name& origin);
};

// Version with calling reload()
class ReloadUpdateType {};
template<>
ConfigurableClientList::ReloadResult
ReloadTest<ReloadUpdateType>::doReload(const Name& origin) {
    return (list_->reload(origin));
};

891
892
893
894
895
896
897
// Version with the ZoneWriter
class WriterUpdateType {};
template<>
ConfigurableClientList::ReloadResult
ReloadTest<WriterUpdateType>::doReload(const Name& origin) {
    ConfigurableClientList::ZoneWriterPair
        result(list_->getCachedZoneWriter(origin));
898
    if (result.first == ConfigurableClientList::ZONE_SUCCESS) {
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
899
        // Can't use ASSERT_NE here, it would want to return(), which
900
901
902
903
904
905
        // it can't in non-void function.
        if (result.second) {
            result.second->load();
            result.second->install();
            result.second->cleanup();
        } else {
906
            ADD_FAILURE() << "getCachedZoneWriter returned ZONE_SUCCESS, "
907
908
909
910
911
912
913
914
                "but the writer is NULL";
        }
    } else {
        EXPECT_EQ(static_cast<memory::ZoneWriter*>(NULL),
                  result.second.get());
    }
    return (result.first);
}
915

916
917
918
// Typedefs for the GTEST guts to make it work
typedef ::testing::Types<ReloadUpdateType, WriterUpdateType> UpdateTypes;
TYPED_TEST_CASE(ReloadTest, UpdateTypes);
919

920
// Test we can reload a zone
921
922
TYPED_TEST(ReloadTest, reloadSuccess) {
    this->list_->configure(this->config_elem_zones_, true);
923
    const Name name("example.org");
924
    this->prepareCache(0, name);
925
926
    // The cache currently contains a tweaked version of zone, which doesn't
    // have apex NS.  So the lookup should result in NXRRSET.
927
    EXPECT_EQ(ZoneFinder::NXRRSET,
928
              this->list_->find(name).finder_->find(name, RRType::NS())->code);
929
    // Now reload the full zone. It should be there now.
930
    EXPECT_EQ(ConfigurableClientList::ZONE_SUCCESS, this->doReload(name));
931
    EXPECT_EQ(ZoneFinder::SUCCESS,
932
              this->list_->find(name).finder_->find(name, RRType::NS())->code);
933
934
935
}

// The cache is not enabled. The load should be rejected.
936
937
TYPED_TEST(ReloadTest, reloadNotEnabled) {
    this->list_->configure(this->config_elem_zones_, false);
938
    const Name name("example.org");
939
    // We put the cache in even when not enabled. This won't confuse the thing.
940
    this->prepareCache(0, name);
941
    // See the reloadSuccess test.  This should result in NXRRSET.
942
    EXPECT_EQ(ZoneFinder::NXRRSET,
943
              this->list_->find(name).finder_->find(name, RRType::NS())->code);
944
    // Now reload. It should reject it.
945
    EXPECT_EQ(ConfigurableClientList::CACHE_DISABLED, this->doReload(name));
946
947
    // Nothing changed here
    EXPECT_EQ(ZoneFinder::NXRRSET,
948
              this->list_->find(name).finder_->find(name, RRType::NS())->code);
949
950
951
}

// Test several cases when the zone does not exist
952
953
TYPED_TEST(ReloadTest, reloadNoSuchZone) {
    this->list_->configure(this->config_elem_zones_, true);
954
    const Name name("example.org");
955
956
957
    // We put the cache in even when not enabled. This won't confuse the
    // reload method, as that one looks at the real state of things, not
    // at the configuration.