BIND 10 master, updated. 741e96ebecac59ad257288094eab660661344b39 Merge branch 'trac2853'
BIND 10 source code commits
bind10-changes at lists.isc.org
Mon Jun 17 12:46:27 UTC 2013
The branch, master has been updated
via 741e96ebecac59ad257288094eab660661344b39 (commit)
via b8679425f97da697e4a8766b773fb9d400af6d79 (commit)
via bee3ea9fabdc724d9751c073ecf6f71f7492c286 (commit)
via e5d3eeb0edd4f47af44fee8a5e7ec1b165d8db30 (commit)
via 55f676522e6df80e3293ac5c0f845805f622f3a8 (commit)
via 9368ade9509ac5a2278c52d4c36c3a84d872531e (commit)
via 45087a113407dbb6b657f6e410dcf8158219effe (commit)
via 3a874c84baf65289d49be69c041b8340c946617a (commit)
via 7775ce49e0e07406db6d7dc158b7c01060aa93dd (commit)
via 922c6e4ecb69334bdcdac0c9fa82c4615c828609 (commit)
via bd74cbbdfc7828d5517089d1c9d4f2768b7bc7de (commit)
via 7448e48ddae3c48408bc95457e21d0ca189a9ea2 (commit)
via c8050a3b7d7e3060c59f14c777af5a8ad675f234 (commit)
via dde7e27a9effb9702e99f2145254ae13821d7667 (commit)
via bd3f52daf52aeeb006cc7ac420f11bdc9cd284ed (commit)
via 157d04befe4e1b7a3a9d3fe34d2a39af5709ef1a (commit)
via 3ffa4a979da3b91b0c721d931f0d9ad333f076a8 (commit)
via 3cc9fc1217967770551c6acb3ba8f6c6a0387864 (commit)
via fbe7def71c07f6f36af8f3d71f1bc83cb14c9e3a (commit)
via 171e04fdad35ccb630a4255efba73af01536f9de (commit)
via 41439e23c82d769ae7e1bb04453723a8770e6c9a (commit)
via c7c8f0af322c42e62e6f5168e554e56776230a79 (commit)
via dcef553fe87fd8c1be039394ad258a20aa8cd0b0 (commit)
via dcd88c02239b954f9617f998852c4399ff1f9a75 (commit)
via 9d2bd293b53d41efc0eb27c3f7e6cff01d264c8d (commit)
via 2cba268b3ae90e44b92227ebbbfa15e0fd3bdb5b (commit)
via 7028594590cdbe96b995ffb104a79551844999ee (commit)
via 92ebcb9a94e233362660b3d3f29df319da919286 (commit)
via 3a6139c33b7db517e76723d631ee49d47dc691dd (commit)
via a555e12ddf75657f9ba417f1bc9366c8ba094854 (commit)
via fae036eecd789c8815f8f52f5954835ef9fcfa82 (commit)
via efea64180e770421ac70e4a607e0b5587433b55a (commit)
via cbd8f4ddcdff128ec9f545b35151a8c64affb063 (commit)
via f61a5e73721ee68e4fb330f59a15fe5baf8fadb2 (commit)
via 3d96ea46c8191bff66e8ec93b00414f6a78a66f5 (commit)
via 358984aecfd6a7944624577f931e09c3fd53febb (commit)
via 86d1383a1a5998ce4fa7cac17b9f15bde7d19712 (commit)
via 38beb040c82df7d6502f3f25b52773aeecad6041 (commit)
via c5352f69e7fe7b9071066b4ce40d47c2d7948a1f (commit)
via 71eec22b3e41413651107079d9f9b5cf34d2c7ca (commit)
via 9b1ceb841266b4df04ac717186c86b22f77fe314 (commit)
via b18071c40b073c7f38643dfe19c7d10f2127abfc (commit)
via fe14d0cfc32360547afc55ceded8c69123a70214 (commit)
via faffa474e24d5e3bedc06088a388a0965d5059af (commit)
via 9163e6f7a614dddc6d1cbb7b9e879b802fda14ce (commit)
via 58335cecf1afa60e325b5ce952b23b40416bd4b9 (commit)
via 632a5596298f9d253693631606e026e01d8e7fdd (commit)
via 1ceeb74c1eba0ce65ea05a55fc86149d3fc733f8 (commit)
via 35c2010ce56c5fafebad8574184d083d1182534d (commit)
via 19030355897c6185f75193f299c8666b595970ae (commit)
via b3befd1fe7fed482817a7ccf05baa6292fd7a56a (commit)
via 71de47f8d14d274ae2223e56423d6022a3cf0369 (commit)
via 2935657cc5e260f54be045b28d7184adb406d3d7 (commit)
via 77010a2090eb1cade512f03e880df4cca13e9eb4 (commit)
via d340d8278228a062664eda13926f5af3daef8b35 (commit)
via f26eae2533b0b2c6591ad6aa9116dc9ba00c462a (commit)
via 70c715efff4255744322403c03a7be1e64d99c5e (commit)
via 6a4ee80ea2995124b737bd718f16f1baadb5c7dd (commit)
via 508e3554d5167a27ed1287a3903f851bfaf11a17 (commit)
from fc951496b21edbdb9c6ac53ab8ed596035177d54 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 741e96ebecac59ad257288094eab660661344b39
Merge: fc95149 b867942
Author: Mukund Sivaraman <muks at isc.org>
Date: Mon Jun 17 17:44:29 2013 +0530
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
-----------------------------------------------------------------------
Summary of changes:
configure.ac | 2 +
doc/Doxyfile-xml | 7 +
doc/Makefile.am | 2 +-
src/lib/datasrc/client_list.cc | 14 +-
src/lib/datasrc/client_list.h | 22 +-
src/lib/datasrc/memory/zone_writer.cc | 3 +-
src/lib/datasrc/memory/zone_writer.h | 2 +-
src/lib/datasrc/tests/client_list_unittest.cc | 43 +-
src/lib/python/isc/datasrc/Makefile.am | 4 +
.../isc/datasrc/configurableclientlist_inc.cc | 133 ++++
.../isc/datasrc/configurableclientlist_python.cc | 210 +++---
src/lib/python/isc/datasrc/datasrc.cc | 7 +
src/lib/python/isc/datasrc/iterator_python.cc | 2 +-
src/lib/python/isc/datasrc/tests/Makefile.am | 7 +-
.../python/isc/datasrc/tests/clientlist_test.py | 223 +++++--
.../isc/datasrc/zonetable_iterator_python.cc | 2 +-
src/lib/python/isc/datasrc/zonewriter_inc.cc | 101 +++
...ble_accessor_python.cc => zonewriter_python.cc} | 157 +++--
...table_iterator_python.h => zonewriter_python.h} | 26 +-
src/lib/util/python/.gitignore | 1 +
src/lib/util/python/Makefile.am | 2 +-
src/lib/util/python/doxygen2pydoc.py.in | 680 ++++++++++++++++++++
22 files changed, 1461 insertions(+), 189 deletions(-)
create mode 100644 doc/Doxyfile-xml
create mode 100644 src/lib/python/isc/datasrc/configurableclientlist_inc.cc
create mode 100644 src/lib/python/isc/datasrc/zonewriter_inc.cc
copy src/lib/python/isc/datasrc/{zonetable_accessor_python.cc => zonewriter_python.cc} (57%)
copy src/lib/python/isc/datasrc/{zonetable_iterator_python.h => zonewriter_python.h} (66%)
create mode 100755 src/lib/util/python/doxygen2pydoc.py.in
-----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index fc216c4..b01be12 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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
diff --git a/doc/Doxyfile-xml b/doc/Doxyfile-xml
new file mode 100644
index 0000000..ae5be8a
--- /dev/null
+++ b/doc/Doxyfile-xml
@@ -0,0 +1,7 @@
+# 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
+
+ at INCLUDE = Doxyfile
+GENERATE_XML = YES
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 3120280..e08de67 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -1,6 +1,6 @@
SUBDIRS = guide
-EXTRA_DIST = version.ent.in differences.txt
+EXTRA_DIST = version.ent.in differences.txt Doxyfile Doxyfile-xml
devel:
mkdir -p html
diff --git a/src/lib/datasrc/client_list.cc b/src/lib/datasrc/client_list.cc
index 01f59f4..50d30e2 100644
--- a/src/lib/datasrc/client_list.cc
+++ b/src/lib/datasrc/client_list.cc
@@ -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);
}
diff --git a/src/lib/datasrc/client_list.h b/src/lib/datasrc/client_list.h
index 318cf5c..ad18f20 100644
--- a/src/lib/datasrc/client_list.h
+++ b/src/lib/datasrc/client_list.h
@@ -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
diff --git a/src/lib/datasrc/memory/zone_writer.cc b/src/lib/datasrc/memory/zone_writer.cc
index ebe6151..3d4469c 100644
--- a/src/lib/datasrc/memory/zone_writer.cc
+++ b/src/lib/datasrc/memory/zone_writer.cc
@@ -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");
}
diff --git a/src/lib/datasrc/memory/zone_writer.h b/src/lib/datasrc/memory/zone_writer.h
index 12a75c6..bdd350c 100644
--- a/src/lib/datasrc/memory/zone_writer.h
+++ b/src/lib/datasrc/memory/zone_writer.h
@@ -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.
diff --git a/src/lib/datasrc/tests/client_list_unittest.cc b/src/lib/datasrc/tests/client_list_unittest.cc
index a686aee..acffd23 100644
--- a/src/lib/datasrc/tests/client_list_unittest.cc
+++ b/src/lib/datasrc/tests/client_list_unittest.cc
@@ -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);
diff --git a/src/lib/python/isc/datasrc/Makefile.am b/src/lib/python/isc/datasrc/Makefile.am
index 8a4b3a7..4278805 100644
--- a/src/lib/python/isc/datasrc/Makefile.am
+++ b/src/lib/python/isc/datasrc/Makefile.am
@@ -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__
diff --git a/src/lib/python/isc/datasrc/configurableclientlist_inc.cc b/src/lib/python/isc/datasrc/configurableclientlist_inc.cc
new file mode 100644
index 0000000..9f26d13
--- /dev/null
+++ b/src/lib/python/isc/datasrc/configurableclientlist_inc.cc
@@ -0,0 +1,133 @@
+// 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
diff --git a/src/lib/python/isc/datasrc/configurableclientlist_python.cc b/src/lib/python/isc/datasrc/configurableclientlist_python.cc
index b5c021d..86b0cd9 100644
--- a/src/lib/python/isc/datasrc/configurableclientlist_python.cc
+++ b/src/lib/python/isc/datasrc/configurableclientlist_python.cc
@@ -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);
@@ -153,6 +158,87 @@ ConfigurableClientList_resetMemorySegment(PyObject* po_self, PyObject* args) {
}
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 =
static_cast<s_ConfigurableClientList*>(po_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
+ (configurableclientlist_type,
+ "CACHE_STATUS_ZONE_NOT_CACHED",
+ Py_BuildValue("I", ConfigurableClientList::ZONE_NOT_CACHED));
+ installClassVariable
+ (configurableclientlist_type,
+ "CACHE_STATUS_ZONE_NOT_FOUND",
+ Py_BuildValue("I", ConfigurableClientList::ZONE_NOT_FOUND));
+ installClassVariable
+ (configurableclientlist_type,
+ "CACHE_STATUS_CACHE_NOT_WRITABLE",
+ Py_BuildValue("I", ConfigurableClientList::CACHE_NOT_WRITABLE));
+ installClassVariable
+ (configurableclientlist_type,
+ "CACHE_STATUS_DATASRC_NOT_FOUND",
+ Py_BuildValue("I", ConfigurableClientList::DATASRC_NOT_FOUND));
+ installClassVariable
+ (configurableclientlist_type,
+ "CACHE_STATUS_ZONE_SUCCESS",
+ Py_BuildValue("I", ConfigurableClientList::ZONE_SUCCESS));
+
+ // MemorySegmentState enum
+ installClassVariable(configurableclientlist_type,
+ "SEGMENT_UNUSED",
+ Py_BuildValue("I", SEGMENT_UNUSED));
+ installClassVariable(configurableclientlist_type,
+ "SEGMENT_WAITING",
+ Py_BuildValue("I", SEGMENT_WAITING));
+ installClassVariable(configurableclientlist_type,
+ "SEGMENT_INUSE",
+ Py_BuildValue("I", SEGMENT_INUSE));
+
+ // 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.
installClassVariable(configurableclientlist_type, "CREATE",
Py_BuildValue("I", ZoneTableSegment::CREATE));
installClassVariable(configurableclientlist_type, "READ_WRITE",
diff --git a/src/lib/python/isc/datasrc/datasrc.cc b/src/lib/python/isc/datasrc/datasrc.cc
index 117e409..11c3e7c 100644
--- a/src/lib/python/isc/datasrc/datasrc.cc
+++ b/src/lib/python/isc/datasrc/datasrc.cc
@@ -35,6 +35,7 @@
#include "zone_loader_python.h"
#include "zonetable_accessor_python.h"
#include "zonetable_iterator_python.h"
+#include "zonewriter_python.h"
#include <util/python/pycppwrapper_util.h>
#include <dns/python/pydnspp_common.h>
@@ -44,6 +45,7 @@
using namespace isc::datasrc;
using namespace isc::datasrc::python;
+using namespace isc::datasrc::memory::python;
using namespace isc::util::python;
using namespace isc::dns::python;
@@ -388,5 +390,10 @@ PyInit_datasrc(void) {
return (NULL);
}
+ if (!initModulePart_ZoneWriter(mod)) {
+ Py_DECREF(mod);
+ return (NULL);
+ }
+
return (mod);
}
diff --git a/src/lib/python/isc/datasrc/iterator_python.cc b/src/lib/python/isc/datasrc/iterator_python.cc
index 9757a3b..0b80f20 100644
--- a/src/lib/python/isc/datasrc/iterator_python.cc
+++ b/src/lib/python/isc/datasrc/iterator_python.cc
@@ -61,7 +61,7 @@ typedef CPPPyObjectContainer<s_ZoneIterator, ZoneIterator>
// General creation and destruction
int
-ZoneIterator_init(s_ZoneIterator* self, PyObject* args) {
+ZoneIterator_init(s_ZoneIterator*, PyObject*) {
// can't be called directly
PyErr_SetString(PyExc_TypeError,
"ZoneIterator cannot be constructed directly");
diff --git a/src/lib/python/isc/datasrc/tests/Makefile.am b/src/lib/python/isc/datasrc/tests/Makefile.am
index a86c2b4..97fc2b6 100644
--- a/src/lib/python/isc/datasrc/tests/Makefile.am
+++ b/src/lib/python/isc/datasrc/tests/Makefile.am
@@ -35,13 +35,18 @@ if ENABLE_PYTHON_COVERAGE
rm -f .coverage
${LN_S} $(abs_top_srcdir)/.coverage .coverage
endif
+if USE_SHARED_MEMORY
+HAVE_SHARED_MEMORY=yes
+else
+HAVE_SHARED_MEMORY=no
+endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
$(LIBRARY_PATH_PLACEHOLDER) \
PYTHONPATH=:$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/python/isc/log:$(abs_top_builddir)/src/lib/python/isc/datasrc/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs \
TESTDATA_PATH=$(abs_srcdir)/testdata \
TESTDATA_WRITE_PATH=$(abs_builddir) \
- GLOBAL_TESTDATA_PATH=$(abs_top_srcdir)/src/lib/testutils/testdata \
+ HAVE_SHARED_MEMORY=$(HAVE_SHARED_MEMORY) \
B10_FROM_BUILD=$(abs_top_builddir) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/lib/python/isc/datasrc/tests/clientlist_test.py b/src/lib/python/isc/datasrc/tests/clientlist_test.py
index 2609b6b..2504c82 100644
--- a/src/lib/python/isc/datasrc/tests/clientlist_test.py
+++ b/src/lib/python/isc/datasrc/tests/clientlist_test.py
@@ -20,7 +20,8 @@ import unittest
import os
import sys
-TESTDATA_PATH = os.environ['GLOBAL_TESTDATA_PATH'] + os.sep
+TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep
+MAPFILE_PATH = os.environ['TESTDATA_WRITE_PATH'] + os.sep + 'test.mapped'
class ClientListTest(unittest.TestCase):
"""
@@ -36,8 +37,18 @@ class ClientListTest(unittest.TestCase):
# last.
self.dsrc = None
self.finder = None
+
+ # If a test created a ZoneWriter with a mapped memory segment,
+ # the writer will hold a reference to the client list which will
+ # need the mapfile to exist until it's destroyed. So we'll make
+ # sure to destroy the writer (by resetting it) before removing
+ # the mapfile below.
+ self.__zone_writer = None
self.clist = None
+ if os.path.exists(MAPFILE_PATH):
+ os.unlink(MAPFILE_PATH)
+
def test_constructors(self):
"""
Test the constructor. It should accept an RRClass. Check it
@@ -54,6 +65,15 @@ class ClientListTest(unittest.TestCase):
self.assertRaises(TypeError, isc.datasrc.ConfigurableClientList,
isc.dns.RRClass.IN, isc.dns.RRClass.IN)
+ def configure_helper(self):
+ self.clist.configure('''[{
+ "type": "MasterFiles",
+ "params": {
+ "example.com": "''' + TESTDATA_PATH + '''example.com"
+ },
+ "cache-enable": true
+ }]''', True)
+
def test_configure(self):
"""
Test we can configure the client list. This tests if the valid
@@ -64,22 +84,16 @@ class ClientListTest(unittest.TestCase):
# This should be NOP now
self.clist.configure("[]", True)
# Check the zone is not there yet
- dsrc, finder, exact = self.clist.find(isc.dns.Name("example.org"))
+ dsrc, finder, exact = self.clist.find(isc.dns.Name("example.com"))
self.assertIsNone(dsrc)
self.assertIsNone(finder)
self.assertFalse(exact)
# We can use this type, as it is not loaded dynamically.
- self.clist.configure('''[{
- "type": "MasterFiles",
- "params": {
- "example.org": "''' + TESTDATA_PATH + '''example.org.zone"
- },
- "cache-enable": true
- }]''', True)
+ self.configure_helper()
# Check the zone is there now. Proper tests of find are in other
# test methods.
self.dsrc, self.finder, exact = \
- self.clist.find(isc.dns.Name("example.org"))
+ self.clist.find(isc.dns.Name("example.com"))
self.assertIsNotNone(self.dsrc)
self.assertTrue(isinstance(self.dsrc, isc.datasrc.DataSourceClient))
self.assertIsNotNone(self.finder)
@@ -97,20 +111,8 @@ class ClientListTest(unittest.TestCase):
self.assertRaises(TypeError, self.clist.configure, "[]")
self.assertRaises(TypeError, self.clist.configure, "[]", "true")
- def test_find(self):
- """
- Test the find accepts the right arguments, some of them can be omitted,
- etc.
- """
- self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
- self.clist.configure('''[{
- "type": "MasterFiles",
- "params": {
- "example.org": "''' + TESTDATA_PATH + '''example.org.zone"
- },
- "cache-enable": true
- }]''', True)
- dsrc, finder, exact = self.clist.find(isc.dns.Name("sub.example.org"))
+ def find_helper(self):
+ dsrc, finder, exact = self.clist.find(isc.dns.Name("sub.example.com"))
self.assertIsNotNone(dsrc)
self.assertTrue(isinstance(dsrc, isc.datasrc.DataSourceClient))
self.assertIsNotNone(finder)
@@ -124,31 +126,31 @@ class ClientListTest(unittest.TestCase):
# We check an exact match in test_configure already
self.assertFalse(exact)
self.dsrc, self.finder, exact = \
- self.clist.find(isc.dns.Name("sub.example.org"), False)
+ self.clist.find(isc.dns.Name("sub.example.com"), False)
self.assertIsNotNone(self.dsrc)
self.assertTrue(isinstance(self.dsrc, isc.datasrc.DataSourceClient))
self.assertIsNotNone(self.finder)
self.assertTrue(isinstance(self.finder, isc.datasrc.ZoneFinder))
self.assertFalse(exact)
self.dsrc, self.finder, exact = \
- self.clist.find(isc.dns.Name("sub.example.org"), True)
+ self.clist.find(isc.dns.Name("sub.example.com"), True)
self.assertIsNone(self.dsrc)
self.assertIsNone(self.finder)
self.assertFalse(exact)
self.dsrc, self.finder, exact = \
- self.clist.find(isc.dns.Name("sub.example.org"), False, False)
+ self.clist.find(isc.dns.Name("sub.example.com"), False, False)
self.assertIsNotNone(self.dsrc)
self.assertTrue(isinstance(self.dsrc, isc.datasrc.DataSourceClient))
self.assertIsNotNone(self.finder)
self.assertTrue(isinstance(self.finder, isc.datasrc.ZoneFinder))
self.assertFalse(exact)
self.dsrc, self.finder, exact = \
- self.clist.find(isc.dns.Name("sub.example.org"), True, False)
+ self.clist.find(isc.dns.Name("sub.example.com"), True, False)
self.assertIsNone(self.dsrc)
self.assertIsNone(self.finder)
self.assertFalse(exact)
# Some invalid inputs
- self.assertRaises(TypeError, self.clist.find, "example.org")
+ self.assertRaises(TypeError, self.clist.find, "example.com")
self.assertRaises(TypeError, self.clist.find)
def test_get_zone_table_accessor(self):
@@ -178,13 +180,7 @@ class ClientListTest(unittest.TestCase):
self.assertEqual(0, len(list(iterator)))
# normal configuration
- self.clist.configure('''[{
- "type": "MasterFiles",
- "params": {
- "example.org": "''' + TESTDATA_PATH + '''example.org.zone"
- },
- "cache-enable": true
- }]''', True)
+ self.configure_helper()
# !use_cache => NotImplemented
self.assertRaises(isc.datasrc.Error,
self.clist.get_zone_table_accessor, None, False)
@@ -196,7 +192,7 @@ class ClientListTest(unittest.TestCase):
self.assertIsNotNone(table)
zonelist = list(table)
self.assertEqual(1, len(zonelist))
- self.assertEqual(zonelist[0][1], isc.dns.Name("example.org"))
+ self.assertEqual(zonelist[0][1], isc.dns.Name("example.com"))
# named datasrc
table = self.clist.get_zone_table_accessor("MasterFiles", True)
@@ -231,6 +227,159 @@ class ClientListTest(unittest.TestCase):
zonelist.remove(zone)
self.assertEqual(0, len(zonelist))
+ def test_find(self):
+ """
+ Test the find accepts the right arguments, some of them can be omitted,
+ etc.
+ """
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ self.configure_helper()
+ self.find_helper()
+
+ @unittest.skipIf(os.environ['HAVE_SHARED_MEMORY'] != 'yes',
+ 'shared memory is not available')
+ def test_find_mapped(self):
+ """
+ Test find on a mapped segment.
+ """
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ self.clist.configure('''[{
+ "type": "MasterFiles",
+ "params": {
+ "example.com": "''' + TESTDATA_PATH + '''example.com"
+ },
+ "cache-enable": true,
+ "cache-type": "mapped"
+ }]''', True)
+
+ map_params = '{"mapped-file": "' + MAPFILE_PATH + '"}'
+ self.clist.reset_memory_segment("MasterFiles",
+ isc.datasrc.ConfigurableClientList.CREATE,
+ map_params)
+ result, self.__zone_writer = self.clist.get_cached_zone_writer(isc.dns.Name("example.com"))
+ self.assertEqual(isc.datasrc.ConfigurableClientList.CACHE_STATUS_ZONE_SUCCESS,
+ result)
+ err_msg = self.__zone_writer.load()
+ self.assertIsNone(err_msg)
+ self.__zone_writer.install()
+ self.__zone_writer.cleanup()
+
+ self.clist.reset_memory_segment("MasterFiles",
+ isc.datasrc.ConfigurableClientList.READ_ONLY,
+ map_params)
+ result, self.__zone_writer = self.clist.get_cached_zone_writer(isc.dns.Name("example.com"))
+ self.assertEqual(isc.datasrc.ConfigurableClientList.CACHE_STATUS_CACHE_NOT_WRITABLE,
+ result)
+
+ # The segment is still in READ_ONLY mode.
+ self.find_helper()
+
+ def test_zone_writer_load_twice(self):
+ """
+ Test that the zone writer throws when load() is called more than
+ once.
+ """
+
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ self.configure_helper()
+
+ result, self.__zone_writer = self.clist.get_cached_zone_writer(isc.dns.Name("example.com"))
+ self.assertEqual(isc.datasrc.ConfigurableClientList.CACHE_STATUS_ZONE_SUCCESS,
+ result)
+ err_msg = self.__zone_writer.load()
+ self.assertIsNone(err_msg)
+ self.assertRaises(isc.datasrc.Error, self.__zone_writer.load)
+ self.__zone_writer.cleanup()
+
+ def test_zone_writer_install_without_load(self):
+ """
+ Test that the zone writer throws when install() is called
+ without calling load() first.
+ """
+
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ self.configure_helper()
+
+ result, self.__zone_writer = self.clist.get_cached_zone_writer(isc.dns.Name("example.com"))
+ self.assertEqual(isc.datasrc.ConfigurableClientList.CACHE_STATUS_ZONE_SUCCESS,
+ result)
+ self.assertRaises(isc.datasrc.Error, self.__zone_writer.install)
+ self.__zone_writer.cleanup()
+
+ def test_get_status(self):
+ """
+ Test getting status of various data sources.
+ """
+
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+
+ status = self.clist.get_status()
+ self.assertIsNotNone(status)
+ self.assertIsInstance(status, list)
+ self.assertEqual(0, len(status))
+
+ self.configure_helper()
+
+ status = self.clist.get_status()
+ self.assertIsNotNone(status)
+ self.assertIsInstance(status, list)
+ self.assertEqual(1, len(status))
+ self.assertIsInstance(status[0], tuple)
+ self.assertTupleEqual(('MasterFiles', 'local',
+ isc.datasrc.ConfigurableClientList.SEGMENT_INUSE),
+ status[0])
+
+ def test_get_status_unused(self):
+ """
+ Test getting status when segment type is mapped, but the cache
+ is disabled.
+ """
+
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ self.clist.configure('''[{
+ "type": "sqlite3",
+ "params": {
+ "database_file": "''' + TESTDATA_PATH + '''example.com.sqlite3"
+ },
+ "cache-zones" : ["example.com"],
+ "cache-type": "mapped",
+ "cache-enable": false
+ }]''', True)
+
+ status = self.clist.get_status()
+ self.assertIsNotNone(status)
+ self.assertIsInstance(status, list)
+ self.assertEqual(1, len(status))
+ self.assertIsInstance(status[0], tuple)
+ self.assertTupleEqual(('sqlite3', None,
+ isc.datasrc.ConfigurableClientList.SEGMENT_UNUSED),
+ status[0])
+
+ def test_get_status_waiting(self):
+ """
+ Test getting status when segment type is mapped and it has not
+ been reset yet.
+ """
+
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ self.clist.configure('''[{
+ "type": "MasterFiles",
+ "params": {
+ "example.com": "''' + TESTDATA_PATH + '''example.com"
+ },
+ "cache-enable": true,
+ "cache-type": "mapped"
+ }]''', True)
+
+ status = self.clist.get_status()
+ self.assertIsNotNone(status)
+ self.assertIsInstance(status, list)
+ self.assertEqual(1, len(status))
+ self.assertIsInstance(status[0], tuple)
+ self.assertTupleEqual(('MasterFiles', 'mapped',
+ isc.datasrc.ConfigurableClientList.SEGMENT_WAITING),
+ status[0])
+
if __name__ == "__main__":
isc.log.init("bind10")
isc.log.resetUnitTestRootLogger()
diff --git a/src/lib/python/isc/datasrc/zonetable_iterator_python.cc b/src/lib/python/isc/datasrc/zonetable_iterator_python.cc
index 9ac7cd9..fbf1ebf 100644
--- a/src/lib/python/isc/datasrc/zonetable_iterator_python.cc
+++ b/src/lib/python/isc/datasrc/zonetable_iterator_python.cc
@@ -49,7 +49,7 @@ public:
// General creation and destruction
int
-ZoneTableIterator_init(s_ZoneTableIterator* self, PyObject* args) {
+ZoneTableIterator_init(s_ZoneTableIterator*, PyObject*) {
// can't be called directly
PyErr_SetString(PyExc_TypeError,
"ZoneTableIterator cannot be constructed directly");
diff --git a/src/lib/python/isc/datasrc/zonewriter_inc.cc b/src/lib/python/isc/datasrc/zonewriter_inc.cc
new file mode 100644
index 0000000..efe1144
--- /dev/null
+++ b/src/lib/python/isc/datasrc/zonewriter_inc.cc
@@ -0,0 +1,101 @@
+// 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 ZoneWriter_doc = "\
+Does an update to a zone.\n\
+\n\
+This represents the work of a (re)load of a zone. The work is divided\n\
+into three stages load(), install() and cleanup(). They should be\n\
+called in this order for the effect to take place.\n\
+\n\
+We divide them so the update of zone data can be done asynchronously,\n\
+in a different thread. The install() operation is the only one that\n\
+needs to be done in a critical section.\n\
+\n\
+This class provides strong exception guarantee for each public method.\n\
+That is, when any of the methods throws, the entire state stays the\n\
+same as before the call.\n\
+\n\
+ZoneWriter objects cannot be constructed directly. They have to be\n\
+obtained by using get_cached_zone_writer() on a ConfigurableClientList.\n\
+\n\
+";
+
+const char* const ZoneWriter_load_doc = "\
+load() -> err_msg\n\
+\n\
+Get the zone data into memory.\n\
+\n\
+This is the part that does the time-consuming loading into the memory.\n\
+This can be run in a separate thread, for example. It has no effect on\n\
+the data actually served, it only prepares them for future use.\n\
+\n\
+This is the first method you should call on the object. Never call it\n\
+multiple times.\n\
+\n\
+Depending on how the ZoneWriter was constructed, in case a load error\n\
+happens, a string with the error message may be returned. When\n\
+ZoneWriter is not constructed to do that, in case of a load error, a\n\
+DataSourceError exception is raised. In all other cases, this method\n\
+returns None.\n\
+\n\
+Exceptions:\n\
+ isc.InvalidOperation if called second time.\n\
+ DataSourceError load related error (not thrown if constructed not to).\n\
+\n\
+";
+
+const char* const ZoneWriter_install_doc = "\
+install() -> void\n\
+\n\
+Put the changes to effect.\n\
+\n\
+This replaces the old version of zone with the one previously prepared\n\
+by load(). It takes ownership of the old zone data, if any.\n\
+\n\
+You may call it only after successful load() and at most once. It\n\
+includes the case the writer is constructed to allow load errors,\n\
+and load() encountered and caught a DataSourceError exception.\n\
+In this case this method installs a special empty zone to\n\
+the table.\n\
+\n\
+The operation is expected to be fast and is meant to be used inside a\n\
+critical section.\n\
+\n\
+This may throw in rare cases. If it throws, you still need to call\n\
+cleanup().\n\
+\n\
+Exceptions:\n\
+ isc.InvalidOperation if called without previous load() or for the\n\
+ second time or cleanup() was called already.\n\
+\n\
+";
+
+const char* const ZoneWriter_cleanup_doc = "\
+cleanup() -> void\n\
+\n\
+Clean up resources.\n\
+\n\
+This releases all resources held by owned zone data. That means the\n\
+one loaded by load() in case install() was not called or was not\n\
+successful, or the one replaced in install().\n\
+\n\
+Exceptions:\n\
+ none\n\
+\n\
+";
+
+} // unnamed namespace
diff --git a/src/lib/python/isc/datasrc/zonewriter_python.cc b/src/lib/python/isc/datasrc/zonewriter_python.cc
new file mode 100644
index 0000000..b3783e8
--- /dev/null
+++ b/src/lib/python/isc/datasrc/zonewriter_python.cc
@@ -0,0 +1,253 @@
+// 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.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+//#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <string>
+#include <stdexcept>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <datasrc/memory/zone_writer.h>
+
+#include "zonewriter_python.h"
+#include "datasrc.h"
+
+#include "zonewriter_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;
+
+//
+// ZoneWriter
+//
+
+namespace {
+
+// The s_* Class simply covers one instantiation of the object
+class s_ZoneWriter : public PyObject {
+public:
+ s_ZoneWriter() :
+ cppobj(ConfigurableClientList::ZoneWriterPtr()),
+ base_obj(NULL)
+ {}
+
+ ConfigurableClientList::ZoneWriterPtr cppobj;
+ // This is a reference to a base object; if the object of this class
+ // depends on another object to be in scope during its lifetime,
+ // we use INCREF the base object upon creation, and DECREF it at
+ // the end of the destructor
+ // This is an optional argument to createXXX(). If NULL, it is ignored.
+ PyObject* base_obj;
+};
+
+int
+ZoneWriter_init(PyObject*, PyObject*, PyObject*) {
+ // can't be called directly
+ PyErr_SetString(PyExc_TypeError,
+ "ZoneWriter cannot be constructed directly");
+
+ return (-1);
+}
+
+void
+ZoneWriter_destroy(PyObject* po_self) {
+ s_ZoneWriter* self = static_cast<s_ZoneWriter*>(po_self);
+ // cppobj is a shared ptr, but to make sure things are not destroyed in
+ // the wrong order, we reset it here.
+ self->cppobj.reset();
+ if (self->base_obj != NULL) {
+ Py_DECREF(self->base_obj);
+ }
+ Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+ZoneWriter_load(PyObject* po_self, PyObject*) {
+ s_ZoneWriter* self = static_cast<s_ZoneWriter*>(po_self);
+ try {
+ std::string error_msg;
+ self->cppobj->load(&error_msg);
+ if (!error_msg.empty()) {
+ return (Py_BuildValue("s", error_msg.c_str()));
+ }
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unknown C++ exception");
+ return (NULL);
+ }
+
+ Py_RETURN_NONE;
+}
+
+PyObject*
+ZoneWriter_install(PyObject* po_self, PyObject*) {
+ s_ZoneWriter* self = static_cast<s_ZoneWriter*>(po_self);
+ try {
+ self->cppobj->install();
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unknown C++ exception");
+ return (NULL);
+ }
+
+ Py_RETURN_NONE;
+}
+
+PyObject*
+ZoneWriter_cleanup(PyObject* po_self, PyObject*) {
+ s_ZoneWriter* self = static_cast<s_ZoneWriter*>(po_self);
+ try {
+ self->cppobj->cleanup();
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unknown C++ exception");
+ return (NULL);
+ }
+
+ Py_RETURN_NONE;
+}
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef ZoneWriter_methods[] = {
+ { "load", ZoneWriter_load, METH_NOARGS,
+ ZoneWriter_load_doc },
+ { "install", ZoneWriter_install, METH_NOARGS,
+ ZoneWriter_install_doc },
+ { "cleanup", ZoneWriter_cleanup, METH_NOARGS,
+ ZoneWriter_cleanup_doc },
+ { NULL, NULL, 0, NULL }
+};
+
+} // end of unnamed namespace
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_ZoneWriter
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject zonewriter_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "datasrc.ZoneWriter",
+ sizeof(s_ZoneWriter), // tp_basicsize
+ 0, // tp_itemsize
+ ZoneWriter_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ NULL, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ ZoneWriter_doc,
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ ZoneWriter_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ ZoneWriter_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_ZoneWriter(PyObject* mod) {
+ // We initialize the static description object with PyType_Ready(),
+ // then add it to the module. This is not just a check! (leaving
+ // this out results in segmentation faults)
+ if (PyType_Ready(&zonewriter_type) < 0) {
+ return (false);
+ }
+ void* p = &zonewriter_type;
+ if (PyModule_AddObject(mod, "ZoneWriter", static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+ Py_INCREF(&zonewriter_type);
+
+ return (true);
+}
+
+PyObject*
+createZoneWriterObject(ConfigurableClientList::ZoneWriterPtr source,
+ PyObject* base_obj)
+{
+ s_ZoneWriter* py_zf = static_cast<s_ZoneWriter*>(
+ zonewriter_type.tp_alloc(&zonewriter_type, 0));
+ if (py_zf != NULL) {
+ py_zf->cppobj = source;
+ py_zf->base_obj = base_obj;
+ if (base_obj != NULL) {
+ Py_INCREF(base_obj);
+ }
+ }
+ return (py_zf);
+}
+
+} // namespace python
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
diff --git a/src/lib/python/isc/datasrc/zonewriter_python.h b/src/lib/python/isc/datasrc/zonewriter_python.h
new file mode 100644
index 0000000..a7c97b3
--- /dev/null
+++ b/src/lib/python/isc/datasrc/zonewriter_python.h
@@ -0,0 +1,50 @@
+// 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.
+
+#ifndef PYTHON_ZONEWRITER_H
+#define PYTHON_ZONEWRITER_H 1
+
+#include <Python.h>
+#include <datasrc/client_list.h>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+namespace python {
+
+extern PyTypeObject zonewriter_type;
+
+bool initModulePart_ZoneWriter(PyObject* mod);
+
+/// \brief Create a ZoneWriter python object
+///
+/// \param source The zone writer pointer to wrap
+/// \param base_obj An optional PyObject that this ZoneWriter depends on
+/// Its refcount is increased, and will be decreased when
+/// this zone iterator is destroyed, making sure that the
+/// base object is never destroyed before this ZoneWriter.
+PyObject* createZoneWriterObject(
+ isc::datasrc::ConfigurableClientList::ZoneWriterPtr source,
+ PyObject* base_obj = NULL);
+
+} // namespace python
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // PYTHON_ZONEWRITER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/python/.gitignore b/src/lib/util/python/.gitignore
index c54df80..619c69f 100644
--- a/src/lib/util/python/.gitignore
+++ b/src/lib/util/python/.gitignore
@@ -1,2 +1,3 @@
+/doxygen2pydoc.py
/gen_wiredata.py
/mkpywrapper.py
diff --git a/src/lib/util/python/Makefile.am b/src/lib/util/python/Makefile.am
index 1e05688..c7ddf3d 100644
--- a/src/lib/util/python/Makefile.am
+++ b/src/lib/util/python/Makefile.am
@@ -1,3 +1,3 @@
-noinst_SCRIPTS = gen_wiredata.py mkpywrapper.py const2hdr.py \
+noinst_SCRIPTS = doxygen2pydoc.py gen_wiredata.py mkpywrapper.py const2hdr.py \
pythonize_constants.py
EXTRA_DIST = const2hdr.py pythonize_constants.py
diff --git a/src/lib/util/python/doxygen2pydoc.py.in b/src/lib/util/python/doxygen2pydoc.py.in
new file mode 100755
index 0000000..7aa74ec
--- /dev/null
+++ b/src/lib/util/python/doxygen2pydoc.py.in
@@ -0,0 +1,680 @@
+#!@PYTHON@
+
+# Copyright (C) 2011 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and 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 INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM 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.
+
+r"""
+A helper to semi-auto generate Python docstring text from C++ Doxygen
+documentation.
+
+This script converts an XML-format doxygen documentation for C++ library
+into a template Python docstring for the corresponding Python version
+of the library. While it's not perfect and you'll still need to edit the
+output by hand, but past experiments showed the script produces a pretty
+good template. It will help provide more compatible documentation for
+both C++ and Python versions of library from a unified source (C++ Doxygen
+documentation) with minimizing error-prone and boring manual conversion.
+
+HOW TO USE IT
+
+1. Generate XML output by doxygen. Use bind10/doc/Doxyfile-xml:
+
+ % cd bind10/doc
+ % doxygen Doxyfile-xml
+ (XML files will be generated under bind10/doc/html/xml)
+
+2. Identify the xml file of the conversion target (C++ class, function, etc)
+
+ This is a bit tricky. You'll probably need to do manual search.
+ For example, to identify the xml file for a C++ class
+ isc::datasrc::memory::ZoneWriter, you might do:
+
+ % cd bind10/doc/html/xml
+ % grep ZoneWriter *.xml | grep 'kind="class"'
+ index.xml: <compound refid="d4/d3c/classisc_1_1datasrc_1_1memory_1_1ZoneWriter" kind="class"><name>isc::datasrc::memory::ZoneWriter</name>
+
+ In this case the file under the d4/d3c directory (with .xml suffix) would
+ be the file you're looking for.
+
+3. Run this script for the xml file:
+
+ % python3 doxygen2pydoc.py <top_srcdir>/doc/html/xml/d4/d3c/classisc_1_1datasrc_1_1memory_1_1ZoneWriter.xml > output.cc
+
+ The template content is dumped to standard out (redirected to file
+ "output.cc" in this example).
+
+ Sometimes the script produces additional output to standard error,
+ like this:
+
+ Replaced camelCased terms:
+ resetMemorySegment => reset_memory_segment
+ getConfiguration => get_configuration
+
+ In BIND 10 naming convention for methods is different for C++ and
+ Python. This script uses some heuristic guess to convert the
+ C++-style method names to likely Python-style ones, and the converted
+ method names are used in the dumped template. In many cases the guessed
+ names are correct, but you should check this list and make adjustments
+ by hand if necessary.
+
+ If there's no standard error output, this type of conversion didn't
+ happen.
+
+4. Edit and copy the template
+
+ The dumped template has the following organization:
+
+ namespace {
+ #ifdef COPY_THIS_TO_MAIN_CC
+ { "cleanup", ZoneWriter_cleanup, METH_NOARGS,
+ ZoneWriter_cleanup_doc },
+ { "install", ZoneWriter_install, METH_NOARGS,
+ ZoneWriter_install_doc },
+ { "load", ZoneWriter_load, METH_VARARGS,
+ ZoneWriter_load_doc },
+ #endif // COPY_THIS_TO_MAIN_CC
+
+ const char* const ZoneWriter_doc = "\
+ ...
+ ";
+
+ const char* const ZoneWriter_install_doc = "\
+ ...
+ ";
+
+ ...
+ }
+
+ The ifdef-ed block is a template for class methods information
+ to be added to the corresponding PyMethodDef structure array
+ (your wrapper C++ source would have something like ZoneWriter_methods
+ of this type). These lines should be copied there. As long as
+ the method names and corresponding wrapper function (such as
+ ZoneWriter_cleanup) are correct you shouldn't have to edit this part
+ (and they would be normally correct, unless the guessed method name
+ conversion was needed).
+
+ The rest of the content is a sequence of constant C-string variables.
+ Usually the first variable corresponds to the class description, and
+ the rest are method descriptions (note that ZoneWriter_install_doc
+ is referenced from the ifdef-ed block). The content of this part
+ would generally make sense, but you'll often need to make some
+ adjsutments by hand. A common examples of such adjustment is to
+ replace "NULL" with "None". Also, it's not uncommon that some part
+ of the description simply doesn't apply to the Python version or
+ that Python specific notes are needed. So go through the description
+ carefully and make necessary changes. A common practice is to add
+ comments for a summary of adjustments like this:
+
+ // Modifications:
+ // NULL->None
+ // - removed notes about derived classes (which doesn't apply for python)
+ const char* const ZoneWriter_doc = "\
+ ...
+ ";
+ This note will help next time you need to auto-generate and edit the
+ template (probably because the original C++ document is updated).
+
+ You can simply copy this part to the main C++ wrapper file, but since
+ it's relatively large a common practice is to maintain it in a separate
+ file that is exclusively included from the main file: if the name of
+ the main file is zonewriter_python.cc, the pydoc strings would be copied
+ in zonewriter_python_inc.cc, and the main file would have this line:
+
+ #include "zonewriter_inc.cc"
+
+ (In case you are C++ language police: it's okay to use the unnamed
+ name space for a file to be included because it's essentially a part
+ of the single .cc file, not expected to be included by others).
+
+ In either case, the ifdef-ed part should be removed.
+
+ADVANCED FEATURES
+
+You can use a special "xmlonly" doxygen command in C++ doxygent document
+in order to include Python code excerpt (while hiding it from the doxygen
+output for the C++ version). This command will be converted to
+a special XML tag in the XML output.
+
+The block enclosed by \xmlonly and \endxmlonly should contain
+a verbatim XML tag named "pythonlisting", in which the python code should
+be placed.
+/// \code
+/// Name name("example.com");
+/// std::cout << name.toText() << std::endl;
+/// \endcode
+///
+/// \xmlonly <pythonlisting>
+/// name = Name("example.com")
+/// print(name.to_text())
+/// </pythonlisting> \endxmlonly
+
+Note that there must be a blank line between \endcode and \xmlonly.
+doxygen2pydoc assume the pythonlisting tag is in a separate <para> node.
+This blank ensures doxygen will produce the XML file that meets the
+assumption.
+
+INTERNAL MEMO (incomplete, and not very unredable yet)
+
+This simplified utility assumes the following structure:
+...
+ <compounddef ...>
+ <compoundname>isc::dns::TSIGError</compoundname>
+ <sectiondef kind="user-defined">
+ constructor, destructor
+ </sectiondef>
+ <sectiondef kind="public-type">
+ ..
+ </sectiondef>
+ <sectiondef kind="public-func">
+ <memberdef kind="function"...>
+ <type>return type (if any)</type>
+ <argsstring>(...) [const]</argsstring>
+ <name>method name</name>
+ <briefdescription>method's brief description</briefdescription>
+ <detaileddescription>
+ <para>...</para>...
+ <para>
+ <parameterlist kind="exception">
+ <parameteritem>
+ <parameternamelist>
+ <parametername>Exception name</parametername>
+ </parameternamelist>
+ <parameterdescription>
+ <para>exception desc</para>
+ </parameterdescription>
+ </parameteritem>
+ </parameterlist>
+ <parameterlist kind="param">
+ <parameteritem>
+ <parameternamelist>
+ <parametername>param name</parametername>
+ </parameternamelist>
+ <parameterdescription>
+ <para>param desc</para>
+ </parameterdescription>
+ </parameteritem>
+ ...
+ </parameterlist>
+ <simplesect kind="return">Return value</simplesect>
+ </para>
+ </detaileddescription>
+ </memberdef>
+ </sectiondef>
+ <sectiondef kind="public-static-attrib|user-defined">
+ <memberdef kind="variable"...>
+ <name>class-specific-constant</name>
+ <initializer>value</initializer>
+ <brief|detaileddescription>paragraph(s)</brief|detaileddescription>
+ </sectiondef>
+ <briefdescription>
+ class's brief description
+ </briefdescription>
+ <detaileddescription>
+ class's detailed description
+ </detaileddescription>
+ </compounddef>
+"""
+
+import re, string, sys, textwrap
+from xml.dom.minidom import parse
+from textwrap import fill, dedent, TextWrapper
+
+camel_replacements = {}
+member_functions = []
+constructors = []
+class_variables = []
+
+RE_CAMELTERM = re.compile('([\s\.]|^)[a-z]+[A-Z]\S*')
+RE_SIMPLECAMEL = re.compile("([a-z])([A-Z])")
+RE_CAMELAFTERUPPER = re.compile("([A-Z])([A-Z])([a-z])")
+
+class Paragraph:
+ TEXT = 0
+ ITEMIZEDLIST = 1
+ CPPLISTING = 2
+ PYLISTING = 3
+ VERBATIM = 4
+
+ def __init__(self, xml_node):
+ if len(xml_node.getElementsByTagName("pythonlisting")) > 0:
+ self.type = self.PYLISTING
+ self.text = re.sub("///", "", get_text(xml_node))
+ elif len(xml_node.getElementsByTagName("verbatim")) > 0:
+ self.type = self.VERBATIM
+ self.text = get_text(xml_node)
+ elif len(xml_node.getElementsByTagName("programlisting")) > 0:
+ # We ignore node containing a "programlisting" tag.
+ # They are C++ example code, and we are not interested in them
+ # in pydoc.
+ self.type = self.CPPLISTING
+ elif len(xml_node.getElementsByTagName("itemizedlist")) > 0:
+ self.type = self.ITEMIZEDLIST
+ self.items = []
+ for item in xml_node.getElementsByTagName("listitem"):
+ self.items.append(get_text(item))
+ else:
+ self.type = self.TEXT
+
+ # A single textual paragraph could have multiple simple sections
+ # if it contains notes.
+
+ self.texts = []
+ subnodes = []
+ for child in xml_node.childNodes:
+ if child.nodeType == child.ELEMENT_NODE and \
+ child.nodeName == 'simplesect' and \
+ child.getAttribute('kind') == 'note':
+ if len(subnodes) > 0:
+ self.texts.append(get_text_fromnodelist(subnodes))
+ subnodes = []
+ subtext = 'Note: '
+ for t in child.childNodes:
+ subtext += get_text(t)
+ self.texts.append(subtext)
+ else:
+ subnodes.append(child)
+ if len(subnodes) > 0:
+ self.texts.append(get_text_fromnodelist(subnodes))
+
+ def dump(self, f, wrapper):
+ if self.type == self.CPPLISTING:
+ return
+ elif self.type == self.ITEMIZEDLIST:
+ for item in self.items:
+ item_wrapper = TextWrapper(\
+ initial_indent=wrapper.initial_indent + "- ",
+ subsequent_indent=wrapper.subsequent_indent + " ")
+ dump_filled_text(f, item_wrapper, item)
+ f.write("\\n\\\n")
+ elif self.type == self.TEXT:
+ for text in self.texts:
+ if text != self.texts[0]:
+ f.write("\\n\\\n")
+ dump_filled_text(f, wrapper, text)
+ f.write("\\n\\\n")
+ else:
+ dump_filled_text(f, None, self.text)
+ f.write("\\n\\\n")
+ f.write("\\n\\\n")
+
+class NamedItem:
+ def __init__(self, name, desc):
+ self.name = name
+ self.desc = desc
+
+ def dump(self, f, wrapper):
+ # we use deeper indent inside the item list.
+ new_initial_indent = wrapper.initial_indent + " " * 2
+ new_subsequent_indent = wrapper.subsequent_indent + " " * (2 + 11)
+ local_wrapper = TextWrapper(initial_indent=new_initial_indent,
+ subsequent_indent=new_subsequent_indent)
+
+ # concatenate name and description with a fixed width (up to 10 chars)
+ # for the name, and wrap the entire text, then dump it to file.
+ dump_filled_text(f, local_wrapper, "%-10s %s" % (self.name, self.desc))
+ f.write("\\n\\\n")
+
+class FunctionDefinition:
+ # function types
+ CONSTRUCTOR = 0
+ COPY_CONSTRUCTOR = 1
+ DESTRUCTOR = 2
+ ASSIGNMENT_OP = 3
+ OTHER = 4
+
+ def __init__(self):
+ self.type = self.OTHER
+ self.name = None
+ self.pyname = None
+ self.args = ""
+ self.ret_type = None
+ self.brief_desc = None
+ self.detailed_desc = []
+ self.exceptions = []
+ self.parameters = []
+ self.returns = None
+ self.have_param = False
+
+ def dump_doc(self, f, wrapper=TextWrapper()):
+ f.write(self.pyname + "(" + self.args + ")")
+ if self.ret_type is not None:
+ f.write(" -> " + self.ret_type)
+ f.write("\\n\\\n\\n\\\n")
+
+ if self.brief_desc is not None:
+ dump_filled_text(f, wrapper, self.brief_desc)
+ f.write("\\n\\\n\\n\\\n")
+
+ for para in self.detailed_desc:
+ para.dump(f, wrapper)
+
+ if len(self.exceptions) > 0:
+ f.write(wrapper.fill("Exceptions:") + "\\n\\\n")
+ for ex_desc in self.exceptions:
+ ex_desc.dump(f, wrapper)
+ f.write("\\n\\\n")
+ if len(self.parameters) > 0:
+ f.write(wrapper.fill("Parameters:") + "\\n\\\n")
+ for param_desc in self.parameters:
+ param_desc.dump(f, wrapper)
+ f.write("\\n\\\n")
+ if self.returns is not None:
+ dump_filled_text(f, wrapper, "Return Value(s): " + self.returns)
+ f.write("\\n\\\n")
+
+ def dump_pymethod_def(self, f, class_name):
+ f.write(' { "' + self.pyname + '", ')
+ f.write(class_name + '_' + self.name + ', ')
+ if len(self.parameters) == 0:
+ f.write('METH_NOARGS,\n')
+ else:
+ f.write('METH_VARARGS,\n')
+ f.write(' ' + class_name + '_' + self.name + '_doc },\n')
+
+class VariableDefinition:
+ def __init__(self, nodelist):
+ self.value = None
+ self.brief_desc = None
+ self.detailed_desc = []
+
+ for node in nodelist:
+ if node.nodeName == "name":
+ self.name = get_text(node)
+ elif node.nodeName == "initializer":
+ self.value = get_text(node)
+ elif node.nodeName == "briefdescription":
+ self.brief_desc = get_text(node)
+ elif node.nodeName == "detaileddescription":
+ for para in node.childNodes:
+ if para.nodeName != "para":
+ # ignore surrounding empty nodes
+ continue
+ self.detailed_desc.append(Paragraph(para))
+
+ def dump_doc(self, f, wrapper=TextWrapper()):
+ name_value = self.name
+ if self.value is not None:
+ name_value += ' = ' + self.value
+ dump_filled_text(f, wrapper, name_value)
+ f.write('\\n\\\n')
+
+ desc_initial_indent = wrapper.initial_indent + " "
+ desc_subsequent_indent = wrapper.subsequent_indent + " "
+ desc_wrapper = TextWrapper(initial_indent=desc_initial_indent,
+ subsequent_indent=desc_subsequent_indent)
+ if self.brief_desc is not None:
+ dump_filled_text(f, desc_wrapper, self.brief_desc)
+ f.write("\\n\\\n\\n\\\n")
+
+ for para in self.detailed_desc:
+ para.dump(f, desc_wrapper)
+
+def dump_filled_text(f, wrapper, text):
+ """Fill given text using wrapper, and dump it to the given file
+ appending an escaped CR at each end of line.
+ """
+ filled_text = wrapper.fill(text) if wrapper is not None else text
+ f.write("".join(re.sub("\n", r"\\n\\\n", filled_text)))
+
+def camel_to_lowerscores(matchobj):
+ oldtext = matchobj.group(0)
+ newtext = re.sub(RE_SIMPLECAMEL, r"\1_\2", oldtext)
+ newtext = re.sub(RE_CAMELAFTERUPPER, r"\1_\2\3", newtext)
+ newtext = newtext.lower()
+ camel_replacements[oldtext] = newtext
+ return newtext.lower()
+
+def cpp_to_python(text):
+ text = text.replace("::", ".")
+ text = text.replace('"', '\\"')
+
+ # convert camelCase to "_"-concatenated format
+ # (e.g. getLength -> get_length)
+ return re.sub(RE_CAMELTERM, camel_to_lowerscores, text)
+
+def convert_type_name(type_name):
+ """Convert C++ type name to python type name using common conventions"""
+ # strip off leading 'const' and trailing '&/*'
+ type_name = re.sub("^const\S*", "", type_name)
+ type_name = re.sub("\S*[&\*]$", "", type_name)
+
+ # We often typedef smart pointers as [Const]TypePtr. Convert them to
+ # just "Type"
+ type_name = re.sub("^Const", "", type_name)
+ type_name = re.sub("Ptr$", "", type_name)
+
+ if type_name == "std::string":
+ return "string"
+ if re.search(r"(int\d+_t|size_t)", type_name):
+ return "integer"
+ return type_name
+
+def get_text(root, do_convert=True):
+ """Recursively extract bare text inside the specified node (root),
+ concatenate all extracted text and return the result.
+ """
+ nodelist = root.childNodes
+ rc = []
+ for node in nodelist:
+ if node.nodeType == node.TEXT_NODE:
+ if do_convert:
+ rc.append(cpp_to_python(node.data))
+ else:
+ rc.append(node.data)
+ elif node.nodeType == node.ELEMENT_NODE:
+ rc.append(get_text(node))
+ # return the result, removing any leading newlines (that often happens for
+ # brief descriptions, which will cause lines not well aligned)
+ return re.sub("^(\n*)", "", ''.join(rc))
+
+def get_text_fromnodelist(nodelist, do_convert=True):
+ """Recursively extract bare text inside the specified node (root),
+ concatenate all extracted text and return the result.
+ """
+ rc = []
+ for node in nodelist:
+ if node.nodeType == node.TEXT_NODE:
+ if do_convert:
+ rc.append(cpp_to_python(node.data))
+ else:
+ rc.append(node.data)
+ elif node.nodeType == node.ELEMENT_NODE:
+ rc.append(get_text(node))
+ # return the result, removing any leading newlines (that often happens for
+ # brief descriptions, which will cause lines not well aligned)
+ return re.sub("^(\n*)", "", ''.join(rc))
+
+def parse_parameters(nodelist):
+ rc = []
+ for node in nodelist:
+ if node.nodeName != "parameteritem":
+ continue
+ # for simplicity, we assume one parametername and one
+ # parameterdescription for each parameter.
+ name = get_text(node.getElementsByTagName("parametername")[0])
+ desc = get_text(node.getElementsByTagName("parameterdescription")[0])
+ rc.append(NamedItem(name, desc))
+ return rc
+
+def parse_function_description(func_def, nodelist):
+ for node in nodelist:
+ # nodelist contains beginning and ending empty text nodes.
+ # ignore them (otherwise they cause disruption below).
+ if node.nodeName != "para":
+ continue
+
+ if node.getElementsByTagName("parameterlist"):
+ # within this node there may be exception list, parameter list,
+ # and description for return value. parse and store them
+ # seprately.
+ for paramlist in node.getElementsByTagName("parameterlist"):
+ if paramlist.getAttribute("kind") == "exception":
+ func_def.exceptions = \
+ parse_parameters(paramlist.childNodes)
+ elif paramlist.getAttribute("kind") == "param":
+ func_def.parameters = \
+ parse_parameters(paramlist.childNodes)
+ if node.getElementsByTagName("simplesect"):
+ simplesect = node.getElementsByTagName("simplesect")[0]
+ if simplesect.getAttribute("kind") == "return":
+ func_def.returns = get_text(simplesect)
+ else:
+ # for normal text, python listing and itemized list, append them
+ # to the list of paragraphs
+ func_def.detailed_desc.append(Paragraph(node))
+
+def parse_function(func_def, class_name, nodelist):
+ for node in nodelist:
+ if node.nodeName == "name":
+ func_def.name = get_text(node, False)
+ func_def.pyname = cpp_to_python(func_def.name)
+ elif node.nodeName == "argsstring":
+ # extract parameter names only, assuming they immediately follow
+ # their type name + space, and are immeidatelly followed by
+ # either "," or ")". If it's a pointer or reference, */& is
+ # prepended to the parameter name without a space:
+ # e.g. (int var1, char *var2, Foo &var3)
+ args = get_text(node, False)
+ # extract parameter names, possibly with */&
+ func_def.args = ', '.join(re.findall(r"\s(\S+)[,)]", args))
+ # then remove any */& symbols
+ func_def.args = re.sub("[\*&]", "", func_def.args)
+ elif node.nodeName == "type" and node.hasChildNodes():
+ func_def.ret_type = convert_type_name(get_text(node, False))
+ elif node.nodeName == "param":
+ func_def.have_param = True
+ elif node.nodeName == "briefdescription":
+ func_def.brief_desc = get_text(node)
+ elif node.nodeName == "detaileddescription":
+ parse_function_description(func_def, node.childNodes)
+ # identify the type of function using the name and arg
+ if func_def.name == class_name and \
+ re.search("^\(const " + class_name + " &[^,]*$", args):
+ # This function is ClassName(const ClassName& param), which is
+ # the copy constructor.
+ func_def.type = func_def.COPY_CONSTRUCTOR
+ elif func_def.name == class_name:
+ # if it's not the copy ctor but the function name == class name,
+ # it's a constructor.
+ func_def.type = func_def.CONSTRUCTOR
+ elif func_def.name == "~" + class_name:
+ func_def.type = func_def.DESTRUCTOR
+ elif func_def.name == "operator=":
+ func_def.type = func_def.ASSIGNMENT_OP
+
+ # register the definition to the approriate list
+ if func_def.type == func_def.CONSTRUCTOR:
+ constructors.append(func_def)
+ elif func_def.type == func_def.OTHER:
+ member_functions.append(func_def)
+
+def parse_functions(class_name, nodelist):
+ for node in nodelist:
+ if node.nodeName == "memberdef" and \
+ node.getAttribute("kind") == "function":
+ func_def = FunctionDefinition()
+ parse_function(func_def, class_name, node.childNodes)
+
+def parse_class_variables(class_name, nodelist):
+ for node in nodelist:
+ if node.nodeName == "memberdef" and \
+ node.getAttribute("kind") == "variable":
+ class_variables.append(VariableDefinition(node.childNodes))
+
+def dump(f, class_name, class_brief_doc, class_detailed_doc):
+ f.write("namespace {\n")
+
+ f.write('#ifdef COPY_THIS_TO_MAIN_CC\n')
+ for func in member_functions:
+ func.dump_pymethod_def(f, class_name)
+ f.write('#endif // COPY_THIS_TO_MAIN_CC\n\n')
+
+ f.write("const char* const " + class_name + '_doc = "\\\n')
+ if class_brief_doc is not None:
+ f.write("".join(re.sub("\n", r"\\n\\\n", fill(class_brief_doc))))
+ f.write("\\n\\\n")
+ f.write("\\n\\\n")
+ if len(class_detailed_doc) > 0:
+ for para in class_detailed_doc:
+ para.dump(f, wrapper=TextWrapper())
+
+ # dump constructors
+ for func in constructors:
+ indent = " " * 4
+ func.dump_doc(f, wrapper=TextWrapper(initial_indent=indent,
+ subsequent_indent=indent))
+
+ # dump class variables
+ if len(class_variables) > 0:
+ f.write("Class constant data:\\n\\\n")
+ for var in class_variables:
+ var.dump_doc(f)
+
+ f.write("\";\n")
+
+ for func in member_functions:
+ f.write("\n")
+ f.write("const char* const " + class_name + "_" + func.name + \
+ "_doc = \"\\\n");
+ func.dump_doc(f)
+ f.write("\";\n")
+
+ f.write("} // unnamed namespace") # close namespace
+
+if __name__ == '__main__':
+ dom = parse(sys.argv[1])
+ class_elements = dom.getElementsByTagName("compounddef")[0].childNodes
+ class_brief_doc = None
+ class_detailed_doc = []
+ for node in class_elements:
+ if node.nodeName == "compoundname":
+ # class name is the last portion of the period-separated fully
+ # qualified class name. (this should exist)
+ class_name = re.split("\.", get_text(node))[-1]
+ if node.nodeName == "briefdescription":
+ # we assume a brief description consists at most one para
+ class_brief_doc = get_text(node)
+ elif node.nodeName == "detaileddescription":
+ # a detaild description consists of one or more paragraphs
+ for para in node.childNodes:
+ if para.nodeName != "para": # ignore surrounding empty nodes
+ continue
+ class_detailed_doc.append(Paragraph(para))
+ elif node.nodeName == "sectiondef" and \
+ node.getAttribute("kind") == "public-func":
+ parse_functions(class_name, node.childNodes)
+ elif node.nodeName == "sectiondef" and \
+ node.getAttribute("kind") == "public-static-attrib":
+ parse_class_variables(class_name, node.childNodes)
+ elif node.nodeName == "sectiondef" and \
+ node.getAttribute("kind") == "user-defined":
+ # there are two possiblities: functions and variables
+ for child in node.childNodes:
+ if child.nodeName != "memberdef":
+ continue
+ if child.getAttribute("kind") == "function":
+ parse_function(FunctionDefinition(), class_name,
+ child.childNodes)
+ elif child.getAttribute("kind") == "variable":
+ class_variables.append(VariableDefinition(child.childNodes))
+
+ dump(sys.stdout, class_name, class_brief_doc, class_detailed_doc)
+
+ if len(camel_replacements) > 0:
+ sys.stderr.write("Replaced camelCased terms:\n")
+ for oldterm in camel_replacements.keys():
+ sys.stderr.write("%s => %s\n" % (oldterm,
+ camel_replacements[oldterm]))
More information about the bind10-changes
mailing list