BIND 10 master, updated. 86daa84368ac9a61c6df7c4557f701a254d1a440 [master] Merge branch 'trac1512'
BIND 10 source code commits
bind10-changes at lists.isc.org
Wed May 23 21:56:59 UTC 2012
The branch, master has been updated
via 86daa84368ac9a61c6df7c4557f701a254d1a440 (commit)
via be4044ab9ca0163c7a736a1baa045f3b9192f11a (commit)
via e2706e3bf587ca97983e0115e998904d9d0cc674 (commit)
via d50547b048f15666f637e47df6ae5854c01102c7 (commit)
via dd418d92a81da33ab22b97d3316205ed5e1ad7ec (commit)
via 40f1d423fdc78bedcfcaa15ffd237307dc26760f (commit)
via fd063d02153814e1ca2e63066b69b8cbf2111b20 (commit)
via 0656026efd474564a43569fc2b7bc40543adb89f (commit)
via df579c404c308a60470a6c05701af590c848726d (commit)
via e08d863413670e210a885eda393afc11b4cc94c5 (commit)
via f1e5916f4fe8f99abb7efc39732376f92b576831 (commit)
via 9bc7701597d68fdb865709a2684336e2d908d878 (commit)
via 9b85fc436278ba51beb0c7d7f358b0e519948845 (commit)
via 3bd7f2b700c2ff6c6111a672baa291d5e204c696 (commit)
via 5f474b3308a695702e8b9875641bfc378656d035 (commit)
via c778e983a18659a86333aa626ad500b47a9ab26c (commit)
via 743fa2c222467587b16bf3c94746ea9e150ce479 (commit)
via b25872688315e81de52a5a2c2d12a626b88a8640 (commit)
via 194808061e1ab60bfabc8683e9f1f779459af0e7 (commit)
via 5588e70dc9d3b19af357d07639aee92069afa7fd (commit)
via 59506a667e333362966b4091cb24c10ca4605ec1 (commit)
via 9246111fe4cb6240ed82483e4ae57b2eaf2da523 (commit)
via 94fc335cc34ceb93bbc8e7caa66174d6c51038b8 (commit)
via 4c11d7a7cb7315255ce893d43be15cfaa9aa82af (commit)
via d4e28625829cd11e16a93f8ce786b77544d6af1c (commit)
via b6453630ee6655c325bd1e833bc34dc4f9a664b2 (commit)
via adbe1bace4e5d7bb7b063a01f769b0ae65362c2e (commit)
via 80c031beb751c45caed9faf239570e13c2aa5459 (commit)
via 4a4d5b700e5a3f2b937b61d4ff2e873278c0db59 (commit)
via 75fa2da75c4222f430efc01a3becbdbf9cff100d (commit)
via f3e8996dc522d21b685529310b1cea25cb87079b (commit)
via 2025f34658956271cf1850bba7c9fa15fe690a3b (commit)
via ef0e888aa8f4b016c1f63f0394d8d7246473b234 (commit)
from 77a9d4c2d5b75e0f636d7d01f7d03999c18acf77 (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 86daa84368ac9a61c6df7c4557f701a254d1a440
Merge: 77a9d4c be4044a
Author: JINMEI Tatuya <jinmei at isc.org>
Date: Wed May 23 14:56:53 2012 -0700
[master] Merge branch 'trac1512'
-----------------------------------------------------------------------
Summary of changes:
configure.ac | 2 +
src/lib/dns/message.cc | 4 +
src/lib/dns/message.h | 6 +
src/lib/dns/python/message_python.cc | 27 +++
src/lib/dns/python/message_python_inc.cc | 17 ++
src/lib/dns/python/name_python.cc | 10 +-
src/lib/dns/python/rrclass_python.cc | 9 +-
src/lib/dns/python/tests/message_python_test.py | 20 +++
src/lib/dns/python/tests/name_python_test.py | 22 +++
src/lib/dns/python/tests/rrclass_python_test.py | 8 +
src/lib/dns/tests/message_unittest.cc | 7 +
src/lib/python/isc/Makefile.am | 2 +-
src/lib/python/isc/ddns/Makefile.am | 23 +++
src/lib/python/isc/{bind10 => ddns}/__init__.py | 0
src/lib/python/isc/ddns/libddns_messages.mes | 41 +++++
src/lib/python/isc/ddns/logger.py | 87 +++++++++
src/lib/python/isc/ddns/session.py | 189 ++++++++++++++++++++
src/lib/python/isc/ddns/tests/Makefile.am | 28 +++
src/lib/python/isc/ddns/tests/session_tests.py | 154 ++++++++++++++++
src/lib/python/isc/ddns/tests/zone_config_tests.py | 117 ++++++++++++
src/lib/python/isc/ddns/zone_config.py | 64 +++++++
src/lib/python/isc/log_messages/Makefile.am | 2 +
.../python/isc/log_messages/libddns_messages.py | 1 +
23 files changed, 837 insertions(+), 3 deletions(-)
create mode 100644 src/lib/python/isc/ddns/Makefile.am
copy src/lib/python/isc/{bind10 => ddns}/__init__.py (100%)
create mode 100644 src/lib/python/isc/ddns/libddns_messages.mes
create mode 100644 src/lib/python/isc/ddns/logger.py
create mode 100644 src/lib/python/isc/ddns/session.py
create mode 100644 src/lib/python/isc/ddns/tests/Makefile.am
create mode 100644 src/lib/python/isc/ddns/tests/session_tests.py
create mode 100644 src/lib/python/isc/ddns/tests/zone_config_tests.py
create mode 100644 src/lib/python/isc/ddns/zone_config.py
create mode 100644 src/lib/python/isc/log_messages/libddns_messages.py
-----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index cf70e3f..566b74d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1067,6 +1067,8 @@ AC_CONFIG_FILES([Makefile
src/lib/python/isc/testutils/Makefile
src/lib/python/isc/bind10/Makefile
src/lib/python/isc/bind10/tests/Makefile
+ src/lib/python/isc/ddns/Makefile
+ src/lib/python/isc/ddns/tests/Makefile
src/lib/python/isc/xfrin/Makefile
src/lib/python/isc/xfrin/tests/Makefile
src/lib/python/isc/server_common/Makefile
diff --git a/src/lib/dns/message.cc b/src/lib/dns/message.cc
index 0db68c6..d54a187 100644
--- a/src/lib/dns/message.cc
+++ b/src/lib/dns/message.cc
@@ -561,6 +561,10 @@ Message::removeRRset(const Section section, RRsetIterator& iterator) {
void
Message::clearSection(const Section section) {
+ if (impl_->mode_ != Message::RENDER) {
+ isc_throw(InvalidMessageOperation,
+ "clearSection performed in non-render mode");
+ }
if (section >= MessageImpl::NUM_SECTIONS) {
isc_throw(OutOfRange, "Invalid message section: " << section);
}
diff --git a/src/lib/dns/message.h b/src/lib/dns/message.h
index 33551c0..73d0c6e 100644
--- a/src/lib/dns/message.h
+++ b/src/lib/dns/message.h
@@ -513,6 +513,12 @@ public:
/// \brief Remove all RRSets from the given Section
///
+ /// This method is only allowed in the \c RENDER mode, and the given
+ /// section must be valid.
+ ///
+ /// \throw InvalidMessageOperation Message is not in the \c RENDER mode
+ /// \throw OutOfRange The specified section is not valid
+ ///
/// \param section Section to remove all rrsets from
void clearSection(const Section section);
diff --git a/src/lib/dns/python/message_python.cc b/src/lib/dns/python/message_python.cc
index c7ad2ff..fdd4a20 100644
--- a/src/lib/dns/python/message_python.cc
+++ b/src/lib/dns/python/message_python.cc
@@ -76,6 +76,7 @@ PyObject* Message_getSection(PyObject* self, PyObject* args);
PyObject* Message_addQuestion(s_Message* self, PyObject* args);
PyObject* Message_addRRset(s_Message* self, PyObject* args);
PyObject* Message_clear(s_Message* self, PyObject* args);
+PyObject* Message_clearSection(PyObject* pyself, PyObject* args);
PyObject* Message_makeResponse(s_Message* self);
PyObject* Message_toText(s_Message* self);
PyObject* Message_str(PyObject* self);
@@ -149,6 +150,8 @@ PyMethodDef Message_methods[] = {
"Clears the message content (if any) and reinitialize the "
"message in the given mode\n"
"The argument must be either Message.PARSE or Message.RENDER"},
+ { "clear_section", Message_clearSection, METH_VARARGS,
+ Message_clearSection_doc },
{ "make_response", reinterpret_cast<PyCFunction>(Message_makeResponse), METH_NOARGS,
"Prepare for making a response from a request.\n"
"This will clear the DNS header except those fields that should be kept "
@@ -564,6 +567,30 @@ Message_clear(s_Message* self, PyObject* args) {
}
PyObject*
+Message_clearSection(PyObject* pyself, PyObject* args) {
+ s_Message* const self = static_cast<s_Message*>(pyself);
+ int section;
+
+ if (!PyArg_ParseTuple(args, "i", §ion)) {
+ return (NULL);
+ }
+ try {
+ self->cppobj->clearSection(static_cast<Message::Section>(section));
+ Py_RETURN_NONE;
+ } catch (const InvalidMessageOperation& imo) {
+ PyErr_SetString(po_InvalidMessageOperation, imo.what());
+ return (NULL);
+ } catch (const isc::OutOfRange& ex) {
+ PyErr_SetString(PyExc_OverflowError, ex.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(po_IscException,
+ "Unexpected exception in adding RRset");
+ return (NULL);
+ }
+}
+
+PyObject*
Message_makeResponse(s_Message* self) {
self->cppobj->makeResponse();
Py_RETURN_NONE;
diff --git a/src/lib/dns/python/message_python_inc.cc b/src/lib/dns/python/message_python_inc.cc
index 561c494..e1fd23d 100644
--- a/src/lib/dns/python/message_python_inc.cc
+++ b/src/lib/dns/python/message_python_inc.cc
@@ -38,4 +38,21 @@ Parameters:\n\
options Parse options\n\
\n\
";
+
+const char* const Message_clearSection_doc = "\
+clear_section(section) -> void\n\
+\n\
+Remove all RRSets from the given Section.\n\
+\n\
+This method is only allowed in the RENDER mode, and the given section\n\
+must be valid.\n\
+\n\
+Exceptions:\n\
+ InvalidMessageOperation Message is not in the RENDER mode\n\
+ OverflowError The specified section is not valid\n\
+\n\
+Parameters:\n\
+ section Section to remove all rrsets from\n\
+\n\
+";
} // unnamed namespace
diff --git a/src/lib/dns/python/name_python.cc b/src/lib/dns/python/name_python.cc
index ce556df..6758d0e 100644
--- a/src/lib/dns/python/name_python.cc
+++ b/src/lib/dns/python/name_python.cc
@@ -20,6 +20,7 @@
#include <dns/exceptions.h>
#include <dns/messagerenderer.h>
#include <dns/name.h>
+#include <dns/labelsequence.h>
#include "pydnspp_common.h"
#include "messagerenderer_python.h"
@@ -114,6 +115,7 @@ PyObject* Name_reverse(s_Name* self);
PyObject* Name_concatenate(s_Name* self, PyObject* args);
PyObject* Name_downcase(s_Name* self);
PyObject* Name_isWildCard(s_Name* self);
+long Name_hash(PyObject* py_self);
PyMethodDef Name_methods[] = {
{ "at", reinterpret_cast<PyCFunction>(Name_at), METH_VARARGS,
@@ -518,6 +520,12 @@ Name_isWildCard(s_Name* self) {
}
}
+long
+Name_hash(PyObject* pyself) {
+ s_Name* const self = static_cast<s_Name*>(pyself);
+ return (LabelSequence(*self->cppobj).getHash(false));
+}
+
} // end of unnamed namespace
namespace isc {
@@ -615,7 +623,7 @@ PyTypeObject name_type = {
NULL, // tp_as_number
NULL, // tp_as_sequence
NULL, // tp_as_mapping
- NULL, // tp_hash
+ Name_hash, // tp_hash
NULL, // tp_call
Name_str, // tp_str
NULL, // tp_getattro
diff --git a/src/lib/dns/python/rrclass_python.cc b/src/lib/dns/python/rrclass_python.cc
index 0014187..2c3dae6 100644
--- a/src/lib/dns/python/rrclass_python.cc
+++ b/src/lib/dns/python/rrclass_python.cc
@@ -52,6 +52,7 @@ PyObject* RRClass_str(PyObject* self);
PyObject* RRClass_toWire(s_RRClass* self, PyObject* args);
PyObject* RRClass_getCode(s_RRClass* self);
PyObject* RRClass_richcmp(s_RRClass* self, s_RRClass* other, int op);
+long RRClass_hash(PyObject* pyself);
// Static function for direct class creation
PyObject* RRClass_IN(s_RRClass *self);
@@ -264,6 +265,12 @@ PyObject* RRClass_ANY(s_RRClass*) {
return (RRClass_createStatic(RRClass::ANY()));
}
+long
+RRClass_hash(PyObject* pyself) {
+ s_RRClass* const self = static_cast<s_RRClass*>(pyself);
+ return (self->cppobj->getCode());
+}
+
} // end anonymous namespace
namespace isc {
@@ -296,7 +303,7 @@ PyTypeObject rrclass_type = {
NULL, // tp_as_number
NULL, // tp_as_sequence
NULL, // tp_as_mapping
- NULL, // tp_hash
+ RRClass_hash, // tp_hash
NULL, // tp_call
RRClass_str, // tp_str
NULL, // tp_getattro
diff --git a/src/lib/dns/python/tests/message_python_test.py b/src/lib/dns/python/tests/message_python_test.py
index 86574fb..1ec0e99 100644
--- a/src/lib/dns/python/tests/message_python_test.py
+++ b/src/lib/dns/python/tests/message_python_test.py
@@ -289,6 +289,26 @@ class MessageTest(unittest.TestCase):
self.assertRaises(TypeError, self.r.clear, "wrong")
self.assertRaises(TypeError, self.r.clear, 3)
+ def test_clear_question_section(self):
+ self.r.add_question(Question(Name("www.example.com"), RRClass.IN(),
+ RRType.A()))
+ self.assertEqual(1, self.r.get_rr_count(Message.SECTION_QUESTION))
+ self.r.clear_section(Message.SECTION_QUESTION)
+ self.assertEqual(0, self.r.get_rr_count(Message.SECTION_QUESTION))
+
+ def test_clear_section(self):
+ for section in [Message.SECTION_ANSWER, Message.SECTION_AUTHORITY,
+ Message.SECTION_ADDITIONAL]:
+ self.r.add_rrset(section, self.rrset_a)
+ self.assertEqual(2, self.r.get_rr_count(section))
+ self.r.clear_section(section)
+ self.assertEqual(0, self.r.get_rr_count(section))
+
+ self.assertRaises(InvalidMessageOperation, self.p.clear_section,
+ Message.SECTION_ANSWER)
+ self.assertRaises(OverflowError, self.r.clear_section,
+ self.bogus_section)
+
def test_to_wire(self):
self.assertRaises(TypeError, self.r.to_wire, 1)
self.assertRaises(InvalidMessageOperation,
diff --git a/src/lib/dns/python/tests/name_python_test.py b/src/lib/dns/python/tests/name_python_test.py
index 5263412..8ea2e35 100644
--- a/src/lib/dns/python/tests/name_python_test.py
+++ b/src/lib/dns/python/tests/name_python_test.py
@@ -218,5 +218,27 @@ class NameTest(unittest.TestCase):
self.assertTrue(self.name4 <= self.name1)
self.assertFalse(self.name2 >= self.name1)
+ def test_hash(self):
+ # The same name should have the same hash value.
+ self.assertEqual(hash(Name('example.com')), hash(Name('example.com')))
+ # Hash is case insensitive.
+ self.assertEqual(hash(Name('example.com')), hash(Name('EXAMPLE.COM')))
+
+ # These pairs happen to be known to have different hashes.
+ # It may be naive to assume the hash value is always the same (we use
+ # an external library and it depends on its internal details). If
+ # it turns out that this assumption isn't always held, we should
+ # disable this test.
+ self.assertNotEqual(hash(Name('example.com')),
+ hash(Name('example.org')))
+
+ # Check insensitiveness for the case of inequality.
+ # Based on the assumption above, this 'if' should be true and
+ # we'll always test the case inside it. We'll still keep the if in
+ # case we end up disabling the above test.
+ if hash(Name('example.com')) != hash(Name('example.org')):
+ self.assertNotEqual(hash(Name('example.com')),
+ hash(Name('EXAMPLE.ORG')))
+
if __name__ == '__main__':
unittest.main()
diff --git a/src/lib/dns/python/tests/rrclass_python_test.py b/src/lib/dns/python/tests/rrclass_python_test.py
index 38d8c8c..a048c4c 100644
--- a/src/lib/dns/python/tests/rrclass_python_test.py
+++ b/src/lib/dns/python/tests/rrclass_python_test.py
@@ -78,6 +78,14 @@ class RRClassTest(unittest.TestCase):
self.assertTrue(self.c1 <= self.c2)
self.assertFalse(self.c1 != other_rrclass)
+ def test_hash(self):
+ # Exploiting the knowledge that the hash value is the numeric class
+ # value, we can predict the comparison result.
+ self.assertEqual(hash(RRClass.IN()), hash(RRClass("IN")))
+ self.assertEqual(hash(RRClass("in")), hash(RRClass("IN")))
+ self.assertNotEqual(hash(RRClass.IN()), hash(RRClass.CH()))
+ self.assertNotEqual(hash(RRClass.IN()), hash(RRClass("CLASS65535")))
+
def test_statics(self):
self.assertEqual(RRClass.IN(), RRClass("IN"))
self.assertEqual(RRClass.CH(), RRClass("CH"))
diff --git a/src/lib/dns/tests/message_unittest.cc b/src/lib/dns/tests/message_unittest.cc
index 3be1436..c5dd3ed 100644
--- a/src/lib/dns/tests/message_unittest.cc
+++ b/src/lib/dns/tests/message_unittest.cc
@@ -466,6 +466,13 @@ TEST_F(MessageTest, clearAdditionalSection) {
EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ADDITIONAL));
}
+TEST_F(MessageTest, badClearSection) {
+ // attempt of clearing a message in the parse mode.
+ EXPECT_THROW(message_parse.clearSection(Message::SECTION_QUESTION),
+ InvalidMessageOperation);
+ // attempt of clearing out-of-range section
+ EXPECT_THROW(message_render.clearSection(bogus_section), OutOfRange);
+}
TEST_F(MessageTest, badBeginSection) {
// valid cases are tested via other tests
diff --git a/src/lib/python/isc/Makefile.am b/src/lib/python/isc/Makefile.am
index aef5dc3..80fd222 100644
--- a/src/lib/python/isc/Makefile.am
+++ b/src/lib/python/isc/Makefile.am
@@ -1,5 +1,5 @@
SUBDIRS = datasrc cc config dns log net notify util testutils acl bind10
-SUBDIRS += xfrin log_messages server_common
+SUBDIRS += xfrin log_messages server_common ddns
python_PYTHON = __init__.py
diff --git a/src/lib/python/isc/ddns/Makefile.am b/src/lib/python/isc/ddns/Makefile.am
new file mode 100644
index 0000000..1b9b6df
--- /dev/null
+++ b/src/lib/python/isc/ddns/Makefile.am
@@ -0,0 +1,23 @@
+SUBDIRS = . tests
+
+python_PYTHON = __init__.py session.py logger.py zone_config.py
+BUILT_SOURCES = $(PYTHON_LOGMSGPKG_DIR)/work/libddns_messages.py
+nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/libddns_messages.py
+pylogmessagedir = $(pyexecdir)/isc/log_messages/
+
+EXTRA_DIST = libddns_messages.mes
+
+CLEANFILES = $(PYTHON_LOGMSGPKG_DIR)/work/libddns_messages.py
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/libddns_messages.pyc
+
+# Define rule to build logging source files from message file
+$(PYTHON_LOGMSGPKG_DIR)/work/libddns_messages.py: libddns_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message \
+ -d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/libddns_messages.mes
+
+pythondir = $(pyexecdir)/isc/ddns
+
+CLEANDIRS = __pycache__
+
+clean-local:
+ rm -rf $(CLEANDIRS)
diff --git a/src/lib/python/isc/ddns/__init__.py b/src/lib/python/isc/ddns/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/lib/python/isc/ddns/libddns_messages.mes b/src/lib/python/isc/ddns/libddns_messages.mes
new file mode 100644
index 0000000..4c48c4c
--- /dev/null
+++ b/src/lib/python/isc/ddns/libddns_messages.mes
@@ -0,0 +1,41 @@
+# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# No namespace declaration - these constants go in the global namespace
+# of the libddns_messages python module.
+
+% LIBDDNS_UPDATE_ERROR update client %1 for zone %2: %3
+Debug message. An error is found in processing a dynamic update
+request. This log message is used for general errors that are not
+normally expected to happen. So, in general, it would mean some
+problem in the client implementation or an interoperability issue
+with this implementation. The client's address, the zone name and
+class, and description of the error are logged.
+
+% LIBDDNS_UPDATE_FORWARD_FAIL update client %1 for zone %2: update forwarding not supported
+Debug message. An update request is sent to a secondary server. This
+is not necessarily invalid, but this implementation does not yet
+support update forwarding as specified in Section 6 of RFC2136 and it
+will simply return a response with an RCODE of NOTIMP to the client.
+The client's address and the zone name/class are logged.
+
+% LIBDDNS_UPDATE_NOTAUTH update client %1 for zone %2: not authoritative for update zone
+Debug message. An update request for a zone for which the receiving
+server doesn't have authority. In theory this is an unexpected event,
+but there are client implementations that could send update requests
+carelessly, so it may not necessarily be so uncommon in practice. If
+possible, you may want to check the implementation or configuration of
+those clients to suppress the requests. As specified in Section 3.1
+of RFC2136, the receiving server will return a response with an RCODE
+of NOTAUTH.
diff --git a/src/lib/python/isc/ddns/logger.py b/src/lib/python/isc/ddns/logger.py
new file mode 100644
index 0000000..9e92a9f
--- /dev/null
+++ b/src/lib/python/isc/ddns/logger.py
@@ -0,0 +1,87 @@
+# Copyright (C) 2012 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.
+
+""" This is a logging utility module for other modules of the ddns library
+package.
+
+"""
+
+import isc.log
+
+# The logger for this package
+logger = isc.log.Logger('libddns')
+
+class ClientFormatter:
+ """A utility class to convert a client address to string.
+
+ This class is constructed with a Python standard socket address tuple.
+ If it's 2-element tuple, it's assumed to be an IPv4 socket address
+ and will be converted to the form of '<addr>:<port>'.
+ If it's 4-element tuple, it's assumed to be an IPv6 socket address.
+ and will be converted to the form of '[<addr>]:<por>'.
+
+ This class is designed to delay the conversion until it's explicitly
+ requested, so the conversion doesn't happen if the corresponding log
+ message is suppressed because of its log level (which is often the case
+ for debug messages).
+
+ Note: this optimization comes with the cost of instantiating the
+ formatter object itself. It's not really clear which overhead is
+ heavier, and we may conclude it's actually better to just generate
+ the strings unconditionally. Alternatively, we can make the stored
+ address of this object replaceable so that this object can be reused.
+ Right now this is an open issue.
+
+ """
+ def __init__(self, addr):
+ self.__addr = addr
+
+ def __str__(self):
+ if len(self.__addr) == 2:
+ return self.__addr[0] + ':' + str(self.__addr[1])
+ elif len(self.__addr) == 4:
+ return '[' + self.__addr[0] + ']:' + str(self.__addr[1])
+ return None
+
+class ZoneFormatter:
+ """A utility class to convert zone name and class to string.
+
+ This class is constructed with a name of a zone (isc.dns.Name object)
+ and its RR class (isc.dns.RRClass object). Its text conversion method
+ (__str__) converts them into a string in the form of
+ '<zone name>/<zone class>' where the trailing dot of the zone name
+ is omitted.
+
+ If the given zone name on construction is None, it's assumed to be
+ the zone isn't identified but needs to be somehow logged. The conversion
+ method returns a special string to indicate this case.
+
+ This class is designed to delay the conversion until it's explicitly
+ requested, so the conversion doesn't happen if the corresponding log
+ message is suppressed because of its log level (which is often the case
+ for debug messages).
+
+ See the note for the ClientFormatter class about overhead tradeoff.
+ This class shares the same discussion.
+
+ """
+ def __init__(self, zname, zclass):
+ self.__zname = zname
+ self.__zclass = zclass
+
+ def __str__(self):
+ if self.__zname is None:
+ return '(zone unknown/not determined)'
+ return self.__zname.to_text(True) + '/' + self.__zclass.to_text()
diff --git a/src/lib/python/isc/ddns/session.py b/src/lib/python/isc/ddns/session.py
new file mode 100644
index 0000000..36541d9
--- /dev/null
+++ b/src/lib/python/isc/ddns/session.py
@@ -0,0 +1,189 @@
+# Copyright (C) 2012 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.
+
+from isc.dns import *
+import isc.ddns.zone_config
+from isc.log import *
+from isc.ddns.logger import logger, ClientFormatter, ZoneFormatter
+from isc.log_messages.libddns_messages import *
+
+# Result codes for UpdateSession.handle()
+UPDATE_SUCCESS = 0
+UPDATE_ERROR = 1
+UPDATE_DROP = 2
+
+# Convenient aliases of update-specific section names
+SECTION_ZONE = Message.SECTION_QUESTION
+SECTION_PREREQUISITE = Message.SECTION_ANSWER
+SECTION_UPDATE = Message.SECTION_AUTHORITY
+
+# Shortcut
+DBGLVL_TRACE_BASIC = logger.DBGLVL_TRACE_BASIC
+
+class UpdateError(Exception):
+ '''Exception for general error in update request handling.
+
+ This exception is intended to be used internally within this module.
+ When UpdateSession.handle() encounters an error in handling an update
+ request it can raise this exception to terminate the handling.
+
+ This class is constructed with some information that may be useful for
+ subsequent possible logging:
+ - msg (string) A string explaining the error.
+ - zname (isc.dns.Name) The zone name. Can be None when not identified.
+ - zclass (isc.dns.RRClass) The zone class. Like zname, can be None.
+ - rcode (isc.dns.RCode) The RCODE to be set in the response message.
+ - nolog (bool) If True, it indicates there's no more need for logging.
+
+ '''
+ def __init__(self, msg, zname, zclass, rcode, nolog=False):
+ Exception.__init__(self, msg)
+ self.zname = zname
+ self.zclass = zclass
+ self.rcode = rcode
+ self.nolog = nolog
+
+class UpdateSession:
+ '''Protocol handling for a single dynamic update request.
+
+ This class is instantiated with a request message and some other
+ information that will be used for handling the request. Its main
+ method, handle(), will process the request, and normally build
+ a response message according to the result. The application of this
+ class can use the message to send a response to the client.
+
+ '''
+ def __init__(self, req_message, req_data, client_addr, zone_config):
+ '''Constructor.
+
+ Note: req_data is not really used as of #1512 but is listed since
+ it's quite likely we need it in a subsequent task soon. We'll
+ also need to get other parameters such as ACLs, for which, it's less
+ clear in which form we want to get the information, so it's left
+ open for now.
+
+ Parameters:
+ - req_message (isc.dns.Message) The request message. This must be
+ in the PARSE mode.
+ - req_data (binary) Wire format data of the request message.
+ It will be used for TSIG verification if necessary.
+ - client_addr (socket address) The address/port of the update client
+ in the form of Python socket address object. This is mainly for
+ logging and access control.
+ - zone_config (ZoneConfig) A tentative container that encapsulates
+ the server's zone configuration. See zone_config.py.
+
+ (It'll soon need to be passed ACL in some way, too)
+
+ '''
+ self.__message = req_message
+ self.__client_addr = client_addr
+ self.__zone_config = zone_config
+
+ def get_message(self):
+ '''Return the update message.
+
+ After handle() is called, it's generally transformed to the response
+ to be returned to the client; otherwise it would be identical to
+ the request message passed on construction.
+
+ '''
+ return self.__message
+
+ def handle(self):
+ '''Handle the update request according to RFC2136.
+
+ This method returns a tuple of the following three elements that
+ indicate the result of the request.
+ - Result code of the request processing, which are:
+ UPDATE_SUCCESS Update request granted and succeeded.
+ UPDATE_ERROR Some error happened to be reported in the response.
+ UPDATE_DROP Error happened and no response should be sent.
+ Except the case of UPDATE_DROP, the UpdateSession object will have
+ created a response that is to be returned to the request client,
+ which can be retrieved by get_message().
+ - The name of the updated zone (isc.dns.Name object) in case of
+ UPDATE_SUCCESS; otherwise None.
+ - The RR class of the updated zone (isc.dns.RRClass object) in case
+ of UPDATE_SUCCESS; otherwise None.
+
+ '''
+ try:
+ datasrc_client, zname, zclass = self.__get_update_zone()
+ # conceptual code that would follow
+ # self.__check_prerequisites()
+ # self.__check_update_acl()
+ # self.__do_update()
+ # self.__make_response(Rcode.NOERROR())
+ return UPDATE_SUCCESS, zname, zclass
+ except UpdateError as e:
+ if not e.nolog:
+ logger.debug(logger.DBGLVL_TRACE_BASIC, LIBDDNS_UPDATE_ERROR,
+ ClientFormatter(self.__client_addr),
+ ZoneFormatter(e.zname, e.zclass), e)
+ self.__make_response(e.rcode)
+ return UPDATE_ERROR, None, None
+
+ def __get_update_zone(self):
+ '''Parse the zone section and find the zone to be updated.
+
+ If the zone section is valid and the specified zone is found in
+ the configuration, it returns a tuple of:
+ - A matching data source that contains the specified zone
+ - The zone name as a Name object
+ - The zone class as an RRClass object
+
+ '''
+ # Validation: the zone section must contain exactly one question,
+ # and it must be of type SOA.
+ n_zones = self.__message.get_rr_count(SECTION_ZONE)
+ if n_zones != 1:
+ raise UpdateError('Invalid number of records in zone section: ' +
+ str(n_zones), None, None, Rcode.FORMERR())
+ zrecord = self.__message.get_question()[0]
+ if zrecord.get_type() != RRType.SOA():
+ raise UpdateError('update zone section contains non-SOA',
+ None, None, Rcode.FORMERR())
+
+ # See if we're serving a primary zone specified in the zone section.
+ zname = zrecord.get_name()
+ zclass = zrecord.get_class()
+ zone_type, datasrc_client = self.__zone_config.find_zone(zname, zclass)
+ if zone_type == isc.ddns.zone_config.ZONE_PRIMARY:
+ return datasrc_client, zname, zclass
+ elif zone_type == isc.ddns.zone_config.ZONE_SECONDARY:
+ # We are a secondary server; since we don't yet support update
+ # forwarding, we return 'not implemented'.
+ logger.debug(DBGLVL_TRACE_BASIC, LIBDDNS_UPDATE_FORWARD_FAIL,
+ ClientFormatter(self.__client_addr),
+ ZoneFormatter(zname, zclass))
+ raise UpdateError('forward', zname, zclass, Rcode.NOTIMP(), True)
+ # zone wasn't found
+ logger.debug(DBGLVL_TRACE_BASIC, LIBDDNS_UPDATE_NOTAUTH,
+ ClientFormatter(self.__client_addr),
+ ZoneFormatter(zname, zclass))
+ raise UpdateError('notauth', zname, zclass, Rcode.NOTAUTH(), True)
+
+ def __make_response(self, rcode):
+ '''Transform the internal message to the update response.
+
+ According RFC2136 Section 3.8, the zone section will be cleared
+ as well as other sections. The response Rcode will be set to the
+ given value.
+
+ '''
+ self.__message.make_response()
+ self.__message.clear_section(SECTION_ZONE)
+ self.__message.set_rcode(rcode)
diff --git a/src/lib/python/isc/ddns/tests/Makefile.am b/src/lib/python/isc/ddns/tests/Makefile.am
new file mode 100644
index 0000000..f8e05b8
--- /dev/null
+++ b/src/lib/python/isc/ddns/tests/Makefile.am
@@ -0,0 +1,28 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
+PYTESTS = session_tests.py zone_config_tests.py
+EXTRA_DIST = $(PYTESTS)
+CLEANFILES = $(builddir)/rwtest.sqlite3.copied
+
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+if SET_ENV_LIBRARY_PATH
+LIBRARY_PATH_PLACEHOLDER = $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+endif
+
+# test using command-line arguments, so use check-local target instead of TESTS
+# B10_FROM_BUILD is necessary to load data source backend from the build tree.
+check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
+ for pytest in $(PYTESTS) ; do \
+ echo Running test: $$pytest ; \
+ $(LIBRARY_PATH_PLACEHOLDER) \
+ TESTDATA_PATH=$(abs_top_srcdir)/src/lib/testutils/testdata \
+ TESTDATA_WRITE_PATH=$(builddir) \
+ B10_FROM_BUILD=$(abs_top_builddir) \
+ PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/dns/python/.libs \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+ done
diff --git a/src/lib/python/isc/ddns/tests/session_tests.py b/src/lib/python/isc/ddns/tests/session_tests.py
new file mode 100644
index 0000000..1bf4e40
--- /dev/null
+++ b/src/lib/python/isc/ddns/tests/session_tests.py
@@ -0,0 +1,154 @@
+# Copyright (C) 2012 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.
+
+import os
+import shutil
+import isc.log
+import unittest
+from isc.dns import *
+from isc.datasrc import DataSourceClient
+from isc.ddns.session import *
+from isc.ddns.zone_config import *
+
+# Some common test parameters
+TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep
+READ_ZONE_DB_FILE = TESTDATA_PATH + "rwtest.sqlite3" # original, to be copied
+TESTDATA_WRITE_PATH = os.environ['TESTDATA_WRITE_PATH'] + os.sep
+WRITE_ZONE_DB_FILE = TESTDATA_WRITE_PATH + "rwtest.sqlite3.copied"
+WRITE_ZONE_DB_CONFIG = "{ \"database_file\": \"" + WRITE_ZONE_DB_FILE + "\"}"
+
+TEST_ZONE_NAME = Name('example.org')
+UPDATE_RRTYPE = RRType.SOA()
+TEST_RRCLASS = RRClass.IN()
+TEST_ZONE_RECORD = Question(TEST_ZONE_NAME, TEST_RRCLASS, UPDATE_RRTYPE)
+TEST_CLIENT6 = ('2001:db8::1', 53, 0, 0)
+TEST_CLIENT4 = ('192.0.2.1', 53)
+
+def create_update_msg(zones=[TEST_ZONE_RECORD]):
+ msg = Message(Message.RENDER)
+ msg.set_qid(5353) # arbitrary chosen
+ msg.set_opcode(Opcode.UPDATE())
+ msg.set_rcode(Rcode.NOERROR())
+ for z in zones:
+ msg.add_question(z)
+
+ renderer = MessageRenderer()
+ msg.to_wire(renderer)
+
+ # re-read the created data in the parse mode
+ msg.clear(Message.PARSE)
+ msg.from_wire(renderer.get_data())
+
+ return renderer.get_data(), msg
+
+class SessionTest(unittest.TestCase):
+ '''Session tests'''
+ def setUp(self):
+ shutil.copyfile(READ_ZONE_DB_FILE, WRITE_ZONE_DB_FILE)
+ self.__datasrc_client = DataSourceClient("sqlite3",
+ WRITE_ZONE_DB_CONFIG)
+ self.__update_msgdata, self.__update_msg = create_update_msg()
+ self.__session = UpdateSession(self.__update_msg,
+ self.__update_msgdata, TEST_CLIENT4,
+ ZoneConfig([], TEST_RRCLASS,
+ self.__datasrc_client))
+
+ def check_response(self, msg, expected_rcode):
+ '''Perform common checks on update resposne message.'''
+ self.assertTrue(msg.get_header_flag(Message.HEADERFLAG_QR))
+ # note: we convert opcode to text it'd be more helpful on failure.
+ self.assertEqual(Opcode.UPDATE().to_text(), msg.get_opcode().to_text())
+ self.assertEqual(expected_rcode.to_text(), msg.get_rcode().to_text())
+ # All sections should be cleared
+ self.assertEqual(0, msg.get_rr_count(SECTION_ZONE))
+ self.assertEqual(0, msg.get_rr_count(SECTION_PREREQUISITE))
+ self.assertEqual(0, msg.get_rr_count(SECTION_UPDATE))
+ self.assertEqual(0, msg.get_rr_count(Message.SECTION_ADDITIONAL))
+
+ def test_handle(self):
+ '''Basic update case'''
+ result, zname, zclass = self.__session.handle()
+ self.assertEqual(UPDATE_SUCCESS, result)
+ self.assertEqual(TEST_ZONE_NAME, zname)
+ self.assertEqual(TEST_RRCLASS, zclass)
+
+ # Just checking these are different from the success code.
+ self.assertNotEqual(UPDATE_ERROR, result)
+ self.assertNotEqual(UPDATE_DROP, result)
+
+ def test_broken_request(self):
+ # Zone section is empty
+ msg_data, msg = create_update_msg(zones=[])
+ session = UpdateSession(msg, msg_data, TEST_CLIENT6, None)
+ result, zname, zclass = session.handle()
+ self.assertEqual(UPDATE_ERROR, result)
+ self.assertEqual(None, zname)
+ self.assertEqual(None, zclass)
+ self.check_response(session.get_message(), Rcode.FORMERR())
+
+ # Zone section contains multiple records
+ msg_data, msg = create_update_msg(zones=[TEST_ZONE_RECORD,
+ TEST_ZONE_RECORD])
+ session = UpdateSession(msg, msg_data, TEST_CLIENT4, None)
+ self.assertEqual(UPDATE_ERROR, session.handle()[0])
+ self.check_response(session.get_message(), Rcode.FORMERR())
+
+ # Zone section's type is not SOA
+ msg_data, msg = create_update_msg(zones=[Question(TEST_ZONE_NAME,
+ TEST_RRCLASS,
+ RRType.A())])
+ session = UpdateSession(msg, msg_data, TEST_CLIENT4, None)
+ self.assertEqual(UPDATE_ERROR, session.handle()[0])
+ self.check_response(session.get_message(), Rcode.FORMERR())
+
+ def test_update_secondary(self):
+ # specified zone is configured as a secondary. Since this
+ # implementation doesn't support update forwarding, the result
+ # should be NOTIMP.
+ msg_data, msg = create_update_msg(zones=[Question(TEST_ZONE_NAME,
+ TEST_RRCLASS,
+ RRType.SOA())])
+ session = UpdateSession(msg, msg_data, TEST_CLIENT4,
+ ZoneConfig([(TEST_ZONE_NAME, TEST_RRCLASS)],
+ TEST_RRCLASS,
+ self.__datasrc_client))
+ self.assertEqual(UPDATE_ERROR, session.handle()[0])
+ self.check_response(session.get_message(), Rcode.NOTIMP())
+
+ def check_notauth(self, zname, zclass=TEST_RRCLASS):
+ '''Common test sequence for the 'notauth' test'''
+ msg_data, msg = create_update_msg(zones=[Question(zname, zclass,
+ RRType.SOA())])
+ session = UpdateSession(msg, msg_data, TEST_CLIENT4,
+ ZoneConfig([(TEST_ZONE_NAME, TEST_RRCLASS)],
+ TEST_RRCLASS,
+ self.__datasrc_client))
+ self.assertEqual(UPDATE_ERROR, session.handle()[0])
+ self.check_response(session.get_message(), Rcode.NOTAUTH())
+
+ def test_update_notauth(self):
+ '''Update attempt for non authoritative zones'''
+ # zone name doesn't match
+ self.check_notauth(Name('example.com'))
+ # zone name is a subdomain of the actual authoritative zone
+ # (match must be exact)
+ self.check_notauth(Name('sub.example.org'))
+ # zone class doesn't match
+ self.check_notauth(Name('example.org'), RRClass.CH())
+
+if __name__ == "__main__":
+ isc.log.init("bind10")
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/lib/python/isc/ddns/tests/zone_config_tests.py b/src/lib/python/isc/ddns/tests/zone_config_tests.py
new file mode 100644
index 0000000..7d4bc76
--- /dev/null
+++ b/src/lib/python/isc/ddns/tests/zone_config_tests.py
@@ -0,0 +1,117 @@
+# Copyright (C) 2012 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.
+
+import isc.log
+import unittest
+from isc.dns import *
+from isc.datasrc import DataSourceClient
+from isc.ddns.zone_config import *
+
+# Some common test parameters
+TEST_ZONE_NAME = Name('example.org')
+TEST_SECONDARY_ZONE_NAME = Name('example.com')
+TEST_RRCLASS = RRClass.IN()
+
+class FakeDataSourceClient:
+ '''Faked data source client used in the ZoneConfigTest.
+
+ It emulates isc.datasrc.DataSourceClient, but only has to provide
+ the find_zone() interface (and only the first element of the return
+ value matters). By default it returns 'SUCCESS' (exact match) for
+ any input. It can be dynamically customized via the set_find_result()
+ method.
+
+ '''
+ def __init__(self):
+ self.__find_result = DataSourceClient.SUCCESS
+
+ def find_zone(self, zname):
+ return (self.__find_result, None)
+
+ def set_find_result(self, result):
+ self.__find_result = result
+
+class ZoneConfigTest(unittest.TestCase):
+ '''Some basic tests for the ZoneConfig class.'''
+ def setUp(self):
+ self.__datasrc_client = FakeDataSourceClient()
+ self.zconfig = ZoneConfig([(TEST_SECONDARY_ZONE_NAME, TEST_RRCLASS)],
+ TEST_RRCLASS, self.__datasrc_client)
+
+ def test_find_zone(self):
+ # Primay zone case: zone is in the data source, and not in secondaries
+ self.assertEqual((ZONE_PRIMARY, self.__datasrc_client),
+ (self.zconfig.find_zone(TEST_ZONE_NAME, TEST_RRCLASS)))
+
+ # Secondary zone case: zone is in the data source and in secondaries.
+ self.assertEqual((ZONE_SECONDARY, None),
+ (self.zconfig.find_zone(TEST_SECONDARY_ZONE_NAME,
+ TEST_RRCLASS)))
+
+ # 'not found' case: zone not in the data source.
+ self.__datasrc_client.set_find_result(DataSourceClient.NOTFOUND)
+ self.assertEqual((ZONE_NOTFOUND, None),
+ (self.zconfig.find_zone(Name('example'),
+ TEST_RRCLASS)))
+ # same for the partial match
+ self.__datasrc_client.set_find_result(DataSourceClient.PARTIALMATCH)
+ self.assertEqual((ZONE_NOTFOUND, None),
+ (self.zconfig.find_zone(Name('example'),
+ TEST_RRCLASS)))
+ # a bit unusual case: zone not in the data source, but in secondaries.
+ # this is probably a configuration error, but ZoneConfig doesn't do
+ # this level check.
+ self.__datasrc_client.set_find_result(DataSourceClient.NOTFOUND)
+ self.assertEqual((ZONE_NOTFOUND, None),
+ (self.zconfig.find_zone(TEST_ZONE_NAME,
+ TEST_RRCLASS)))
+ # zone class doesn't match (but zone name matches)
+ self.__datasrc_client.set_find_result(DataSourceClient.SUCCESS)
+ zconfig = ZoneConfig([(TEST_SECONDARY_ZONE_NAME, TEST_RRCLASS)],
+ RRClass.CH(), self.__datasrc_client)
+ self.assertEqual((ZONE_NOTFOUND, None),
+ (zconfig.find_zone(TEST_ZONE_NAME, TEST_RRCLASS)))
+ # similar to the previous case, but also in the secondary list
+ zconfig = ZoneConfig([(TEST_ZONE_NAME, TEST_RRCLASS)],
+ RRClass.CH(), self.__datasrc_client)
+ self.assertEqual((ZONE_NOTFOUND, None),
+ (zconfig.find_zone(TEST_ZONE_NAME, TEST_RRCLASS)))
+
+ # check some basic tests varying the secondary list.
+ # empty secondary list doesn't cause any disruption.
+ zconfig = ZoneConfig([], TEST_RRCLASS, self.__datasrc_client)
+ self.assertEqual((ZONE_PRIMARY, self.__datasrc_client),
+ (self.zconfig.find_zone(TEST_ZONE_NAME, TEST_RRCLASS)))
+ # adding some mulitle tuples, including subdomainof the test zone name,
+ # and the same zone name but a different class
+ zconfig = ZoneConfig([(TEST_SECONDARY_ZONE_NAME, TEST_RRCLASS),
+ (Name('example'), TEST_RRCLASS),
+ (Name('sub.example.org'), TEST_RRCLASS),
+ (TEST_ZONE_NAME, RRClass.CH())],
+ TEST_RRCLASS, self.__datasrc_client)
+ self.assertEqual((ZONE_PRIMARY, self.__datasrc_client),
+ (self.zconfig.find_zone(TEST_ZONE_NAME, TEST_RRCLASS)))
+ # secondary zone list has a duplicate entry, which is just
+ # (effecitivey) ignored
+ zconfig = ZoneConfig([(TEST_SECONDARY_ZONE_NAME, TEST_RRCLASS),
+ (TEST_SECONDARY_ZONE_NAME, TEST_RRCLASS)],
+ TEST_RRCLASS, self.__datasrc_client)
+ self.assertEqual((ZONE_PRIMARY, self.__datasrc_client),
+ (self.zconfig.find_zone(TEST_ZONE_NAME, TEST_RRCLASS)))
+
+if __name__ == "__main__":
+ isc.log.init("bind10")
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/lib/python/isc/ddns/zone_config.py b/src/lib/python/isc/ddns/zone_config.py
new file mode 100644
index 0000000..5b1af0c
--- /dev/null
+++ b/src/lib/python/isc/ddns/zone_config.py
@@ -0,0 +1,64 @@
+# Copyright (C) 2012 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.
+
+import isc.dns
+from isc.datasrc import DataSourceClient
+
+# Constants representing zone types
+ZONE_NOTFOUND = -1 # Zone isn't found in find_zone()
+ZONE_PRIMARY = 0 # Primary zone
+ZONE_SECONDARY = 1 # Secondary zone
+
+class ZoneConfig:
+ '''A temporary helper class to encapsulate zone related configuration.
+
+ Its find_zone method will search the conceptual configuration for a
+ given zone, and return a tuple of zone type (primary or secondary) and
+ the client object to access the data source stroing the zone.
+ It's very likely that details of zone related configurations like this
+ will change in near future, so the main purpose of this class is to
+ provide an independent interface for the main DDNS session module
+ until the details are fixed.
+
+ '''
+ def __init__(self, secondaries, datasrc_class, datasrc_client):
+ '''Constructor.
+
+ Parameters:
+ - secondaries: a list of 2-element tuples. Each element is a pair
+ of isc.dns.Name and isc.dns.RRClass, and identifies a single
+ secondary zone.
+ - datasrc_class: isc.dns.RRClass object. Specifies the RR class
+ of datasrc_client.
+ - datasrc_client: isc.dns.DataSourceClient object. A data source
+ class for the RR class of datasrc_class. It's expected to contain
+ a zone that is eventually updated in the ddns package.
+
+ '''
+ self.__secondaries = set()
+ for (zname, zclass) in secondaries:
+ self.__secondaries.add((zname, zclass))
+ self.__datasrc_class = datasrc_class
+ self.__datasrc_client = datasrc_client
+
+ def find_zone(self, zone_name, zone_class):
+ '''Return the type and accessor client object for given zone.'''
+ if self.__datasrc_class == zone_class and \
+ self.__datasrc_client.find_zone(zone_name)[0] == \
+ DataSourceClient.SUCCESS:
+ if (zone_name, zone_class) in self.__secondaries:
+ return ZONE_SECONDARY, None
+ return ZONE_PRIMARY, self.__datasrc_client
+ return ZONE_NOTFOUND, None
diff --git a/src/lib/python/isc/log_messages/Makefile.am b/src/lib/python/isc/log_messages/Makefile.am
index 6b4be94..6d23df3 100644
--- a/src/lib/python/isc/log_messages/Makefile.am
+++ b/src/lib/python/isc/log_messages/Makefile.am
@@ -12,6 +12,7 @@ EXTRA_DIST += zonemgr_messages.py
EXTRA_DIST += cfgmgr_messages.py
EXTRA_DIST += config_messages.py
EXTRA_DIST += notify_out_messages.py
+EXTRA_DIST += libddns_messages.py
EXTRA_DIST += libxfrin_messages.py
EXTRA_DIST += server_common_messages.py
EXTRA_DIST += dbutil_messages.py
@@ -28,6 +29,7 @@ CLEANFILES += zonemgr_messages.pyc
CLEANFILES += cfgmgr_messages.pyc
CLEANFILES += config_messages.pyc
CLEANFILES += notify_out_messages.pyc
+CLEANFILES += libddns_messages.pyc
CLEANFILES += libxfrin_messages.pyc
CLEANFILES += server_common_messages.pyc
CLEANFILES += dbutil_messages.pyc
diff --git a/src/lib/python/isc/log_messages/libddns_messages.py b/src/lib/python/isc/log_messages/libddns_messages.py
new file mode 100644
index 0000000..58d886d
--- /dev/null
+++ b/src/lib/python/isc/log_messages/libddns_messages.py
@@ -0,0 +1 @@
+from work.libddns_messages import *
More information about the bind10-changes
mailing list