Commit 2812fa5c authored by JINMEI Tatuya's avatar JINMEI Tatuya
Browse files

[1068] Merge branch 'trac1068' into trac1068review2 except the updated tests

for database_unittests (due to too many conflicts).
parents 255bf5b1 4c98f3dc
......@@ -180,6 +180,20 @@ public:
isc_throw(isc::NotImplemented,
"Data source doesn't support iteration");
}
/// TBD
///
/// We allow having a read-only data source. For such data source
/// this method will result in a NotImplemented exception.
///
/// To avoid throwing the exception accidentally with a lazy
/// implementation, we still keep this method pure virtual without
/// an implementation. All derived classes must explicitly write the
/// definition of this method, even if it simply throws the NotImplemented
/// exception.
virtual ZoneUpdaterPtr startUpdateZone(const isc::dns::Name& name,
bool replace)
const = 0;
};
}
}
......
......@@ -12,6 +12,7 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <string>
#include <vector>
#include <datasrc/database.h>
......@@ -21,6 +22,8 @@
#include <exceptions/exceptions.h>
#include <dns/name.h>
#include <dns/rrclass.h>
#include <dns/rrttl.h>
#include <dns/rrset.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
......@@ -29,17 +32,18 @@
#include <boost/foreach.hpp>
#include <string>
using namespace isc::dns;
using std::string;
using namespace std;
using boost::shared_ptr;
using namespace isc::dns::rdata;
namespace isc {
namespace datasrc {
DatabaseClient::DatabaseClient(boost::shared_ptr<DatabaseAccessor>
DatabaseClient::DatabaseClient(RRClass rrclass,
boost::shared_ptr<DatabaseAccessor>
accessor) :
accessor_(accessor)
rrclass_(rrclass), accessor_(accessor)
{
if (!accessor_) {
isc_throw(isc::InvalidParameter,
......@@ -604,5 +608,135 @@ DatabaseClient::getIterator(const isc::dns::Name& name) const {
return (ZoneIteratorPtr(new DatabaseIterator(context, RRClass::IN())));
}
ZoneUpdaterPtr
DatabaseClient::startUpdateZone(const isc::dns::Name& name,
bool replace) const
{
shared_ptr<DatabaseAccessor> update_accessor(accessor_->clone());
const std::pair<bool, int> zone(update_accessor->startUpdateZone(
name.toText(), replace));
if (!zone.first) {
return (ZoneUpdaterPtr());
}
return (ZoneUpdaterPtr(new Updater(update_accessor, zone.second,
name, rrclass_)));
}
DatabaseClient::Updater::Updater(shared_ptr<DatabaseAccessor> accessor,
int zone_id, const Name& zone_name,
const RRClass& zone_class) :
committed_(false), accessor_(accessor), zone_id_(zone_id),
db_name_(accessor->getDBName()), zone_name_(zone_name.toText()),
zone_class_(zone_class),
finder_(new Finder(accessor_, zone_id_, zone_name))
{
logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_CREATED)
.arg(zone_name_).arg(zone_class_).arg(db_name_);
}
DatabaseClient::Updater::~Updater() {
if (!committed_) {
accessor_->rollbackUpdateZone();
logger.info(DATASRC_DATABASE_UPDATER_ROLLBACK)
.arg(zone_name_).arg(zone_class_).arg(db_name_);
}
logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_DESTROYED)
.arg(zone_name_).arg(zone_class_).arg(db_name_);
}
ZoneFinder&
DatabaseClient::Updater::getFinder() {
return (*finder_);
}
void
DatabaseClient::Updater::addRRset(const RRset& rrset) {
if (committed_) {
isc_throw(DataSourceError, "Add attempt after commit to zone: "
<< zone_name_ << "/" << zone_class_);
}
if (rrset.getClass() != zone_class_) {
isc_throw(DataSourceError, "An RRset of a different class is being "
<< "added to " << zone_name_ << "/" << zone_class_ << ": "
<< rrset.toText());
}
RdataIteratorPtr it = rrset.getRdataIterator();
if (it->isLast()) {
isc_throw(DataSourceError, "An empty RRset is being added for "
<< rrset.getName() << "/" << zone_class_ << "/"
<< rrset.getType());
}
add_columns_[DatabaseAccessor::ADD_NAME] = rrset.getName().toText();
add_columns_[DatabaseAccessor::ADD_REV_NAME] =
rrset.getName().reverse().toText();
add_columns_[DatabaseAccessor::ADD_TTL] = rrset.getTTL().toText();
add_columns_[DatabaseAccessor::ADD_TYPE] = rrset.getType().toText();
for (; !it->isLast(); it->next()) {
if (rrset.getType() == RRType::RRSIG()) {
// XXX: the current interface (based on the current sqlite3
// data source schema) requires a separate "sigtype" column,
// even though it won't be used in a newer implementation.
// We should eventually clean up the schema design and simplify
// the interface, but until then we have to conform to the schema.
const generic::RRSIG& rrsig_rdata =
dynamic_cast<const generic::RRSIG&>(it->getCurrent());
add_columns_[DatabaseAccessor::ADD_SIGTYPE] =
rrsig_rdata.typeCovered().toText();
}
add_columns_[DatabaseAccessor::ADD_RDATA] = it->getCurrent().toText();
accessor_->addRecordToZone(add_columns_);
}
}
void
DatabaseClient::Updater::deleteRRset(const RRset& rrset) {
if (committed_) {
isc_throw(DataSourceError, "Delete attempt after commit on zone: "
<< zone_name_ << "/" << zone_class_);
}
if (rrset.getClass() != zone_class_) {
isc_throw(DataSourceError, "An RRset of a different class is being "
<< "deleted from " << zone_name_ << "/" << zone_class_
<< ": " << rrset.toText());
}
RdataIteratorPtr it = rrset.getRdataIterator();
if (it->isLast()) {
isc_throw(DataSourceError, "An empty RRset is being deleted for "
<< rrset.getName() << "/" << zone_class_ << "/"
<< rrset.getType());
}
del_params_[DatabaseAccessor::DEL_NAME] = rrset.getName().toText();
del_params_[DatabaseAccessor::DEL_TYPE] = rrset.getType().toText();
for (; !it->isLast(); it->next()) {
del_params_[DatabaseAccessor::DEL_RDATA] =
it->getCurrent().toText();
accessor_->deleteRecordInZone(del_params_);
}
}
void
DatabaseClient::Updater::commit() {
if (committed_) {
isc_throw(DataSourceError, "Duplicate commit attempt for "
<< zone_name_ << "/" << zone_class_ << " on "
<< db_name_);
}
accessor_->commitUpdateZone();
// We release the accessor immediately after commit is completed so that
// we don't hold the possible internal resource any longer.
accessor_.reset();
committed_ = true;
logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_COMMIT)
.arg(zone_name_).arg(zone_class_).arg(db_name_);
}
}
}
......@@ -15,6 +15,14 @@
#ifndef __DATABASE_DATASRC_H
#define __DATABASE_DATASRC_H
#include <string>
#include <boost/scoped_ptr.hpp>
#include <dns/rrclass.h>
#include <dns/rrclass.h>
#include <dns/rrset.h>
#include <datasrc/client.h>
#include <dns/name.h>
......@@ -109,6 +117,7 @@ public:
* classes in polymorphic way.
*/
virtual ~DatabaseAccessor() { }
/**
* \brief Retrieve a zone identifier
*
......@@ -420,6 +429,9 @@ public:
/// to the method or internal database error.
virtual void rollbackUpdateZone() = 0;
/// TBD
virtual boost::shared_ptr<DatabaseAccessor> clone() = 0;
/**
* \brief Returns a string identifying this dabase backend
*
......@@ -459,11 +471,14 @@ public:
* \exception isc::InvalidParameter if database is NULL. It might throw
* standard allocation exception as well, but doesn't throw anything else.
*
* \param rrclass The RR class of the zones that this client will handle.
* \param database The database to use to get data. As the parameter
* suggests, the client takes ownership of the database and will
* delete it when itself deleted.
*/
DatabaseClient(boost::shared_ptr<DatabaseAccessor> database);
DatabaseClient(isc::dns::RRClass rrclass,
boost::shared_ptr<DatabaseAccessor> database);
/**
* \brief Corresponding ZoneFinder implementation
*
......@@ -568,6 +583,7 @@ public:
boost::shared_ptr<DatabaseAccessor> accessor_;
const int zone_id_;
const isc::dns::Name origin_;
/**
* \brief Searches database for an RRset
*
......@@ -614,6 +630,7 @@ public:
bool want_ns, const
isc::dns::Name*
construct_name = NULL);
/**
* \brief Checks if something lives below this domain.
*
......@@ -625,6 +642,29 @@ public:
bool hasSubdomains(const std::string& name);
};
class Updater : public ZoneUpdater {
public:
Updater(boost::shared_ptr<DatabaseAccessor> database, int zone_id,
const isc::dns::Name& zone_name,
const isc::dns::RRClass& zone_class);
~Updater();
virtual ZoneFinder& getFinder();
virtual void addRRset(const isc::dns::RRset& rrset);
virtual void deleteRRset(const isc::dns::RRset& rrset);
virtual void commit();
private:
bool committed_;
boost::shared_ptr<DatabaseAccessor> accessor_;
const int zone_id_;
std::string db_name_;
const std::string zone_name_;
const isc::dns::RRClass zone_class_;
boost::scoped_ptr<Finder::Finder> finder_;
std::string add_columns_[DatabaseAccessor::ADD_COLUMN_COUNT];
std::string del_params_[DatabaseAccessor::DEL_PARAM_COUNT];
};
/**
* \brief Find a zone in the database
*
......@@ -660,7 +700,14 @@ public:
*/
virtual ZoneIteratorPtr getIterator(const isc::dns::Name& name) const;
/// TBD
virtual ZoneUpdaterPtr startUpdateZone(const isc::dns::Name& name,
bool replace) const;
private:
/// \brief The RR class that this client handles.
const isc::dns::RRClass rrclass_;
/// \brief The accessor to our database.
const boost::shared_ptr<DatabaseAccessor> accessor_;
};
......@@ -668,4 +715,8 @@ private:
}
}
#endif
#endif // __DATABASE_DATASRC_H
// Local Variables:
// mode: c++
// End:
......@@ -590,3 +590,25 @@ data source.
% DATASRC_UNEXPECTED_QUERY_STATE unexpected query state
This indicates a programming error. An internal task of unknown type was
generated.
% DATASRC_DATABASE_UPDATER_CREATED zone updater created for '%1/%2' on %3
Debug information. A zone updater object is created to make updates to
the shown zone on the shown backend database.
% DATASRC_DATABASE_UPDATER_DESTROYED zone updater destroyed for '%1/%2' on %3
Debug information. A zone updater object is destroyed, either successfully
or after failure of, making updates to the shown zone on the shown backend
database.
%DATASRC_DATABASE_UPDATER_ROLLBACK zone updates roll-backed for '%1/%2' on %3
A zone updater is being destroyed without committing the changes.
This would typically mean the update attempt was aborted due to some
error, but may also be a bug of the application that forgets committing
the changes. The intermediate changes made through the updater won't
be applied to the underlying database. The zone name, its class, and
the underlying database name are shown in the log message.
% DATASRC_DATABASE_UPDATER_COMMIT updates committed for '%1/%2' on %3
Debug information. A set of updates to a zone has been successfully
committed to the corresponding database backend. The zone name,
its class and the database name are printed.
......@@ -17,6 +17,8 @@
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>
#include <exceptions/exceptions.h>
#include <dns/name.h>
#include <dns/rrclass.h>
#include <dns/rrsetlist.h>
......@@ -793,5 +795,9 @@ InMemoryClient::getIterator(const Name& name) const {
return (ZoneIteratorPtr(new MemoryIterator(zone->impl_->domains_, name)));
}
ZoneUpdaterPtr
InMemoryClient::startUpdateZone(const isc::dns::Name&, bool) const {
isc_throw(isc::NotImplemented, "Update attempt on in memory data source");
}
} // end of namespace datasrc
} // end of namespace dns
......@@ -266,6 +266,11 @@ public:
/// \brief Implementation of the getIterator method
virtual ZoneIteratorPtr getIterator(const isc::dns::Name& name) const;
/// In-memory data source is read-only, so this derived method will
/// result in a NotImplemented (once merged) exception.
virtual ZoneUpdaterPtr startUpdateZone(const isc::dns::Name& name,
bool replace) const;
private:
// TODO: Do we still need the PImpl if nobody should manipulate this class
// directly any more (it should be handled through DataSourceClient)?
......
......@@ -128,6 +128,7 @@ private:
SQLite3Accessor::SQLite3Accessor(const std::string& filename,
const isc::dns::RRClass& rrclass) :
dbparameters_(new SQLite3Parameters),
filename_(filename),
class_(rrclass.toText()),
database_name_("sqlite3_" +
isc::util::Filename(filename).nameAndExtension())
......@@ -137,6 +138,25 @@ SQLite3Accessor::SQLite3Accessor(const std::string& filename,
open(filename);
}
SQLite3Accessor::SQLite3Accessor(const std::string& filename,
const string& rrclass) :
dbparameters_(new SQLite3Parameters),
filename_(filename),
class_(rrclass),
database_name_("sqlite3_" +
isc::util::Filename(filename).nameAndExtension())
{
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_NEWCONN);
open(filename);
}
boost::shared_ptr<DatabaseAccessor>
SQLite3Accessor::clone() {
return (boost::shared_ptr<DatabaseAccessor>(new SQLite3Accessor(filename_,
class_)));
}
namespace {
// This is a helper class to initialize a Sqlite3 DB safely. An object of
......
......@@ -71,6 +71,17 @@ public:
*/
SQLite3Accessor(const std::string& filename,
const isc::dns::RRClass& rrclass);
/**
* \brief Constructor
*
* Same as the other version, but takes rrclass as a bare string.
* we should obsolete the other version and unify the constructor to
* this version; the SQLite3Accessor is expected to be "dumb" and
* shouldn't care about DNS specific information such as RRClass.
*/
SQLite3Accessor(const std::string& filename, const std::string& rrclass);
/**
* \brief Destructor
*
......@@ -78,6 +89,9 @@ public:
*/
~SQLite3Accessor();
/// TBD
virtual boost::shared_ptr<DatabaseAccessor> clone();
/**
* \brief Look up a zone
*
......@@ -158,6 +172,8 @@ public:
private:
/// \brief Private database data
boost::scoped_ptr<SQLite3Parameters> dbparameters_;
/// \brief The filename of the DB (necessary for clone())
const std::string filename_;
/// \brief The class for which the queries are done
const std::string class_;
/// \brief Opens the database
......
......@@ -32,6 +32,9 @@ public:
virtual FindResult findZone(const isc::dns::Name&) const {
return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
}
virtual ZoneUpdaterPtr startUpdateZone(const isc::dns::Name&, bool) const {
return (ZoneUpdaterPtr());
}
};
class ClientTest : public ::testing::Test {
......
......@@ -58,6 +58,10 @@ public:
}
}
virtual shared_ptr<DatabaseAccessor> clone() {
return (shared_ptr<DatabaseAccessor>()); // bogus data, but unused
}
virtual std::pair<bool, int> startUpdateZone(const std::string&, bool) {
// return dummy value. unused anyway.
return (pair<bool, int>(true, 0));
......@@ -508,8 +512,9 @@ public:
*/
void createClient() {
current_accessor_ = new MockAccessor();
client_.reset(new DatabaseClient(shared_ptr<DatabaseAccessor>(
current_accessor_)));
client_.reset(new DatabaseClient(RRClass::IN(),
shared_ptr<DatabaseAccessor>(
current_accessor_)));
}
// Will be deleted by client_, just keep the current value for comparison.
MockAccessor* current_accessor_;
......@@ -566,7 +571,8 @@ TEST_F(DatabaseClientTest, superZone) {
TEST_F(DatabaseClientTest, noAccessorException) {
// We need a dummy variable here; some compiler would regard it a mere
// declaration instead of an instantiation and make the test fail.
EXPECT_THROW(DatabaseClient dummy((shared_ptr<DatabaseAccessor>())),
EXPECT_THROW(DatabaseClient dummy(RRClass::IN(),
shared_ptr<DatabaseAccessor>()),
isc::InvalidParameter);
}
......@@ -578,13 +584,15 @@ TEST_F(DatabaseClientTest, noZoneIterator) {
// If the zone doesn't exist and iteration is not implemented, it still throws
// the exception it doesn't exist
TEST_F(DatabaseClientTest, noZoneNotImplementedIterator) {
EXPECT_THROW(DatabaseClient(boost::shared_ptr<DatabaseAccessor>(
new NopAccessor())).getIterator(Name("example.com")),
EXPECT_THROW(DatabaseClient(RRClass::IN(),
boost::shared_ptr<DatabaseAccessor>(
new NopAccessor())).getIterator(
Name("example.com")),
DataSourceError);
}
TEST_F(DatabaseClientTest, notImplementedIterator) {
EXPECT_THROW(DatabaseClient(shared_ptr<DatabaseAccessor>(
EXPECT_THROW(DatabaseClient(RRClass::IN(), shared_ptr<DatabaseAccessor>(
new NopAccessor())).getIterator(Name("example.org")),
isc::NotImplemented);
}
......
......@@ -197,6 +197,11 @@ TEST_F(InMemoryClientTest, getZoneCount) {
EXPECT_EQ(2, memory_client.getZoneCount());
}
TEST_F(InMemoryClientTest, startUpdateZone) {
EXPECT_THROW(memory_client.startUpdateZone(Name("example.org"), false),
isc::NotImplemented);
}
// A helper callback of masterLoad() used in InMemoryZoneFinderTest.
void
setRRset(RRsetPtr rrset, vector<RRsetPtr*>::iterator& it) {
......@@ -1097,5 +1102,4 @@ TEST_F(InMemoryZoneFinderTest, getFileName) {
EXPECT_EQ(TEST_DATA_DIR "/root.zone", zone_finder_.getFileName());
EXPECT_TRUE(rootzone.getFileName().empty());
}
}
......@@ -409,6 +409,34 @@ TEST_F(SQLite3Create, lockedtest) {
SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, RRClass::IN());
}
TEST_F(SQLite3AccessorTest, clone) {
shared_ptr<DatabaseAccessor> cloned = accessor->clone();
EXPECT_EQ(accessor->getDBName(), cloned->getDBName());
// The cloned accessor should have a separate connection and search
// context, so it should be able to perform search in concurrent with
// the original accessor.
string columns1[DatabaseAccessor::COLUMN_COUNT];
string columns2[DatabaseAccessor::COLUMN_COUNT];
const std::pair<bool, int> zone_info1(
accessor->getZone("example.com."));
DatabaseAccessor::IteratorContextPtr iterator1 =
accessor->getRecords("foo.example.com.", zone_info1.second);
const std::pair<bool, int> zone_info2(
accessor->getZone("example.com."));
DatabaseAccessor::IteratorContextPtr iterator2 =
cloned->getRecords("foo.example.com.", zone_info2.second);
ASSERT_TRUE(iterator1->getNext(columns1));
checkRecordRow(columns1, "CNAME", "3600", "", "cnametest.example.org.",
"");
ASSERT_TRUE(iterator2->getNext(columns2));
checkRecordRow(columns2, "CNAME", "3600", "", "cnametest.example.org.",
"");
}
//
// Commonly used data for update tests
//
......
BUILT_SOURCES = rwtest.sqlite3.copied
CLEANFILES = *.copied
rwtest.sqlite3.copied: rwtest.sqlite3
cp $(srcdir)/rwtest.sqlite3 $@
......@@ -15,9 +15,11 @@
#ifndef __ZONE_H
#define __ZONE_H 1
#include <datasrc/result.h>
#include <dns/rrset.h>
#include <dns/rrsetlist.h>
#include <datasrc/result.h>
namespace isc {
namespace datasrc {
......@@ -207,8 +209,51 @@ typedef boost::shared_ptr<ZoneFinder> ZoneFinderPtr;
/// \brief A pointer-like type pointing to a \c ZoneFinder object.
typedef boost::shared_ptr<const ZoneFinder> ConstZoneFinderPtr;
}
}
/// The base class to make updates to a single zone.
class ZoneUpdater {
protected:
ZoneUpdater() {}
public:
virtual ~ZoneUpdater() {}
/// TBD
///
/// The finder is not expected to provide meaningful data once commit()
/// was performed.
virtual ZoneFinder& getFinder() = 0;
/// TBD
///
/// Notes about unexpected input: class mismatch will be rejected.
/// The owner name isn't checked; it's the caller's responsibility.
///
/// Open issues: we may eventually want to return result values such as
/// there's a duplicate, etc.
///
/// The added RRset must not be empty (i.e., it must have at least one
/// RDATA).
///
/// This method must not be called once commit() is performed.
virtual void addRRset(const isc::dns::RRset& rrset) = 0;
/// TBD