client_list.cc 17.8 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

16
#include "client_list.h"
17
#include "client.h"
18
#include "factory.h"
19
#include "memory/memory_client.h"
20
21
#include "memory/zone_table_segment.h"
#include "memory/zone_writer.h"
22
#include "memory/zone_data_loader.h"
23
#include "logger.h"
24
#include <dns/masterload.h>
25
#include <util/memory_segment_local.h>
26
27
28

#include <memory>
#include <boost/foreach.hpp>
29
#include <boost/bind.hpp>
30
31

using namespace isc::data;
32
using namespace isc::dns;
33
using namespace std;
34
using isc::util::MemorySegment;
35
36
37
using boost::lexical_cast;
using boost::shared_ptr;
using boost::dynamic_pointer_cast;
38
using isc::datasrc::memory::InMemoryClient;
39
40
41
42

namespace isc {
namespace datasrc {

43
44
ConfigurableClientList::DataSourceInfo::DataSourceInfo(
    DataSourceClient* data_src_client,
45
46
    const DataSourceClientContainerPtr& container, bool has_cache,
    const RRClass& rrclass, MemorySegment& mem_sgmt) :
47
48
49
    data_src_client_(data_src_client),
    container_(container)
{
50
    if (has_cache) {
51
        cache_.reset(new InMemoryClient(mem_sgmt, rrclass));
52
53
54
    }
}

55
56
ConfigurableClientList::DataSourceInfo::DataSourceInfo(
    const RRClass& rrclass, MemorySegment& mem_sgmt, bool has_cache) :
57
58
59
    data_src_client_(NULL)
{
    if (has_cache) {
60
        cache_.reset(new InMemoryClient(mem_sgmt, rrclass));
61
62
63
    }
}

64
65
66
67
68
const DataSourceClient*
ConfigurableClientList::DataSourceInfo::getCacheClient() const {
    return (cache_.get());
}

69
70
71
72
73
74
75
ConfigurableClientList::ConfigurableClientList(const RRClass& rrclass) :
    rrclass_(rrclass),
    mem_sgmt_(new util::MemorySegmentLocal),
    configuration_(new isc::data::ListElement),
    allow_cache_(false)
{}

76
77
78
79
80
81
82
83
84
85
ConfigurableClientList::~ConfigurableClientList() {
    // Explicitly clear the contained data source clients, and check memory
    // leak.  assert() (with abort on failure) may be too harsh, but
    // it's probably better to find more leaks initially.  Once it's stabilized
    // we should probably revisit it.

    data_sources_.clear();
    assert(mem_sgmt_->allMemoryDeallocated());
}

86
void
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
87
88
89
ConfigurableClientList::configure(const ConstElementPtr& config,
                                  bool allow_cache)
{
90
91
92
    if (!config) {
        isc_throw(isc::BadValue, "NULL configuration passed");
    }
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
93
    // TODO: Implement recycling from the old configuration.
94
95
96
    size_t i(0); // Outside of the try to be able to access it in the catch
    try {
        vector<DataSourceInfo> new_data_sources;
97
        for (; i < config->size(); ++i) {
98
            // Extract the parameters
99
            const ConstElementPtr dconf(config->get(i));
100
101
102
103
104
105
106
107
108
109
            const ConstElementPtr typeElem(dconf->get("type"));
            if (typeElem == ConstElementPtr()) {
                isc_throw(ConfigurationError, "Missing the type option in "
                          "data source no " << i);
            }
            const string type(typeElem->stringValue());
            ConstElementPtr paramConf(dconf->get("params"));
            if (paramConf == ConstElementPtr()) {
                paramConf.reset(new NullElement());
            }
110
111
112
            const bool want_cache(allow_cache &&
                                  dconf->contains("cache-enable") &&
                                  dconf->get("cache-enable")->boolValue());
113
114
115
116
117

            if (type == "MasterFiles") {
                // In case the cache is not allowed, we just skip the master
                // files (at least for now)
                if (!allow_cache) {
118
119
120
121
122
123
124
125
126
                    // We're not going to load these zones. Issue warnings about it.
                    const map<string, ConstElementPtr>
                        zones_files(paramConf->mapValue());
                    for (map<string, ConstElementPtr>::const_iterator
                         it(zones_files.begin()); it != zones_files.end();
                         ++it) {
                        LOG_WARN(logger, DATASRC_LIST_NOT_CACHED).
                            arg(it->first).arg(rrclass_);
                    }
127
128
129
130
131
132
                    continue;
                }
                if (!want_cache) {
                    isc_throw(ConfigurationError, "The cache must be enabled "
                              "for the MasterFiles type");
                }
133
134
                new_data_sources.push_back(DataSourceInfo(rrclass_, *mem_sgmt_,
                                                          true));
135
136
137
138
139
140
            } else {
                // Ask the factory to create the data source for us
                const DataSourcePair ds(this->getDataSourceClient(type,
                                                                  paramConf));
                // And put it into the vector
                new_data_sources.push_back(DataSourceInfo(ds.first, ds.second,
141
142
                                                          want_cache, rrclass_,
                                                          *mem_sgmt_));
143
144
            }

145
            if (want_cache) {
146
                if (!dconf->contains("cache-zones") && type != "MasterFiles") {
147
148
149
                    isc_throw(isc::NotImplemented, "Auto-detection of zones "
                              "to cache is not yet implemented, supply "
                              "cache-zones parameter");
150
151
                    // TODO: Auto-detect list of all zones in the
                    // data source.
152
                }
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170

                // List the zones we are loading
                vector<string> zones_origins;
                if (type == "MasterFiles") {
                    const map<string, ConstElementPtr>
                        zones_files(paramConf->mapValue());
                    for (map<string, ConstElementPtr>::const_iterator
                         it(zones_files.begin()); it != zones_files.end();
                         ++it) {
                        zones_origins.push_back(it->first);
                    }
                } else {
                    const ConstElementPtr zones(dconf->get("cache-zones"));
                    for (size_t i(0); i < zones->size(); ++i) {
                        zones_origins.push_back(zones->get(i)->stringValue());
                    }
                }

171
172
173
174
                const shared_ptr<InMemoryClient>
                    cache(new_data_sources.back().cache_);
                const DataSourceClient* const
                    client(new_data_sources.back().data_src_client_);
175
176
                for (vector<string>::const_iterator it(zones_origins.begin());
                     it != zones_origins.end(); ++it) {
177
178
179
                    const Name origin(*it);
                    if (type == "MasterFiles") {
                        try {
180
181
                            cache->load(origin,
                                        paramConf->get(*it)->stringValue());
182
183
184
                        } catch (const isc::dns::MasterLoadError& mle) {
                            LOG_ERROR(logger, DATASRC_MASTERLOAD_ERROR)
                                .arg(mle.what());
185
                        }
186
187
188
189
190
191
192
193
194
195
196
197
198
                    } else {
                        ZoneIteratorPtr iterator;
                        try {
                            iterator = client->getIterator(origin);
                        } catch (const DataSourceError&) {
                            isc_throw(ConfigurationError, "Unable to "
                                      "cache non-existent zone "
                                      << origin);
                        }
                        if (!iterator) {
                            isc_throw(isc::Unexpected, "Got NULL iterator "
                                      "for zone " << origin);
                        }
199
                        cache->load(origin, *iterator);
200
                    }
201
202
                }
            }
203
204
205
206
207
        }
        // If everything is OK up until now, we have the new configuration
        // ready. So just put it there and let the old one die when we exit
        // the scope.
        data_sources_.swap(new_data_sources);
208
        configuration_ = config;
209
        allow_cache_ = allow_cache;
210
    } catch (const TypeError& te) {
211
212
213
        isc_throw(ConfigurationError, "Malformed configuration at data source "
                  "no. " << i << ": " << te.what());
    }
214
215
}

216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
namespace {

class CacheKeeper : public ClientList::FindResult::LifeKeeper {
public:
    CacheKeeper(const boost::shared_ptr<InMemoryClient>& cache) :
        cache_(cache)
    {}
private:
    const boost::shared_ptr<InMemoryClient> cache_;
};

class ContainerKeeper : public ClientList::FindResult::LifeKeeper {
public:
    ContainerKeeper(const DataSourceClientContainerPtr& container) :
        container_(container)
    {}
private:
    const DataSourceClientContainerPtr container_;
};

boost::shared_ptr<ClientList::FindResult::LifeKeeper>
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
237
238
239
240
241
genKeeper(const ConfigurableClientList::DataSourceInfo* info) {
    if (info == NULL) {
        return (boost::shared_ptr<ClientList::FindResult::LifeKeeper>());
    }
    if (info->cache_) {
242
        return (boost::shared_ptr<ClientList::FindResult::LifeKeeper>(
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
243
            new CacheKeeper(info->cache_)));
244
245
    } else {
        return (boost::shared_ptr<ClientList::FindResult::LifeKeeper>(
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
246
            new ContainerKeeper(info->container_)));
247
248
249
250
251
    }
}

}

252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
// We have this class as a temporary storage, as the FindResult can't be
// assigned.
struct ConfigurableClientList::MutableResult {
    MutableResult() :
        datasrc_client(NULL),
        matched_labels(0),
        matched(false),
        exact(false),
        info(NULL)
    {}
    DataSourceClient* datasrc_client;
    ZoneFinderPtr finder;
    uint8_t matched_labels;
    bool matched;
    bool exact;
    const DataSourceInfo* info;
    operator FindResult() const {
        // Conversion to the right result.
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
270
        return (FindResult(datasrc_client, finder, exact, genKeeper(info)));
271
272
273
    }
};

274
275
ClientList::FindResult
ConfigurableClientList::find(const dns::Name& name, bool want_exact_match,
276
                             bool want_finder) const
277
{
278
279
280
281
    MutableResult result;
    findInternal(result, name, want_exact_match, want_finder);
    return (result);
}
282

283
284
285
286
287
void
ConfigurableClientList::findInternal(MutableResult& candidate,
                                     const dns::Name& name,
                                     bool want_exact_match, bool) const
{
288
    BOOST_FOREACH(const DataSourceInfo& info, data_sources_) {
289
290
291
292
293
        DataSourceClient* client(info.cache_ ? info.cache_.get() :
                                 info.data_src_client_);
        const DataSourceClient::FindResult result(client->findZone(name));
        // TODO: Once we mark the zones that are not loaded, but are present
        // in the data source somehow, check them too.
294
        switch (result.code) {
295
            case result::SUCCESS:
296
297
                // If we found an exact match, we have no hope to getting
                // a better one. Stop right here.
298
299
300

                // TODO: In case we have only the datasource and not the finder
                // and the need_updater parameter is true, get the zone there.
301
302
303
304
305
306
                candidate.datasrc_client = client;
                candidate.finder = result.zone_finder;
                candidate.matched = true;
                candidate.exact = true;
                candidate.info = &info;
                return;
307
            case result::PARTIALMATCH:
308
309
310
                if (!want_exact_match) {
                    // In case we have a partial match, check if it is better
                    // than what we have. If so, replace it.
311
312
313
314
315
316
317
318
319
320
321
322
323
324
                    //
                    // We don't need the labels at the first partial match,
                    // we have nothing to compare with. So we don't get it
                    // (as a performance) and hope we will not need it at all.
                    const uint8_t labels(candidate.matched ?
                        result.zone_finder->getOrigin().getLabelCount() : 0);
                    if (candidate.matched && candidate.matched_labels == 0) {
                        // But if the hope turns out to be false, we need to
                        // compute it for the first match anyway.
                        candidate.matched_labels = candidate.finder->
                            getOrigin().getLabelCount();
                    }
                    if (labels > candidate.matched_labels ||
                        !candidate.matched) {
325
                        // This one is strictly better. Replace it.
326
                        candidate.datasrc_client = client;
327
328
                        candidate.finder = result.zone_finder;
                        candidate.matched_labels = labels;
329
                        candidate.matched = true;
330
                        candidate.info = &info;
331
332
333
                    }
                }
                break;
334
            default:
335
                // Nothing found, nothing to do.
336
                break;
337
338
        }
    }
339
340
341

    // TODO: In case we have only the datasource and not the finder
    // and the need_updater parameter is true, get the zone there.
342
}
343

344
345
// We still provide this method for backward compatibility. But to not have
// duplicate code, it is a thin wrapper around getCachedZoneWriter only.
346
347
ConfigurableClientList::ReloadResult
ConfigurableClientList::reload(const Name& name) {
348
349
350
351
352
    ZoneWriterPair result(getCachedZoneWriter(name));
    if (result.second) {
        result.second->load();
        result.second->install();
        result.second->cleanup();
353
    }
354
    return (result.first);
355
356
}

357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
namespace {

// We would like to use boost::bind for this. However, the loadZoneData takes
// a reference, while we have a shared pointer to the iterator -- and we need
// to keep it alive as long as the ZoneWriter is alive. Therefore we can't
// really just dereference it and pass it, since it would get destroyed once
// the getCachedZoneWriter would end. This class holds the shared pointer
// alive, otherwise is mostly simple.
//
// It might be doable with nested boost::bind, but it would probably look
// more awkward and complicated than this.
class IteratorLoader {
public:
    IteratorLoader(const RRClass& rrclass, const Name& name,
                   const ZoneIteratorPtr& iterator) :
        rrclass_(rrclass),
        name_(name),
        iterator_(iterator)
    {}
    memory::ZoneData* operator()(util::MemorySegment& segment) {
        return (memory::loadZoneData(segment, rrclass_, name_, *iterator_));
    }
private:
    const RRClass rrclass_;
    const Name name_;
    ZoneIteratorPtr iterator_;
};

// We can't use the loadZoneData function directly in boost::bind, since
// it is overloaded and the compiler can't choose the correct version
// reliably and fails. So we simply wrap it into an unique name.
memory::ZoneData*
loadZoneDataFromFile(util::MemorySegment& segment, const RRClass& rrclass,
                     const Name& name, const string& filename)
{
    return (memory::loadZoneData(segment, rrclass, name, filename));
}

}

397
ConfigurableClientList::ZoneWriterPair
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
ConfigurableClientList::getCachedZoneWriter(const Name& name) {
    if (!allow_cache_) {
        return (ZoneWriterPair(CACHE_DISABLED, ZoneWriterPtr()));
    }
    // Try to find the correct zone.
    MutableResult result;
    findInternal(result, name, true, true);
    if (!result.finder) {
        return (ZoneWriterPair(ZONE_NOT_FOUND, ZoneWriterPtr()));
    }
    // Try to get the in-memory cache for the zone. If there's none,
    // we can't provide the result.
    if (!result.info->cache_) {
        return (ZoneWriterPair(ZONE_NOT_CACHED, ZoneWriterPtr()));
    }
    memory::LoadAction load_action;
    DataSourceClient* client(result.info->data_src_client_);
    if (client) {
        // Now finally provide the writer.
        // If it does not exist in client,
        // DataSourceError is thrown, which is exactly the result what we
        // want, so no need to handle it.
        ZoneIteratorPtr iterator(client->getIterator(name));
        if (!iterator) {
            isc_throw(isc::Unexpected, "Null iterator from " << name);
        }
424
425
426
        // And wrap the iterator into the correct functor (which
        // keeps it alive as long as it is needed).
        load_action = IteratorLoader(rrclass_, name, iterator);
427
428
429
430
431
432
433
    } else {
        // The MasterFiles special case
        const string filename(result.info->cache_->getFileName(name));
        if (filename.empty()) {
            isc_throw(isc::Unexpected, "Confused about missing both filename "
                      "and data source");
        }
434
435
436
        // boost::bind is enough here.
        load_action = boost::bind(loadZoneDataFromFile, _1, rrclass_, name,
                                  filename);
437
438
439
440
    }
    return (ZoneWriterPair(ZONE_RELOADED,
                           ZoneWriterPtr(result.info->cache_->getZoneTableSegment().
                                         getZoneWriter(load_action, name, rrclass_))));
441
442
}

443
444
445
// NOTE: This function is not tested, it would be complicated. However, the
// purpose of the function is to provide a very thin wrapper to be able to
// replace the call to DataSourceClientContainer constructor in tests.
446
ConfigurableClientList::DataSourcePair
447
448
449
ConfigurableClientList::getDataSourceClient(const string& type,
                                            const ConstElementPtr&
                                            configuration)
450
451
452
453
454
455
{
    DataSourceClientContainerPtr
        container(new DataSourceClientContainer(type, configuration));
    return (DataSourcePair(&container->getInstance(), container));
}

456
457
}
}