Parcourir la source

Merge remote-tracking branch 'origin/trac565'

Michal 'vorner' Vaner il y a 14 ans
Parent
commit
0ac0b4602f
2 fichiers modifiés avec 296 ajouts et 79 suppressions
  1. 85 7
      src/bin/bind10/bind10.py.in
  2. 211 72
      src/bin/bind10/tests/bind10_test.py

+ 85 - 7
src/bin/bind10/bind10.py.in

@@ -209,23 +209,68 @@ class BoB:
         self.ccs = None
         self.ccs = None
         self.cfg_start_auth = True
         self.cfg_start_auth = True
         self.cfg_start_resolver = False
         self.cfg_start_resolver = False
+        self.started_auth_family = False
+        self.started_resolver_family = False
         self.curproc = None
         self.curproc = None
         self.dead_processes = {}
         self.dead_processes = {}
         self.msgq_socket_file = msgq_socket_file
         self.msgq_socket_file = msgq_socket_file
         self.nocache = nocache
         self.nocache = nocache
         self.processes = {}
         self.processes = {}
+        self.expected_shutdowns = {}
         self.runnable = False
         self.runnable = False
         self.uid = setuid
         self.uid = setuid
         self.username = username
         self.username = username
         self.verbose = verbose
         self.verbose = verbose
 
 
     def config_handler(self, new_config):
     def config_handler(self, new_config):
+        # If this is initial update, don't do anything now, leave it to startup
+        if not self.runnable:
+            return
+        # Now we declare few functions used only internally here. Besides the
+        # benefit of not polluting the name space, they are closures, so we
+        # don't need to pass some variables
+        def start_stop(name, started, start, stop):
+            if not'start_' + name in new_config:
+                return
+            if new_config['start_' + name]:
+                if not started:
+                    if self.uid is not None:
+                        sys.stderr.write("[bind10] Starting " + name + " as " +
+                            "a user, not root. This might fail.\n")
+                    start()
+            else:
+                stop()
+        # These four functions are passed to start_stop (smells like functional
+        # programming little bit)
+        def resolver_on():
+            self.start_resolver(self.c_channel_env)
+            self.started_resolver_family = True
+        def resolver_off():
+            self.stop_resolver()
+            self.started_resolver_family = False
+        def auth_on():
+            self.start_auth(self.c_channel_env)
+            self.start_xfrout(self.c_channel_env)
+            self.start_xfrin(self.c_channel_env)
+            self.start_zonemgr(self.c_channel_env)
+            self.started_auth_family = True
+        def auth_off():
+            self.stop_zonemgr()
+            self.stop_xfrin()
+            self.stop_xfrout()
+            self.stop_auth()
+            self.started_auth_family = False
+
+        # The real code of the config handler function follows here
         if self.verbose:
         if self.verbose:
             sys.stdout.write("[bind10] Handling new configuration: " +
             sys.stdout.write("[bind10] Handling new configuration: " +
                 str(new_config) + "\n")
                 str(new_config) + "\n")
+        start_stop('resolver', self.started_resolver_family, resolver_on,
+            resolver_off)
+        start_stop('auth', self.started_auth_family, auth_on, auth_off)
+
         answer = isc.config.ccsession.create_answer(0)
         answer = isc.config.ccsession.create_answer(0)
         return answer
         return answer
-        # TODO
 
 
     def command_handler(self, command, args):
     def command_handler(self, command, args):
         if self.verbose:
         if self.verbose:
@@ -464,11 +509,12 @@ class BoB:
         # XXX: we hardcode port 8080
         # XXX: we hardcode port 8080
         self.start_simple("b10-cmdctl", c_channel_env, 8080)
         self.start_simple("b10-cmdctl", c_channel_env, 8080)
 
 
-    def start_all_processes(self, c_channel_env):
+    def start_all_processes(self):
         """
         """
             Starts up all the processes.  Any exception generated during the
             Starts up all the processes.  Any exception generated during the
             starting of the processes is handled by the caller.
             starting of the processes is handled by the caller.
         """
         """
+        c_channel_env = self.c_channel_env
         self.start_msgq(c_channel_env)
         self.start_msgq(c_channel_env)
         self.start_cfgmgr(c_channel_env)
         self.start_cfgmgr(c_channel_env)
         self.start_ccsession(c_channel_env)
         self.start_ccsession(c_channel_env)
@@ -485,6 +531,7 @@ class BoB:
         # ... and resolver (if selected):
         # ... and resolver (if selected):
         if self.cfg_start_resolver:
         if self.cfg_start_resolver:
             self.start_resolver(c_channel_env)
             self.start_resolver(c_channel_env)
