BIND 10 trac1671, updated. dd510bf02a5429e6789c1895fdcd75f9d3e520cd Merge branch 'master' into trac1671
BIND 10 source code commits
bind10-changes at lists.isc.org
Tue Feb 28 09:49:53 UTC 2012
The branch, trac1671 has been updated
via dd510bf02a5429e6789c1895fdcd75f9d3e520cd (commit)
via 555c57669226bca31e961412fde174f04db876ab (commit)
via 04f9d8ebe54788a4f4f3aa7be7d50347a26565c0 (commit)
via 90e30a79b4fdfe52a70fbc72f9f84a8dd1968506 (commit)
via bb52e879ee7b68c162b5ad36648b9f2c0fc18f19 (commit)
via d125d7da8b990b238993f59353e8a11832785706 (commit)
via 3b8892835a95f7b17f93f5f3ca36c14da1a82363 (commit)
via ff013364643f9bfa736b2d23fec39ac35872d6ad (commit)
via c5ba45e293726f7642f6e081de16bc3b2daff98b (commit)
via 280742f34c2471024aebdf2736edf5570f1f3da2 (commit)
via 79e99b7285b93325862f80746d3a4d1b5938ca4d (commit)
via 5a7953933a49a0ddd4ee1feaddc908cd2285522d (commit)
via 064dd51b725493270dec9f1cd2c2b7164135ee6b (commit)
via d2b5da0a8146343bd6f72f3a145fc90be7888d18 (commit)
via 56b495e4bd8aeffed8642bfb6bb4802ba39a6277 (commit)
via 429c88cfe0483e0c67f4625481995c2510485792 (commit)
via ffd2c98603eb6db553d4cf18d2fd7ac29a62080f (commit)
via d0fc36b1522db78541bbcc560f5f05cbefd4c5bc (commit)
via 5ba4c6e1f349f890200322b2e2d2ff0e5b179944 (commit)
via cf00216570a36d2e3e688b197deea781bfbe7d8d (commit)
via e4b2c2633ebb3859286e9a4c19e97e17bcac41b3 (commit)
via 883ef5b0d7466d080916f98af4ea6a94258ca655 (commit)
via dad96d1c2aac267f2a11b9bdb7c83ce33c1a3f34 (commit)
via 6b3f5f252791b1c4b92589deaa3810e5e1eb867f (commit)
via a72ef46cca91d1718b2830e9d827040e6742d643 (commit)
via a11e3b2165821e9ca039196b12d70025845a5d22 (commit)
via fcbe40140a5e21ecf3ff078206afe1d68e945462 (commit)
via 07dd87ca1a14058c926497ec1c6ac61ffd3a41f2 (commit)
via 73b4d32109e8103c50f73dcd0c171930c3d9b322 (commit)
via cb1ca47492ba2c493f88a45725f91d1b53251023 (commit)
via 9d56a4097505b05e6a8e69ebb967a30416e39b41 (commit)
via 324be1acbdb7d432f4204d9cd6ed28e71a4fe7cf (commit)
via 766508d03d1304dc4d66b814a7a21d160b3404c2 (commit)
via a4789bb27d209a54652bc2d7c6bf0992613c18df (commit)
via bc4fc1865977c94d4ad6eddf0cf96a7881a71536 (commit)
via f43af5ae2a1a904d1fa091d227f6d5cb1c580fa3 (commit)
via ccc2fb769ced7cef416b55d9074591022b8a673b (commit)
via 69360a12e78e7567f88dcd3079c9a56be534ea7d (commit)
via 920106980b5a66212397f5f699f2aac9e6d69cbc (commit)
via c430386cb0a163ffabc477d864635b4a21f2ef59 (commit)
via 6ec73d5de3e72a8d6adbb7e3bc353b68584b344f (commit)
via d6d42bcdfcc80b450b89937ffd5ba39b071b3f37 (commit)
via 0c0161c6e555e2c8b33b3291043727bc07324dc2 (commit)
via ecd7dbe0f231c04d19eba3cd02ff513a23a9003a (commit)
via f472a687b70982f906bcc96900fdbc3ee8d36ff6 (commit)
via aa0318899f62fc3069572bb18bc6dd071bb8448d (commit)
via 5758984e174f25954af41dcf07979e2dfca12763 (commit)
via 5f4eb187db9895ceb2297711b6eef6fdcc520625 (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)
via a0f1ca23cd394e0d94845ecc08c35e56bf313b01 (commit)
via e494212d02fa6efcb437f966708992de8b02cb74 (commit)
from 0b2496f97e5253bc4b4dd0214c33a8f210a5655b (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 dd510bf02a5429e6789c1895fdcd75f9d3e520cd
Merge: 0b2496f97e5253bc4b4dd0214c33a8f210a5655b 555c57669226bca31e961412fde174f04db876ab
Author: Mukund Sivaraman <muks at banu.com>
Date: Tue Feb 28 15:19:27 2012 +0530
Merge branch 'master' into trac1671
-----------------------------------------------------------------------
Summary of changes:
configure.ac | 36 ++-
doc/guide/bind10-guide.xml | 18 +-
ext/asio/asio/detail/impl/kqueue_reactor.ipp | 2 +
ext/asio/asio/detail/impl/socket_ops.ipp | 36 ++--
src/bin/sockcreator/sockcreator.cc | 61 ++++-
src/bin/sockcreator/sockcreator.h | 15 +-
src/bin/sockcreator/tests/sockcreator_tests.cc | 81 ++++-
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/cppcheck-suppress.lst | 18 +-
src/lib/asiodns/Makefile.am | 1 +
src/lib/asiodns/dns_server.h | 16 -
src/lib/asiodns/sync_udp_server.cc | 216 +++++++++++++
src/lib/asiodns/sync_udp_server.h | 159 ++++++++++
src/lib/asiodns/tcp_server.h | 3 -
src/lib/asiodns/tests/dns_server_unittest.cc | 119 +++++---
src/lib/asiodns/udp_server.cc | 5 -
src/lib/asiodns/udp_server.h | 10 -
src/lib/config/module_spec.cc | 1 -
src/lib/datasrc/Makefile.am | 6 +-
src/lib/datasrc/datasrc_config.h.pre.in | 2 +-
src/lib/datasrc/memory_datasrc.cc | 1 -
src/lib/dhcp/iface_mgr.cc | 4 +-
src/lib/dns/python/message_python.cc | 3 +-
src/lib/dns/tests/name_unittest.cc | 4 +-
src/lib/dns/tests/rrttl_unittest.cc | 4 +-
src/lib/nsas/glue_hints.cc | 4 +-
src/lib/nsas/hash_table.h | 2 +-
src/lib/nsas/tests/nameserver_entry_unittest.cc | 4 +-
src/lib/nsas/zone_entry.cc | 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 ++++++++
.../multi_instance/multi_auth.config.orig | 1 +
tests/lettuce/features/multi_instance.feature | 35 +++
tests/lettuce/features/terrain/bind10_control.py | 92 ++++++
tests/lettuce/features/terrain/terrain.py | 8 +-
47 files changed, 1567 insertions(+), 297 deletions(-)
create mode 100644 src/lib/asiodns/sync_udp_server.cc
create mode 100644 src/lib/asiodns/sync_udp_server.h
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
create mode 100644 tests/lettuce/configurations/multi_instance/multi_auth.config.orig
create mode 100644 tests/lettuce/features/multi_instance.feature
-----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index 1a0ec6a..af8c2fe 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"
@@ -294,7 +295,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)
@@ -534,21 +535,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=
@@ -1018,6 +1020,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/ext/asio/asio/detail/impl/kqueue_reactor.ipp b/ext/asio/asio/detail/impl/kqueue_reactor.ipp
index 8db77cb..00ebfeb 100644
--- a/ext/asio/asio/detail/impl/kqueue_reactor.ipp
+++ b/ext/asio/asio/detail/impl/kqueue_reactor.ipp
@@ -301,12 +301,14 @@ void kqueue_reactor::run(bool block, op_queue<operation>& ops)
EV_ADD | EV_ONESHOT, EV_OOBAND, 0, descriptor_data);
else
continue;
+ break;
case EVFILT_WRITE:
if (!descriptor_data->op_queue_[write_op].empty())
ASIO_KQUEUE_EV_SET(&event, descriptor, EVFILT_WRITE,
EV_ADD | EV_ONESHOT, 0, 0, descriptor_data);
else
continue;
+ break;
default:
break;
}
diff --git a/ext/asio/asio/detail/impl/socket_ops.ipp b/ext/asio/asio/detail/impl/socket_ops.ipp
index 66006ea..a1b0ec6 100644
--- a/ext/asio/asio/detail/impl/socket_ops.ipp
+++ b/ext/asio/asio/detail/impl/socket_ops.ipp
@@ -282,15 +282,26 @@ int close(socket_type s, state_type& state,
int result = 0;
if (s != invalid_socket)
{
-#if defined(BOOST_WINDOWS) || defined(__CYGWIN__)
- if ((state & non_blocking) && (state & user_set_linger))
+ if (destruction && (state & user_set_linger))
{
- ioctl_arg_type arg = 0;
- ::ioctlsocket(s, FIONBIO, &arg);
- state &= ~non_blocking;
+ ::linger opt;
+ opt.l_onoff = 0;
+ opt.l_linger = 0;
+ asio::error_code ignored_ec;
+ socket_ops::setsockopt(s, state, SOL_SOCKET,
+ SO_LINGER, &opt, sizeof(opt), ignored_ec);
}
+
+ clear_last_error();
+#if defined(BOOST_WINDOWS) || defined(__CYGWIN__)
+ result = error_wrapper(::closesocket(s), ec);
#else // defined(BOOST_WINDOWS) || defined(__CYGWIN__)
- if (state & non_blocking)
+ result = error_wrapper(::close(s), ec);
+#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__)
+
+ if (result != 0
+ && (ec == asio::error::would_block
+ || ec == asio::error::try_again))
{
#if defined(__SYMBIAN32__)
int flags = ::fcntl(s, F_GETFL, 0);
@@ -301,18 +312,6 @@ int close(socket_type s, state_type& state,
::ioctl(s, FIONBIO, &arg);
#endif // defined(__SYMBIAN32__)
state &= ~non_blocking;
- }
-#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__)
-
- if (destruction && (state & user_set_linger))
- {
- ::linger opt;
- opt.l_onoff = 0;
- opt.l_linger = 0;
- asio::error_code ignored_ec;
- socket_ops::setsockopt(s, state, SOL_SOCKET,
- SO_LINGER, &opt, sizeof(opt), ignored_ec);
- }
clear_last_error();
#if defined(BOOST_WINDOWS) || defined(__CYGWIN__)
@@ -320,6 +319,7 @@ int close(socket_type s, state_type& state,
#else // defined(BOOST_WINDOWS) || defined(__CYGWIN__)
result = error_wrapper(::close(s), ec);
#endif // defined(BOOST_WINDOWS) || defined(__CYGWIN__)
+ }
}
if (result == 0)
diff --git a/src/bin/sockcreator/sockcreator.cc b/src/bin/sockcreator/sockcreator.cc
index d7fe1f8..167e3f0 100644
--- a/src/bin/sockcreator/sockcreator.cc
+++ b/src/bin/sockcreator/sockcreator.cc
@@ -157,7 +157,7 @@ handleRequest(const int input_fd, const int output_fd,
}
// Obtain the socket
- const int result = get_sock(sock_type, addr, addr_len);
+ const int result = get_sock(sock_type, addr, addr_len, close_fun);
if (result >= 0) {
// Got the socket, send it to the client.
writeMessage(output_fd, "S", 1);
@@ -186,6 +186,52 @@ handleRequest(const int input_fd, const int output_fd,
}
}
+// Sets the MTU related flags for IPv6 UDP sockets.
+// It is borrowed from bind-9 lib/isc/unix/socket.c and modified
+// to compile here.
+//
+// The function returns -2 if it fails or the socket file descriptor
+// on success (for convenience, so the result can be just returned).
+int
+mtu(int fd) {
+#ifdef IPV6_USE_MIN_MTU /* RFC 3542, not too common yet*/
+ const int on(1);
+ // use minimum MTU
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_USE_MIN_MTU, &on, sizeof(on)) < 0) {
+ return (-2);
+ }
+#else // Try the following as fallback
+#ifdef IPV6_MTU
+ // Use minimum MTU on systems that don't have the IPV6_USE_MIN_MTU
+ const int mtu = 1280;
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_MTU, &mtu, sizeof(mtu)) < 0) {
+ return (-2);
+ }
+#endif
+#if defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_DONT)
+ // Turn off Path MTU discovery on IPv6/UDP sockets.
+ const int action = IPV6_PMTUDISC_DONT;
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &action,
+ sizeof(action)) < 0) {
+
+ return (-2);
+ }
+#endif
+#endif
+ return (fd);
+}
+
+// This one closes the socket if result is negative. Used not to leak socket
+// on error.
+int maybeClose(const int result, const int socket, const close_t close_fun) {
+ if (result < 0) {
+ if (close_fun(socket) == -1) {
+ isc_throw(InternalError, "Error closing socket");
+ }
+ }
+ return (result);
+}
+
} // Anonymous namespace
namespace isc {
@@ -193,7 +239,8 @@ namespace socket_creator {
// Get the socket and bind to it.
int
-getSock(const int type, struct sockaddr* bind_addr, const socklen_t addr_len) {
+getSock(const int type, struct sockaddr* bind_addr, const socklen_t addr_len,
+ const close_t close_fun) {
const int sock = socket(bind_addr->sa_family, type, 0);
if (sock == -1) {
return (-1);
@@ -201,15 +248,19 @@ getSock(const int type, struct sockaddr* bind_addr, const socklen_t addr_len) {
const int on = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
// This is part of the binding process, so it's a bind error
- return (-2);
+ return (maybeClose(-2, sock, close_fun));
}
if (bind_addr->sa_family == AF_INET6 &&
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) == -1) {
// This is part of the binding process, so it's a bind error
- return (-2);
+ return (maybeClose(-2, sock, close_fun));
}
if (bind(sock, bind_addr, addr_len) == -1) {
- return (-2);
+ return (maybeClose(-2, sock, close_fun));
+ }
+ if (type == SOCK_DGRAM && bind_addr->sa_family == AF_INET6) {
+ // Set some MTU flags on IPv6 UDP sockets.
+ return (maybeClose(mtu(sock), sock, close_fun));
}
return (sock);
}
diff --git a/src/bin/sockcreator/sockcreator.h b/src/bin/sockcreator/sockcreator.h
index 012d8c3..e5d4783 100644
--- a/src/bin/sockcreator/sockcreator.h
+++ b/src/bin/sockcreator/sockcreator.h
@@ -73,6 +73,9 @@ public:
};
+// Type of the close() function, so it can be passed as a parameter.
+// Argument is the same as that for close(2).
+typedef int (*close_t)(int);
/// \short Create a socket and bind it.
///
@@ -82,13 +85,16 @@ public:
/// \param type The type of socket to create (SOCK_STREAM, SOCK_DGRAM, etc).
/// \param bind_addr The address to bind.
/// \param addr_len The actual length of bind_addr.
+/// \param close_fun The furction used to close a socket if there's an error
+/// after the creation.
///
/// \return The file descriptor of the newly created socket, if everything
/// goes well. A negative number is returned if an error occurs -
/// -1 if the socket() call fails or -2 if bind() fails. In case of
/// error, errno is set (or left intact from socket() or bind()).
int
-getSock(const int type, struct sockaddr* bind_addr, const socklen_t addr_len);
+getSock(const int type, struct sockaddr* bind_addr, const socklen_t addr_len,
+ const close_t close_fun);
// Define some types for functions used to perform socket-related operations.
// These are typedefed so that alternatives can be passed through to the
@@ -96,16 +102,13 @@ getSock(const int type, struct sockaddr* bind_addr, const socklen_t addr_len);
// Type of the function to get a socket and to pass it as parameter.
// Arguments are those described above for getSock().
-typedef int (*get_sock_t)(const int, struct sockaddr *, const socklen_t);
+typedef int (*get_sock_t)(const int, struct sockaddr *, const socklen_t,
+ const close_t close_fun);
// Type of the send_fd() function, so it can be passed as a parameter.
// Arguments are the same as those of the send_fd() function.
typedef int (*send_fd_t)(const int, const int);
-// Type of the close() function, so it can be passed as a parameter.
-// Argument is the same as that for close(2).
-typedef int (*close_t)(int);
-
/// \brief Infinite loop parsing commands and returning the sockets.
///
diff --git a/src/bin/sockcreator/tests/sockcreator_tests.cc b/src/bin/sockcreator/tests/sockcreator_tests.cc
index eccc3ed..9604567 100644
--- a/src/bin/sockcreator/tests/sockcreator_tests.cc
+++ b/src/bin/sockcreator/tests/sockcreator_tests.cc
@@ -22,6 +22,7 @@
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
+#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
@@ -105,17 +106,47 @@ typedef void (*socket_check_t)(const int);
// The other argument is the socket descriptor number.
// IPv4 check
-void addressFamilySpecificCheck(const sockaddr_in*, const int) {
+void addressFamilySpecificCheck(const sockaddr_in*, const int, const int) {
}
// IPv6 check
-void addressFamilySpecificCheck(const sockaddr_in6*, const int socknum) {
+void addressFamilySpecificCheck(const sockaddr_in6*, const int socknum,
+ const int socket_type)
+{
int options;
socklen_t len = sizeof(options);
- EXPECT_EQ(0, getsockopt(socknum, IPPROTO_IPV6, IPV6_V6ONLY, &options, &len));
+ EXPECT_EQ(0, getsockopt(socknum, IPPROTO_IPV6, IPV6_V6ONLY, &options,
+ &len));
EXPECT_NE(0, options);
+ if (socket_type == SOCK_DGRAM) {
+ // Some more checks for UDP - MTU
+#ifdef IPV6_USE_MIN_MTU /* RFC 3542, not too common yet*/
+ // use minimum MTU
+ EXPECT_EQ(0, getsockopt(socknum, IPPROTO_IPV6, IPV6_USE_MIN_MTU,
+ &options, &len)) << strerror(errno);
+ EXPECT_NE(0, options);
+#else
+ // We do not check for the IPV6_MTU, because while setting works (eg.
+ // the packets are fragmented correctly), the getting does not. If
+ // we try to getsockopt it, an error complaining it can't be accessed
+ // on unconnected socket is returned. If we try to connect it, the
+ // MTU of the interface is returned, not the one we set. So we live
+ // in belief it works because we examined the packet dump.
+#if defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_DONT)
+ // Turned off Path MTU discovery on IPv6/UDP sockets?
+ EXPECT_EQ(0, getsockopt(socknum, IPPROTO_IPV6, IPV6_MTU_DISCOVER,
+ &options, &len)) << strerror(errno);
+ EXPECT_EQ(IPV6_PMTUDISC_DONT, options);
+#endif
+#endif
+ }
}
+// Just ignore the fd and pretend success. We close invalid fds in the tests.
+int
+closeIgnore(int) {
+ return (0);
+}
// Generic version of the socket test. It creates the socket and checks that
// it is a valid descriptor. The family-specific check functions are called
@@ -133,7 +164,8 @@ void testAnyCreate(int socket_type, socket_check_t socket_check) {
memset(&addr, 0, sizeof(addr));
setAddressFamilyFields(&addr);
sockaddr* addr_ptr = reinterpret_cast<sockaddr*>(&addr);
- const int socket = getSock(socket_type, addr_ptr, sizeof(addr));
+ const int socket = getSock(socket_type, addr_ptr, sizeof(addr),
+ closeIgnore);
ASSERT_GE(socket, 0) << "Couldn't create socket: failed with " <<
"return code " << socket << " and error " << strerror(errno);
@@ -147,7 +179,7 @@ void testAnyCreate(int socket_type, socket_check_t socket_check) {
EXPECT_NE(0, options);
// ...and the address-family specific tests.
- addressFamilySpecificCheck(&addr, socket);
+ addressFamilySpecificCheck(&addr, socket, socket_type);
// Tidy up and exit.
EXPECT_EQ(0, close(socket));
@@ -171,12 +203,40 @@ TEST(get_sock, tcp6_create) {
testAnyCreate<sockaddr_in6>(SOCK_STREAM, tcpCheck);
}
+bool close_called(false);
+
+// You can use it as a close mockup. If you care about checking if it was really
+// called, you can use the close_called variable. But set it to false before the
+// test.
+int closeCall(int socket) {
+ close(socket);
+ close_called = true;
+ return (0);
+}
+
// Ask the get_sock function for some nonsense and test if it is able to report
// an error.
TEST(get_sock, fail_with_nonsense) {
sockaddr addr;
memset(&addr, 0, sizeof(addr));
- ASSERT_LT(getSock(0, &addr, sizeof addr), 0);
+ close_called = false;
+ ASSERT_EQ(-1, getSock(0, &addr, sizeof addr, closeCall));
+ ASSERT_FALSE(close_called); // The "socket" call should have failed already
+}
+
+// Bind should have failed here
+TEST(get_sock, fail_with_bind) {
+ sockaddr_in addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = 1;
+ // No host should have this address on the interface, so it should not be
+ // possible to bind it.
+ addr.sin_addr.s_addr = inet_addr("192.0.2.1");
+ close_called = false;
+ ASSERT_EQ(-2, getSock(SOCK_STREAM, reinterpret_cast<sockaddr*>(&addr),
+ sizeof addr, closeCall));
+ ASSERT_TRUE(close_called); // The "socket" call should have failed already
}
// The main run() function in the socket creator takes three functions to
@@ -198,7 +258,8 @@ TEST(get_sock, fail_with_nonsense) {
// -1: The simulated bind() call has failed
// -2: The simulated socket() call has failed
int
-getSockDummy(const int type, struct sockaddr* addr, const socklen_t) {
+getSockDummy(const int type, struct sockaddr* addr, const socklen_t,
+ const close_t) {
int result = 0;
int port = 0;
@@ -253,12 +314,6 @@ send_FdDummy(const int destination, const int what) {
return (status ? 0 : -1);
}
-// Just ignore the fd and pretend success. We close invalid fds in the tests.
-int
-closeIgnore(int) {
- return (0);
-}
-
// Generic test that it works, with various inputs and outputs.
// It uses different functions to create the socket and send it and pass
// data to it and check it returns correct data back, to see if the run()
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/cppcheck-suppress.lst b/src/cppcheck-suppress.lst
index 1020ffe..56b2d34 100644
--- a/src/cppcheck-suppress.lst
+++ b/src/cppcheck-suppress.lst
@@ -4,8 +4,16 @@ debug
missingInclude
// This is a template, and should be excluded from the check
unreadVariable:src/lib/dns/rdata/template.cc:61
-// Intentional self assignment tests. Suppress warning about them.
-selfAssignment:src/lib/dns/tests/name_unittest.cc:293
-selfAssignment:src/lib/dns/tests/rdata_unittest.cc:228
-selfAssignment:src/lib/dns/tests/tsigkey_unittest.cc:137
-selfAssignment:src/lib/dns/tests/rdata_txt_like_unittest.cc:222
+
+// Intentional self-comparisons
+duplicateExpression:src/lib/dns/tests/name_unittest.cc:569
+duplicateExpression:src/lib/dns/tests/name_unittest.cc:580
+duplicateExpression:src/lib/dns/tests/rrttl_unittest.cc:164
+duplicateExpression:src/lib/dns/tests/rrttl_unittest.cc:175
+
+// Intentional self-comparisons
+uselessCallsCompare:src/lib/dns/tests/rdata_dhcid_unittest.cc:96
+uselessCallsCompare:src/lib/dns/tests/rdata_in_a_unittest.cc:98
+uselessCallsCompare:src/lib/dns/tests/rdata_in_aaaa_unittest.cc:94
+uselessCallsCompare:src/lib/dns/tests/rdata_mx_unittest.cc:104
+uselessCallsCompare:src/lib/dns/tests/rdata_unittest.cc:254
diff --git a/src/lib/asiodns/Makefile.am b/src/lib/asiodns/Makefile.am
index b5d030d..a03f147 100644
--- a/src/lib/asiodns/Makefile.am
+++ b/src/lib/asiodns/Makefile.am
@@ -24,6 +24,7 @@ libasiodns_la_SOURCES += dns_server.h
libasiodns_la_SOURCES += dns_service.cc dns_service.h
libasiodns_la_SOURCES += tcp_server.cc tcp_server.h
libasiodns_la_SOURCES += udp_server.cc udp_server.h
+libasiodns_la_SOURCES += sync_udp_server.cc sync_udp_server.h
libasiodns_la_SOURCES += io_fetch.cc io_fetch.h
libasiodns_la_SOURCES += logger.h logger.cc
diff --git a/src/lib/asiodns/dns_server.h b/src/lib/asiodns/dns_server.h
index d3a8528..119aa66 100644
--- a/src/lib/asiodns/dns_server.h
+++ b/src/lib/asiodns/dns_server.h
@@ -88,22 +88,6 @@ public:
/// to return.
virtual void resume(const bool done) { self_->resume(done); }
- /// \brief Indicate whether the server is able to send an answer
- /// to a query.
- ///
- /// This is presently used only for testing purposes.
- virtual bool hasAnswer() { return (self_->hasAnswer()); }
-
- /// \brief Returns the current value of the 'coroutine' object
- ///
- /// This is a temporary method, intended to be used for debugging
- /// purposes during development and removed later. It allows
- /// callers from outside the coroutine object to retrieve information
- /// about its current state.
- ///
- /// \return The value of the 'coroutine' object
- virtual int value() { return (self_->value()); }
-
/// \brief Returns a pointer to a clone of this DNSServer object.
///
/// When a \c DNSServer object is copied or assigned, the result will
diff --git a/src/lib/asiodns/sync_udp_server.cc b/src/lib/asiodns/sync_udp_server.cc
new file mode 100644
index 0000000..b60315f
--- /dev/null
+++ b/src/lib/asiodns/sync_udp_server.cc
@@ -0,0 +1,216 @@
+// 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.
+
+#include <config.h>
+
+#include <asio.hpp>
+#include <asio/error.hpp>
+
+#include "sync_udp_server.h"
+#include "logger.h"
+
+#include <asiolink/dummy_io_cb.h>
+#include <asiolink/udp_endpoint.h>
+#include <asiolink/udp_socket.h>
+
+#include <boost/bind.hpp>
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <unistd.h> // for some IPC/network system calls
+#include <errno.h>
+
+using namespace std;
+using namespace boost;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace asiodns {
+
+SyncUDPServer::SyncUDPServer(asio::io_service& io_service,
+ const asio::ip::address& addr,
+ const uint16_t port,
+ asiolink::SimpleCallback* checkin,
+ DNSLookup* lookup, DNSAnswer* answer) :
+ output_buffer_(new isc::util::OutputBuffer(0)),
+ query_(new isc::dns::Message(isc::dns::Message::PARSE)),
+ answer_(new isc::dns::Message(isc::dns::Message::RENDER)),
+ io_(io_service), checkin_callback_(checkin), lookup_callback_(lookup),
+ answer_callback_(answer), stopped_(false)
+{
+ // We must use different instantiations for v4 and v6;
+ // otherwise ASIO will bind to both
+ asio::ip::udp proto = addr.is_v4() ? asio::ip::udp::v4() :
+ asio::ip::udp::v6();
+ socket_.reset(new asio::ip::udp::socket(io_service, proto));
+ socket_->set_option(asio::socket_base::reuse_address(true));
+ if (addr.is_v6()) {
+ socket_->set_option(asio::ip::v6_only(true));
+ }
+ socket_->bind(asio::ip::udp::endpoint(addr, port));
+}
+
+SyncUDPServer::SyncUDPServer(asio::io_service& io_service, const int fd,
+ const int af, asiolink::SimpleCallback* checkin,
+ DNSLookup* lookup, DNSAnswer* answer) :
+ output_buffer_(new isc::util::OutputBuffer(0)),
+ query_(new isc::dns::Message(isc::dns::Message::PARSE)),
+ answer_(new isc::dns::Message(isc::dns::Message::RENDER)),
+ io_(io_service), checkin_callback_(checkin), lookup_callback_(lookup),
+ answer_callback_(answer), stopped_(false)
+{
+ if (af != AF_INET && af != AF_INET6) {
+ isc_throw(InvalidParameter, "Address family must be either AF_INET "
+ "or AF_INET6, not " << af);
+ }
+ LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, ASIODNS_FD_ADD_UDP).arg(fd);
+ try {
+ socket_.reset(new asio::ip::udp::socket(io_service));
+ socket_->assign(af == AF_INET6 ? asio::ip::udp::v6() :
+ asio::ip::udp::v4(), fd);
+ } catch (const std::exception& exception) {
+ // Whatever the thing throws, it is something from ASIO and we
+ // convert it
+ isc_throw(IOError, exception.what());
+ }
+}
+
+void
+SyncUDPServer::scheduleRead() {
+ socket_->async_receive_from(asio::buffer(data_, MAX_LENGTH), sender_,
+ boost::bind(&SyncUDPServer::handleRead, this,
+ _1, _2));
+}
+
+void
+SyncUDPServer::handleRead(const asio::error_code& ec, const size_t length) {
+ // Abort on fatal errors
+ if (ec) {
+ using namespace asio::error;
+ if (ec.value() != would_block && ec.value() != try_again &&
+ ec.value() != interrupted) {
+ return;
+ }
+ }
+ // Some kind of interrupt, spurious wakeup, or like that. Just try reading
+ // again.
+ if (ec || length == 0) {
+ scheduleRead();
+ return;
+ }
+ // OK, we have a real packet of data. Let's dig into it!
+
+ // XXX: This is taken (and ported) from UDPSocket class. What the hell does
+ // it really mean?
+
+ // The UDP socket class has been extended with asynchronous functions
+ // and takes as a template parameter a completion callback class. As
+ // UDPServer does not use these extended functions (only those defined
+ // in the IOSocket base class) - but needs a UDPSocket to get hold of
+ // the underlying Boost UDP socket - DummyIOCallback is used. This
+ // provides the appropriate operator() but is otherwise functionless.
+ UDPSocket<DummyIOCallback> socket(*socket_);
+ UDPEndpoint endpoint(sender_);
+ IOMessage message(data_, length, socket, endpoint);
+ if (checkin_callback_ != NULL) {
+ (*checkin_callback_)(message);
+ if (stopped_) {
+ return;
+ }
+ }
+
+ // If we don't have a DNS Lookup provider, there's no point in
+ // continuing; we exit the coroutine permanently.
+ if (lookup_callback_ == NULL) {
+ scheduleRead();
+ return;
+ }
+
+ // Make sure the buffers are fresh
+ output_buffer_->clear();
+ query_->clear(isc::dns::Message::PARSE);
+ answer_->clear(isc::dns::Message::RENDER);
+
+ // Mark that we don't have an answer yet.
+ done_ = false;
+ resume_called_ = false;
+
+ // Call the actual lookup
+ (*lookup_callback_)(message, query_, answer_, output_buffer_, this);
+
+ if (!resume_called_) {
+ isc_throw(isc::Unexpected,
+ "No resume called from the lookup callback");
+ }
+
+ if (stopped_) {
+ return;
+ }
+
+ if (done_) {
+ // Good, there's an answer.
+ // Call the answer callback to render it.
+ (*answer_callback_)(message, query_, answer_, output_buffer_);
+
+ if (stopped_) {
+ return;
+ }
+
+ socket_->send_to(asio::buffer(output_buffer_->getData(),
+ output_buffer_->getLength()),
+ sender_);
+ }
+
+ // And schedule handling another socket.
+ scheduleRead();
+}
+
+void
+SyncUDPServer::operator()(asio::error_code, size_t) {
+ // To start the server, we just schedule reading of data when they
+ // arrive.
+ scheduleRead();
+}
+
+/// Stop the UDPServer
+void
+SyncUDPServer::stop() {
+ /// Using close instead of cancel, because cancel
+ /// will only cancel the asynchornized event already submitted
+ /// to io service, the events post to io service after
+ /// cancel still can be scheduled by io service, if
+ /// the socket is cloesed, all the asynchronized event
+ /// for it won't be scheduled by io service not matter it is
+ /// submit to io serice before or after close call. And we will
+ //. get bad_descriptor error
+ socket_->close();
+ stopped_ = true;
+}
+
+/// Post this coroutine on the ASIO service queue so that it will
+/// resume processing where it left off. The 'done' parameter indicates
+/// whether there is an answer to return to the client.
+void
+SyncUDPServer::resume(const bool done) {
+ resume_called_ = true;
+ done_ = done;
+}
+
+bool
+SyncUDPServer::hasAnswer() {
+ return (done_);
+}
+
+} // namespace asiodns
+} // namespace isc
diff --git a/src/lib/asiodns/sync_udp_server.h b/src/lib/asiodns/sync_udp_server.h
new file mode 100644
index 0000000..85a9e37
--- /dev/null
+++ b/src/lib/asiodns/sync_udp_server.h
@@ -0,0 +1,159 @@
+// 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.
+
+#ifndef __SYNC_UDP_SERVER_H
+#define __SYNC_UDP_SERVER_H 1
+
+#ifndef ASIO_HPP
+#error "asio.hpp must be included before including this, see asiolink.h as to why"
+#endif
+
+#include "dns_answer.h"
+#include "dns_lookup.h"
+#include "dns_server.h"
+
+#include <dns/message.h>
+#include <asiolink/simple_callback.h>
+#include <util/buffer.h>
+#include <exceptions.h>
+
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace asiodns {
+
+/// \brief An UDP server that doesn't asynchronous lookup handlers.
+///
+/// That means, the lookup handler must provide the answer right away.
+/// This allows for implementation with less overhead, compared with
+/// the UDPClass.
+class SyncUDPServer : public DNSServer, public boost::noncopyable {
+public:
+ /// \brief Constructor
+ /// \param io_service the asio::io_service to work with
+ /// \param addr the IP address to listen for queries on
+ /// \param port the port to listen for queries on
+ /// \param checkin the callbackprovider for non-DNS events
+ /// \param lookup the callbackprovider for DNS lookup events
+ /// \param answer the callbackprovider for DNS answer events
+ explicit SyncUDPServer(asio::io_service& io_service,
+ const asio::ip::address& addr, const uint16_t port,
+ isc::asiolink::SimpleCallback* checkin = NULL,
+ DNSLookup* lookup = NULL,
+ DNSAnswer* answer = NULL);
+
+ /// \brief Constructor
+ /// \param io_service the asio::io_service to work with
+ /// \param fd the file descriptor of opened UDP socket
+ /// \param af address family, either AF_INET or AF_INET6
+ /// \param checkin the callbackprovider for non-DNS events
+ /// \param lookup the callbackprovider for DNS lookup events
+ /// \param answer the callbackprovider for DNS answer events
+ /// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6
+ /// \throw isc::asiolink::IOError when a low-level error happens, like the
+ /// fd is not a valid descriptor.
+ SyncUDPServer(asio::io_service& io_service, const int fd, const int af,
+ isc::asiolink::SimpleCallback* checkin = NULL,
+ DNSLookup* lookup = NULL, DNSAnswer* answer = NULL);
+
+ /// \brief Start the SyncUDPServer.
+ ///
+ /// This is the function operator to keep interface with other server
+ /// classes. They need that because they're coroutines.
+ virtual void operator()(asio::error_code ec = asio::error_code(),
+ size_t length = 0);
+
+ /// \brief Calls the lookup callback
+ virtual void asyncLookup() {
+ isc_throw(Unexpected,
+ "SyncUDPServer doesn't support asyncLookup by design, use "
+ "UDPServer if you need it.");
+ }
+
+ /// \brief Stop the running server
+ /// \note once the server stopped, it can't restart
+ virtual void stop();
+
+ /// \brief Resume operation
+ ///
+ /// Note that unlike other servers, this one expects it to be called
+ /// directly from the lookup callback. If it isn't, the server will
+ /// throw an Unexpected exception (probably to the event loop, which
+ /// would usually lead to termination of the program, but that's OK,
+ /// as it would be serious programmer error).
+ ///
+ /// \param done Set this to true if the lookup action is done and
+ /// we have an answer
+ virtual void resume(const bool done);
+
+ /// \brief Check if we have an answer
+ ///
+ /// \return true if we have an answer
+ virtual bool hasAnswer();
+
+ /// \brief Clones the object
+ ///
+ /// Since cloning is for the use of coroutines, the synchronous UDP server
+ /// does not need to be cloned. Therefore supporting it would be needless
+ /// work, and trying to clone it would be a programmer error anyway, this
+ /// throws Unexpected.
+ ///
+ /// \return a newly allocated copy of this object
+ virtual DNSServer* clone() {
+ isc_throw(Unexpected, "SyncUDPServer can't be cloned.");
+ }
+private:
+ // Internal state & buffers. We don't use the PIMPL idiom, as this class
+ // isn't usually used directly anyway.
+
+ // Maximum size of incoming UDP packet
+ static const size_t MAX_LENGTH = 4096;
+ // Buffer for incoming data
+ uint8_t data_[MAX_LENGTH];
+ // The buffer to render the output to and send it.
+ // If it was OK to have just a buffer, not the wrapper class,
+ // we could reuse the data_
+ isc::util::OutputBufferPtr output_buffer_;
+ // Objects to hold the query message and the answer
+ isc::dns::MessagePtr query_, answer_;
+ // The socket used for the communication
+ std::auto_ptr<asio::ip::udp::socket> socket_;
+ // The event loop we use
+ asio::io_service& io_;
+ // Place the socket puts the sender of a packet when it is received
+ asio::ip::udp::endpoint sender_;
+ // Callbacks
+ const asiolink::SimpleCallback* checkin_callback_;
+ const DNSLookup* lookup_callback_;
+ const DNSAnswer* answer_callback_;
+ // Answers from the lookup callback (not sent directly, but signalled
+ // through resume()
+ bool resume_called_, done_;
+ // This turns true when the server stops. Allows for not sending the
+ // answer after we closed the socket.
+ bool stopped_;
+
+ // Auxiliary functions
+
+ // Schedule next read on the socket. Just a wrapper around
+ // socket_->async_read_from with the correct parameters.
+ void scheduleRead();
+ // Callback from the socket's read call (called when there's an error or
+ // when a new packet comes).
+ void handleRead(const asio::error_code& ec, const size_t length);
+};
+
+} // namespace asiodns
+} // namespace isc
+#endif // __SYNC_UDP_SERVER_H
diff --git a/src/lib/asiodns/tcp_server.h b/src/lib/asiodns/tcp_server.h
index d079e97..a75fddb 100644
--- a/src/lib/asiodns/tcp_server.h
+++ b/src/lib/asiodns/tcp_server.h
@@ -62,9 +62,6 @@ public:
void asyncLookup();
void stop();
void resume(const bool done);
- bool hasAnswer() { return (done_); }
- int value() { return (get_value()); }
-
DNSServer* clone() {
TCPServer* s = new TCPServer(*this);
return (s);
diff --git a/src/lib/asiodns/tests/dns_server_unittest.cc b/src/lib/asiodns/tests/dns_server_unittest.cc
index c9bbe7c..0064bba 100644
--- a/src/lib/asiodns/tests/dns_server_unittest.cc
+++ b/src/lib/asiodns/tests/dns_server_unittest.cc
@@ -19,6 +19,7 @@
#include <asiolink/io_endpoint.h>
#include <asiolink/io_error.h>
#include <asiodns/udp_server.h>
+#include <asiodns/sync_udp_server.h>
#include <asiodns/tcp_server.h>
#include <asiodns/dns_answer.h>
#include <asiodns/dns_lookup.h>
@@ -112,15 +113,22 @@ class DummyChecker : public SimpleCallback, public ServerStopper {
// \brief no lookup logic at all,just provide a checkpoint to stop the server
class DummyLookup : public DNSLookup, public ServerStopper {
- public:
- void operator()(const IOMessage& io_message,
- isc::dns::MessagePtr message,
- isc::dns::MessagePtr answer_message,
- isc::util::OutputBufferPtr buffer,
- DNSServer* server) const {
- stopServer();
+public:
+ DummyLookup() :
+ allow_resume_(true)
+ { }
+ void operator()(const IOMessage& io_message,
+ isc::dns::MessagePtr message,
+ isc::dns::MessagePtr answer_message,
+ isc::util::OutputBufferPtr buffer,
+ DNSServer* server) const {
+ stopServer();
+ if (allow_resume_) {
server->resume(true);
}
+ }
+ // If you want it not to call resume, set this to false
+ bool allow_resume_;
};
// \brief copy the data received from user to the answer part
@@ -314,10 +322,11 @@ class TCPClient : public SimpleClient {
// two servers, UDP client will only communicate with UDP server, same for TCP
// client
//
-// This is only the active part of the test. We run the test case twice, once
+// This is only the active part of the test. We run the test case four times, once
// for each type of initialization (once when giving it the address and port,
-// once when giving the file descriptor), to ensure it works both ways exactly
-// the same.
+// once when giving the file descriptor) multiplied by once for each type of UDP
+// server (UDPServer and SyncUDPServer), to ensure it works exactly the same.
+template<class UDPServerClass>
class DNSServerTestBase : public::testing::Test {
protected:
DNSServerTestBase() :
@@ -396,7 +405,7 @@ class DNSServerTestBase : public::testing::Test {
SimpleAnswer* const answer_;
UDPClient* const udp_client_;
TCPClient* const tcp_client_;
- UDPServer* udp_server_;
+ UDPServerClass* udp_server_;
TCPServer* tcp_server_;
// To access them in signal handle function, the following
@@ -406,18 +415,23 @@ class DNSServerTestBase : public::testing::Test {
};
// Initialization with name and port
-class AddrPortInit : public DNSServerTestBase {
+template<class UDPServerClass>
+class AddrPortInit : public DNSServerTestBase<UDPServerClass> {
protected:
AddrPortInit() {
- udp_server_ = new UDPServer(service, server_address_, server_port,
- checker_, lookup_, answer_);
- tcp_server_ = new TCPServer(service, server_address_, server_port,
- checker_, lookup_, answer_);
+ this->udp_server_ = new UDPServerClass(this->service,
+ this->server_address_,
+ server_port, this->checker_,
+ this->lookup_, this->answer_);
+ this->tcp_server_ = new TCPServer(this->service, this->server_address_,
+ server_port, this->checker_,
+ this->lookup_, this->answer_);
}
};
// Initialization by the file descriptor
-class FdInit : public DNSServerTestBase {
+template<class UDPServerClass>
+class FdInit : public DNSServerTestBase<UDPServerClass> {
private:
// Opens the file descriptor for us
// It uses the low-level C api, as it seems to be the easiest way to get
@@ -465,12 +479,14 @@ protected:
void SetUp() {
const int fdUDP(getFd(SOCK_DGRAM));
ASSERT_NE(-1, fdUDP) << strerror(errno);
- udp_server_ = new UDPServer(service, fdUDP, AF_INET6, checker_,
- lookup_, answer_);
+ this->udp_server_ = new UDPServerClass(this->service, fdUDP, AF_INET6,
+ this->checker_, this->lookup_,
+ this->answer_);
const int fdTCP(getFd(SOCK_STREAM));
ASSERT_NE(-1, fdTCP) << strerror(errno);
- tcp_server_ = new TCPServer(service, fdTCP, AF_INET6, checker_,
- lookup_, answer_);
+ this->tcp_server_ = new TCPServer(this->service, fdTCP, AF_INET6,
+ this->checker_, this->lookup_,
+ this->answer_);
}
};
@@ -478,11 +494,24 @@ protected:
template<class Parent>
class DNSServerTest : public Parent { };
-typedef ::testing::Types<AddrPortInit, FdInit> ServerTypes;
+typedef ::testing::Types<AddrPortInit<UDPServer>, AddrPortInit<SyncUDPServer>,
+ FdInit<UDPServer>, FdInit<SyncUDPServer> >
+ ServerTypes;
TYPED_TEST_CASE(DNSServerTest, ServerTypes);
-bool DNSServerTestBase::io_service_is_time_out = false;
-asio::io_service* DNSServerTestBase::current_service(NULL);
+typedef ::testing::Types<UDPServer, SyncUDPServer> UDPServerTypes;
+TYPED_TEST_CASE(DNSServerTestBase, UDPServerTypes);
+
+template<class UDPServerClass>
+bool DNSServerTestBase<UDPServerClass>::io_service_is_time_out = false;
+template<class UDPServerClass>
+asio::io_service* DNSServerTestBase<UDPServerClass>::current_service(NULL);
+
+typedef ::testing::Types<AddrPortInit<SyncUDPServer>, FdInit<SyncUDPServer> >
+ SyncTypes;
+template<class Parent>
+class SyncServerTest : public Parent { };
+TYPED_TEST_CASE(SyncServerTest, SyncTypes);
// Test whether server stopped successfully after client get response
// client will send query and start to wait for response, once client
@@ -608,17 +637,20 @@ TYPED_TEST(DNSServerTest, stopTCPServeMoreThanOnce) {
}
// It raises an exception when invalid address family is passed
-TEST_F(DNSServerTestBase, invalidFamily) {
+// The parameter here doesn't mean anything
+TYPED_TEST(DNSServerTestBase, invalidFamily) {
// We abuse DNSServerTestBase for this test, as we don't need the
// initialization.
- EXPECT_THROW(UDPServer(service, 0, AF_UNIX, checker_, lookup_,
- answer_), isc::InvalidParameter);
- EXPECT_THROW(TCPServer(service, 0, AF_UNIX, checker_, lookup_,
- answer_), isc::InvalidParameter);
+ EXPECT_THROW(TypeParam(this->service, 0, AF_UNIX, this->checker_,
+ this->lookup_, this->answer_),
+ isc::InvalidParameter);
+ EXPECT_THROW(TCPServer(this->service, 0, AF_UNIX, this->checker_,
+ this->lookup_, this->answer_),
+ isc::InvalidParameter);
}
// It raises an exception when invalid address family is passed
-TEST_F(DNSServerTestBase, invalidTCPFD) {
+TYPED_TEST(DNSServerTestBase, invalidTCPFD) {
// We abuse DNSServerTestBase for this test, as we don't need the
// initialization.
/*
@@ -630,11 +662,12 @@ TEST_F(DNSServerTestBase, invalidTCPFD) {
EXPECT_THROW(UDPServer(service, -1, AF_INET, checker_, lookup_,
answer_), isc::asiolink::IOError);
*/
- EXPECT_THROW(TCPServer(service, -1, AF_INET, checker_, lookup_,
- answer_), isc::asiolink::IOError);
+ EXPECT_THROW(TCPServer(this->service, -1, AF_INET, this->checker_,
+ this->lookup_, this->answer_),
+ isc::asiolink::IOError);
}
-TEST_F(DNSServerTestBase, DISABLED_invalidUDPFD) {
+TYPED_TEST(DNSServerTestBase, DISABLED_invalidUDPFD) {
/*
FIXME: The UDP server doesn't fail reliably with an invalid FD.
We need to find a way to trigger it reliably (it seems epoll
@@ -642,8 +675,24 @@ TEST_F(DNSServerTestBase, DISABLED_invalidUDPFD) {
not the others, maybe we could make it run this at least on epoll-based
systems).
*/
- EXPECT_THROW(UDPServer(service, -1, AF_INET, checker_, lookup_,
- answer_), isc::asiolink::IOError);
+ EXPECT_THROW(TypeParam(this->service, -1, AF_INET, this->checker_,
+ this->lookup_, this->answer_),
+ isc::asiolink::IOError);
+}
+
+// Check it rejects some of the unsupported operatirons
+TYPED_TEST(SyncServerTest, unsupportedOps) {
+ EXPECT_THROW(this->udp_server_->clone(), isc::Unexpected);
+ EXPECT_THROW(this->udp_server_->asyncLookup(), isc::Unexpected);
+}
+
+// Check it rejects forgotten resume (eg. insists that it is synchronous)
+TYPED_TEST(SyncServerTest, mustResume) {
+ this->lookup_->allow_resume_ = false;
+ ASSERT_THROW(this->testStopServerByStopper(this->udp_server_,
+ this->udp_client_,
+ this->lookup_),
+ isc::Unexpected);
}
}
diff --git a/src/lib/asiodns/udp_server.cc b/src/lib/asiodns/udp_server.cc
index 820db4c..990949e 100644
--- a/src/lib/asiodns/udp_server.cc
+++ b/src/lib/asiodns/udp_server.cc
@@ -343,10 +343,5 @@ UDPServer::resume(const bool done) {
data_->io_.post(*this);
}
-bool
-UDPServer::hasAnswer() {
- return (data_->done_);
-}
-
} // namespace asiodns
} // namespace isc
diff --git a/src/lib/asiodns/udp_server.h b/src/lib/asiodns/udp_server.h
index c82b78c..2b6a574 100644
--- a/src/lib/asiodns/udp_server.h
+++ b/src/lib/asiodns/udp_server.h
@@ -83,16 +83,6 @@ public:
/// we have an answer
void resume(const bool done);
- /// \brief Check if we have an answer
- ///
- /// \return true if we have an answer
- bool hasAnswer();
-
- /// \brief Returns the coroutine state value
- ///
- /// \return the coroutine state value
- int value() { return (get_value()); }
-
/// \brief Clones the object
///
/// \return a newly allocated copy of this object
diff --git a/src/lib/config/module_spec.cc b/src/lib/config/module_spec.cc
index bebe695..0defd6d 100644
--- a/src/lib/config/module_spec.cc
+++ b/src/lib/config/module_spec.cc
@@ -466,7 +466,6 @@ ModuleSpec::validateSpecList(ConstElementPtr spec, ConstElementPtr data,
const bool full, ElementPtr errors) const
{
bool validated = true;
- std::string cur_item_name;
BOOST_FOREACH(ConstElementPtr cur_spec_el, spec->listValue()) {
if (!validateSpec(cur_spec_el, data, full, errors)) {
validated = 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/datasrc/memory_datasrc.cc b/src/lib/datasrc/memory_datasrc.cc
index dfe1614..09c2c02 100644
--- a/src/lib/datasrc/memory_datasrc.cc
+++ b/src/lib/datasrc/memory_datasrc.cc
@@ -1328,7 +1328,6 @@ checkConfig(ConstElementPtr config, ElementPtr errors) {
}
return (result);
- return true;
}
} // end anonymous namespace
diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc
index b704370..14de5a0 100644
--- a/src/lib/dhcp/iface_mgr.cc
+++ b/src/lib/dhcp/iface_mgr.cc
@@ -130,7 +130,7 @@ IfaceMgr::IfaceMgr()
// interface detection is implemented. Otherwise
// it is not possible to run tests in a portable
// way (see detectIfaces() method).
- throw ex;
+ throw;
}
}
@@ -191,7 +191,7 @@ IfaceMgr::stubDetectIfaces() {
// TODO Do LOG_FATAL here
std::cerr << "Interface detection failed." << std::endl;
- throw ex;
+ throw;
}
}
diff --git a/src/lib/dns/python/message_python.cc b/src/lib/dns/python/message_python.cc
index 2f1e2e5..c7ad2ff 100644
--- a/src/lib/dns/python/message_python.cc
+++ b/src/lib/dns/python/message_python.cc
@@ -377,8 +377,9 @@ Message_getTSIGRecord(s_Message* self) {
if (tsig_record == NULL) {
Py_RETURN_NONE;
+ } else {
+ return (createTSIGRecordObject(*tsig_record));
}
- return (createTSIGRecordObject(*tsig_record));
} catch (const InvalidMessageOperation& ex) {
PyErr_SetString(po_InvalidMessageOperation, ex.what());
} catch (const exception& ex) {
diff --git a/src/lib/dns/tests/name_unittest.cc b/src/lib/dns/tests/name_unittest.cc
index 6434229..523aae1 100644
--- a/src/lib/dns/tests/name_unittest.cc
+++ b/src/lib/dns/tests/name_unittest.cc
@@ -543,7 +543,7 @@ TEST_F(NameTest, leq) {
// small <= small is true
EXPECT_TRUE(small_name.leq(small_name));
- EXPECT_TRUE(small_name <= small_name);
+ EXPECT_LE(small_name, small_name);
// large <= small is false
EXPECT_FALSE(large_name.leq(small_name));
@@ -555,7 +555,7 @@ TEST_F(NameTest, geq) {
EXPECT_TRUE(large_name >= small_name);
EXPECT_TRUE(large_name.geq(large_name));
- EXPECT_TRUE(large_name >= large_name);
+ EXPECT_GE(large_name, large_name);
EXPECT_FALSE(small_name.geq(large_name));
EXPECT_FALSE(small_name >= large_name);
diff --git a/src/lib/dns/tests/rrttl_unittest.cc b/src/lib/dns/tests/rrttl_unittest.cc
index fe75eb6..b3f6fad 100644
--- a/src/lib/dns/tests/rrttl_unittest.cc
+++ b/src/lib/dns/tests/rrttl_unittest.cc
@@ -138,7 +138,7 @@ TEST_F(RRTTLTest, leq) {
// small <= small is true
EXPECT_TRUE(ttl_small.leq(ttl_small));
- EXPECT_TRUE(ttl_small <= ttl_small);
+ EXPECT_LE(ttl_small, ttl_small);
// large <= small is false
EXPECT_FALSE(ttl_large.leq(ttl_small));
@@ -150,7 +150,7 @@ TEST_F(RRTTLTest, geq) {
EXPECT_TRUE(ttl_large >= ttl_small);
EXPECT_TRUE(ttl_large.geq(ttl_large));
- EXPECT_TRUE(ttl_large >= ttl_large);
+ EXPECT_GE(ttl_large, ttl_large);
EXPECT_FALSE(ttl_small.geq(ttl_large));
EXPECT_FALSE(ttl_small >= ttl_large);
diff --git a/src/lib/nsas/glue_hints.cc b/src/lib/nsas/glue_hints.cc
index 02c27ee..3caae25 100644
--- a/src/lib/nsas/glue_hints.cc
+++ b/src/lib/nsas/glue_hints.cc
@@ -86,8 +86,8 @@ GlueHints::GlueHints(const std::string& zone_name,
bool
GlueHints::hasGlue(AddressFamily family) const {
- return ((addresses_v4.size() > 0 && (family == ANY_OK || family == V4_ONLY)) ||
- (addresses_v6.size() > 0 && (family == ANY_OK || family == V6_ONLY)));
+ return ((!addresses_v4.empty() && (family == ANY_OK || family == V4_ONLY)) ||
+ (!addresses_v6.empty() && (family == ANY_OK || family == V6_ONLY)));
}
NameserverAddress
diff --git a/src/lib/nsas/hash_table.h b/src/lib/nsas/hash_table.h
index c028fa4..6028473 100644
--- a/src/lib/nsas/hash_table.h
+++ b/src/lib/nsas/hash_table.h
@@ -59,7 +59,7 @@ struct HashTableSlot {
/// \brief Copy Constructor
///
/// ... which as noted in the class description does not copy.
- HashTableSlot(const HashTableSlot<T>&)
+ HashTableSlot(const HashTableSlot<T>&) : mutex_(), list_()
{ }
public:
diff --git a/src/lib/nsas/tests/nameserver_entry_unittest.cc b/src/lib/nsas/tests/nameserver_entry_unittest.cc
index 3435d26..3aca08f 100644
--- a/src/lib/nsas/tests/nameserver_entry_unittest.cc
+++ b/src/lib/nsas/tests/nameserver_entry_unittest.cc
@@ -139,7 +139,7 @@ TEST_F(NameserverEntryTest, SetRTT) {
NameserverEntry::AddressVector vec;
alpha->getAddresses(vec);
- ASSERT_TRUE(vec.size() > 0);
+ ASSERT_FALSE(vec.empty());
// Take the first address and change the RTT.
IOAddress first_address = vec[0].getAddress();
@@ -174,7 +174,7 @@ TEST_F(NameserverEntryTest, Unreachable) {
NameserverEntry::AddressVector vec;
alpha->getAddresses(vec);
- ASSERT_TRUE(vec.size() > 0);
+ ASSERT_FALSE(vec.empty());
// Take the first address and mark as unreachable.
IOAddress first_address = vec[0].getAddress();
diff --git a/src/lib/nsas/zone_entry.cc b/src/lib/nsas/zone_entry.cc
index 1c5df03..8a72e5f 100644
--- a/src/lib/nsas/zone_entry.cc
+++ b/src/lib/nsas/zone_entry.cc
@@ -329,7 +329,7 @@ updateAddressSelector(std::vector<NameserverAddress>& addresses,
it != probabilities.end(); ++it){
(*it) /= sum;
}
- } else if(probabilities.size() > 0){
+ } else if(!probabilities.empty()){
// If all the nameservers are unreachable, the sum will be 0
// So give each server equal opportunity to be selected.
for(vector<double>::iterator it = probabilities.begin();
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
diff --git a/tests/lettuce/configurations/multi_instance/multi_auth.config.orig b/tests/lettuce/configurations/multi_instance/multi_auth.config.orig
new file mode 100644
index 0000000..f181d42
--- /dev/null
+++ b/tests/lettuce/configurations/multi_instance/multi_auth.config.orig
@@ -0,0 +1 @@
+{"version": 2, "Auth": {"listen_on": [{"port": 47806, "address": "0.0.0.0"}]}, "Boss": {"components": {"b10-auth-2": {"kind": "needed", "special": "auth"}, "b10-auth": {"kind": "needed", "special": "auth"}, "b10-cmdctl": {"kind": "needed", "special": "cmdctl"}}}}
diff --git a/tests/lettuce/features/multi_instance.feature b/tests/lettuce/features/multi_instance.feature
new file mode 100644
index 0000000..864431d
--- /dev/null
+++ b/tests/lettuce/features/multi_instance.feature
@@ -0,0 +1,35 @@
+Feature: Multiple instances
+ This feature tests whether multiple instances can be run, and whether
+ removing them does not affect the running of other instances
+
+ Scenario: Multiple instances of Auth
+ # This config should have two running instances
+ Given I have bind10 running with configuration multi_instance/multi_auth.config
+ And bind10 module Auth should be running
+ A query for example.com should have rcode REFUSED
+
+ # this also checks whether the process is running
+ If I remember the pid of process b10-auth
+ And remember the pid of process b10-auth-2
+
+ When I remove bind10 configuration Boss/components value b10-auth-2
+
+ Then the pid of process b10-auth should not have changed
+ And a query for example.com should have rcode REFUSED
+
+ When I send bind10 the following commands
+ """
+ config add Boss/components b10-auth-2
+ config set Boss/components/b10-auth-2/special auth
+ config set Boss/components/b10-auth-2/kind needed
+ config commit
+ """
+ And wait for new bind10 stderr message AUTH_SERVER_STARTED
+ And remember the pid of process b10-auth-2
+
+ Then the pid of process b10-auth should not have changed
+ A query for example.com should have rcode REFUSED
+
+ When I remove bind10 configuration Boss/components value b10-auth
+ Then the pid of process b10-auth-2 should not have changed
+ A query for example.com should have rcode REFUSED
diff --git a/tests/lettuce/features/terrain/bind10_control.py b/tests/lettuce/features/terrain/bind10_control.py
index 7ccd2b3..b2a367c 100644
--- a/tests/lettuce/features/terrain/bind10_control.py
+++ b/tests/lettuce/features/terrain/bind10_control.py
@@ -16,6 +16,7 @@
from lettuce import *
import subprocess
import re
+import json
@step('start bind10(?: with configuration (\S+))?' +\
'(?: with cmdctl port (\d+))?' +\
@@ -167,6 +168,83 @@ def check_bindctl_output(step, stderr, notv, string):
"' was found in bindctl output:\n" +\
output
+def parse_bindctl_output_as_data_structure():
+ """Helper function for data-related command tests: evaluates the
+ last output of bindctl as a data structure that can then be
+ inspected.
+ If the bindctl output is not valid (json) data, this call will
+ fail with an assertion failure.
+ If it is valid, it is parsed and returned as whatever data
+ structure it represented.
+ """
+ # strip any extra output after a charater that commonly terminates a valid
+ # JSON expression, i.e., ']', '}' and '"'. (The extra output would
+ # contain 'Exit from bindctl' message, and depending on environment some
+ # other control-like characters...but why is this message even there?)
+ # Note that this filter is not perfect. For example, it cannot recognize
+ # a simple expression of true/false/null.
+ output = re.sub("(.*)([^]}\"]*$)", r"\1", world.last_bindctl_stdout)
+ try:
+ return json.loads(output)
+ except ValueError as ve:
+ assert False, "Last bindctl output does not appear to be a " +\
+ "parseable data structure: '" + output + "': " + str(ve)
+
+def find_process_pid(step, process_name):
+ """Helper function to request the running processes from Boss, and
+ return the pid of the process with the given process_name.
+ Fails with an assert if the response from boss is not valid JSON,
+ or if the process with the given name is not found.
+ """
+ # show_processes output is a list of lists, where the inner lists
+ # are of the form [ pid, "name" ]
+ # Not checking data form; errors will show anyway (if these turn
+ # out to be too vague, we can change this)
+ step.given('send bind10 the command Boss show_processes')
+ running_processes = parse_bindctl_output_as_data_structure()
+
+ for process in running_processes:
+ if process[1] == process_name:
+ return process[0]
+ assert False, "Process named " + process_name +\
+ " not found in output of Boss show_processes";
+
+ at step("remember the pid of process ([\S]+)")
+def remember_pid(step, process_name):
+ """Stores the PID of the process with the given name as returned by
+ Boss show_processes command.
+ Fails if the process with the given name does not appear to exist.
+ Stores the component_name->pid value in the dict world.process_pids.
+ This should only be used by the related step
+ 'the pid of process <name> should (not) have changed'
+ Arguments:
+ process name ('process <name>') the name of the component to store
+ the pid of.
+ """
+ if world.process_pids is None:
+ world.process_pids = {}
+ world.process_pids[process_name] = find_process_pid(step, process_name)
+
+ at step('pid of process ([\S]+) should not have changed')
+def check_pid(step, process_name):
+ """Checks the PID of the process with the given name as returned by
+ Boss show_processes command.
+ Fails if the process with the given name does not appear to exist.
+ Fails if the process with the given name exists, but has a different
+ pid than it had when the step 'remember the pid of process' was
+ called.
+ Fails if that step has not been called (since world.process_pids
+ does not exist).
+ """
+ assert world.process_pids is not None, "No process pids stored"
+ assert process_name in world.process_pids, "Process named " +\
+ process_name +\
+ " was not stored"
+ pid = find_process_pid(step, process_name)
+ assert world.process_pids[process_name] == pid,\
+ "Expected pid: " + str(world.process_pids[process_name]) +\
+ " Got pid: " + str(pid)
+
@step('set bind10 configuration (\S+) to (.*)(?: with cmdctl port (\d+))?')
def config_set_command(step, name, value, cmdctl_port):
"""
@@ -183,6 +261,20 @@ def config_set_command(step, name, value, cmdctl_port):
"quit"]
run_bindctl(commands, cmdctl_port)
+ at step('send bind10 the following commands(?: with cmdctl port (\d+))?')
+def send_multiple_commands(step, cmdctl_port):
+ """
+ Run bindctl, and send it the given multiline set of commands.
+ A quit command is always appended.
+ cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
+ the command to. Defaults to 47805.
+ Fails if cmdctl does not exit with status code 0.
+ """
+ commands = step.multiline.split("\n")
+ # Always add quit
+ commands.append("quit")
+ run_bindctl(commands, cmdctl_port)
+
@step('remove bind10 configuration (\S+)(?: value (\S+))?(?: with cmdctl port (\d+))?')
def config_remove_command(step, name, value, cmdctl_port):
"""
diff --git a/tests/lettuce/features/terrain/terrain.py b/tests/lettuce/features/terrain/terrain.py
index 90531a1..2bfddd6 100644
--- a/tests/lettuce/features/terrain/terrain.py
+++ b/tests/lettuce/features/terrain/terrain.py
@@ -45,7 +45,9 @@ copylist = [
["configurations/example.org.config.orig",
"configurations/example.org.config"],
["configurations/resolver/resolver_basic.config.orig",
- "configurations/resolver/resolver_basic.config"]
+ "configurations/resolver/resolver_basic.config"],
+ ["configurations/multi_instance/multi_auth.config.orig",
+ "configurations/multi_instance/multi_auth.config"]
]
# This is a list of files that, if present, will be removed before a scenario
@@ -343,6 +345,10 @@ def initialize(scenario):
# Convenience variable to access the last query result from querying.py
world.last_query_result = None
+ # For slightly better errors, initialize a process_pids for the relevant
+ # steps
+ world.process_pids = None
+
# Some tests can modify the settings. If the tests fail half-way, or
# don't clean up, this can leave configurations or data in a bad state,
# so we copy them from originals before each scenario
More information about the bind10-changes
mailing list