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
 from isc.notify.notify_out import ZONE_NEW_DATA_READY_CMD
 import isc.server_common.tsig_keyring
 import isc.server_common.tsig_keyring
 from isc.datasrc import DataSourceClient
 from isc.datasrc import DataSourceClient
+from isc.server_common.auth_command import auth_loadzone_command
 import select
 import select
 import errno
 import errno
 
 
@@ -82,6 +83,7 @@ isc.util.process.rename()
 
 
 # Cooperating modules
 # Cooperating modules
 XFROUT_MODULE_NAME = 'Xfrout'
 XFROUT_MODULE_NAME = 'Xfrout'
+AUTH_MODULE_NAME = 'Auth'
 
 
 class DDNSConfigError(Exception):
 class DDNSConfigError(Exception):
     '''An exception indicating an error in updating ddns configuration.
     '''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(),
         ret = self.__send_response(sock, self.__response_renderer.get_data(),
                                    remote_addr)
                                    remote_addr)
         if result == isc.ddns.session.UPDATE_SUCCESS:
         if result == isc.ddns.session.UPDATE_SUCCESS:
-            self.__notify_update(zname, zclass)
+            self.__notify_auth(zname, zclass)
+            self.__notify_xfrout(zname, zclass)
         return ret
         return ret
 
 
     def __send_response(self, sock, data, dest):
     def __send_response(self, sock, data, dest):
@@ -409,8 +412,20 @@ class DDNSServer:
 
 
         return True
         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
         Note that we use blocking communication here.  While the internal
         communication bus is generally expected to be pretty responsive and
         communication bus is generally expected to be pretty responsive and
@@ -428,9 +443,6 @@ class DDNSServer:
         of the cc session.
         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:
         try:
             seq = self._cc._session.group_sendmsg(msg, modname)
             seq = self._cc._session.group_sendmsg(msg, modname)
             answer, _ = self._cc._session.group_recvmsg(False, seq)
             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
         self._stopped = False
         # Used as the return value of get_remote_config_value.  Customizable.
         # Used as the return value of get_remote_config_value.  Customizable.
         self.auth_db_file = READ_ZONE_DB_FILE
         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
         # faked cc channel, providing group_send/recvmsg itself.  The following
         # attributes are for inspection/customization in tests.
         # attributes are for inspection/customization in tests.
         self._session = self
         self._session = self
@@ -190,6 +192,11 @@ class MyCCSession(isc.config.ConfigData):
     def get_remote_config_value(self, module_name, item):
     def get_remote_config_value(self, module_name, item):
         if module_name == "Auth" and item == "database_file":
         if module_name == "Auth" and item == "database_file":
             return self.auth_db_file, False
             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):
     def group_sendmsg(self, msg, group):
         # remember the passed parameter, and return dummy sequence
         # 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))
         num_rrsets = len(self.__req_message.get_section(SECTION_PREREQUISITE))
         self.assertEqual(2, num_rrsets)
         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.'''
         '''Check post update communication with other modules.'''
         # iff the update succeeds, b10-ddns should tell interested other
         # 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>,
         # {'command': ['notify', {'zone_name': <updated_zone_name>,
         #                         'zone_class', <updated_zone_class>}]}
         #                         '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().
         # and expect an answer by calling group_recvmsg().
         #
         #
         # expect_recv indicates the expected number of calls to
         # 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:
         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)
             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']
             sent_cmd = sent_msg['command']
             self.assertEqual('Xfrout', sent_group)
             self.assertEqual('Xfrout', sent_group)
             self.assertEqual('notify', sent_cmd[0])
             self.assertEqual('notify', sent_cmd[0])
@@ -815,8 +844,32 @@ class TestDDNSSession(unittest.TestCase):
         self.__cc_session._sendmsg_exception = RuntimeError('unexpected')
         self.__cc_session._sendmsg_exception = RuntimeError('unexpected')
         self.assertRaises(RuntimeError, self.check_session)
         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):
     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 don't have to explore various cases in detail in this test.
         We're just checking if the expected configured objects are passed
         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
 from isc.datasrc import DataSourceClient, ZoneFinder
 import isc.net.parse
 import isc.net.parse
 from isc.xfrin.diff import Diff
 from isc.xfrin.diff import Diff
+from isc.server_common.auth_command import auth_loadzone_command
 from isc.log_messages.xfrin_messages import *
 from isc.log_messages.xfrin_messages import *
 
 
 isc.log.init("b10-xfrin")
 isc.log.init("b10-xfrin")
@@ -1248,50 +1249,13 @@ class ZoneInfo:
                 (str(self.master_addr), self.master_port))
                 (str(self.master_addr), self.master_port))
 
 
 def _do_auth_loadzone(server, zone_name, zone_class):
 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:
 class Xfrin:
     def __init__(self):
     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
 # No namespace declaration - these constants go in the global namespace
 # of the xfrin messages python module.
 # 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
 % 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
 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
 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
 SUBDIRS = tests
 
 
-python_PYTHON = __init__.py tsig_keyring.py
+python_PYTHON = __init__.py tsig_keyring.py auth_command.py
 
 
 pythondir = $(pyexecdir)/isc/server_common
 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
 # 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
 # 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
 % PYSERVER_COMMON_TSIG_KEYRING_DEINIT Deinitializing global TSIG keyring
 A debug message noting that the global TSIG keyring is being removed from
 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.
 memory. Most programs don't do that, they just exit, which is OK.