Unverified Commit 70f71c54 authored by Michal 'vorner' Vaner's avatar Michal 'vorner' Vaner
Browse files

Merge #2377

The isc::dns::MasterLoader class and its basic implementation. Further handling
is going to be added in future branches, this handles just the simplest form of
master files.
parents 550a5e0d 92ac412b
......@@ -48,6 +48,19 @@ logWarning(const isc::dns::Name& name, const isc::dns::RRClass& rrclass,
arg(name).arg(rrclass).arg(reason);
}
void
addRR(const isc::dns::Name& name, const isc::dns::RRClass& rrclass,
const isc::dns::RRType& type, const isc::dns::RRTTL& ttl,
const isc::dns::rdata::RdataPtr& data, ZoneUpdater* updater)
{
// We get description of one RR. The updater takes RRset, so we
// wrap it up and push there. It should collate the RRsets of the
// same name and type together, since the addRRset should "merge".
isc::dns::BasicRRset rrset(name, rrclass, type, ttl);
rrset.addRdata(data);
updater->addRRset(rrset);
}
}
isc::dns::MasterLoaderCallbacks
......@@ -61,12 +74,9 @@ createMasterLoaderCallbacks(const isc::dns::Name& name,
rrclass, _1, _2, _3)));
}
isc::dns::AddRRsetCallback
isc::dns::AddRRCallback
createMasterLoaderAddCallback(ZoneUpdater& updater) {
return (boost::bind(&ZoneUpdater::addRRset, &updater,
// The callback provides a shared pointer, we
// need the object. This bind unpacks the object.
boost::bind(&isc::dns::RRsetPtr::operator*, _1)));
return (boost::bind(addRR, _1, _2, _3, _4, _5, &updater));
}
}
......
......@@ -58,7 +58,7 @@ createMasterLoaderCallbacks(const isc::dns::Name& name,
/// \param updater The zone updater to use.
/// \return The callback to be passed to MasterLoader.
/// \throw std::bad_alloc when allocation fails.
isc::dns::AddRRsetCallback
isc::dns::AddRRCallback
createMasterLoaderAddCallback(ZoneUpdater& updater);
}
......
......@@ -18,6 +18,9 @@
#include <dns/rrset.h>
#include <dns/rrclass.h>
#include <dns/rrttl.h>
#include <dns/rdata.h>
#include <testutils/dnsmessage_test.h>
#include <exceptions/exceptions.h>
......@@ -40,8 +43,21 @@ public:
// the correct ones, according to a predefined set in a list.
virtual void addRRset(const isc::dns::AbstractRRset& rrset) {
ASSERT_FALSE(expected_rrsets_.empty());
// In our tests, pointer equality is enough.
EXPECT_EQ(expected_rrsets_.front().get(), &rrset);
// As the rrsetCheck requires a shared pointer, we need to create
// a copy.
isc::dns::RRsetPtr copy(new isc::dns::BasicRRset(rrset.getName(),
rrset.getClass(),
rrset.getType(),
rrset.getTTL()));
EXPECT_FALSE(rrset.getRRsig()) << "Unexpected RRSIG on rrset, not "
"copying. Following check will likely fail as a result.";
for (isc::dns::RdataIteratorPtr it(rrset.getRdataIterator());
!it->isLast(); it->next()) {
copy->addRdata(it->getCurrent());
}
isc::testutils::rrsetCheck(expected_rrsets_.front(), copy);
// And remove this RRset, as it has been used.
expected_rrsets_.pop_front();
}
......@@ -67,14 +83,22 @@ protected:
isc::dns::RRClass::IN(), &ok_))
{}
// Generate a new RRset, put it to the updater and return it.
isc::dns::RRsetPtr generateRRset() {
void generateRRset(isc::dns::AddRRCallback callback) {
const isc::dns::RRsetPtr
result(new isc::dns::RRset(isc::dns::Name("example.org"),
isc::dns::RRClass::IN(),
isc::dns::RRType::A(),
isc::dns::RRTTL(3600)));
const isc::dns::rdata::RdataPtr
data(isc::dns::rdata::createRdata(isc::dns::RRType::A(),
isc::dns::RRClass::IN(),
"192.0.2.1"));
result->addRdata(data);
updater_.expected_rrsets_.push_back(result);
return (result);
callback(result->getName(), result->getClass(), result->getType(),
result->getTTL(), data);
}
// An updater to be passed to the context
MockUpdater updater_;
......@@ -112,11 +136,11 @@ TEST_F(MasterLoaderCallbackTest, callbacks) {
// Try adding some RRsets.
TEST_F(MasterLoaderCallbackTest, addRRset) {
isc::dns::AddRRsetCallback
isc::dns::AddRRCallback
callback(createMasterLoaderAddCallback(updater_));
// Put some of them in.
EXPECT_NO_THROW(callback(generateRRset()));
EXPECT_NO_THROW(callback(generateRRset()));
EXPECT_NO_THROW(generateRRset(callback));
EXPECT_NO_THROW(generateRRset(callback));
// They all get pushed there right away, so there are none in the queue
EXPECT_TRUE(updater_.expected_rrsets_.empty());
}
......
......@@ -102,6 +102,7 @@ libb10_dns___la_SOURCES += labelsequence.h labelsequence.cc
libb10_dns___la_SOURCES += masterload.h masterload.cc
libb10_dns___la_SOURCES += master_lexer.h master_lexer.cc
libb10_dns___la_SOURCES += master_lexer_state.h
libb10_dns___la_SOURCES += master_loader.h master_loader.cc
libb10_dns___la_SOURCES += message.h message.cc
libb10_dns___la_SOURCES += messagerenderer.h messagerenderer.cc
libb10_dns___la_SOURCES += name.h name.cc
......
// 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.
#include <dns/master_loader.h>
#include <dns/master_lexer.h>
#include <dns/name.h>
#include <dns/rrttl.h>
#include <dns/rrclass.h>
#include <dns/rrtype.h>
#include <dns/rdata.h>
#include <string>
#include <memory>
using std::string;
using std::auto_ptr;
namespace isc {
namespace dns {
class MasterLoader::MasterLoaderImpl {
public:
MasterLoaderImpl(const char* master_file,
const Name& zone_origin,
const RRClass& zone_class,
const MasterLoaderCallbacks& callbacks,
const AddRRCallback& add_callback,
MasterLoader::Options options) :
lexer_(),
zone_origin_(zone_origin),
zone_class_(zone_class),
callbacks_(callbacks),
add_callback_(add_callback),
options_(options),
master_file_(master_file),
initialized_(false),
ok_(true),
many_errors_((options & MANY_ERRORS) != 0),
complete_(false),
seen_error_(false)
{}
void reportError(const std::string& filename, size_t line,
const std::string& reason)
{
seen_error_ = true;
callbacks_.error(filename, line, reason);
if (!many_errors_) {
// In case we don't have the lenient mode, every error is fatal
// and we throw
ok_ = false;
complete_ = true;
isc_throw(MasterLoaderError, reason.c_str());
}
}
void pushSource(const std::string& filename) {
std::string error;
if (!lexer_.pushSource(filename.c_str(), &error)) {
if (initialized_) {
// $INCLUDE file
reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
error);
} else {
// Top-level file
reportError("", 0, error);
ok_ = false;
}
}
initialized_ = true;
}
void pushStreamSource(std::istream& stream) {
lexer_.pushSource(stream);
initialized_ = true;
}
// Get a string token. Handle it as error if it is not string.
const string getString() {
lexer_.getNextToken(MasterToken::STRING).getString(string_token_);
return (string_token_);
}
bool loadIncremental(size_t count_limit);
private:
MasterLexer lexer_;
const Name zone_origin_;
const RRClass zone_class_;
MasterLoaderCallbacks callbacks_;
AddRRCallback add_callback_;
const MasterLoader::Options options_;
const std::string master_file_;
std::string string_token_;
bool initialized_;
bool ok_; // Is it OK to continue loading?
const bool many_errors_; // Are many errors allowed (or should we abort
// on the first)
public:
bool complete_; // All work done.
bool seen_error_; // Was there at least one error during the
// load?
};
bool
MasterLoader::MasterLoaderImpl::loadIncremental(size_t count_limit) {
if (count_limit == 0) {
isc_throw(isc::InvalidParameter, "Count limit set to 0");
}
if (complete_) {
isc_throw(isc::InvalidOperation,
"Trying to load when already loaded");
}
if (!initialized_) {
pushSource(master_file_);
}
size_t count = 0;
while (ok_ && count < count_limit) {
try {
// Skip all EOLNs (empty lines) and finish on EOF
bool empty = true;
do {
const MasterToken& empty_token(lexer_.getNextToken());
if (empty_token.getType() == MasterToken::END_OF_FILE) {
// TODO: Check if this is the last source, possibly pop
return (true);
}
empty = empty_token.getType() == MasterToken::END_OF_LINE;
} while (empty);
// Return the last token, as it was not empty
lexer_.ungetToken();
const MasterToken::StringRegion&
name_string(lexer_.getNextToken(MasterToken::QSTRING).
getStringRegion());
// TODO $ handling
const Name name(name_string.beg, name_string.len,
&zone_origin_);
// TODO: Some more flexibility. We don't allow omitting
// anything yet
// The parameters
const RRTTL ttl(getString());
const RRClass rrclass(getString());
const RRType rrtype(getString());
// TODO: Some more validation?
if (rrclass != zone_class_) {
// It doesn't really matter much what type of exception
// we throw, we catch it just below.
isc_throw(isc::BadValue, "Class mismatch: " << rrclass <<
"vs. " << zone_class_);
}
// TODO: Check if it is SOA, it should be at the origin.
const rdata::RdataPtr data(rdata::createRdata(rrtype, rrclass,
lexer_,
&zone_origin_,
options_,
callbacks_));
// In case we get NULL, it means there was error creating
// the Rdata. The errors should have been reported by
// callbacks_ already. We need to decide if we want to continue
// or not.
if (data) {
add_callback_(name, rrclass, rrtype, ttl, data);
// Good, we loaded another one
++count;
} else {
seen_error_ = true;
if (!many_errors_) {
ok_ = false;
complete_ = true;
// We don't have the exact error here, but it was reported
// by the error callback.
isc_throw(MasterLoaderError, "Invalid RR data");
}
}
} catch (const MasterLoaderError&) {
// This is a hack. We exclude the MasterLoaderError from the
// below case. Once we restrict the below to some smaller
// exception, we should remove this.
throw;
} catch (const isc::Exception& e) {
// TODO: Once we do #2518, catch only the DNSTextError here,
// not isc::Exception. The rest should be just simply
// propagated.
reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
e.what());
// We want to continue. Try to read until the end of line
bool end = false;
do {
const MasterToken& token(lexer_.getNextToken());
switch (token.getType()) {
case MasterToken::END_OF_FILE:
callbacks_.warning(lexer_.getSourceName(),
lexer_.getSourceLine(),
"File does not end with newline");
// TODO: Try pop in case this is not the only
// source
return (true);
case MasterToken::END_OF_LINE:
end = true;
break;
default:
// Do nothing. This is just to make compiler
// happy
break;
}
} while (!end);
}
}
// When there was a fatal error and ok is false, we say we are done.
return (!ok_);
}
MasterLoader::MasterLoader(const char* master_file,
const Name& zone_origin,
const RRClass& zone_class,
const MasterLoaderCallbacks& callbacks,
const AddRRCallback& add_callback,
Options options)
{
if (add_callback.empty()) {
isc_throw(isc::InvalidParameter, "Empty add RR callback");
}
impl_ = new MasterLoaderImpl(master_file, zone_origin,
zone_class, callbacks, add_callback, options);
}
MasterLoader::MasterLoader(std::istream& stream,
const Name& zone_origin,
const RRClass& zone_class,
const MasterLoaderCallbacks& callbacks,
const AddRRCallback& add_callback,
Options options)
{
if (add_callback.empty()) {
isc_throw(isc::InvalidParameter, "Empty add RR callback");
}
auto_ptr<MasterLoaderImpl> impl(new MasterLoaderImpl("", zone_origin,
zone_class, callbacks,
add_callback,
options));
impl->pushStreamSource(stream);
impl_ = impl.release();
}
MasterLoader::~MasterLoader() {
delete impl_;
}
bool
MasterLoader::loadIncremental(size_t count_limit) {
const bool result = impl_->loadIncremental(count_limit);
impl_->complete_ = result;
return (result);
}
bool
MasterLoader::loadedSucessfully() const {
return (impl_->complete_ && !impl_->seen_error_);
}
} // end namespace dns
} // end namespace isc
......@@ -15,20 +15,139 @@
#ifndef MASTER_LOADER_H
#define MASTER_LOADER_H
#include <dns/master_loader_callbacks.h>
#include <boost/noncopyable.hpp>
namespace isc {
namespace dns {
// Placeholder introduced by #2497. The real class should be updated in
// #2377.
class MasterLoader {
class Name;
class RRClass;
/// \brief Error while loading by MasterLoader without specifying the
/// MANY_ERRORS option.
class MasterLoaderError : public isc::Exception {
public:
MasterLoaderError(const char* file, size_t line, const char* what) :
Exception(file, line, what)
{}
};
/// \brief A class able to load DNS master files
///
/// This class is able to produce a stream of RRs from a master file.
/// It is able to load all of the master file at once, or by blocks
/// incrementally.
///
/// It reports the loaded RRs and encountered errors by callbacks.
class MasterLoader : boost::noncopyable {
public:
/// \brief Options how the parsing should work.
enum Options {
MANY_ERRORS, // lenient mode
// also eventually some check policies like "check NS name"
DEFAULT = 0, ///< Nothing special.
MANY_ERRORS = 1 ///< Lenient mode (see documentation of MasterLoader
/// constructor).
};
/// \brief Constructor
///
/// This creates a master loader and provides it with all
/// relevant information.
///
/// Except for the exceptions listed below, the constructor doesn't
/// throw. Most errors (like non-existent master file) are reported
/// by the callbacks during load() or loadIncremental().
///
/// \param master_file Path to the file to load.
/// \param zone_origin The origin of zone to be expected inside
/// the master file. Currently unused, but it is expected to
/// be used for some validation.
/// \param zone_class The class of zone to be expected inside the
/// master file.
/// \param callbacks The callbacks by which it should report problems.
/// Usually, the callback carries a filename and line number of the
/// input where the problem happens. There's a special case of empty
/// filename and zero line in case the opening of the top-level master
/// file fails.
/// \param add_callback The callback which would be called with each
/// loaded RR.
/// \param options Options for the parsing, which is bitwise-or of
/// the Options values or DEFAULT. If the MANY_ERRORS option is
/// included, the parser tries to continue past errors. If it
/// is not included, it stops at first encountered error.
/// \throw std::bad_alloc when there's not enough memory.
/// \throw isc::InvalidParameter if add_callback is empty.
MasterLoader(const char* master_file,
const Name& zone_origin,
const RRClass& zone_class,
const MasterLoaderCallbacks& callbacks,
const AddRRCallback& add_callback,
Options options = DEFAULT);
/// \brief Constructor from a stream
///
/// This is a constructor very similar to the previous one. The only
/// difference is it doesn't take a filename, but an input stream
/// to read the data from. It is expected to be mostly used in tests,
/// but it is public as it may possibly be useful for other currently
/// unknown purposes.
MasterLoader(std::istream& input,
const Name& zone_origin,
const RRClass& zone_class,
const MasterLoaderCallbacks& callbacks,
const AddRRCallback& add_callback,
Options options = DEFAULT);
/// \brief Destructor
~MasterLoader();
/// \brief Load some RRs
///
/// This method loads at most count_limit RRs and reports them. In case
/// an error (either fatal or without MANY_ERRORS) or end of file is
/// encountered, they may be less.
///
/// \param count_limit Upper limit on the number of RRs loaded.
/// \return In case it stops because of the count limit, it returns false.
/// It returns true if the loading is done.
/// \throw isc::InvalidOperation when called after loading was done
/// already.
/// \throw MasterLoaderError when there's an error in the input master
/// file and the MANY_ERRORS is not specified. It never throws this
/// in case MANY_ERRORS is specified.
bool loadIncremental(size_t count_limit);
/// \brief Load everything
///
/// This simply calls loadIncremental until the loading is done.
/// \throw isc::InvalidOperation when called after loading was done
/// already.
/// \throw MasterLoaderError when there's an error in the input master
/// file and the MANY_ERRORS is not specified. It never throws this
/// in case MANY_ERRORS is specified.
void load() {
while (!loadIncremental(1000)) { // 1000 = arbitrary largish number
// Body intentionally left blank
}
}
/// \brief Was the loading successful?
///
/// \return true if and only if the loading was complete (after a call of
/// load or after loadIncremental returned true) and there was no
/// error. In other cases, return false.
/// \note While this method works even before the loading is complete (by
/// returning false in that case), it is meant to be called only after
/// finishing the load.
bool loadedSucessfully() const;
private:
class MasterLoaderImpl;
MasterLoaderImpl* impl_;
};
}
}
} // end namespace dns
} // end namespace isc
#endif // MASTER_LOADER_H
......@@ -23,20 +23,30 @@
namespace isc {
namespace dns {
class Name;
class RRClass;
class RRType;
class RRTTL;
namespace rdata {
class Rdata;
typedef boost::shared_ptr<Rdata> RdataPtr;
}
class AbstractRRset;
typedef boost::shared_ptr<AbstractRRset> RRsetPtr;
/// \brief Type of callback to add a RRset.
/// \brief Type of callback to add a RR.
///
/// This type of callback is used by the loader to report another loaded
/// RRset. The RRset is no longer preserved by the loader and is fully
/// RR. The Rdata is no longer preserved by the loader and is fully
/// owned by the callback.
///
/// \param RRset The rrset to add. It does not contain the accompanying
/// RRSIG (if the zone is signed), they are reported with separate
/// calls to the callback.
typedef boost::function<void(const RRsetPtr& rrset)> AddRRsetCallback;
/// \param name The domain name where the RR belongs.
/// \param rrclass The class of the RR.
/// \param rrtype Type of the RR.
/// \param rrttl Time to live of the RR.
/// \param rdata The actual carried data of the RR.
typedef boost::function<void(const Name& name, const RRClass& rrclass,
const RRType& rrtype, const RRTTL& rrttl,
const rdata::RdataPtr& rdata)>
AddRRCallback;
/// \brief Set of issue callbacks for a loader.
///
......
......@@ -770,9 +770,6 @@ public:
//@{
/// \brief Return pointer to this RRset's RRSIG RRset
///
/// \exception NotImplemented Always thrown. Associated RRSIG RRsets are
/// not supported in this class.
///
/// \return Null pointer, as this class does not support RRSIG records.
virtual RRsetPtr getRRsig() const {
return (RRsetPtr());
......
......@@ -27,6 +27,7 @@ run_unittests_SOURCES += labelsequence_unittest.cc
run_unittests_SOURCES += messagerenderer_unittest.cc
run_unittests_SOURCES += master_lexer_token_unittest.cc
run_unittests_SOURCES += master_lexer_unittest.cc
run_unittests_SOURCES += master_loader_unittest.cc
run_unittests_SOURCES += master_lexer_state_unittest.cc
run_unittests_SOURCES += name_unittest.cc
run_unittests_SOURCES += nsec3hash_unittest.cc
......