+            self.started_resolver_family = True
 
 
         # Everything after the main components can run as non-root.
         # Everything after the main components can run as non-root.
         # TODO: this is only temporary - once the privileged socket creator is
         # TODO: this is only temporary - once the privileged socket creator is
@@ -498,6 +545,7 @@ class BoB:
             self.start_xfrout(c_channel_env)
             self.start_xfrout(c_channel_env)
             self.start_xfrin(c_channel_env)
             self.start_xfrin(c_channel_env)
             self.start_zonemgr(c_channel_env)
             self.start_zonemgr(c_channel_env)
+            self.started_auth_family = True
 
 
         # ... and finally start the remaining processes
         # ... and finally start the remaining processes
         self.start_stats(c_channel_env)
         self.start_stats(c_channel_env)
@@ -528,7 +576,8 @@ class BoB:
         # Start all processes.  If any one fails to start, kill all started
         # Start all processes.  If any one fails to start, kill all started
         # processes and exit with an error indication.
         # processes and exit with an error indication.
         try:
         try:
-            self.start_all_processes(c_channel_env)
+            self.c_channel_env = c_channel_env
+            self.start_all_processes()
         except Exception as e:
         except Exception as e:
             self.kill_started_processes()
             self.kill_started_processes()
             return "Unable to start " + self.curproc + ": " + str(e)
             return "Unable to start " + self.curproc + ": " + str(e)
@@ -550,10 +599,35 @@ class BoB:
         self.cc_session.group_sendmsg(cmd, "Zonemgr", "Zonemgr")
         self.cc_session.group_sendmsg(cmd, "Zonemgr", "Zonemgr")
         self.cc_session.group_sendmsg(cmd, "Stats", "Stats")
         self.cc_session.group_sendmsg(cmd, "Stats", "Stats")
 
 
-    def stop_process(self, process):
-        """Stop the given process, friendly-like."""
-        # XXX nothing yet
-        pass
+    def stop_process(self, process, recipient):
+        """
+        Stop the given process, friendly-like. The process is the name it has
+        (in logs, etc), the recipient is the address on msgq.
+        """
+        if self.verbose:
+            sys.stdout.write("[bind10] Asking %s to terminate\n" % process)
+        # TODO: Some timeout to solve processes that don't want to die would
+        # help. We can even store it in the dict, it is used only as a set
+        self.expected_shutdowns[process] = 1
+        # Ask the process to die willingly
+        self.cc_session.group_sendmsg({'command': ['shutdown']}, recipient,
+            recipient)
+
+    # Series of stop_process wrappers
+    def stop_resolver(self):
+        self.stop_process('b10-resolver', 'Resolver')
+
+    def stop_auth(self):
+        self.stop_process('b10-auth', 'Auth')
+
+    def stop_xfrout(self):
+        self.stop_process('b10-xfrout', 'Xfrout')
+
+    def stop_xfrin(self):
+        self.stop_process('b10-xfrin', 'Xfrin')
+
+    def stop_zonemgr(self):
+        self.stop_process('b10-zonemgr', 'Zonemgr')
 
 
     def shutdown(self):
     def shutdown(self):
         """Stop the BoB instance."""
         """Stop the BoB instance."""
@@ -659,6 +733,10 @@ class BoB:
         still_dead = {}
         still_dead = {}
         now = time.time()
         now = time.time()
         for proc_info in self.dead_processes.values():
         for proc_info in self.dead_processes.values():
+            if proc_info.name in self.expected_shutdowns:
+                # We don't restart, we wanted it to die
+                del self.expected_shutdowns[proc_info.name]
+                continue
             restart_time = proc_info.restart_schedule.get_restart_time(now)
             restart_time = proc_info.restart_schedule.get_restart_time(now)
             if restart_time > now:
             if restart_time > now:
                 if (next_restart is None) or (next_restart > restart_time):
                 if (next_restart is None) or (next_restart > restart_time):

+ 211 - 72
src/bin/bind10/tests/bind10_test.py

@@ -142,13 +142,13 @@ class TestBoB(unittest.TestCase):
         self.assertEqual(bob.cfg_start_auth, True)
         self.assertEqual(bob.cfg_start_auth, True)
         self.assertEqual(bob.cfg_start_resolver, False)
         self.assertEqual(bob.cfg_start_resolver, False)
 
 
