client_list_unittest.cc 45.9 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/zone_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_a) :
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
            rrsets_.push_back(soa_);

92
93
94
95
96
97
98
99
100
101
102
103
104
            RRsetPtr rrset(new RRset(origin_, RRClass::IN(), RRType::NS(),
                                     RRTTL(3600)));
            rrset->addRdata(rdata::generic::NS(Name::ROOT_NAME()));
            rrsets_.push_back(rrset);

            if (include_a) {
                 // Dummy A rrset. This is used for checking zone data
                 // after reload.
                 rrset.reset(new RRset(Name("tstzonedata").concatenate(origin_),
                                       RRClass::IN(), RRType::A(),
                                       RRTTL(3600)));
                 rrset->addRdata(rdata::in::A("192.0.2.1"));
                 rrsets_.push_back(rrset);
105
            }
106

107
108
109
            rrsets_.push_back(ConstRRsetPtr());

            it_ = rrsets_.begin();
110
111
        }
        virtual isc::dns::ConstRRsetPtr getNextRRset() {
112
113
114
            ConstRRsetPtr result = *it_;
            ++it_;
            return (result);
115
116
117
118
119
120
        }
        virtual isc::dns::ConstRRsetPtr getSOA() const {
            return (soa_);
        }
    private:
        const Name origin_;
121
122
123
        const RRsetPtr soa_;
        std::vector<ConstRRsetPtr> rrsets_;
        std::vector<ConstRRsetPtr>::const_iterator it_;
124
    };
125
    // Constructor from a list of zones.
126
    MockDataSourceClient(const char* zone_names[]) :
127
        have_a_(true), use_baditerator_(true)
128
    {
129
        for (const char** zone(zone_names); *zone; ++zone) {
130
131
132
            zones.insert(Name(*zone));
        }
    }
133
134
    // Constructor from configuration. The list of zones will be empty, but
    // it will keep the configuration inside for further inspection.
135
136
    MockDataSourceClient(const string& type,
                         const ConstElementPtr& configuration) :
137
        type_(type),
138
        configuration_(configuration),
139
        have_a_(true), use_baditerator_(true)
140
    {
141
142
        EXPECT_NE("MasterFiles", type) << "MasterFiles is a special case "
            "and it never should be created as a data source client";
143
144
145
146
147
148
        if (configuration_->getType() == Element::list) {
            for (size_t i(0); i < configuration_->size(); ++i) {
                zones.insert(Name(configuration_->get(i)->stringValue()));
            }
        }
    }
149
    virtual FindResult findZone(const Name& name) const {
150
        if (zones.empty()) {
151
            return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
152
153
        }
        set<Name>::const_iterator it(zones.upper_bound(name));
154
155
156
        if (it == zones.begin()) {
            return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
        }
157
        --it;
158
159
160
161
162
163
164
165
166
        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()));
167
        }
168
169
170
171
172
173
174
175
176
177
178
    }
    // 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");
    }
179
    virtual ZoneIteratorPtr getIterator(const Name& name, bool) const {
180
        if (use_baditerator_ && name == Name("noiter.org")) {
181
            isc_throw(isc::NotImplemented, "Asked not to be implemented");
182
        } else if (use_baditerator_ && name == Name("null.org")) {
183
184
            return (ZoneIteratorPtr());
        } else {
185
186
            FindResult result(findZone(name));
            if (result.code == isc::datasrc::result::SUCCESS) {
187
                return (ZoneIteratorPtr(new Iterator(name, have_a_)));
188
189
190
            } else {
                isc_throw(DataSourceError, "No such zone");
            }
191
        }
192
    }
193
    void disableA() { have_a_ = false; }
194
    void disableBadIterator() { use_baditerator_ = false; }
195
196
    const string type_;
    const ConstElementPtr configuration_;
197
198
private:
    set<Name> zones;
199
    bool have_a_; // control the iterator behavior whether to include A record
200
    bool use_baditerator_; // whether to use bogus zone iterators for tests
201
202
};

203
204
205

// The test version is the same as the normal version. We, however, add
// some methods to dig directly in the internals, for the tests.
206
class TestedList : public ConfigurableClientList {
207
public:
208
209
210
    TestedList(const RRClass& rrclass) :
        ConfigurableClientList(rrclass)
    {}
211
    DataSources& getDataSources() { return (data_sources_); }
212
    // Overwrite the list's method to get a data source with given type
213
214
    // and configuration. We mock the data source and don't create the
    // container. This is just to avoid some complexity in the tests.
215
216
217
    virtual DataSourcePair getDataSourceClient(const string& type,
                                               const ConstElementPtr&
                                               configuration)
218
    {
219
220
221
        if (type == "error") {
            isc_throw(DataSourceError, "The error data source type");
        }
222
223
        shared_ptr<MockDataSourceClient>
            ds(new MockDataSourceClient(type, configuration));
224
        // Make sure it is deleted when the test list is deleted.
225
226
227
228
229
230
        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.
231
    vector<shared_ptr<MockDataSourceClient> > to_delete_;
232
};
233

234
const char* ds_zones[][3] = {
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
    {
        "example.org.",
        "example.com.",
        NULL
    },
    {
        "sub.example.org.",
        NULL, NULL
    },
    {
        NULL, NULL, NULL
    },
    {
        "sub.example.org.",
        NULL, NULL
    }
251
252
};

253
const size_t ds_count = (sizeof(ds_zones) / sizeof(*ds_zones));
254

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

