Browse Source

[master] Merge branch 'trac1978-2'

JINMEI Tatuya 13 years ago
parent
commit
ef10033b84

+ 18 - 6
src/bin/ddns/ddns.py.in

@@ -31,6 +31,7 @@ import isc.util.cio.socketsession
 from isc.notify.notify_out import ZONE_NEW_DATA_READY_CMD
 import isc.server_common.tsig_keyring
 from isc.datasrc import DataSourceClient
+from isc.server_common.auth_command import auth_loadzone_command
 import select
 import errno
 
@@ -82,6 +83,7 @@ isc.util.process.rename()
 
 # Cooperating modules
 XFROUT_MODULE_NAME = 'Xfrout'
+AUTH_MODULE_NAME = 'Auth'
 
 class DDNSConfigError(Exception):
     '''An exception indicating an error in updating ddns configuration.
@@ -380,7 +382,8 @@ class DDNSServer:
         ret = self.__send_response(sock, self.__response_renderer.get_data(),
                                    remote_addr)
         if result == isc.ddns.session.UPDATE_SUCCESS:
-            self.__notify_update(zname, zclass)
+            self.__notify_auth(zname, zclass)
+            self.__notify_xfrout(zname, zclass)
         return ret
 
     def __send_response(self, sock, data, dest):
@@ -409,8 +412,20 @@ class DDNSServer:
 
         return True
 
-    def __notify_update(self, zname, zclass):
-        '''Notify other modules of the update.
+    def __notify_auth(self, zname, zclass):
+        '''Notify auth of the update, if necessary.'''
+        msg = auth_loadzone_command(self._cc, zname, zclass)
+        if msg is not None:
+            self.__notify_update(AUTH_MODULE_NAME, msg, zname, zclass)
+
+    def __notify_xfrout(self, zname, zclass):
+        '''Notify xfrout of the update.'''
+        param = {'zone_name': zname.to_text(), 'zone_class': zclass.to_text()}
+        msg = create_command(ZONE_NEW_DATA_READY_CMD, param)
+        self.__notify_update(XFROUT_MODULE_NAME, msg, zname, zclass)
+
+    def __notify_update(self, modname, msg, zname, zclass):
+        '''Notify other module of the update.
 
         Note that we use blocking communication here.  While the internal
         communication bus is generally expected to be pretty responsive and
@@ -428,9 +443,6 @@ class DDNSServer:
         of the cc session.
 
         '''
-        param = {'zone_name': zname.to_text(), 'zone_class': zclass.to_text()}
-        msg = create_command(ZONE_NEW_DATA_READY_CMD, param)
-        modname = XFROUT_MODULE_NAME
         try:
             seq = self._cc._session.group_sendmsg(msg, modname)
             answer, _ = self._cc._session.group_recvmsg(False, seq)

+ 59 - 6
src/bin/ddns/tests/ddns_test.py

@@ -161,6 +161,8 @@ class MyCCSession(isc.config.ConfigData):
         self._stopped = False
         # Used as the return value of get_remote_config_value.  Customizable.
         self.auth_db_file = READ_ZONE_DB_FILE
+        # Used as the return value of get_remote_config_value.  Customizable.
+        self.auth_datasources = None
         # faked cc channel, providing group_send/recvmsg itself.  The following
         # attributes are for inspection/customization in tests.
         self._session = self
@@ -190,6 +192,11 @@ class MyCCSession(isc.config.ConfigData):
     def get_remote_config_value(self, module_name, item):
         if module_name == "Auth" and item == "database_file":
             return self.auth_db_file, False
+        if module_name == "Auth" and item == "datasources":
+            if self.auth_datasources is None:
+                return [], True # default
+            else:
+                return self.auth_datasources, False
 
     def group_sendmsg(self, msg, group):
         # remember the passed parameter, and return dummy sequence
@@ -745,20 +752,42 @@ class TestDDNSSession(unittest.TestCase):
         num_rrsets = len(self.__req_message.get_section(SECTION_PREREQUISITE))
         self.assertEqual(2, num_rrsets)
 