-# Class for testing the Bob.start_all_processes() method call.
+# Class for testing the BoB start/stop components routines.
 #
 #
 # Although testing that external processes start is outside the scope
 # Although testing that external processes start is outside the scope
 # of the unit test, by overriding the process start methods we can check
 # of the unit test, by overriding the process start methods we can check
 # that the right processes are started depending on the configuration
 # that the right processes are started depending on the configuration
 # options.
 # options.
-class StartAllProcessesBob(BoB):
+class StartStopCheckBob(BoB):
     def __init__(self):
     def __init__(self):
         BoB.__init__(self)
         BoB.__init__(self)
 
 
@@ -163,6 +163,7 @@ class StartAllProcessesBob(BoB):
         self.zonemgr = False
         self.zonemgr = False
         self.stats = False
         self.stats = False
         self.cmdctl = False
         self.cmdctl = False
+        self.c_channel_env = {}
 
 
     def read_bind10_config(self):
     def read_bind10_config(self):
         # Configuration options are set directly
         # Configuration options are set directly
@@ -198,118 +199,256 @@ class StartAllProcessesBob(BoB):
     def start_cmdctl(self, c_channel_env):
     def start_cmdctl(self, c_channel_env):
         self.cmdctl = True
         self.cmdctl = True
 
 
-# Check that the start_all_processes method starts the right combination
-# of processes.
-class TestStartAllProcessesBob(unittest.TestCase):
+    # We don't really use all of these stop_ methods. But it might turn out
+    # someone would add some stop_ method to BoB and we want that one overriden
+    # in case he forgets to update the tests.
+    def stop_msgq(self):
+        self.msgq = False
+
+    def stop_cfgmgr(self):
+        self.cfgmgr = False
+
+    def stop_ccsession(self):
+        self.ccsession = False
+
+    def stop_auth(self):
+        self.auth = False
+
+    def stop_resolver(self):
+        self.resolver = False
+
+    def stop_xfrout(self):
+        self.xfrout = False
+
+    def stop_xfrin(self):
+        self.xfrin = False
+
+    def stop_zonemgr(self):
+        self.zonemgr = False
+
+    def stop_stats(self):
+        self.stats = False
+
+    def stop_cmdctl(self):
+        self.cmdctl = False
+
+class TestStartStopProcessesBob(unittest.TestCase):
+    """
+    Check that the start_all_processes method starts the right combination
+    of processes and that the right processes are started and stopped
+    according to changes in configuration.
+    """
+    def check_started(self, bob, core, auth, resolver):
+        """
+        Check that the right sets of services are started. The ones that
+        should be running are specified by the core, auth and resolver parameters
+        (they are groups of processes, eg. auth means b10-auth, -xfrout, -xfrin
+        and -zonemgr).
+        """
+        self.assertEqual(bob.msgq, core)
+        self.assertEqual(bob.cfgmgr, core)
+        self.assertEqual(bob.ccsession, core)
+        self.assertEqual(bob.auth, auth)
+        self.assertEqual(bob.resolver, resolver)
+        self.assertEqual(bob.xfrout, auth)
+        self.assertEqual(bob.xfrin, auth)
+        self.assertEqual(bob.zonemgr, auth)
+        self.assertEqual(bob.stats, core)
+        self.assertEqual(bob.cmdctl, core)
+
     def check_preconditions(self, bob):
     def check_preconditions(self, bob):
-        self.assertEqual(bob.msgq, False)
-        self.assertEqual(bob.cfgmgr, False)
-        self.assertEqual(bob.ccsession, False)
-        self.assertEqual(bob.auth, False)
-        self.assertEqual(bob.resolver, False)
-        self.assertEqual(bob.xfrout, False)
-        self.assertEqual(bob.xfrin, False)
-        self.assertEqual(bob.zonemgr, False)
-        self.assertEqual(bob.stats, False)
-        self.assertEqual(bob.cmdctl, False)
+        self.check_started(bob, False, False, False)
+
+    def check_started_none(self, bob):
+        """
+        Check that the situation is according to configuration where no servers
+        should be started. Some processes still need to be running.
+        """
+        self.check_started(bob, True, False, False)
+
+    def check_started_both(self, bob):
+        """
+        Check the situation is according to configuration where both servers
+        (auth and resolver) are enabled.
+        """
+        self.check_started(bob, True, True, True)
+
+    def check_started_auth(self, bob):
+        """
+        Check the set of processes needed to run auth only is started.
+        """
+        self.check_started(bob, True, True, False)
+
+    def check_started_resolver(self, bob):
+        """
+        Check the set of processes needed to run resolver only is started.
+        """
+        self.check_started(bob, True, False, True)
 
 
     # Checks the processes started when starting neither auth nor resolver
     # Checks the processes started when starting neither auth nor resolver
     # is specified.
     # is specified.
     def test_start_none(self):
     def test_start_none(self):
