diff --git a/src/bin/ddns/b10-ddns.8 b/src/bin/ddns/b10-ddns.8
index d1e05d9f3d..3e2e102f9c 100644
--- a/src/bin/ddns/b10-ddns.8
+++ b/src/bin/ddns/b10-ddns.8
@@ -68,7 +68,7 @@ The configurable settings are:
.PP
\fIzones\fR
-The zones option is a named set of zones that can be updated with DDNS\&. Each entry has one element called update_acls, which itself is a list of ACLs that define update permissions\&. By default this is empty; DDNS must be explicitely enabled per zone\&.
+The zones option is a named set of zones that can be updated with DDNS\&. Each entry has one element called update_acl, which is is a list of access control rules that define update permissions\&. By default this is empty; DDNS must be explicitely enabled per zone\&.
.PP
The module commands are:
.PP
diff --git a/src/bin/ddns/b10-ddns.xml b/src/bin/ddns/b10-ddns.xml
index 73bdbc24d7..d38b5f1066 100644
--- a/src/bin/ddns/b10-ddns.xml
+++ b/src/bin/ddns/b10-ddns.xml
@@ -106,8 +106,8 @@
zones
The zones option is a named set of zones that can be updated with
- DDNS. Each entry has one element called update_acls, which itself
- is a list of ACLs that define update permissions.
+ DDNS. Each entry has one element called update_acl, which is
+ is a list of access control rules that define update permissions.
By default this is empty; DDNS must be explicitely enabled per zone.
diff --git a/src/bin/ddns/ddns.py.in b/src/bin/ddns/ddns.py.in
index d296fdfd0f..35279792db 100755
--- a/src/bin/ddns/ddns.py.in
+++ b/src/bin/ddns/ddns.py.in
@@ -63,15 +63,6 @@ class DDNSSession:
def __init__(self):
'''Initialize a DDNS Session'''
- self._handle()
-
- def _handle(self):
- '''
- Handle a DDNS update.
- This should be called from the initializer, and should contain the
- logic for doing the checks, handling the update, and responding with
- the result.
- '''
pass
class DDNSServer:
@@ -120,6 +111,7 @@ class DDNSServer:
so the main loop in run() stops.
'''
self._shutdown = True
+ logger.info(DDNS_SHUTDOWN)
def run(self):
'''
@@ -128,7 +120,13 @@ class DDNSServer:
'''
logger.info(DDNS_RUNNING)
while not self._shutdown:
+ # We do not catch any exceptions here right now, but this would
+ # be a good place to catch any exceptions that b10-ddns can
+ # recover from. We currently have no exception hierarchy to
+ # make such a distinction easily, but once we do, this would
+ # be the place to catch.
self._cc.check_command(False)
+ logger.info(DDNS_STOPPED)
def create_signal_handler(ddns_server):
'''
@@ -142,8 +140,7 @@ def create_signal_handler(ddns_server):
here, the actual signal is not checked and the server is simply shut
down.
'''
- if ddns_server is not None:
- ddns_server.shutdown()
+ ddns_server.shutdown()
return signal_handler
def set_signal_handler(signal_handler):
@@ -160,7 +157,17 @@ def set_cmd_options(parser):
parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
help="display more about what is going on")
-if '__main__' == __name__:
+def main(ddns_server=None):
+ '''
+ The main function.
+ Parameters:
+ ddns_server: If None (default), a DDNSServer object is initialized.
+ If specified, the given DDNSServer will be used. This is
+ mainly used for testing.
+ cc_session: If None (default), a new ModuleCCSession will be set up.
+ If specified, the given session will be used. This is
+ mainly used for testing.
+ '''
try:
parser = OptionParser()
set_cmd_options(parser)
@@ -168,8 +175,8 @@ if '__main__' == __name__:
if options.verbose:
print("[b10-ddns] Warning: -v verbose option is ignored at this point.")
- ddns_server = DDNSServer()
- logger.debug(10, DDNS_STOPPED_BY_KEYBOARD)
+ if ddns_server is None:
+ ddns_server = DDNSServer()
set_signal_handler(create_signal_handler(ddns_server))
ddns_server.run()
except KeyboardInterrupt:
@@ -182,3 +189,8 @@ if '__main__' == __name__:
logger.error(DDNS_CONFIG_ERROR, str(e))
except SessionTimeout as e:
logger.error(DDNS_CC_SESSION_TIMEOUT_ERROR)
+ except Exception as e:
+ logger.error(DDNS_UNCAUGHT_EXCEPTION, type(e).__name__, str(e))
+
+if '__main__' == __name__:
+ main()
diff --git a/src/bin/ddns/ddns.spec b/src/bin/ddns/ddns.spec
index 03899e4b3f..07cd2a99f9 100644
--- a/src/bin/ddns/ddns.spec
+++ b/src/bin/ddns/ddns.spec
@@ -12,12 +12,12 @@
"item_type": "map",
"item_optional": true,
"item_default": {
- "update_acls": [{"action": "ACCEPT", "from": "127.0.0.1"},
- {"action": "ACCEPT", "from": "::1"}]
+ "update_acl": [{"action": "ACCEPT", "from": "127.0.0.1"},
+ {"action": "ACCEPT", "from": "::1"}]
},
"map_item_spec": [
{
- "item_name": "update_acls",
+ "item_name": "update_acl",
"item_type": "list",
"item_optional": false,
"list_item_spec": {
diff --git a/src/bin/ddns/ddns_messages.mes b/src/bin/ddns/ddns_messages.mes
index 286ae7f690..36c6ed16a1 100644
--- a/src/bin/ddns/ddns_messages.mes
+++ b/src/bin/ddns/ddns_messages.mes
@@ -15,9 +15,13 @@
# No namespace declaration - these constants go in the global namespace
# of the ddns messages python module.
+# When you add a message to this file, it is a good idea to run
+# /tools/reorder_message_file.py to make sure the
+# messages are in the correct order.
+
% DDNS_CC_SESSION_ERROR error reading from cc channel: %1
There was a problem reading from the command and control channel. The
-most likely cause is that the msgq daemon is not running.
+most likely cause is that the msgq process is not running.
% DDNS_CC_SESSION_TIMEOUT_ERROR timeout waiting for cc response
There was a problem reading a response from another module over the
@@ -36,13 +40,27 @@ failed to start at initialization. A detailed error message from the module
will also be displayed.
% DDNS_RECEIVED_SHUTDOWN_COMMAND shutdown command received
-The ddns daemon received a shutdown command from the command channel
+The ddns process received a shutdown command from the command channel
and will now shut down.
% DDNS_RUNNING ddns server is running and listening for updates
-The ddns daemon has successfully started and is now ready to receive commands
+The ddns process has successfully started and is now ready to receive commands
and updates.
+% DDNS_SHUTDOWN ddns server shutting down
+The ddns process is shutting down. It will no longer listen for new commands
+or updates. Any command or update that is being addressed at this moment will
+be completed, after which the process will exit.
+
+% DDNS_STOPPED ddns server has stopped
+The ddns process has successfully stopped and is no longer listening for or
+handling commands or updates, and will now exit.
+
% DDNS_STOPPED_BY_KEYBOARD keyboard interrupt, shutting down
-There was a keyboard interrupt signal to stop the ddns daemon. The
-daemon will now shut down.
+There was a keyboard interrupt signal to stop the ddns process. The
+process will now shut down.
+
+% DDNS_UNCAUGHT_EXCEPTION uncaught exception of type %1: %2
+The b10-ddns process encountered an uncaught exception and will now shut
+down. This is indicative of a programming error and should not happen under
+normal circumstances. The exception type and message are printed.
diff --git a/src/bin/ddns/tests/ddns_test.in b/src/bin/ddns/tests/ddns_test.in
deleted file mode 100644
index e61c9245ce..0000000000
--- a/src/bin/ddns/tests/ddns_test.in
+++ /dev/null
@@ -1,27 +0,0 @@
-#! /bin/sh
-
-# Copyright (C) 2011 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-PYTHON_EXEC=${PYTHON_EXEC:-@PYTHON@}
-export PYTHON_EXEC
-
-TEST_PATH=@abs_top_srcdir@/src/bin/ddns/tests
-PYTHONPATH=@abs_top_srcdir@/src/bin/ddns:@abs_top_srcdir@/src/lib/python
-export PYTHONPATH
-
-cd ${TEST_PATH}
-exec ${PYTHON_EXEC} -O ddns_test.py $*
-
diff --git a/src/bin/ddns/tests/ddns_test.py b/src/bin/ddns/tests/ddns_test.py
index e18a990a6d..0ebfe7927d 100755
--- a/src/bin/ddns/tests/ddns_test.py
+++ b/src/bin/ddns/tests/ddns_test.py
@@ -32,6 +32,32 @@ class MyCCSession(isc.config.ConfigData):
'''Called by DDNSServer initialization, but not used in tests'''
self._started = True
+class MyDDNSServer():
+ '''Fake DDNS server used to test the main() function'''
+ def __init__(self):
+ self.reset()
+
+ def run(self):
+ '''
+ Fake the run() method of the DDNS server. This will set
+ self._run_called to True.
+ If self._exception is not None, this is raised as an exception
+ '''
+ self.run_called = True
+ if self._exception is not None:
+ self.exception_raised = True
+ raise self._exception
+
+ def set_exception(self, exception):
+ '''Set an exception to be raised when run() is called'''
+ self._exception = exception
+
+ def reset(self):
+ '''(Re)set to initial values'''
+ self.run_called = False
+ self.exception_raised = False
+ self._exception = None
+
class TestDDNSServer(unittest.TestCase):
def setUp(self):
cc_session = MyCCSession()
@@ -67,6 +93,65 @@ class TestDDNSServer(unittest.TestCase):
signal_handler(None, None)
self.assertTrue(self.ddns_server._shutdown)
+class TestMain(unittest.TestCase):
+ def setUp(self):
+ self._server = MyDDNSServer()
+
+ def test_main(self):
+ self.assertFalse(self._server.run_called)
+ ddns.main(self._server)
+ self.assertTrue(self._server.run_called)
+
+ def test_exceptions(self):
+ '''
+ Test whether exceptions are caught in main()
+ These exceptions should not bubble up.
+ '''
+ self._server.set_exception(KeyboardInterrupt())
+ self.assertFalse(self._server.exception_raised)
+ ddns.main(self._server)
+ self.assertTrue(self._server.exception_raised)
+
+ # Should technically not be necessary, but reset server to be sure
+ self._server.reset()
+ self.assertFalse(self._server.exception_raised)
+ self._server.set_exception(isc.cc.SessionError("error"))
+ ddns.main(self._server)
+ self.assertTrue(self._server.exception_raised)
+
+ self._server.reset()
+ self.assertFalse(self._server.exception_raised)
+ self._server.set_exception(isc.config.ModuleCCSessionError("error"))
+ ddns.main(self._server)
+ self.assertTrue(self._server.exception_raised)
+
+ self._server.reset()
+ self.assertFalse(self._server.exception_raised)
+ self._server.set_exception(ddns.DDNSConfigError("error"))
+ ddns.main(self._server)
+ self.assertTrue(self._server.exception_raised)
+
+ self._server.reset()
+ self.assertFalse(self._server.exception_raised)
+ self._server.set_exception(isc.cc.SessionTimeout("error"))
+ ddns.main(self._server)
+ self.assertTrue(self._server.exception_raised)
+
+ self._server.reset()
+ self.assertFalse(self._server.exception_raised)
+ self._server.set_exception(Exception("error"))
+ ddns.main(self._server)
+ self.assertTrue(self._server.exception_raised)
+
+ # Add one that is not a subclass of Exception, and hence not
+ # caught. Misuse BaseException for that.
+ self._server.reset()
+ self.assertFalse(self._server.exception_raised)
+ self._server.set_exception(BaseException("error"))
+ self.assertRaises(BaseException, ddns.main, self._server)
+ self.assertTrue(self._server.exception_raised)
+
+
if __name__== "__main__":
isc.log.resetUnitTestRootLogger()
unittest.main()