-    def check_session_msg(self, result, expect_recv=1):
+    def check_session_msg(self, result, expect_recv=1, notify_auth=False):
         '''Check post update communication with other modules.'''
         # iff the update succeeds, b10-ddns should tell interested other
-        # modules the information about the update zone in the form of
+        # modules the information about the update zone.  Possible modules
+        # are xfrout and auth: for xfrout, the message format should be:
         # {'command': ['notify', {'zone_name': <updated_zone_name>,
         #                         'zone_class', <updated_zone_class>}]}
+        # for auth, it should be:
+        # {'command': ['loadzone', {'origin': <updated_zone_name>,
+        #                           'class', <updated_zone_class>,
+        #                           'datasrc', <datasrc type, should be
+        #                                       "memory" in practice>}]}
         # and expect an answer by calling group_recvmsg().
         #
         # expect_recv indicates the expected number of calls to
-        # group_recvmsg(), which is normally 1, but can be 0 if send fails.
+        # group_recvmsg(), which is normally 1, but can be 0 if send fails;
+        # if the message is to be sent
         if result == UPDATE_SUCCESS:
-            self.assertEqual(1, len(self.__cc_session._sent_msg))
+            expected_sentmsg = 2 if notify_auth else 1
+            self.assertEqual(expected_sentmsg,
+                             len(self.__cc_session._sent_msg))
             self.assertEqual(expect_recv, self.__cc_session._recvmsg_called)
-            sent_msg, sent_group = self.__cc_session._sent_msg[0]
+            msg_cnt = 0
+            if notify_auth:
+                sent_msg, sent_group = self.__cc_session._sent_msg[msg_cnt]
+                sent_cmd = sent_msg['command']
+                self.assertEqual('Auth', sent_group)
+                self.assertEqual('loadzone', sent_cmd[0])
+                self.assertEqual(3, len(sent_cmd[1]))
+                self.assertEqual(TEST_ZONE_NAME.to_text(),
+                                 sent_cmd[1]['origin'])
+                self.assertEqual(TEST_RRCLASS.to_text(),
+                                 sent_cmd[1]['class'])
+                self.assertEqual('memory', sent_cmd[1]['datasrc'])
+                msg_cnt += 1
+            sent_msg, sent_group = self.__cc_session._sent_msg[msg_cnt]
             sent_cmd = sent_msg['command']
             self.assertEqual('Xfrout', sent_group)
             self.assertEqual('notify', sent_cmd[0])
@@ -815,8 +844,32 @@ class TestDDNSSession(unittest.TestCase):
         self.__cc_session._sendmsg_exception = RuntimeError('unexpected')
         self.assertRaises(RuntimeError, self.check_session)
 
+    def test_session_msg_for_auth(self):
+        '''Test post update communication with other modules including Auth.'''
+        # Let the CC session return in-memory config with sqlite3 backend.
+        # (The default case was covered by other tests.)
+        self.__cc_session.auth_datasources = \
+            [{'type': 'memory', 'class': 'IN', 'zones': [
+                    {'origin': TEST_ZONE_NAME_STR, 'filetype': 'sqlite3'}]}]
+        self.check_session()
+        self.check_session_msg(UPDATE_SUCCESS, expect_recv=2, notify_auth=True)
+
+        # Let sendmsg() raise an exception.  The first exception shouldn't
+        # stop sending the second message.  There's just no recv calls.
+        self.__cc_session.clear_msg()
+        self.__cc_session._sendmsg_exception = SessionError('send error')
+        self.check_session()
+        self.check_session_msg(UPDATE_SUCCESS, expect_recv=0, notify_auth=True)
+
+        # Likewise, in the case recvmsg() raises (and there should be recv
+        # calls in this case)
+        self.__cc_session.clear_msg()
+        self.__cc_session._recvmsg_exception = SessionError('recv error')
+        self.check_session()
+        self.check_session_msg(UPDATE_SUCCESS, expect_recv=2, notify_auth=True)
+
     def test_session_with_config(self):