-        # Created Bob and ensure initialization correct
-        bob = StartAllProcessesBob()
+        # Create BoB and ensure correct initialization
+        bob = StartStopCheckBob()
         self.check_preconditions(bob)
         self.check_preconditions(bob)
 
 
         # Start processes and check what was started
         # Start processes and check what was started
-        c_channel_env = {}
         bob.cfg_start_auth = False
         bob.cfg_start_auth = False
         bob.cfg_start_resolver = False
         bob.cfg_start_resolver = False
 
 
-        bob.start_all_processes(c_channel_env)
-
-        self.assertEqual(bob.msgq, True)
-        self.assertEqual(bob.cfgmgr, True)
-        self.assertEqual(bob.ccsession, True)
-        self.assertEqual(bob.auth, False)
-        self.assertEqual(bob.resolver, False)
-        self.assertEqual(bob.xfrout, False)
-        self.assertEqual(bob.xfrin, False)
-        self.assertEqual(bob.zonemgr, False)
-        self.assertEqual(bob.stats, True)
-        self.assertEqual(bob.cmdctl, True)
+        bob.start_all_processes()
+        self.check_started_none(bob)
 
 
     # Checks the processes started when starting only the auth process
     # Checks the processes started when starting only the auth process
     def test_start_auth(self):
     def test_start_auth(self):
-        # Created Bob and ensure initialization correct
-        bob = StartAllProcessesBob()
+        # Create BoB and ensure correct initialization
+        bob = StartStopCheckBob()
         self.check_preconditions(bob)
         self.check_preconditions(bob)
 
 
         # Start processes and check what was started
         # Start processes and check what was started
-        c_channel_env = {}
         bob.cfg_start_auth = True
         bob.cfg_start_auth = True
         bob.cfg_start_resolver = False
         bob.cfg_start_resolver = False
 
 
-        bob.start_all_processes(c_channel_env)
+        bob.start_all_processes()
 
 
-        self.assertEqual(bob.msgq, True)
-        self.assertEqual(bob.cfgmgr, True)
-        self.assertEqual(bob.ccsession, True)
-        self.assertEqual(bob.auth, True)
-        self.assertEqual(bob.resolver, False)
-        self.assertEqual(bob.xfrout, True)
-        self.assertEqual(bob.xfrin, True)
-        self.assertEqual(bob.zonemgr, True)
-        self.assertEqual(bob.stats, True)
-        self.assertEqual(bob.cmdctl, True)
+        self.check_started_auth(bob)
 
 
     # Checks the processes started when starting only the resolver process
     # Checks the processes started when starting only the resolver process
     def test_start_resolver(self):
     def test_start_resolver(self):
-        # Created Bob and ensure initialization correct
-        bob = StartAllProcessesBob()
+        # Create BoB and ensure correct initialization
+        bob = StartStopCheckBob()
         self.check_preconditions(bob)
         self.check_preconditions(bob)
 
 
         # Start processes and check what was started
         # Start processes and check what was started
-        c_channel_env = {}
         bob.cfg_start_auth = False
         bob.cfg_start_auth = False
         bob.cfg_start_resolver = True
         bob.cfg_start_resolver = True
 
 
-        bob.start_all_processes(c_channel_env)
+        bob.start_all_processes()
 
 
-        self.assertEqual(bob.msgq, True)
-        self.assertEqual(bob.cfgmgr, True)
-        self.assertEqual(bob.ccsession, True)
-        self.assertEqual(bob.auth, False)
-        self.assertEqual(bob.resolver, True)
-        self.assertEqual(bob.xfrout, False)
-        self.assertEqual(bob.xfrin, False)
-        self.assertEqual(bob.zonemgr, False)
-        self.assertEqual(bob.stats, True)
-        self.assertEqual(bob.cmdctl, True)
+        self.check_started_resolver(bob)
 
 
     # Checks the processes started when starting both auth and resolver process
     # Checks the processes started when starting both auth and resolver process
     def test_start_both(self):
     def test_start_both(self):
-        # Created Bob and ensure initialization correct
-        bob = StartAllProcessesBob()
+        # Create BoB and ensure correct initialization
+        bob = StartStopCheckBob()
         self.check_preconditions(bob)
         self.check_preconditions(bob)
 
 
         # Start processes and check what was started
         # Start processes and check what was started