    // 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.
296
        mock_client.disableA();
297
298
299
300
        mock_client.disableBadIterator();

        // Create cache from the temporary data source, and push it to the
        // client list.
301
302
        const shared_ptr<InMemoryClient> cache(
            new InMemoryClient(ztable_segment_, rrclass_));
303
304
305
306
307
        cache->load(zone, *mock_client.getIterator(zone, false));

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

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

389
// Test the test itself
390
TEST_F(ListTest, selfTest) {
391
392
393
394
395
    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);
396
397
    EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("aaa")).code);
    EXPECT_EQ(result::NOTFOUND, ds_[0]->findZone(Name("zzz")).code);
398
399
    // Nothing to keep alive here.
    EXPECT_EQ(shared_ptr<ClientList::FindResult::LifeKeeper>(),
400
                  negative_result_.life_keeper_);
401
402
}

403
404
405
// Test the list we create with empty configuration is, in fact, empty
TEST_F(ListTest, emptyList) {
    EXPECT_TRUE(list_->getDataSources().empty());
406
407
}

408
// Check the values returned by a find on an empty list. It should be
409
410
// a negative answer (nothing found) no matter if we want an exact or inexact
// match.
411
TEST_F(ListTest, emptySearch) {
412
    // No matter what we try, we don't get an answer.
413
414
415

    // Note: we don't have operator<< for the result class, so we cannot use
    // EXPECT_EQ.  Same for other similar cases.
416
417
418
419
420
421
422
423
    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));
424
425
}

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

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

455
const char* const test_names[] = {
456
457
458
459
460
461
    "Sub second",
    "Sub first",
    "With empty",
    "With a duplicity"
};

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

481
TEST_F(ListTest, multiBestMatch) {
482
483
484
485
486
    // 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
487
        EXPECT_TRUE(negative_result_ == list_->find(Name("org.")));
488
        // This one is there exactly.
489
490
        positiveResult(list_->find(Name("example.org")), ds_[0],
                       Name("example.org"), true, "Exact match");
491
        // This one too, but in a different data source.
492
493
        positiveResult(list_->find(Name("sub.example.org.")), ds_[1],
                       Name("sub.example.org"), true, "Subdomain match");
494
495
        // But this one is in neither data source. But it is a subdomain
        // of one of the zones in the first data source.
496
497
        positiveResult(list_->find(Name("sub.example.com.")), ds_[0],
                       Name("example.com."), false, "Subdomain in com");
498
    }
499
500
}

501
// Check the configuration is empty when the list is empty
502
TEST_F(ListTest, configureEmpty) {
503
    const ConstElementPtr elem(new ListElement);
504
    list_->configure(elem, true);
505
    EXPECT_TRUE(list_->getDataSources().empty());
506
507
    // Check the exact configuration is preserved
    EXPECT_EQ(elem, list_->getConfiguration());
508
509
510
}

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

// Check we can pass whatever we want to the params
533
TEST_F(ListTest, configureParams) {
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
    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\","
549
            "   \"cache-enable\": false,"
550
551
            "   \"params\": ") + *param +
            "}]"));
552
        list_->configure(elem, true);
553
        EXPECT_EQ(1, list_->getDataSources().size());
554
        checkDS(0, "t", *param, false);
555
556
557
    }
}

558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
TEST_F(ListTest, status) {
    EXPECT_TRUE(list_->getStatus().empty());
    const ConstElementPtr elem(Element::fromJSON("["
        "{"
        "   \"type\": \"type1\","
        "   \"cache-enable\": false,"
        "   \"params\": {}"
        "},"
        "{"
        "   \"type\": \"type2\","
        "   \"cache-enable\": true,"
        "   \"cache-zones\": [],"
        "   \"name\": \"Test name\","
        "   \"params\": {}"
        "}]"
    ));
    list_->configure(elem, true);
    const vector<DataSourceStatus> statuses(list_->getStatus());
    ASSERT_EQ(2, statuses.size());
    EXPECT_EQ("type1", statuses[0].getName());
578
    EXPECT_EQ(SEGMENT_UNUSED, statuses[0].getSegmentState());
579
    EXPECT_THROW(statuses[0].getSegmentType(), isc::InvalidOperation);
580
    EXPECT_EQ("Test name", statuses[1].getName());
581
    EXPECT_EQ(SEGMENT_INUSE, statuses[1].getSegmentState());
582
    EXPECT_EQ(SEGMENT_LOCAL, statuses[1].getSegmentType());
583
584
}