-        '''Check a session with more relistic config setups
+        '''Check a session with more realistic config setups.
 
         We don't have to explore various cases in detail in this test.
         We're just checking if the expected configured objects are passed

+ 8 - 44
src/bin/xfrin/xfrin.py.in

@@ -33,6 +33,7 @@ import isc.util.process
 from isc.datasrc import DataSourceClient, ZoneFinder
 import isc.net.parse
 from isc.xfrin.diff import Diff
+from isc.server_common.auth_command import auth_loadzone_command
 from isc.log_messages.xfrin_messages import *
 
 isc.log.init("b10-xfrin")
@@ -1248,50 +1249,13 @@ class ZoneInfo:
                 (str(self.master_addr), self.master_port))
 
 def _do_auth_loadzone(server, zone_name, zone_class):
-    # On a successful zone transfer, if the zone is served by
-    # b10-auth in the in-memory data source using sqlite3 as a
-    # backend, send the "loadzone" command for the zone to auth.
-    datasources, is_default =\
-        server._module_cc.get_remote_config_value(AUTH_MODULE_NAME, "datasources")
-    if is_default:
-        return
-    for d in datasources:
-        if "type" not in d:
-            continue
-        try:
-            if "class" in d:
-                dclass = RRClass(d["class"])
-            else:
-                dclass = RRClass("IN")
-        except InvalidRRClass as err:
-            logger.info(XFRIN_AUTH_CONFIG_RRCLASS_ERROR, str(err))
-            continue
-
-        if d["type"].lower() == "memory" and dclass == zone_class:
-            for zone in d["zones"]:
-                if "filetype" not in zone:
-                    continue
-                if "origin" not in zone:
-                    continue
-                if "filetype" not in zone:
-                    continue
-                try:
-                    name = Name(zone["origin"])
-                except (EmptyLabel, TooLongLabel, BadLabelType, BadEscape, TooLongName, IncompleteName):
-                    logger.info(XFRIN_AUTH_CONFIG_NAME_PARSER_ERROR, str(err))
-                    continue
-
-                if zone["filetype"].lower() == "sqlite3" and name == zone_name:
-                    param = {"origin": zone_name.to_text(),
-                             "class": zone_class.to_text(),
-                             "datasrc": d["type"]}
-
-                    logger.debug(DBG_XFRIN_TRACE, XFRIN_AUTH_LOADZONE,
-                                 param["origin"], param["class"], param["datasrc"])
-
-                    msg = create_command("loadzone", param)
-                    seq = server._send_cc_session.group_sendmsg(msg, AUTH_MODULE_NAME)
-                    answer, env = server._send_cc_session.group_recvmsg(False, seq)
+    msg = auth_loadzone_command(server._module_cc, zone_name, zone_class)
+    if msg is not None:
+        param = msg['command'][1]
+        logger.debug(DBG_XFRIN_TRACE, XFRIN_AUTH_LOADZONE, param["origin"],
+                     param["class"], param["datasrc"])
+        seq = server._send_cc_session.group_sendmsg(msg, AUTH_MODULE_NAME)
+        answer, env = server._send_cc_session.group_recvmsg(False, seq)
 
 class Xfrin:
     def __init__(self):

+ 0 - 6
src/bin/xfrin/xfrin_messages.mes

@@ -15,12 +15,6 @@
 # No namespace declaration - these constants go in the global namespace
 # of the xfrin messages python module.
 
-% XFRIN_AUTH_CONFIG_NAME_PARSER_ERROR Invalid name when parsing Auth configuration: %1
-There was an invalid name when parsing Auth configuration.
-
-% XFRIN_AUTH_CONFIG_RRCLASS_ERROR Invalid RRClass when parsing Auth configuration: %1
-There was an invalid RR class when parsing Auth configuration.
-
 % XFRIN_AUTH_LOADZONE sending Auth loadzone for origin=%1, class=%2, datasrc=%3
 There was a successful zone transfer, and the zone is served by b10-auth
 in the in-memory data source using sqlite3 as a backend. We send the