-        c_channel_env = {}
         bob.cfg_start_auth = True
         bob.cfg_start_auth = True
         bob.cfg_start_resolver = True
         bob.cfg_start_resolver = True
 
 
-        bob.start_all_processes(c_channel_env)
+        bob.start_all_processes()
+
+        self.check_started_both(bob)
+
+    def test_config_start(self):
+        """
+        Test that the configuration starts and stops processes according
+        to configuration changes.
+        """
+
+        # Create BoB and ensure correct initialization
+        bob = StartStopCheckBob()
+        self.check_preconditions(bob)
+
+        # Start processes (nothing much should be started, as in
+        # test_start_none)
+        bob.cfg_start_auth = False
+        bob.cfg_start_resolver = False
+
+        bob.start_all_processes()
+        bob.runnable = True
+        self.check_started_none(bob)
+
+        # Enable both at once
+        bob.config_handler({'start_auth': True, 'start_resolver': True})
+        self.check_started_both(bob)
+
+        # Not touched by empty change
+        bob.config_handler({})
+        self.check_started_both(bob)
+
+        # Not touched by change to the same configuration
+        bob.config_handler({'start_auth': True, 'start_resolver': True})
+        self.check_started_both(bob)
+
+        # Turn them both off again
+        bob.config_handler({'start_auth': False, 'start_resolver': False})
+        self.check_started_none(bob)
+
+        # Not touched by empty change
+        bob.config_handler({})
+        self.check_started_none(bob)
+
+        # Not touched by change to the same configuration
+        bob.config_handler({'start_auth': False, 'start_resolver': False})
+        self.check_started_none(bob)
+
+        # Start and stop auth separately
+        bob.config_handler({'start_auth': True})
+        self.check_started_auth(bob)
+
+        bob.config_handler({'start_auth': False})
+        self.check_started_none(bob)
+
+        # Start and stop resolver separately
+        bob.config_handler({'start_resolver': True})
+        self.check_started_resolver(bob)
+
+        bob.config_handler({'start_resolver': False})
+        self.check_started_none(bob)
+
+        # Alternate
+        bob.config_handler({'start_auth': True})
+        self.check_started_auth(bob)
+
+        bob.config_handler({'start_auth': False, 'start_resolver': True})
+        self.check_started_resolver(bob)
+
+        bob.config_handler({'start_auth': True, 'start_resolver': False})
+        self.check_started_auth(bob)
+
+    def test_config_start_once(self):
+        """
+        Tests that a process is started only once.
+        """
+        # Create BoB and ensure correct initialization
+        bob = StartStopCheckBob()
+        self.check_preconditions(bob)
+
+        # Start processes (both)
+        bob.cfg_start_auth = True
+        bob.cfg_start_resolver = True
+
+        bob.start_all_processes()
+        bob.runnable = True
+        self.check_started_both(bob)
+
+        bob.start_auth = lambda: self.fail("Started auth again")
+        bob.start_xfrout = lambda: self.fail("Started xfrout again")
+        bob.start_xfrin = lambda: self.fail("Started xfrin again")
+        bob.start_zonemgr = lambda: self.fail("Started zonemgr again")
+        bob.start_resolver = lambda: self.fail("Started resolver again")
+
+        # Send again we want to start them. Should not do it, as they are.
+        bob.config_handler({'start_auth': True})
+        bob.config_handler({'start_resolver': True})
+
+    def test_config_not_started_early(self):
+        """
+        Test that processes are not started by the config handler before
+        startup.
+        """
+        bob = StartStopCheckBob()
+        self.check_preconditions(bob)
 
 
-        self.assertEqual(bob.msgq, True)
-        self.assertEqual(bob.cfgmgr, True)
-        self.assertEqual(bob.ccsession, True)
-        self.assertEqual(bob.auth, True)
-        self.assertEqual(bob.resolver, True)
-        self.assertEqual(bob.xfrout, True)
-        self.assertEqual(bob.xfrin, True)
-        self.assertEqual(bob.zonemgr, True)
-        self.assertEqual(bob.stats, True)
-        self.assertEqual(bob.cmdctl, True)
+        bob.start_auth = lambda: self.fail("Started auth again")
+        bob.start_xfrout = lambda: self.fail("Started xfrout again")
+        bob.start_xfrin = lambda: self.fail("Started xfrin again")
+        bob.start_zonemgr = lambda: self.fail("Started zonemgr again")
+        bob.start_resolver = lambda: self.fail("Started resolver again")
 
 
+        bob.config_handler({'start_auth': True, 'start_resolver': True})
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
     unittest.main()
     unittest.main()