BIND 10 master, updated. 79e99b7285b93325862f80746d3a4d1b5938ca4d Merge #1691
BIND 10 source code commits
bind10-changes at lists.isc.org
Mon Feb 27 14:08:19 UTC 2012
The branch, master has been updated
via 79e99b7285b93325862f80746d3a4d1b5938ca4d (commit)
via 5a7953933a49a0ddd4ee1feaddc908cd2285522d (commit)
via 064dd51b725493270dec9f1cd2c2b7164135ee6b (commit)
via d2b5da0a8146343bd6f72f3a145fc90be7888d18 (commit)
via 429c88cfe0483e0c67f4625481995c2510485792 (commit)
via dad96d1c2aac267f2a11b9bdb7c83ce33c1a3f34 (commit)
via 6b3f5f252791b1c4b92589deaa3810e5e1eb867f (commit)
via a72ef46cca91d1718b2830e9d827040e6742d643 (commit)
via a11e3b2165821e9ca039196b12d70025845a5d22 (commit)
via fcbe40140a5e21ecf3ff078206afe1d68e945462 (commit)
via 07dd87ca1a14058c926497ec1c6ac61ffd3a41f2 (commit)
via 766508d03d1304dc4d66b814a7a21d160b3404c2 (commit)
via a4789bb27d209a54652bc2d7c6bf0992613c18df (commit)
via 920106980b5a66212397f5f699f2aac9e6d69cbc (commit)
via c430386cb0a163ffabc477d864635b4a21f2ef59 (commit)
via 4fea1ab53d06b5deaa87def247818f67839a9c9e (commit)
via 719a5941f529e8139cc2cf970d8903adf0741043 (commit)
via ac91cd885b563c71193cca2a33d6d11aa4a25927 (commit)
via 73506bcbd64a043f7e66c193a15b2d09a0a47bf0 (commit)
via f8cecc0e0625c40be6a44f24b8bcd0420e19946b (commit)
via c540444d6220133977db22de4c0adc116afd24a6 (commit)
via 66d14dd2dff194e69a12db0c89176c399ac5ce0b (commit)
via 30f55bf6510a4463d511a90c0b7deaeb046f80f2 (commit)
via 38742cb1561683410ab0f61388dbccd57e9f0f3c (commit)
via 451fd576615b47baa616d49ccaccedfae5595a76 (commit)
via a265a99016e8fe7943e1b6e8d4f2a69c0b5fb391 (commit)
via 6129c3537cc81af4561269d5652321ac2250ab93 (commit)
via 097a376ed65be53dbfe4cd1f2f46df288fb9d6ae (commit)
from 56b495e4bd8aeffed8642bfb6bb4802ba39a6277 (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 79e99b7285b93325862f80746d3a4d1b5938ca4d
Merge: 5a7953933a49a0ddd4ee1feaddc908cd2285522d 429c88cfe0483e0c67f4625481995c2510485792
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Mon Feb 27 14:57:18 2012 +0100
Merge #1691
commit 5a7953933a49a0ddd4ee1feaddc908cd2285522d
Merge: 56b495e4bd8aeffed8642bfb6bb4802ba39a6277 064dd51b725493270dec9f1cd2c2b7164135ee6b
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Mon Feb 27 14:40:42 2012 +0100
Merge #1643
commit 064dd51b725493270dec9f1cd2c2b7164135ee6b
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Mon Feb 27 14:38:54 2012 +0100
[1643] Few more tests
commit d2b5da0a8146343bd6f72f3a145fc90be7888d18
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date: Mon Feb 27 14:23:34 2012 +0100
[1643] Omit unneeded variable
-----------------------------------------------------------------------
Summary of changes:
configure.ac | 36 ++-
doc/guide/bind10-guide.xml | 18 +-
src/bin/xfrout/b10-xfrout.xml | 14 -
src/bin/xfrout/tests/xfrout_test.py.in | 50 +++-
src/bin/xfrout/xfrout.py.in | 22 +--
src/bin/xfrout/xfrout.spec.pre.in | 12 -
src/lib/datasrc/Makefile.am | 6 +-
src/lib/datasrc/datasrc_config.h.pre.in | 2 +-
src/lib/python/isc/Makefile.am | 2 +-
src/lib/python/isc/config/ccsession.py | 93 +++++--
src/lib/python/isc/config/tests/ccsession_test.py | 323 ++++++++++++++++----
src/lib/python/isc/log_messages/Makefile.am | 2 +
.../isc/log_messages/server_common_messages.py | 1 +
src/lib/python/isc/server_common/Makefile.am | 24 ++
.../isc/{bind10 => server_common}/__init__.py | 0
.../isc/server_common/server_common_messages.mes | 36 +++
.../isc/{xfrin => server_common}/tests/Makefile.am | 2 +-
.../isc/server_common/tests/tsig_keyring_test.py | 193 ++++++++++++
src/lib/python/isc/server_common/tsig_keyring.py | 121 ++++++++
19 files changed, 792 insertions(+), 165 deletions(-)
create mode 100644 src/lib/python/isc/log_messages/server_common_messages.py
create mode 100644 src/lib/python/isc/server_common/Makefile.am
copy src/lib/python/isc/{bind10 => server_common}/__init__.py (100%)
create mode 100644 src/lib/python/isc/server_common/server_common_messages.mes
copy src/lib/python/isc/{xfrin => server_common}/tests/Makefile.am (97%)
create mode 100644 src/lib/python/isc/server_common/tests/tsig_keyring_test.py
create mode 100644 src/lib/python/isc/server_common/tsig_keyring.py
-----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index dc9efdb..a988754 100644
--- a/configure.ac
+++ b/configure.ac
@@ -5,6 +5,7 @@ AC_PREREQ([2.59])
AC_INIT(bind10-devel, 20120127, bind10-dev at isc.org)
AC_CONFIG_SRCDIR(README)
AM_INIT_AUTOMAKE
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])dnl be backward compatible
AC_CONFIG_HEADERS([config.h])
# Checks for programs.
@@ -274,7 +275,7 @@ AC_DEFUN([BIND10_CXX_TRY_FLAG], [
bind10_save_CXXFLAGS="$CXXFLAGS"
CXXFLAGS="$CXXFLAGS $1"
- AC_LINK_IFELSE([int main(void){ return 0;} ],
+ AC_LINK_IFELSE([int main(void){ return 0;}],
[bind10_cxx_flag=yes], [bind10_cxx_flag=no])
CXXFLAGS="$bind10_save_CXXFLAGS"
@@ -296,7 +297,7 @@ CXXFLAGS="$CXXFLAGS -library=stlport4 -features=tmplife -features=tmplrefstatic"
MULTITHREADING_FLAG="-mt"
fi
-BIND10_CXX_TRY_FLAG(-Wno-missing-field-initializers,
+BIND10_CXX_TRY_FLAG([-Wno-missing-field-initializers],
[WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG="-Wno-missing-field-initializers"])
AC_SUBST(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
@@ -521,21 +522,22 @@ else
AC_PATH_PROG([BOTAN_CONFIG], [botan-config])
fi
fi
-
-BOTAN_LIBS=`${BOTAN_CONFIG} --libs`
-BOTAN_INCLUDES=`${BOTAN_CONFIG} --cflags`
-
-# We expect botan-config --libs to contain -L<path_to_libbotan>, but
-# this is not always the case. As a heuristics workaround we add
-# -L`botan-config --prefix/lib` in this case (if not present already).
-# Same for BOTAN_INCLUDES (but using include instead of lib) below.
-if [ $BOTAN_CONFIG --prefix >/dev/null 2>&1 ] ; then
- echo ${BOTAN_LIBS} | grep -- -L > /dev/null || \
- BOTAN_LIBS="-L`${BOTAN_CONFIG} --prefix`/lib ${BOTAN_LIBS}"
- echo ${BOTAN_INCLUDES} | grep -- -I > /dev/null || \
- BOTAN_INCLUDES="-I`${BOTAN_CONFIG} --prefix`/include ${BOTAN_INCLUDES}"
+if test "x${BOTAN_CONFIG}" != "x"
+then
+ BOTAN_LIBS=`${BOTAN_CONFIG} --libs`
+ BOTAN_INCLUDES=`${BOTAN_CONFIG} --cflags`
+
+ # We expect botan-config --libs to contain -L<path_to_libbotan>, but
+ # this is not always the case. As a heuristics workaround we add
+ # -L`botan-config --prefix/lib` in this case (if not present already).
+ # Same for BOTAN_INCLUDES (but using include instead of lib) below.
+ if [ ${BOTAN_CONFIG} --prefix >/dev/null 2>&1 ] ; then
+ echo ${BOTAN_LIBS} | grep -- -L > /dev/null || \
+ BOTAN_LIBS="-L`${BOTAN_CONFIG} --prefix`/lib ${BOTAN_LIBS}"
+ echo ${BOTAN_INCLUDES} | grep -- -I > /dev/null || \
+ BOTAN_INCLUDES="-I`${BOTAN_CONFIG} --prefix`/include ${BOTAN_INCLUDES}"
+ fi
fi
-
# botan-config script (and the way we call pkg-config) returns -L and -l
# as one string, but we need them in separate values
BOTAN_LDFLAGS=
@@ -1005,6 +1007,8 @@ AC_CONFIG_FILES([Makefile
src/lib/python/isc/bind10/tests/Makefile
src/lib/python/isc/xfrin/Makefile
src/lib/python/isc/xfrin/tests/Makefile
+ src/lib/python/isc/server_common/Makefile
+ src/lib/python/isc/server_common/tests/Makefile
src/lib/config/Makefile
src/lib/config/tests/Makefile
src/lib/config/tests/testdata/Makefile
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index eafbbd8..8bc2bcb 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -1629,31 +1629,23 @@ Xfrout/transfer_acl[0] {"action": "ACCEPT"} any (default)</screen>
</simpara></note>
<para>
- If you want to require TSIG in access control, a separate TSIG
- "key ring" must be configured specifically
- for <command>b10-xfrout</command> as well as a system wide
- key ring, both containing a consistent set of keys.
+ If you want to require TSIG in access control, a system wide TSIG
+ "key ring" must be configured.
For example, to change the previous example to allowing requests
from 192.0.2.1 signed by a TSIG with a key name of
"key.example", you'll need to do this:
</para>
<screen>> <userinput>config set tsig_keys/keys ["key.example:<base64-key>"]</userinput>
-> <userinput>config set Xfrout/tsig_keys/keys ["key.example:<base64-key>"]</userinput>
> <userinput>config set Xfrout/zone_config[0]/transfer_acl [{"action": "ACCEPT", "from": "192.0.2.1", "key": "key.example"}]</userinput>
> <userinput>config commit</userinput></screen>
- <para>
- The first line of configuration defines a system wide key ring.
- This is necessary because the <command>b10-auth</command> server
- also checks TSIGs and it uses the system wide configuration.
- </para>
+ <para>Both Xfrout and Auth will use the system wide keyring to check
+ TSIGs in the incomming messages and to sign responses.</para>
<note><simpara>
- In a future version, <command>b10-xfrout</command> will also
- use the system wide TSIG configuration.
The way to specify zone specific configuration (ACLs, etc) is
- likely to be changed, too.
+ likely to be changed.
</simpara></note>
<!--
diff --git a/src/bin/xfrout/b10-xfrout.xml b/src/bin/xfrout/b10-xfrout.xml
index 87d0267..2875564 100644
--- a/src/bin/xfrout/b10-xfrout.xml
+++ b/src/bin/xfrout/b10-xfrout.xml
@@ -98,13 +98,6 @@
that can run concurrently. The default is 10.
</para>
<para>
- <varname>tsig_key_ring</varname>
- A list of TSIG keys (each of which is in the form of
- <replaceable>name:base64-key[:algorithm]</replaceable>)
- used for access control on transfer requests.
- The default is an empty list.
- </para>
- <para>
<varname>transfer_acl</varname>
A list of ACL elements that apply to all transfer requests by
default (unless overridden in <varname>zone_config</varname>).
@@ -160,13 +153,6 @@
</simpara></note>
-<!--
-
-tsig_key_ring list of
-tsig_key string
-
--->
-
<!-- TODO: formating -->
<para>
The configuration commands are:
diff --git a/src/bin/xfrout/tests/xfrout_test.py.in b/src/bin/xfrout/tests/xfrout_test.py.in
index 3e953da..b60535c 100644
--- a/src/bin/xfrout/tests/xfrout_test.py.in
+++ b/src/bin/xfrout/tests/xfrout_test.py.in
@@ -28,6 +28,7 @@ from xfrout import *
import xfrout
import isc.log
import isc.acl.dns
+import isc.server_common.tsig_keyring
TESTDATA_SRCDIR = os.getenv("TESTDATASRCDIR")
TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==")
@@ -1155,6 +1156,39 @@ class TestUnixSockServer(unittest.TestCase):
self.write_sock, self.read_sock = socket.socketpair()
self.unix = MyUnixSockServer()
+ def test_tsig_keyring(self):
+ """
+ Check we use the global keyring when starting a request.
+ """
+ try:
+ # These are just so the keyring can be started
+ self.unix._cc.add_remote_config_by_name = \
+ lambda name, callback: None
+ self.unix._cc.get_remote_config_value = \
+ lambda module, name: ([], True)
+ self.unix._cc.remove_remote_config = lambda name: None
+ isc.server_common.tsig_keyring.init_keyring(self.unix._cc)
+ # These are not really interesting for the test. These are just
+ # handled over, so strings are OK.
+ self.unix._guess_remote = lambda sock: "Address"
+ self.unix._zone_config = "Zone config"
+ self.unix._acl = "acl"
+ # This would be the handler class, but we just check it is passed
+ # the right parametes, so function is enough for that.
+ keys = isc.server_common.tsig_keyring.get_keyring()
+ def handler(sock, data, server, keyring, address, acl, config):
+ self.assertEqual("sock", sock)
+ self.assertEqual("data", data)
+ self.assertEqual(self.unix, server)
+ self.assertEqual(keys, keyring)
+ self.assertEqual("Address", address)
+ self.assertEqual("acl", acl)
+ self.assertEqual("Zone config", config)
+ self.unix.RequestHandlerClass = handler
+ self.unix.finish_request("sock", "data")
+ finally:
+ isc.server_common.tsig_keyring.deinit_keyring()
+
def test_guess_remote(self):
"""Test we can guess the remote endpoint when we have only the
file descriptor. This is needed, because we get only that one
@@ -1214,25 +1248,12 @@ class TestUnixSockServer(unittest.TestCase):
def test_update_config_data(self):
self.check_default_ACL()
- tsig_key_str = 'example.com:SFuWd/q99SzF8Yzd1QbB9g=='
- tsig_key_list = [tsig_key_str]
- bad_key_list = ['bad..example.com:SFuWd/q99SzF8Yzd1QbB9g==']
self.unix.update_config_data({'transfers_out':10 })
self.assertEqual(self.unix._max_transfers_out, 10)
- self.assertTrue(self.unix.tsig_key_ring is not None)
self.check_default_ACL()
- self.unix.update_config_data({'transfers_out':9,
- 'tsig_key_ring':tsig_key_list})
+ self.unix.update_config_data({'transfers_out':9})
self.assertEqual(self.unix._max_transfers_out, 9)
- self.assertEqual(self.unix.tsig_key_ring.size(), 1)
- self.unix.tsig_key_ring.remove(Name("example.com."))
- self.assertEqual(self.unix.tsig_key_ring.size(), 0)
-
- # bad tsig key
- config_data = {'transfers_out':9, 'tsig_key_ring': bad_key_list}
- self.assertRaises(None, self.unix.update_config_data(config_data))
- self.assertEqual(self.unix.tsig_key_ring.size(), 0)
# Load the ACL
self.unix.update_config_data({'transfer_acl': [{'from': '127.0.0.1',
@@ -1449,7 +1470,6 @@ class TestXfroutServer(unittest.TestCase):
self.assertTrue(self.xfrout_server._notifier.shutdown_called)
self.assertTrue(self.xfrout_server._cc.stopped)
-
if __name__== "__main__":
isc.log.resetUnitTestRootLogger()
unittest.main()
diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in
index 5c82f19..165560b 100755
--- a/src/bin/xfrout/xfrout.py.in
+++ b/src/bin/xfrout/xfrout.py.in
@@ -34,6 +34,7 @@ import select
import errno
from optparse import OptionParser, OptionValueError
from isc.util import socketserver_mixin
+import isc.server_common.tsig_keyring
from isc.log_messages.xfrout_messages import *
@@ -769,7 +770,7 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
zone_config = self._zone_config
self._lock.release()
self.RequestHandlerClass(sock_fd, request_data, self,
- self.tsig_key_ring,
+ isc.server_common.tsig_keyring.get_keyring(),
self._guess_remote(sock_fd), acl, zone_config)
def _remove_unused_sock_file(self, sock_file):
@@ -833,7 +834,6 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
self._acl = new_acl
self._zone_config = new_zone_config
self._max_transfers_out = new_config.get('transfers_out')
- self.set_tsig_key_ring(new_config.get('tsig_key_ring'))
except Exception as e:
self._lock.release()
raise e
@@ -870,21 +870,6 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
zclass_str + ': ' + str(e))
return new_config
- def set_tsig_key_ring(self, key_list):
- """Set the tsig_key_ring , given a TSIG key string list representation. """
-
- # XXX add values to configure zones/tsig options
- self.tsig_key_ring = TSIGKeyRing()
- # If key string list is empty, create a empty tsig_key_ring
- if not key_list:
- return
-
- for key_item in key_list:
- try:
- self.tsig_key_ring.add(TSIGKey(key_item))
- except InvalidParameter as ipe:
- logger.error(XFROUT_BAD_TSIG_KEY_STRING, str(key_item))
-
def get_db_file(self):
file, is_default = self._cc.get_remote_config_value("Auth", "database_file")
# this too should be unnecessary, but currently the
@@ -920,7 +905,8 @@ class XfroutServer:
self._cc = isc.config.ModuleCCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler)
self._config_data = self._cc.get_full_config()
self._cc.start()
- self._cc.add_remote_config(AUTH_SPECFILE_LOCATION);
+ self._cc.add_remote_config(AUTH_SPECFILE_LOCATION)
+ isc.server_common.tsig_keyring.init_keyring(self._cc)
self._start_xfr_query_listener()
self._start_notifier()
diff --git a/src/bin/xfrout/xfrout.spec.pre.in b/src/bin/xfrout/xfrout.spec.pre.in
index 6a97dea..31556ff 100644
--- a/src/bin/xfrout/xfrout.spec.pre.in
+++ b/src/bin/xfrout/xfrout.spec.pre.in
@@ -39,18 +39,6 @@
"item_default": 1048576
},
{
- "item_name": "tsig_key_ring",
- "item_type": "list",
- "item_optional": true,
- "item_default": [],
- "list_item_spec" :
- {
- "item_name": "tsig_key",
- "item_type": "string",
- "item_optional": true
- }
- },
- {
"item_name": "transfer_acl",
"item_type": "list",
"item_optional": false,
diff --git a/src/lib/datasrc/Makefile.am b/src/lib/datasrc/Makefile.am
index b6c314c..98214d8 100644
--- a/src/lib/datasrc/Makefile.am
+++ b/src/lib/datasrc/Makefile.am
@@ -7,10 +7,10 @@ AM_CPPFLAGS += $(SQLITE_CFLAGS)
AM_CXXFLAGS = $(B10_CXXFLAGS)
-pkglibexecdir = $(libexecdir)/@PACKAGE@/backends
+pkglibdir = $(libexecdir)/@PACKAGE@/backends
datasrc_config.h: datasrc_config.h.pre
- $(SED) -e "s|@@PKGLIBEXECDIR@@|$(pkglibexecdir)|" datasrc_config.h.pre >$@
+ $(SED) -e "s|@@PKGLIBDIR@@|$(pkglibdir)|" datasrc_config.h.pre >$@
CLEANFILES = *.gcno *.gcda datasrc_messages.h datasrc_messages.cc
CLEANFILES += datasrc_config.h
@@ -31,7 +31,7 @@ libdatasrc_la_SOURCES += database.h database.cc
libdatasrc_la_SOURCES += factory.h factory.cc
nodist_libdatasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
-pkglibexec_LTLIBRARIES = sqlite3_ds.la memory_ds.la
+pkglib_LTLIBRARIES = sqlite3_ds.la memory_ds.la
sqlite3_ds_la_SOURCES = sqlite3_accessor.h sqlite3_accessor.cc
sqlite3_ds_la_LDFLAGS = -module
diff --git a/src/lib/datasrc/datasrc_config.h.pre.in b/src/lib/datasrc/datasrc_config.h.pre.in
index ff99601..9074df6 100644
--- a/src/lib/datasrc/datasrc_config.h.pre.in
+++ b/src/lib/datasrc/datasrc_config.h.pre.in
@@ -23,7 +23,7 @@ namespace datasrc {
/// such as memory_ds.so and sqlite3_ds.so are found. It is used by the
/// DataSourceClient loader if no absolute path is used and
/// B10_FROM_BUILD is not set in the environment.
-const char* const BACKEND_LIBRARY_PATH = "@@PKGLIBEXECDIR@@/";
+const char* const BACKEND_LIBRARY_PATH = "@@PKGLIBDIR@@/";
} // end namespace datasrc
} // end namespace isc
diff --git a/src/lib/python/isc/Makefile.am b/src/lib/python/isc/Makefile.am
index a3e74c5..aef5dc3 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
+SUBDIRS += xfrin log_messages server_common
python_PYTHON = __init__.py
diff --git a/src/lib/python/isc/config/ccsession.py b/src/lib/python/isc/config/ccsession.py
index b505399..703d196 100644
--- a/src/lib/python/isc/config/ccsession.py
+++ b/src/lib/python/isc/config/ccsession.py
@@ -38,6 +38,7 @@
from isc.cc import Session
from isc.config.config_data import ConfigData, MultiConfigData, BIND10_CONFIG_DATA_VERSION
+import isc.config.module_spec
import isc
from isc.util.file import path_search
import bind10_config
@@ -327,43 +328,97 @@ class ModuleCCSession(ConfigData):
and return an answer created with create_answer()"""
self._command_handler = command_handler
- def add_remote_config(self, spec_file_name, config_update_callback = None):
- """Gives access to the configuration of a different module.
- These remote module options can at this moment only be
- accessed through get_remote_config_value(). This function
- also subscribes to the channel of the remote module name
- to receive the relevant updates. It is not possible to
- specify your own handler for this right now.
- start() must have been called on this CCSession
- prior to the call to this method.
- Returns the name of the module."""
- module_spec = isc.config.module_spec_from_file(spec_file_name)
+ def _add_remote_config_internal(self, module_spec,
+ config_update_callback=None):
+ """The guts of add_remote_config and add_remote_config_by_name"""
module_cfg = ConfigData(module_spec)
module_name = module_spec.get_module_name()
+
self._session.group_subscribe(module_name)
# Get the current config for that module now
seq = self._session.group_sendmsg(create_command(COMMAND_GET_CONFIG, { "module_name": module_name }), "ConfigManager")
try:
- answer, env = self._session.group_recvmsg(False, seq)
+ answer, _ = self._session.group_recvmsg(False, seq)
except isc.cc.SessionTimeout:
raise ModuleCCSessionError("No answer from ConfigManager when "
"asking about Remote module " +
module_name)
+ call_callback = False
if answer:
rcode, value = parse_answer(answer)
if rcode == 0:
- if value != None and module_spec.validate_config(False, value):
- module_cfg.set_local_config(value)
- if config_update_callback is not None:
- config_update_callback(value, module_cfg)
+ if value != None:
+ if module_spec.validate_config(False, value):
+ module_cfg.set_local_config(value)
+ call_callback = True
+ else:
+ raise ModuleCCSessionError("Bad config data for " +
+ module_name + ": " +
+ str(value))
+ else:
+ raise ModuleCCSessionError("Failure requesting remote " +
+ "configuration data for " +
+ module_name)
# all done, add it
self._remote_module_configs[module_name] = module_cfg
self._remote_module_callbacks[module_name] = config_update_callback
+ if call_callback and config_update_callback is not None:
+ config_update_callback(value, module_cfg)
+
+ def add_remote_config_by_name(self, module_name,
+ config_update_callback=None):
+ """
+ This does the same as add_remote_config, but you provide the module name
+ instead of the name of the spec file.
+ """
+ seq = self._session.group_sendmsg(create_command(COMMAND_GET_MODULE_SPEC,
+ { "module_name":
+ module_name }),
+ "ConfigManager")
+ try:
+ answer, env = self._session.group_recvmsg(False, seq)
+ except isc.cc.SessionTimeout:
+ raise ModuleCCSessionError("No answer from ConfigManager when " +
+ "asking about for spec of Remote " +
+ "module " + module_name)
+ if answer:
+ rcode, value = parse_answer(answer)
+ if rcode == 0:
+ module_spec = isc.config.module_spec.ModuleSpec(value)
+ if module_spec.get_module_name() != module_name:
+ raise ModuleCCSessionError("Module name mismatch: " +
+ module_name + " and " +
+ module_spec.get_module_name())
+ self._add_remote_config_internal(module_spec,
+ config_update_callback)
+ else:
+ raise ModuleCCSessionError("Error code " + str(rcode) +
+ "when asking for module spec of " +
+ module_name)
+ else:
+ raise ModuleCCSessionError("No answer when asking for module " +
+ "spec of " + module_name)
+ # Just to be consistent with the add_remote_config
return module_name
-
+
+ def add_remote_config(self, spec_file_name, config_update_callback=None):
+ """Gives access to the configuration of a different module.
+ These remote module options can at this moment only be
+ accessed through get_remote_config_value(). This function
+ also subscribes to the channel of the remote module name
+ to receive the relevant updates. It is not possible to
+ specify your own handler for this right now, but you can
+ specify a callback that is called after the change happened.
+ start() must have been called on this CCSession
+ prior to the call to this method.
+ Returns the name of the module."""
+ module_spec = isc.config.module_spec_from_file(spec_file_name)
+ self._add_remote_config_internal(module_spec, config_update_callback)
+ return module_spec.get_module_name()
+
def remove_remote_config(self, module_name):
"""Removes the remote configuration access for this module"""
if module_name in self._remote_module_configs:
@@ -501,8 +556,8 @@ class UIModuleCCSession(MultiConfigData):
self.set_value(identifier, cur_map)
else:
raise isc.cc.data.DataAlreadyPresentError(value +
- " already in "
- + identifier)
+ " already in " +
+ identifier)
def add_value(self, identifier, value_str = None, set_value_str = None):
"""Add a value to a configuration list. Raises a DataTypeError
diff --git a/src/lib/python/isc/config/tests/ccsession_test.py b/src/lib/python/isc/config/tests/ccsession_test.py
index df39550..d1060bf 100644
--- a/src/lib/python/isc/config/tests/ccsession_test.py
+++ b/src/lib/python/isc/config/tests/ccsession_test.py
@@ -488,45 +488,6 @@ class TestModuleCCSession(unittest.TestCase):
self.assertEqual({'result': [0]},
fake_session.get_message('Spec2', None))
- def test_check_command_without_recvmsg_remote_module(self):
- "copied from test_check_command3"
- fake_session = FakeModuleCCSession()
- mccs = self.create_session("spec1.spec", None, None, fake_session)
- mccs.set_config_handler(self.my_config_handler_ok)
- self.assertEqual(len(fake_session.message_queue), 0)
-
- fake_session.group_sendmsg(None, 'Spec2')
- rmodname = mccs.add_remote_config(self.spec_file("spec2.spec"))
- print(fake_session.message_queue)
- self.assertEqual({'command': ['get_config', {'module_name': 'Spec2'}]},
- fake_session.get_message('ConfigManager', None))
- self.assertEqual(len(fake_session.message_queue), 0)
-
- cmd = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_CONFIG_UPDATE, { 'Spec2': { 'item1': 2 }})
- env = { 'group':'Spec2', 'from':None }
- self.assertEqual(len(fake_session.message_queue), 0)
- mccs.check_command_without_recvmsg(cmd, env)
- self.assertEqual(len(fake_session.message_queue), 0)
-
- def test_check_command_without_recvmsg_remote_module2(self):
- "copied from test_check_command3"
- fake_session = FakeModuleCCSession()
- mccs = self.create_session("spec1.spec", None, None, fake_session)
- mccs.set_config_handler(self.my_config_handler_ok)
- self.assertEqual(len(fake_session.message_queue), 0)
-
- fake_session.group_sendmsg(None, 'Spec2')
- rmodname = mccs.add_remote_config(self.spec_file("spec2.spec"))
- self.assertEqual({'command': ['get_config', {'module_name': 'Spec2'}]},
- fake_session.get_message('ConfigManager', None))
- self.assertEqual(len(fake_session.message_queue), 0)
-
- cmd = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_CONFIG_UPDATE, { 'Spec3': { 'item1': 2 }})
- env = { 'group':'Spec3', 'from':None }
- self.assertEqual(len(fake_session.message_queue), 0)
- mccs.check_command_without_recvmsg(cmd, env)
- self.assertEqual(len(fake_session.message_queue), 0)
-
def test_check_command_block_timeout(self):
"""Check it works if session has timeout and it sets it back."""
def cmd_check(mccs, session):
@@ -554,16 +515,65 @@ class TestModuleCCSession(unittest.TestCase):
mccs.set_command_handler(self.my_command_handler_ok)
self.assertRaises(WouldBlockForever, lambda: mccs.check_command(False))
- def test_remote_module(self):
+ # Now there's a group of tests testing both add_remote_config and
+ # add_remote_config_by_name. Since they are almost the same (they differ
+ # just in the parameter and that the second one asks one more question over
+ # the bus), the actual test code is shared.
+ #
+ # These three functions are helper functions to easy up the writing of them.
+ # To write a test, there need to be 3 functions. First, the function that
+ # does the actual test. It looks like:
+ # def _internal_test(self, function_lambda, param, fill_other_messages):
+ #
+ # The function_lambda provides the tested function if called on the
+ # ccsession. The param is the parameter to pass to the function (either
+ # the module name or the spec file name. The fill_other_messages fills
+ # needed messages (the answer containing the module spec in case of add by
+ # name, no messages in the case of adding by spec file) into the fake bus.
+ # So, the code would look like:
+ #
+ # * Create the fake session and tested ccsession object
+ # * function = function_lambda(ccsession object)
+ # * fill_other_messages(fake session)
+ # * Fill in answer to the get_module_config command
+ # * Test by calling function(param)
+ #
+ # Then you need two wrappers that do launch the tests. There are helpers
+ # for that, so you can just call:
+ # def test_by_spec(self)
+ # self._common_remote_module_test(self._internal_test)
+ # def test_by_name(self)
+ # self._common_remote_module_by_name_test(self._internal_test)
+ def _common_remote_module_test(self, internal_test):
+ internal_test(lambda ccs: ccs.add_remote_config,
+ self.spec_file("spec2.spec"),
+ lambda session: None)
+
+ def _prepare_spec_message(self, session, spec_name):
+ # It could have been one command, but the line would be way too long
+ # to even split it
+ spec_file = self.spec_file(spec_name)
+ spec = isc.config.module_spec_from_file(spec_file)
+ session.group_sendmsg({'result': [0, spec.get_full_spec()]}, "Spec1")
+
+ def _common_remote_module_by_name_test(self, internal_test):
+ internal_test(lambda ccs: ccs.add_remote_config_by_name, "Spec2",
+ lambda session: self._prepare_spec_message(session,
+ "spec2.spec"))
+
+ def _internal_remote_module(self, function_lambda, parameter,
+ fill_other_messages):
fake_session = FakeModuleCCSession()
mccs = self.create_session("spec1.spec", None, None, fake_session)
mccs.remove_remote_config("Spec2")
+ function = function_lambda(mccs)
self.assertRaises(ModuleCCSessionError, mccs.get_remote_config_value, "Spec2", "item1")
self.assertFalse("Spec2" in fake_session.subscriptions)
+ fill_other_messages(fake_session)
fake_session.group_sendmsg(None, 'Spec2')
- rmodname = mccs.add_remote_config(self.spec_file("spec2.spec"))
+ rmodname = function(parameter)
self.assertTrue("Spec2" in fake_session.subscriptions)
self.assertEqual("Spec2", rmodname)
self.assertRaises(isc.cc.data.DataNotFoundError, mccs.get_remote_config_value, rmodname, "asdf")
@@ -575,36 +585,77 @@ class TestModuleCCSession(unittest.TestCase):
self.assertFalse("Spec2" in fake_session.subscriptions)
self.assertRaises(ModuleCCSessionError, mccs.get_remote_config_value, "Spec2", "item1")
- # test if unsubscription is alse sent when object is deleted
+ # test if unsubscription is also sent when object is deleted
+ fill_other_messages(fake_session)
fake_session.group_sendmsg({'result' : [0]}, 'Spec2')
- rmodname = mccs.add_remote_config(self.spec_file("spec2.spec"))
+ rmodname = function(parameter)
self.assertTrue("Spec2" in fake_session.subscriptions)
mccs = None
+ function = None
self.assertFalse("Spec2" in fake_session.subscriptions)
- def test_remote_module_with_custom_config(self):
+ def test_remote_module(self):
+ """
+ Test we can add a remote config and get the configuration.
+ Remote module specified by the spec file name.
+ """
+ self._common_remote_module_test(self._internal_remote_module)
+
+ def test_remote_module_by_name(self):
+ """
+ Test we can add a remote config and get the configuration.
+ Remote module specified its name.
+ """
+ self._common_remote_module_by_name_test(self._internal_remote_module)
+
+ def _internal_remote_module_with_custom_config(self, function_lambda,
+ parameter,
+ fill_other_messages):
fake_session = FakeModuleCCSession()
mccs = self.create_session("spec1.spec", None, None, fake_session)
- # override the default config value for "item1". add_remote_config()
- # should incorporate the overridden value, and we should be abel to
+ function = function_lambda(mccs)
+ # override the default config value for "item1". add_remote_config[_by_name]()
+ # should incorporate the overridden value, and we should be able to
# get it via get_remote_config_value().
+ fill_other_messages(fake_session)
fake_session.group_sendmsg({'result': [0, {"item1": 10}]}, 'Spec2')
- rmodname = mccs.add_remote_config(self.spec_file("spec2.spec"))
+ rmodname = function(parameter)
value, default = mccs.get_remote_config_value(rmodname, "item1")
self.assertEqual(10, value)
self.assertEqual(False, default)
- def test_ignore_command_remote_module(self):
+ def test_remote_module_with_custom_config(self):
+ """
+ Test the config of module will load non-default values on
+ initialization.
+ Remote module specified by the spec file name.
+ """
+ self._common_remote_module_test(
+ self._internal_remote_module_with_custom_config)
+
+ def test_remote_module_by_name_with_custom_config(self):
+ """
+ Test the config of module will load non-default values on
+ initialization.
+ Remote module its name.
+ """
+ self._common_remote_module_by_name_test(
+ self._internal_remote_module_with_custom_config)
+
+ def _internal_ignore_command_remote_module(self, function_lambda, param,
+ fill_other_messages):
# Create a Spec1 module and subscribe to remote config for Spec2
fake_session = FakeModuleCCSession()
mccs = self.create_session("spec1.spec", None, None, fake_session)
mccs.set_command_handler(self.my_command_handler_ok)
+ function = function_lambda(mccs)
+ fill_other_messages(fake_session)
fake_session.group_sendmsg(None, 'Spec2')
- rmodname = mccs.add_remote_config(self.spec_file("spec2.spec"))
+ rmodname = function(param)
- # remove the 'get config' from the queue
- self.assertEqual(len(fake_session.message_queue), 1)
- fake_session.get_message("ConfigManager")
+ # remove the commands from queue
+ while len(fake_session.message_queue) > 0:
+ fake_session.get_message("ConfigManager")
# check if the command for the module itself is received
cmd = isc.config.ccsession.create_command("just_some_command", { 'foo': 'a' })
@@ -622,6 +673,174 @@ class TestModuleCCSession(unittest.TestCase):
mccs.check_command()
self.assertEqual(len(fake_session.message_queue), 0)
+ def test_ignore_commant_remote_module(self):
+ """
+ Test that commands for remote modules aren't handled.
+ Remote module specified by the spec file name.
+ """
+ self._common_remote_module_test(
+ self._internal_ignore_command_remote_module)
+
+ def test_ignore_commant_remote_module_by_name(self):
+ """
+ Test that commands for remote modules aren't handled.
+ Remote module specified by its name.
+ """
+ self._common_remote_module_by_name_test(
+ self._internal_ignore_command_remote_module)
+
+ def _internal_check_command_without_recvmsg_remote_module(self,
+ function_lambda,
+ param,
+ fill_other_messages):
+ fake_session = FakeModuleCCSession()
+ mccs = self.create_session("spec1.spec", None, None, fake_session)
+ mccs.set_config_handler(self.my_config_handler_ok)
+ function = function_lambda(mccs)
+ self.assertEqual(len(fake_session.message_queue), 0)
+
+ fill_other_messages(fake_session)
+ fake_session.group_sendmsg(None, 'Spec2')
+ rmodname = function(param)
+ if (len(fake_session.message_queue) == 2):
+ self.assertEqual({'command': ['get_module_spec',
+ {'module_name': 'Spec2'}]},
+ fake_session.get_message('ConfigManager', None))
+ self.assertEqual({'command': ['get_config', {'module_name': 'Spec2'}]},
+ fake_session.get_message('ConfigManager', None))
+ self.assertEqual(len(fake_session.message_queue), 0)
+
+ cmd = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_CONFIG_UPDATE, { 'Spec2': { 'item1': 2 }})
+ env = { 'group':'Spec2', 'from':None }
+ self.assertEqual(len(fake_session.message_queue), 0)
+ mccs.check_command_without_recvmsg(cmd, env)
+ self.assertEqual(len(fake_session.message_queue), 0)
+
+ def test_check_command_without_recvmsg_remote_module(self):
+ """
+ Test updates on remote module.
+ The remote module is specified by the spec file name.
+ """
+ self._common_remote_module_test(
+ self._internal_check_command_without_recvmsg_remote_module)
+
+ def test_check_command_without_recvmsg_remote_module_by_name(self):
+ """
+ Test updates on remote module.
+ The remote module is specified by its name.
+ """
+ self._common_remote_module_by_name_test(
+ self._internal_check_command_without_recvmsg_remote_module)
+
+ def _internal_check_command_without_recvmsg_remote_module2(self,
+ function_lambda,
+ param,
+ fill_other_messages):
+ fake_session = FakeModuleCCSession()
+ mccs = self.create_session("spec1.spec", None, None, fake_session)
+ mccs.set_config_handler(self.my_config_handler_ok)
+ function = function_lambda(mccs)
+ self.assertEqual(len(fake_session.message_queue), 0)
+
+ fill_other_messages(fake_session)
+ fake_session.group_sendmsg(None, 'Spec2')
+ rmodname = function(param)
+ if (len(fake_session.message_queue) == 2):
+ self.assertEqual({'command': ['get_module_spec',
+ {'module_name': 'Spec2'}]},
+ fake_session.get_message('ConfigManager', None))
+ self.assertEqual({'command': ['get_config', {'module_name': 'Spec2'}]},
+ fake_session.get_message('ConfigManager', None))
+ self.assertEqual(len(fake_session.message_queue), 0)
+
+ cmd = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_CONFIG_UPDATE, { 'Spec3': { 'item1': 2 }})
+ env = { 'group':'Spec3', 'from':None }
+ self.assertEqual(len(fake_session.message_queue), 0)
+ mccs.check_command_without_recvmsg(cmd, env)
+ self.assertEqual(len(fake_session.message_queue), 0)
+
+ def test_check_command_without_recvmsg_remote_module2(self):
+ """
+ Test updates on remote module.
+ The remote module is specified by the spec file name.
+ """
+ self._common_remote_module_test(
+ self._internal_check_command_without_recvmsg_remote_module2)
+
+ def test_check_command_without_recvmsg_remote_module_by_name2(self):
+ """
+ Test updates on remote module.
+ The remote module is specified by its name.
+ """
+ self._common_remote_module_by_name_test(
+ self._internal_check_command_without_recvmsg_remote_module2)
+
+ def _internal_remote_module_bad_config(self, function_lambda, parameter,
+ fill_other_messages):
+ fake_session = FakeModuleCCSession()
+ mccs = self.create_session("spec1.spec", None, None, fake_session)
+ function = function_lambda(mccs)
+ # Provide wrong config data. It should be rejected.
+ fill_other_messages(fake_session)
+ fake_session.group_sendmsg({'result': [0, {"bad_item": -1}]}, 'Spec2')
+ self.assertRaises(isc.config.ModuleCCSessionError,
+ function, parameter)
+
+ def test_remote_module_bad_config(self):
+ """
+ Test the remote module rejects bad config data.
+ """
+ self._common_remote_module_test(
+ self._internal_remote_module_bad_config)
+
+ def test_remote_module_by_name_bad_config(self):
+ """
+ Test the remote module rejects bad config data.
+ """
+ self._common_remote_module_by_name_test(
+ self._internal_remote_module_bad_config)
+
+ def _internal_remote_module_error_response(self, function_lambda,
+ parameter, fill_other_messages):
+ fake_session = FakeModuleCCSession()
+ mccs = self.create_session("spec1.spec", None, None, fake_session)
+ function = function_lambda(mccs)
+ # Provide wrong config data. It should be rejected.
+ fill_other_messages(fake_session)
+ fake_session.group_sendmsg({'result': [1, "An error, and I mean it!"]},
+ 'Spec2')
+ self.assertRaises(isc.config.ModuleCCSessionError,
+ function, parameter)
+
+ def test_remote_module_bad_config(self):
+ """
+ Test the remote module complains if there's an error response."
+ """
+ self._common_remote_module_test(
+ self._internal_remote_module_error_response)
+
+ def test_remote_module_by_name_bad_config(self):
+ """
+ Test the remote module complains if there's an error response."
+ """
+ self._common_remote_module_by_name_test(
+ self._internal_remote_module_error_response)
+
+ def test_remote_module_bad_config(self):
+ """
+ Test the remote module rejects bad config data.
+ """
+ self._common_remote_module_by_name_test(
+ self._internal_remote_module_bad_config)
+
+ def test_module_name_mismatch(self):
+ fake_session = FakeModuleCCSession()
+ mccs = self.create_session("spec1.spec", None, None, fake_session)
+ mccs.set_config_handler(self.my_config_handler_ok)
+ self._prepare_spec_message(fake_session, 'spec1.spec')
+ self.assertRaises(isc.config.ModuleCCSessionError,
+ mccs.add_remote_config_by_name, "Spec2")
+
def test_logconfig_handler(self):
# test whether default_logconfig_handler reacts nicely to
# bad data. We assume the actual logger output is tested
diff --git a/src/lib/python/isc/log_messages/Makefile.am b/src/lib/python/isc/log_messages/Makefile.am
index 4b084cc..4b7e1d1 100644
--- a/src/lib/python/isc/log_messages/Makefile.am
+++ b/src/lib/python/isc/log_messages/Makefile.am
@@ -13,6 +13,7 @@ EXTRA_DIST += cfgmgr_messages.py
EXTRA_DIST += config_messages.py
EXTRA_DIST += notify_out_messages.py
EXTRA_DIST += libxfrin_messages.py
+EXTRA_DIST += server_common_messages.py
CLEANFILES = __init__.pyc
CLEANFILES += bind10_messages.pyc
@@ -27,6 +28,7 @@ CLEANFILES += cfgmgr_messages.pyc
CLEANFILES += config_messages.pyc
CLEANFILES += notify_out_messages.pyc
CLEANFILES += libxfrin_messages.pyc
+CLEANFILES += server_common_messages.pyc
CLEANDIRS = __pycache__
diff --git a/src/lib/python/isc/log_messages/server_common_messages.py b/src/lib/python/isc/log_messages/server_common_messages.py
new file mode 100644
index 0000000..a491071
--- /dev/null
+++ b/src/lib/python/isc/log_messages/server_common_messages.py
@@ -0,0 +1 @@
+from work.server_common_messages import *
diff --git a/src/lib/python/isc/server_common/Makefile.am b/src/lib/python/isc/server_common/Makefile.am
new file mode 100644
index 0000000..275e34a
--- /dev/null
+++ b/src/lib/python/isc/server_common/Makefile.am
@@ -0,0 +1,24 @@
+SUBDIRS = tests
+
+python_PYTHON = __init__.py tsig_keyring.py
+
+pythondir = $(pyexecdir)/isc/server_common
+
+BUILT_SOURCES = $(PYTHON_LOGMSGPKG_DIR)/work/server_common_messages.py
+nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/server_common_messages.py
+
+pylogmessagedir = $(pyexecdir)/isc/logmessages/
+
+CLEANFILES = $(PYTHON_LOGMSGPKG_DIR)/work/server_common_messages.py
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/server_common_messages.pyc
+
+CLEANDIRS = __pycache__
+
+EXTRA_DIST = server_common_messages.mes
+
+$(PYTHON_LOGMSGPKG_DIR)/work/server_common_messages.py : server_common_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message \
+ -d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/server_common_messages.mes
+
+clean-local:
+ rm -rf $(CLEANDIRS)
diff --git a/src/lib/python/isc/server_common/__init__.py b/src/lib/python/isc/server_common/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/lib/python/isc/server_common/server_common_messages.mes b/src/lib/python/isc/server_common/server_common_messages.mes
new file mode 100644
index 0000000..b32205c
--- /dev/null
+++ b/src/lib/python/isc/server_common/server_common_messages.mes
@@ -0,0 +1,36 @@
+# 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 config_messages python module.
+
+# since these messages are for the python server_common library, care must
+# be taken that names do not conflict with the messages from the c++
+# server_common library. A checker script should verify that, but we do not
+# have that at this moment. So when adding a message, make sure that
+# the name is not already used in src/lib/config/config_messages.mes
+
+% PYSERVER_COMMON_TSIG_KEYRING_DEINIT Deinitializing global TSIG keyring
+A debug message noting that the global TSIG keyring is being removed from
+memory. Most programs don't do that, they just exit, which is OK.
+
+% PYSERVER_COMMON_TSIG_KEYRING_INIT Initializing global TSIG keyring
+A debug message noting the TSIG keyring storage is being prepared. It should
+appear at most once in the lifetime of a program. The keyring still needs
+to be loaded from configuration.
+
+% PYSERVER_COMMON_TSIG_KEYRING_UPDATE Updating global TSIG keyring
+A debug message. The TSIG keyring is being (re)loaded from configuration.
+This happens at startup or when the configuration changes. The old keyring
+is removed and new one created with all the keys.
diff --git a/src/lib/python/isc/server_common/tests/Makefile.am b/src/lib/python/isc/server_common/tests/Makefile.am
new file mode 100644
index 0000000..4829edc
--- /dev/null
+++ b/src/lib/python/isc/server_common/tests/Makefile.am
@@ -0,0 +1,24 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
+PYTESTS = tsig_keyring_test.py
+EXTRA_DIST = $(PYTESTS)
+
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+LIBRARY_PATH_PLACEHOLDER =
+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
+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) \
+ 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/server_common/tests/tsig_keyring_test.py b/src/lib/python/isc/server_common/tests/tsig_keyring_test.py
new file mode 100644
index 0000000..e9a2174
--- /dev/null
+++ b/src/lib/python/isc/server_common/tests/tsig_keyring_test.py
@@ -0,0 +1,193 @@
+# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+#
+# 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.
+
+"""
+Tests for isc.server_common.tsig_keyring.
+"""
+
+import unittest
+import isc.log
+from isc.server_common.tsig_keyring import *
+import isc.dns
+from isc.testutils.ccsession_mock import MockModuleCCSession
+
+class Session(MockModuleCCSession):
+ """
+ A class pretending to be the config session.
+ """
+ def __init__(self):
+ MockModuleCCSession.__init__(self)
+ self._name = None
+ self._callback = None
+ self._remove_name = None
+ self._data = None
+
+ def add_remote_config_by_name(self, name, callback):
+ self._name = name
+ self._callback = callback
+
+ def remove_remote_config(self, name):
+ self._remove_name = name
+
+ def get_remote_config_value(self, module, name):
+ if module != 'tsig_keys' or name != 'keys':
+ raise Exception("Asked for bad data element")
+ return (self._data, False)
+
+class TSIGKeyRingTest(unittest.TestCase):
+ """
+ Tests for the isc.server_common.tsig_keyring module.
+ """
+ def setUp(self):
+ self.__session = Session()
+ self.__sha1name = isc.dns.Name('hmac-sha1')
+ self.__md5name = isc.dns.Name('hmac-md5.sig-alg.reg.int')
+
+ def tearDown(self):
+ deinit_keyring()
+
+ def __do_init(self):
+ init_keyring(self.__session)
+ # Some initialization happened
+ self.assertEqual('tsig_keys', self.__session._name)
+
+ def test_initialization(self):
+ """
+ Test we can initialize and deintialize the keyring. It also
+ tests the interaction with the keyring() function.
+ """
+ # The keyring function raises until initialized
+ self.assertRaises(Unexpected, get_keyring)
+ self.__do_init()
+ current_keyring = get_keyring()
+ self.assertTrue(isinstance(current_keyring, isc.dns.TSIGKeyRing))
+ # Another initialization does nothing
+ self.__do_init()
+ self.assertEqual(current_keyring, get_keyring())
+ # When we deinitialize it, it no longer provides the keyring
+ deinit_keyring()
+ self.assertEqual('tsig_keys', self.__session._remove_name)
+ self.__session._remove_name = None
+ self.assertRaises(Unexpected, get_keyring)
+ # Another deinitialization doesn't change anything
+ deinit_keyring()
+ self.assertRaises(Unexpected, get_keyring)
+ self.assertIsNone(self.__session._remove_name)
+ # Test we can init it again (not expected, but not forbidden)
+ self.__do_init()
+ self.assertTrue(isinstance(get_keyring(), isc.dns.TSIGKeyRing))
+
+ def test_load(self):
+ """
+ Test it can load the keys from the configuration and reload them
+ when the data change.
+ """
+ # Initial load
+ self.__session._data = ['key:MTIzNAo=:hmac-sha1']
+ self.__do_init()
+ keys = get_keyring()
+ self.assertEqual(1, keys.size())
+ (rcode, key) = keys.find(isc.dns.Name('key'), self.__sha1name)
+ self.assertEqual(isc.dns.TSIGKeyRing.SUCCESS, rcode)
+ self.assertEqual(isc.dns.Name('key'), key.get_key_name())
+ # There's a change in the configuration
+ # (The key has a different name)
+ self.__session._data = ['key.example:MTIzNAo=:hmac-sha1']
+ self.__session._callback()
+ orig_keys = keys
+ keys = get_keyring()
+ self.assertNotEqual(keys, orig_keys)
+ self.assertEqual(1, keys.size())
+ # The old key is not here
+ (rcode, key) = keys.find(isc.dns.Name('key'), self.__sha1name)
+ self.assertEqual(isc.dns.TSIGKeyRing.NOTFOUND, rcode)
+ self.assertIsNone(key)
+ # But the new one is
+ (rcode, key) = keys.find(isc.dns.Name('key.example'), self.__sha1name)
+ self.assertEqual(isc.dns.TSIGKeyRing.SUCCESS, rcode)
+ self.assertEqual(isc.dns.Name('key.example'), key.get_key_name())
+
+ def test_empty_update(self):
+ """
+ Test an update that doesn't carry the correct element doesn't change
+ anything.
+ """
+ self.__session._data = ['key:MTIzNAo=:hmac-sha1']
+ self.__do_init()
+ keys = get_keyring()
+ self.__session._data = None
+ self.__session._callback()
+ self.assertEqual(keys, get_keyring())
+
+ def test_no_keys_update(self):
+ """
+ Test we can update the keyring to be empty.
+ """
+ self.__session._data = ['key:MTIzNAo=:hmac-sha1']
+ self.__do_init()
+ keys = get_keyring()
+ self.assertEqual(1, keys.size())
+ self.__session._data = []
+ self.__session._callback()
+ keys = get_keyring()
+ self.assertEqual(0, keys.size())
+
+ def test_update_multi(self):
+ """
+ Test we can handle multiple keys in startup/update.
+ """
+ # Init
+ self.__session._data = ['key:MTIzNAo=:hmac-sha1', 'key2:MTIzNAo=']
+ self.__do_init()
+ keys = get_keyring()
+ self.assertEqual(2, keys.size())
+ (rcode, key) = keys.find(isc.dns.Name('key'), self.__sha1name)
+ self.assertEqual(isc.dns.TSIGKeyRing.SUCCESS, rcode)
+ self.assertEqual(isc.dns.Name('key'), key.get_key_name())
+ (rcode, key) = keys.find(isc.dns.Name('key2'), self.__md5name)
+ self.assertEqual(isc.dns.TSIGKeyRing.SUCCESS, rcode)
+ self.assertEqual(isc.dns.Name('key2'), key.get_key_name())
+ # Update
+ self.__session._data = ['key1:MTIzNAo=:hmac-sha1', 'key3:MTIzNAo=']
+ self.__session._callback()
+ keys = get_keyring()
+ self.assertEqual(2, keys.size())
+ (rcode, key) = keys.find(isc.dns.Name('key1'), self.__sha1name)
+ self.assertEqual(isc.dns.TSIGKeyRing.SUCCESS, rcode)
+ self.assertEqual(isc.dns.Name('key1'), key.get_key_name())
+ (rcode, key) = keys.find(isc.dns.Name('key3'), self.__md5name)
+ self.assertEqual(isc.dns.TSIGKeyRing.SUCCESS, rcode)
+ self.assertEqual(isc.dns.Name('key3'), key.get_key_name())
+
+ def test_update_bad(self):
+ """
+ Test it raises on bad updates and doesn't change anything.
+ """
+ self.__session._data = ['key:MTIzNAo=:hmac-sha1']
+ self.__do_init()
+ keys = get_keyring()
+ # Bad TSIG string
+ self.__session._data = ['key:this makes no sense:really']
+ self.assertRaises(isc.dns.InvalidParameter, self.__session._callback)
+ self.assertEqual(keys, get_keyring())
+ # A duplicity
+ self.__session._data = ['key:MTIzNAo=:hmac-sha1', 'key:MTIzNAo=:hmac-sha1']
+ self.assertRaises(AddError, self.__session._callback)
+ self.assertEqual(keys, get_keyring())
+
+if __name__ == "__main__":
+ isc.log.init("bind10") # FIXME Should this be needed?
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/lib/python/isc/server_common/tsig_keyring.py b/src/lib/python/isc/server_common/tsig_keyring.py
new file mode 100644
index 0000000..308cfd4
--- /dev/null
+++ b/src/lib/python/isc/server_common/tsig_keyring.py
@@ -0,0 +1,121 @@
+# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+#
+# 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 module conveniently keeps a copy of TSIG keyring loaded from the
+tsig_keys module.
+"""
+
+import isc.dns
+import isc.log
+from isc.log_messages.server_common_messages import *
+
+updater = None
+logger = isc.log.Logger("server_common")
+
+class Unexpected(Exception):
+ """
+ Raised when an unexpected operation is requested by the user of this
+ module. For example if calling keyring() before init_keyring().
+ """
+ pass
+
+class AddError(Exception):
+ """
+ Raised when a key can not be added. This usually means there's a
+ duplicate.
+ """
+ pass
+
+class Updater:
+ """
+ The updater of tsig key ring. Not to be used directly.
+ """
+ def __init__(self, session):
+ """
+ Constructor. Pass the ccsession object so the key ring can be
+ downloaded.
+ """
+ logger.debug(logger.DBGLVL_TRACE_BASIC,
+ PYSERVER_COMMON_TSIG_KEYRING_INIT)
+ self.__session = session
+ self.__keyring = isc.dns.TSIGKeyRing()
+ session.add_remote_config_by_name('tsig_keys', self.__update)
+ self.__update()
+
+ def __update(self, value=None, module_cfg=None):
+ """
+ Update the key ring by the configuration.
+
+ Note that this function is used as a callback, but can raise
+ on bad data. The bad data is expected to be handled by the
+ configuration plugin and not be allowed as far as here.
+
+ The parameters are there just to match the signature which
+ the callback should have (i.e. they are ignored).
+ """
+ logger.debug(logger.DBGLVL_TRACE_BASIC,
+ PYSERVER_COMMON_TSIG_KEYRING_UPDATE)
+ (data, _) = self.__session.get_remote_config_value('tsig_keys', 'keys')
+ if data is not None: # There's an update
+ keyring = isc.dns.TSIGKeyRing()
+ for key_data in data:
+ key = isc.dns.TSIGKey(key_data)
+ if keyring.add(key) != isc.dns.TSIGKeyRing.SUCCESS:
+ raise AddError("Can't add key " + str(key))
+ self.__keyring = keyring
+
+ def get_keyring(self):
+ """
+ Return the current key ring.
+ """
+ return self.__keyring
+
+ def deinit(self):
+ """
+ Unregister from getting updates. The object will not be
+ usable any more after this.
+ """
+ logger.debug(logger.DBGLVL_TRACE_BASIC,
+ PYSERVER_COMMON_TSIG_KEYRING_DEINIT)
+ self.__session.remove_remote_config('tsig_keys')
+
+def get_keyring():
+ """
+ Get the current key ring. You need to call init_keyring first.
+ """
+ if updater is None:
+ raise Unexpected("You need to initialize the keyring first by " +
+ "init_keyring()")
+ return updater.get_keyring()
+
+def init_keyring(session):
+ """
+ Initialize the key ring for future use. It does nothing if already
+ initialized.
+ """
+ global updater
+ if updater is None:
+ updater = Updater(session)
+
+def deinit_keyring():
+ """
+ Deinit key ring. Yoeu can no longer access keyring() after this.
+ Does nothing if not initialized.
+ """
+ global updater
+ if updater is not None:
+ updater.deinit()
+ updater = None
More information about the bind10-changes
mailing list