+ 1 - 1
src/lib/python/isc/server_common/Makefile.am

@@ -1,6 +1,6 @@
 SUBDIRS = tests
 
-python_PYTHON = __init__.py tsig_keyring.py
+python_PYTHON = __init__.py tsig_keyring.py auth_command.py
 
 pythondir = $(pyexecdir)/isc/server_common
 

+ 94 - 0
src/lib/python/isc/server_common/auth_command.py

@@ -0,0 +1,94 @@
+# 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 is a utility to create some intermodule command(s) for Auth.'''
+
+from isc.dns import *
+import isc.log
+from isc.config.ccsession import create_command
+from isc.log_messages.server_common_messages import *
+
+# Import tsig_keyring just to share the logger.  Once #2003 is merged, this
+# should be replaced with the package level logger:
+# from isc.server_common.logger import logger
+from isc.server_common.tsig_keyring import logger
+
+AUTH_MODULE_NAME = 'Auth'
+
+def auth_loadzone_command(module_cc, zone_name, zone_class):
+    '''Create a 'loadzone' command with a given zone for Auth server.
+
+    This function checks the Auth module configuration to see if it
+    servers a given zone via an in-memory data source on top of SQLite3
+    data source, and, if so, generate an inter-module command for Auth
+    to force it to reload the zone.
+
+    Parameters:
+    module_cc (CCSession): a CC session that can get access to auth module
+      configuration as a remote configuration
+    zone_name (isc.dns.Name): the zone name to be possibly reloaded
+    zone_class (isc.dns.RRClass): the RR class of the zone to be possibly
+      reloaded.
+
+    Return: a CC command message for the reload if the zone is found;
+      otherwise None.
+
+    '''
+    # Note: this function was originally a dedicated subroutine of xfrin,
+    # but was moved here so it can be shared by some other modules
+    # (specifically, by ddns).  It's expected that we'll soon fundamentally
+    # revisit the whole data source related configuration, at which point
+    # this function should be substantially modified if not completely
+    # deprecated (which is a more likely scenario).  For this reason, the
+    # corresponding tests were still kept in xfrin.
+
+    datasources, is_default =\
+        module_cc.get_remote_config_value(AUTH_MODULE_NAME, "datasources")
+    if is_default:
+        return None
+    for d in datasources:
+        if "type" not in d:
+            continue
+        try:
+            if "class" in d:
+                dclass = RRClass(d["class"])
+            else:
+                dclass = RRClass("IN")
+        except InvalidRRClass as err:
+            logger.info(PYSERVER_COMMON_AUTH_CONFIG_RRCLASS_ERROR, err)
+            continue
+
+        if d["type"].lower() == "memory" and dclass == zone_class:
+            for zone in d["zones"]:
+                if "filetype" not in zone:
+                    continue
+                if "origin" not in zone:
+                    continue
+                if "filetype" not in zone:
+                    continue
+                try:
+                    name = Name(zone["origin"])
+                except (EmptyLabel, TooLongLabel, BadLabelType, BadEscape,
+                        TooLongName, IncompleteName):
+                    logger.info(PYSERVER_COMMON_AUTH_CONFIG_NAME_PARSER_ERROR,
+                                err)
+                    continue
+
+                if zone["filetype"].lower() == "sqlite3" and name == zone_name:
+                    param = {"origin": zone_name.to_text(),
+                             "class": zone_class.to_text(),
+                             "datasrc": d["type"]}
+                    return create_command("loadzone", param)
+    return None

+ 6 - 0
src/lib/python/isc/server_common/server_common_messages.mes

@@ -21,6 +21,12 @@
 # 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_AUTH_CONFIG_NAME_PARSER_ERROR Invalid name when parsing Auth configuration: %1
+There was an invalid name when parsing Auth configuration.
+
+% PYSERVER_COMMON_AUTH_CONFIG_RRCLASS_ERROR Invalid RRClass when parsing Auth configuration: %1
+There was an invalid RR class when parsing Auth configuration.
+
 % 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.