Commit 741e96eb authored by Mukund Sivaraman's avatar Mukund Sivaraman
Browse files

Merge branch 'trac2853'

Conflicts:
	src/lib/python/isc/datasrc/Makefile.am
	src/lib/python/isc/datasrc/configurableclientlist_python.cc
	src/lib/python/isc/datasrc/datasrc.cc
	src/lib/python/isc/datasrc/tests/clientlist_test.py
parents fc951496 b8679425
......@@ -1408,6 +1408,7 @@ AC_OUTPUT([doc/version.ent
src/lib/log/tests/logger_lock_test.sh
src/lib/log/tests/severity_test.sh
src/lib/log/tests/tempdir.h
src/lib/util/python/doxygen2pydoc.py
src/lib/util/python/mkpywrapper.py
src/lib/util/python/gen_wiredata.py
src/lib/server_common/tests/data_path.h
......@@ -1439,6 +1440,7 @@ AC_OUTPUT([doc/version.ent
chmod +x src/lib/log/tests/local_file_test.sh
chmod +x src/lib/log/tests/logger_lock_test.sh
chmod +x src/lib/log/tests/severity_test.sh
chmod +x src/lib/util/python/doxygen2pydoc.py
chmod +x src/lib/util/python/mkpywrapper.py
chmod +x src/lib/util/python/gen_wiredata.py
chmod +x src/lib/python/isc/log/tests/log_console.py
......
# This is a doxygen configuration for generating XML output as well as HTML.
#
# Inherit everything from our default Doxyfile except GENERATE_XML, which
# will be reset to YES
@INCLUDE = Doxyfile
GENERATE_XML = YES
SUBDIRS = guide
EXTRA_DIST = version.ent.in differences.txt
EXTRA_DIST = version.ent.in differences.txt Doxyfile Doxyfile-xml
devel:
mkdir -p html
......
......@@ -410,11 +410,15 @@ vector<DataSourceStatus>
ConfigurableClientList::getStatus() const {
vector<DataSourceStatus> result;
BOOST_FOREACH(const DataSourceInfo& info, data_sources_) {
// TODO: Once we support mapped cache, decide when we need the
// SEGMENT_WAITING.
result.push_back(DataSourceStatus(info.name_, info.cache_ ?
SEGMENT_INUSE : SEGMENT_UNUSED,
"local"));
if (info.ztable_segment_) {
result.push_back(DataSourceStatus(
info.name_,
(info.ztable_segment_->isUsable() ?
SEGMENT_INUSE : SEGMENT_WAITING),
info.ztable_segment_->getImplType()));
} else {
result.push_back(DataSourceStatus(info.name_));
}
}
return (result);
}
......
......@@ -81,13 +81,26 @@ class DataSourceStatus {
public:
/// \brief Constructor
///
/// Sets initial values. It doesn't matter what is provided for the type
/// if state is SEGMENT_UNUSED, the value is effectively ignored.
/// Sets initial values. If you want to use \c SEGMENT_UNUSED as the
/// state, please use the other constructor.
DataSourceStatus(const std::string& name, MemorySegmentState state,
const std::string& type) :
name_(name),
type_(type),
state_(state)
{
assert (state != SEGMENT_UNUSED);
assert (!type.empty());
}
/// \brief Constructor
///
/// Sets initial values. The state is set as \c SEGMENT_UNUSED and
/// the type is effectively unspecified.
DataSourceStatus(const std::string& name) :
name_(name),
type_(""),
state_(SEGMENT_UNUSED)
{}
/// \brief Get the segment state
......@@ -377,10 +390,9 @@ public:
memory::ZoneTableSegment::MemorySegmentOpenMode mode,
isc::data::ConstElementPtr config_params);
private:
/// \brief Convenience type shortcut
typedef boost::shared_ptr<memory::ZoneWriter> ZoneWriterPtr;
public:
/// \brief Codes indicating in-memory cache status for a given zone name.
///
/// This is used as a result of the getCachedZoneWriter() method.
......@@ -422,7 +434,7 @@ public:
/// \param zone The origin of the zone to load.
/// \param datasrc_name If not empty, the name of the data source
/// to be used for loading the zone (see above).
/// \return The result has two parts. The first one is a status describing
/// \return The result has two parts. The first one is a status indicating
/// if it worked or not (and in case it didn't, also why). If the
/// status is ZONE_SUCCESS, the second part contains a shared pointer
/// to the writer. If the status is anything else, the second part is
......
......@@ -139,7 +139,8 @@ ZoneWriter::install() {
// segment. Once there is, we should provide the test.
while (impl_->state_ != Impl::ZW_INSTALLED) {
try {
ZoneTable* table(impl_->segment_.getHeader().getTable());
ZoneTableHeader& header = impl_->segment_.getHeader();
ZoneTable* table(header.getTable());
if (!table) {
isc_throw(isc::Unexpected, "No zone table present");
}
......
......@@ -91,7 +91,7 @@ public:
/// later.
/// \throw isc::InvalidOperation if called second time.
/// \throw DataSourceError load related error (not thrown if constructed
/// with catch_load_error being false).
/// with catch_load_error being \c true).
///
/// \param error_msg If non NULL, used as a placeholder to store load error
/// messages.
......
......@@ -116,6 +116,7 @@ public:
const std::string& datasrc_name,
ZoneTableSegment::MemorySegmentOpenMode mode,
ConstElementPtr config_params) = 0;
virtual std::string getType() = 0;
};
class ListTest : public ::testing::TestWithParam<SegmentType*> {
......@@ -202,14 +203,14 @@ public:
memory::ZoneTableSegment::CREATE,
config_ztable_segment);
boost::scoped_ptr<memory::ZoneWriter> writer(
new memory::ZoneWriter(
*dsrc_info.ztable_segment_,
cache_conf->getLoadAction(rrclass_, zone),
zone, rrclass_, false));
writer->load();
writer->install();
writer->cleanup(); // not absolutely necessary, but just in case
const ConfigurableClientList::ZoneWriterPair result =
list_->getCachedZoneWriter(zone, dsrc_info.name_);
ASSERT_EQ(ConfigurableClientList::ZONE_SUCCESS, result.first);
result.second->load();
result.second->install();
// not absolutely necessary, but just in case
result.second->cleanup();
GetParam()->reset(*list_, dsrc_info.name_,
memory::ZoneTableSegment::READ_WRITE,
......@@ -332,6 +333,9 @@ public:
ConstElementPtr) {
// We must not call reset on local ZoneTableSegments.
}
virtual std::string getType() {
return ("local");
}
};
LocalSegmentType local_segment_type;
......@@ -360,6 +364,9 @@ public:
ConstElementPtr config_params) {
list.resetMemorySegment(datasrc_name, mode, config_params);
}
virtual std::string getType() {
return ("mapped");
}
};
MappedSegmentType mapped_segment_type;
......@@ -1002,6 +1009,13 @@ ListTest::doReload(const Name& origin, const string& datasrc_name) {
// Test we can reload a zone
TEST_P(ListTest, reloadSuccess) {
list_->configure(config_elem_zones_, true);
const vector<DataSourceStatus> statii_before(list_->getStatus());
ASSERT_EQ(1, statii_before.size());
EXPECT_EQ("test_type", statii_before[0].getName());
EXPECT_EQ(SEGMENT_UNUSED, statii_before[0].getSegmentState());
EXPECT_THROW(statii_before[0].getSegmentType(), isc::InvalidOperation);
const Name name("example.org");
prepareCache(0, name);
// The cache currently contains a tweaked version of zone, which
......@@ -1017,10 +1031,19 @@ TEST_P(ListTest, reloadSuccess) {
list_->find(name).finder_->
find(Name("tstzonedata").concatenate(name),
RRType::A())->code);
const vector<DataSourceStatus> statii_after(list_->getStatus());
ASSERT_EQ(1, statii_after.size());
EXPECT_EQ("test_type", statii_after[0].getName());
EXPECT_EQ(SEGMENT_INUSE, statii_after[0].getSegmentState());
EXPECT_EQ(GetParam()->getType(), statii_after[0].getSegmentType());
}
// The cache is not enabled. The load should be rejected.
TEST_P(ListTest, reloadNotAllowed) {
//
// FIXME: This test is broken by #2853 and needs to be fixed or
// removed. Please see #2991 for details.
TEST_P(ListTest, DISABLED_reloadNotAllowed) {
list_->configure(config_elem_zones_, false);
const Name name("example.org");
// We put the cache in even when not enabled. This won't confuse the thing.
......@@ -1334,7 +1357,7 @@ TEST(DataSourceStatus, status) {
EXPECT_EQ("Test", status.getName());
EXPECT_EQ(SEGMENT_INUSE, status.getSegmentState());
EXPECT_EQ("local", status.getSegmentType());
const DataSourceStatus status_unused("Unused", SEGMENT_UNUSED, "");
const DataSourceStatus status_unused("Unused");
EXPECT_EQ("Unused", status_unused.getName());
EXPECT_EQ(SEGMENT_UNUSED, status_unused.getSegmentState());
EXPECT_THROW(status_unused.getSegmentType(), isc::InvalidOperation);
......
......@@ -10,6 +10,7 @@ python_PYTHON = __init__.py sqlite3_ds.py
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += $(SQLITE_CFLAGS)
AM_CXXFLAGS = $(B10_CXXFLAGS)
python_LTLIBRARIES = datasrc.la
datasrc_la_SOURCES = datasrc.cc datasrc.h
......@@ -23,6 +24,7 @@ datasrc_la_SOURCES += configurableclientlist_python.h
datasrc_la_SOURCES += zone_loader_python.cc zone_loader_python.h
datasrc_la_SOURCES += zonetable_accessor_python.cc zonetable_accessor_python.h
datasrc_la_SOURCES += zonetable_iterator_python.cc zonetable_iterator_python.h
datasrc_la_SOURCES += zonewriter_python.cc zonewriter_python.h
datasrc_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
datasrc_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
......@@ -34,11 +36,13 @@ datasrc_la_LIBADD += $(top_builddir)/src/lib/dns/python/libb10-pydnspp.la
datasrc_la_LIBADD += $(PYTHON_LIB)
EXTRA_DIST = client_inc.cc
EXTRA_DIST += configurableclientlist_inc.cc
EXTRA_DIST += finder_inc.cc
EXTRA_DIST += iterator_inc.cc
EXTRA_DIST += updater_inc.cc
EXTRA_DIST += journal_reader_inc.cc
EXTRA_DIST += zone_loader_inc.cc
EXTRA_DIST += zonewriter_inc.cc
CLEANDIRS = __pycache__
......
// Copyright (C) 2013 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.
namespace {
const char* const ConfigurableClientList_doc = "\
The list of data source clients\n\
\n\
The purpose is to have several data source clients of the same class\
and then be able to search through them to identify the one containing\
a given zone.\n\
\n\
Unlike the C++ version, we don't have the abstract base class. Abstract\
classes are not needed due to the duck typing nature of python.\
";
const char* const ConfigurableClientList_configure_doc = "\
configure(configuration, allow_cache) -> None\n\
\n\
Wrapper around C++ ConfigurableClientList::configure\n\
\n\
This sets the active configuration. It fills the ConfigurableClientList with\
corresponding data source clients.\n\
\n\
If any error is detected, an exception is raised and the previous\
configuration preserved.\n\
\n\
Parameters:\n\
configuration The configuration, as a JSON encoded string.\
allow_cache If caching is allowed.\
";
const char* const ConfigurableClientList_reset_memory_segment_doc = "\
reset_memory_segment(datasrc_name, mode, config_params) -> None\n\
\n\
This method resets the zone table segment for a datasource with a new\n\
memory segment.\n\
\n\
Parameters:\n\
datasrc_name The name of the data source whose segment to reset.\n\
mode The open mode for the new memory segment.\n\
config_params The configuration for the new memory segment, as a JSON encoded string.\n\
";
const char* const ConfigurableClientList_get_zone_table_accessor_doc = "\
get_zone_table_accessor(datasrc_name, use_cache) -> \
isc.datasrc.ZoneTableAccessor\n\
\n\
Create a ZoneTableAccessor object for the specified data source.\n\
\n\
Parameters:\n\
datasrc_name If not empty, the name of the data source\n\
use_cache If true, create a zone table for in-memory cache.\n\
";
const char* const ConfigurableClientList_get_cached_zone_writer_doc = "\
get_cached_zone_writer(zone, datasrc_name) -> status, zone_writer\n\
\n\
This method returns a ZoneWriter that can be used to (re)load a zone.\n\
\n\
By default this method identifies the first data source in the list\n\
that should serve the zone of the given name, and returns a ZoneWriter\n\
object that can be used to load the content of the zone, in a specific\n\
way for that data source.\n\
\n\
If the optional datasrc_name parameter is provided with a non empty\n\
string, this method only tries to load the specified zone into or with\n\
the data source which has the given name, regardless where in the list\n\
that data source is placed. Even if the given name of zone doesn't\n\
exist in the data source, other data sources are not searched and\n\
this method simply returns ZONE_NOT_FOUND in the first element\n\
of the pair.\n\
\n\
Two elements are returned. The first element is a status\n\
indicating if it worked or not (and in case it didn't, also why). If the\n\
status is ZONE_SUCCESS, the second element contains a ZoneWriter object. If\n\
the status is anything else, the second element is None.\n\
\n\
Parameters:\n\
zone The origin of the zone to (re)load.\n\
datasrc_name The name of the data source where the zone is to be loaded (optional).\n\
";
const char* const ConfigurableClientList_get_status_doc = "\
get_status() -> list of tuples\n\
\n\
This method returns a list of tuples, with each tuple containing the\n\
status of a data source client. If there are no data source clients\n\
present, an empty list is returned.\n\
\n\
The tuples contain (name, segment_type, segment_state):\n\
name The name of the data source.\n\
segment_type A string indicating the type of memory segment in use.\n\
segment_state The state of the memory segment.\n\
\n\
If segment_state is SEGMENT_UNUSED, None is returned for the segment_type.\n\
";
const char* const ConfigurableClientList_find_doc = "\
find(zone, want_exact_match=False, want_finder=True) -> datasrc_client,\
zone_finder, exact_match\n\
\n\
Look for a data source containing the given zone.\n\
\n\
It searches through the contained data sources and returns a data source\
containing the zone, the zone finder of the zone and a boolean if the answer\
is an exact match.\n\
\n\
The first parameter is isc.dns.Name object of a name in the zone. If the\
want_exact_match is True, only zone with this exact origin is returned.\
If it is False, the best matching zone is returned.\n\
\n\
If the want_finder is False, the returned zone_finder might be None even\
if the data source is identified (in such case, the datasrc_client is not\
None). Setting it to false allows the client list some optimisations, if\
you don't need it, but if you do need it, it is better to set it to True\
instead of getting it from the datasrc_client later.\n\
\n\
If no answer is found, the datasrc_client and zone_finder are None.\
";
} // unnamed namespace
......@@ -36,12 +36,16 @@
#include "finder_python.h"
#include "client_python.h"
#include "zonetable_accessor_python.h"
#include "zonewriter_python.h"
#include "configurableclientlist_inc.cc"
using namespace std;
using namespace isc::util::python;
using namespace isc::datasrc;
using namespace isc::datasrc::memory;
using namespace isc::datasrc::python;
using namespace isc::datasrc::memory::python;
using namespace isc::dns::python;
//
......@@ -68,7 +72,8 @@ ConfigurableClientList_init(PyObject* po_self, PyObject* args, PyObject*) {
return (0);
}
} catch (const exception& ex) {
const string ex_what = "Failed to construct ConfigurableClientList object: " +
const string ex_what =
"Failed to construct ConfigurableClientList object: " +
string(ex.what());
PyErr_SetString(getDataSourceException("Error"), ex_what.c_str());
return (-1);
......@@ -152,6 +157,87 @@ ConfigurableClientList_resetMemorySegment(PyObject* po_self, PyObject* args) {
return (NULL);
}
PyObject*
ConfigurableClientList_getCachedZoneWriter(PyObject* po_self, PyObject* args) {
s_ConfigurableClientList* self =
static_cast<s_ConfigurableClientList*>(po_self);
try {
PyObject* name_obj;
const char* datasrc_name_p = "";
if (PyArg_ParseTuple(args, "O!|s", &isc::dns::python::name_type,
&name_obj, &datasrc_name_p)) {
const isc::dns::Name
name(isc::dns::python::PyName_ToName(name_obj));
const std::string datasrc_name(datasrc_name_p);
const ConfigurableClientList::ZoneWriterPair result =
self->cppobj->getCachedZoneWriter(name, datasrc_name);
PyObjectContainer writer;
if (!result.second) {
// Use the Py_BuildValue, as it takes care of the
// reference counts correctly.
writer.reset(Py_BuildValue(""));
} else {
// Make sure it keeps the writer alive.
writer.reset(createZoneWriterObject(result.second,
po_self));
}
return (Py_BuildValue("IO", result.first, writer.get()));
} else {
return (NULL);
}
} catch (const std::exception& exc) {
PyErr_SetString(getDataSourceException("Error"), exc.what());
return (NULL);
} catch (...) {
PyErr_SetString(getDataSourceException("Error"),
"Unknown C++ exception");
return (NULL);
}
}
PyObject*
ConfigurableClientList_getStatus(PyObject* po_self, PyObject*) {
s_ConfigurableClientList* self =
static_cast<s_ConfigurableClientList*>(po_self);
try {
const std::vector<DataSourceStatus> status = self->cppobj->getStatus();
PyObjectContainer slist(PyList_New(status.size()));
for (size_t i = 0; i < status.size(); ++i) {
PyObjectContainer segment_type;
if (status[i].getSegmentState() != SEGMENT_UNUSED) {
segment_type.reset(Py_BuildValue(
"s", status[i].getSegmentType().c_str()));
} else {
Py_INCREF(Py_None);
segment_type.reset(Py_None);
}
PyObjectContainer tup(Py_BuildValue("(sOI)",
status[i].getName().c_str(),
segment_type.get(),
status[i].getSegmentState()));
// The following "steals" our reference on tup, so we must
// not decref.
PyList_SET_ITEM(slist.get(), i, tup.release());
}
return (slist.release());
} catch (const std::exception& exc) {
PyErr_SetString(getDataSourceException("Error"), exc.what());
return (NULL);
} catch (...) {
PyErr_SetString(getDataSourceException("Error"),
"Unknown C++ exception");
return (NULL);
}
}
PyObject*
ConfigurableClientList_find(PyObject* po_self, PyObject* args) {
s_ConfigurableClientList* self =
......@@ -245,78 +331,21 @@ ConfigurableClientList_getZoneTableAccessor(PyObject* po_self, PyObject* args) {
// 3. Argument type
// 4. Documentation
PyMethodDef ConfigurableClientList_methods[] = {
{ "configure", ConfigurableClientList_configure, METH_VARARGS,
"configure(configuration, allow_cache) -> None\n\
\n\
Wrapper around C++ ConfigurableClientList::configure\n\
\n\
This sets the active configuration. It fills the ConfigurableClientList with\
corresponding data source clients.\n\
\n\
If any error is detected, an exception is raised and the previous\
configuration preserved.\n\
\n\
Parameters:\n\
configuration The configuration, as a JSON encoded string.\
allow_cache If caching is allowed." },
{ "configure", ConfigurableClientList_configure,
METH_VARARGS, ConfigurableClientList_configure_doc },
{ "reset_memory_segment", ConfigurableClientList_resetMemorySegment,
METH_VARARGS,
"reset_memory_segment(datasrc_name, mode, config_params) -> None\n\
\n\
Wrapper around C++ ConfigurableClientList::resetMemorySegment\n\
\n\
This resets the zone table segment for a datasource with a new\n\
memory segment.\n\
\n\
Parameters:\n\
datasrc_name The name of the data source whose segment to reset.\
mode The open mode for the new memory segment.\
config_params The configuration for the new memory segment, as a JSON encoded string." },
{ "find", ConfigurableClientList_find, METH_VARARGS,
"find(zone, want_exact_match=False, want_finder=True) -> datasrc_client,\
zone_finder, exact_match\n\
\n\
Look for a data source containing the given zone.\n\
\n\
It searches through the contained data sources and returns a data source\
containing the zone, the zone finder of the zone and a boolean if the answer\
is an exact match.\n\
\n\
The first parameter is isc.dns.Name object of a name in the zone. If the\
want_exact_match is True, only zone with this exact origin is returned.\
If it is False, the best matching zone is returned.\n\
\n\
If the want_finder is False, the returned zone_finder might be None even\
if the data source is identified (in such case, the datasrc_client is not\
None). Setting it to false allows the client list some optimisations, if\
you don't need it, but if you do need it, it is better to set it to True\
instead of getting it from the datasrc_client later.\n\
\n\
If no answer is found, the datasrc_client and zone_finder are None." },
METH_VARARGS, ConfigurableClientList_reset_memory_segment_doc },
{ "get_zone_table_accessor", ConfigurableClientList_getZoneTableAccessor,
METH_VARARGS,
"get_zone_table_accessor(datasrc_name, use_cache) -> \
isc.datasrc.ZoneTableAccessor\n\
\n\
Create a ZoneTableAccessor object for the specified data source.\n\
\n\
Parameters:\n\
datasrc_name If not empty, the name of the data source\
use_cache If true, create a zone table for in-memory cache." },
METH_VARARGS, ConfigurableClientList_get_zone_table_accessor_doc },
{ "get_cached_zone_writer", ConfigurableClientList_getCachedZoneWriter,
METH_VARARGS, ConfigurableClientList_get_cached_zone_writer_doc },
{ "get_status", ConfigurableClientList_getStatus,
METH_NOARGS, ConfigurableClientList_get_status_doc },
{ "find", ConfigurableClientList_find,
METH_VARARGS, ConfigurableClientList_find_doc },
{ NULL, NULL, 0, NULL }
};
const char* const ConfigurableClientList_doc = "\
The list of data source clients\n\
\n\
The purpose is to have several data source clients of the same class\
and then be able to search through them to identify the one containing\
a given zone.\n\
\n\
Unlike the C++ version, we don't have the abstract base class. Abstract\
classes are not needed due to the duck typing nature of python.\
";
} // end of unnamed namespace
namespace isc {
......@@ -391,11 +420,48 @@ initModulePart_ConfigurableClientList(PyObject* mod) {
}
Py_INCREF(&configurableclientlist_type);
// FIXME: These should eventually be moved to the ZoneTableSegment
// class when we add Python bindings for the memory data source
// specific bits. But for now, we add these enums here to support
// reloading a zone table segment.
try {
// ConfigurableClientList::CacheStatus enum
installClassVariable
(configurableclientlist_type,
"CACHE_STATUS_CACHE_DISABLED",
Py_BuildValue("I", ConfigurableClientList::CACHE_DISABLED));
installClassVariable