585
TEST_F(ListTest, wrongConfig) {
586
587
    const char* configs[] = {
        // A lot of stuff missing from there
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
588
        "[{\"type\": \"test_type\", \"params\": 13}, {}]",
589
590
591
592
593
        // Some bad types completely
        "{}",
        "true",
        "42",
        "null",
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
594
595
596
        "[{\"type\": \"test_type\", \"params\": 13}, true]",
        "[{\"type\": \"test_type\", \"params\": 13}, []]",
        "[{\"type\": \"test_type\", \"params\": 13}, 42]",
597
        // Bad type of type
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
598
599
600
601
602
        "[{\"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\": {}}]",
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
        // 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\": {}}]",
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
        // 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\": {\".\": {}}}]",
666
667
        NULL
    };
668
    // Put something inside to see it survives the exception
669
    list_->configure(config_elem_, true);
670
    checkDS(0, "test_type", "{}", false);
671
672
673
    for (const char** config(configs); *config; ++config) {
        SCOPED_TRACE(*config);
        ConstElementPtr elem(Element::fromJSON(*config));
674
        EXPECT_THROW(list_->configure(elem, true),
675
                     ConfigurableClientList::ConfigurationError);
676
        // Still untouched
677
        checkDS(0, "test_type", "{}", false);
678
        EXPECT_EQ(1, list_->getDataSources().size());
679
680
681
682
    }
}

// The param thing defaults to null. Cache is not used yet.
683
TEST_F(ListTest, defaults) {
684
    const ConstElementPtr elem(Element::fromJSON("["
685
686
687
        "{"
        "   \"type\": \"type1\""
        "}]"));
688
    list_->configure(elem, true);
689
    EXPECT_EQ(1, list_->getDataSources().size());
690
    checkDS(0, "type1", "null", false);
691
692
}

693
// Check we can call the configure multiple times, to change the configuration
694
TEST_F(ListTest, reconfigure) {
695
    const ConstElementPtr empty(new ListElement);
696
    list_->configure(config_elem_, true);
697
    checkDS(0, "test_type", "{}", false);
698
    list_->configure(empty, true);
699
    EXPECT_TRUE(list_->getDataSources().empty());
700
    list_->configure(config_elem_, true);
701
    checkDS(0, "test_type", "{}", false);
702
703
}

704
// Make sure the data source error exception from the factory is propagated
705
TEST_F(ListTest, dataSrcError) {
706
    const ConstElementPtr elem(Element::fromJSON("["
707
708
709
        "{"
        "   \"type\": \"error\""
        "}]"));
710
    list_->configure(config_elem_, true);
711
    checkDS(0, "test_type", "{}", false);
712
    EXPECT_THROW(list_->configure(elem, true), DataSourceError);
713
    checkDS(0, "test_type", "{}", false);
714
715
}

716
717
// Check we can get the cache
TEST_F(ListTest, configureCacheEmpty) {
718
    const ConstElementPtr elem(Element::fromJSON("["
719
720
721
722
723
724
725
726
727
728
729
730
731
        "{"
        "   \"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
732
    list_->configure(elem, true);
733
734
735
736
737
738
739
    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) {
740
    const ConstElementPtr elem(Element::fromJSON("["
741
742
743
744
745
746
747
748
749
750
751
752
753
        "{"
        "   \"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
754
    list_->configure(elem, false);
755
756
757
758
759
    EXPECT_EQ(2, list_->getDataSources().size());
    checkDS(0, "type1", "{}", false);
    checkDS(1, "type2", "{}", false);
}

760
761
// Put some zones into the cache
TEST_F(ListTest, cacheZones) {
762
    const ConstElementPtr elem(Element::fromJSON("["
763
764
765
766
767
768
        "{"
        "   \"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
769
    list_->configure(elem, true);
770
771
772
773
774
775
776
777
778
    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);
779
780
    EXPECT_EQ(RRClass::IN(),
              cache->findZone(Name("example.org")).zone_finder->getClass());
781
782
783
784
785
786
787
788
789

    // 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.
790
    EXPECT_TRUE(negative_result_ == list_->find(Name("example.cz.")));
791
792
}

793
794
795
// 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
796
    list_->configure(config_elem_, true);
797
798
799
800
801
802
803
804
805
    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
806
    EXPECT_THROW(list_->configure(elem1, true),
807
808
809
810
811
812
813
814
815
816
                 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
817
    EXPECT_THROW(list_->configure(elem2, true), isc::NotImplemented);
818
819
820
821
822
823
824
825
826
    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
827
    EXPECT_THROW(list_->configure(elem3, true), isc::Unexpected);
828
829
830
831
832
833
834
835
    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
836
    EXPECT_THROW(list_->configure(elem4, true), isc::NotImplemented);
837
838
839
    checkDS(0, "test_type", "{}", false);
}

840
841
842
843
844
845
846
847
848
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
849
    list_->configure(elem, true);
850
851

    // It has only the cache
852
853
    EXPECT_EQ(static_cast<const DataSourceClient*>(NULL),
              list_->getDataSources()[0].data_src_client_);
854
855
856
857
858
859

    // 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
860
    list_->configure(elem, false);
861
862
863
    EXPECT_EQ(0, list_->getDataSources().size());
}

864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
// Test the names are set correctly and collission is detected.
TEST_F(ListTest, names) {
    // Explicit name
    const ConstElementPtr elem1(Element::fromJSON("["
        "{"
        "   \"type\": \"MasterFiles\","
        "   \"cache-enable\": true,"
        "   \"params\": {"
        "       \".\": \"" TEST_DATA_DIR "/root.zone\""
        "   },"
        "   \"name\": \"Whatever\""
        "}]"));
    list_->configure(elem1, true);
    EXPECT_EQ("Whatever", list_->getDataSources()[0].name_);

    // Default name
    const ConstElementPtr elem2(Element::fromJSON("["
        "{"
        "   \"type\": \"MasterFiles\","
        "   \"cache-enable\": true,"
        "   \"params\": {"
        "       \".\": \"" TEST_DATA_DIR "/root.zone\""
        "   }"
        "}]"));
    list_->configure(elem2, true);
    EXPECT_EQ("MasterFiles", list_->getDataSources()[0].name_);

    // Collission
    const ConstElementPtr elem3(Element::fromJSON("["
        "{"
        "   \"type\": \"MasterFiles\","
        "   \"cache-enable\": true,"
        "   \"params\": {"
        "       \".\": \"" TEST_DATA_DIR "/root.zone\""
        "   }"
        "},"
        "{"
        "   \"type\": \"MasterFiles\","
        "   \"cache-enable\": true,"
        "   \"params\": {"
        "       \".\": \"" TEST_DATA_DIR "/root.zone\""
        "   },"
        "   \"name\": \"MasterFiles\""
        "}]"));
    EXPECT_THROW(list_->configure(elem3, true),
                 ConfigurableClientList::ConfigurationError);
}

912
TEST_F(ListTest, BadMasterFile) {
913
914
    // Configuration should succeed, and the good zones in the list
    // below should be loaded. No bad zones should be loaded.
915
916
917
918
919
    const ConstElementPtr elem(Element::fromJSON("["
        "{"
        "   \"type\": \"MasterFiles\","
        "   \"cache-enable\": true,"
        "   \"params\": {"
920
921

        // good zone
922
        "       \"example.com.\": \"" TEST_DATA_DIR "/example.com.flattened\","
923
924

        // bad zone (empty file)
925
        "       \"example.net.\": \"" TEST_DATA_DIR "/example.net-empty\","
926
927

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

        // bad zone (file doesn't exist)
931
        "       \"example.info.\": \"" TEST_DATA_DIR "/example.info-nonexist\","
932
933

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

        // good zone
937
        "       \".\": \"" TEST_DATA_DIR "/root.zone\""
938

939
940
        "   }"
        "}]"));
941

942
943
944
945
946
    EXPECT_NO_THROW({
        // This should not throw even if there are any zone loading
        // errors.
        list_->configure(elem, true);
    });
947

948
949
    positiveResult(list_->find(Name("example.com."), true), ds_[0],
                   Name("example.com."), true, "example.com", true);
950
951
    EXPECT_TRUE(negative_result_ == list_->find(Name("example.org."), true));
    EXPECT_TRUE(negative_result_ == list_->find(Name("foo.bar"), true));
952
953
954
    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));
955
    positiveResult(list_->find(Name(".")), ds_[0], Name("."), true, "root",
956
957
958
                   true);
}

959
960
// 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
961
// playing with that). Once we deprecate reload(), we should revert this
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
// 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));
};

977
978
979
980
981
982
983
// Version with the ZoneWriter
class WriterUpdateType {};
template<>
ConfigurableClientList::ReloadResult
ReloadTest<WriterUpdateType>::doReload(const Name& origin) {
    ConfigurableClientList::ZoneWriterPair
        result(list_->getCachedZoneWriter(origin));
984
    if (result.first == ConfigurableClientList::ZONE_SUCCESS) {
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
985
        // Can't use ASSERT_NE here, it would want to return(), which
986
987
988
989
990
991
        // it can't in non-void function.