Browse Source

Merge branch 'trac213-incremental-config' into trac213-incremental

Conflicts:
	src/bin/bind10/bind10_messages.mes
	src/bin/bind10/bind10_src.py.in
	src/bin/bind10/tests/bind10_test.py.in
Michal 'vorner' Vaner 13 years ago
parent
commit
bde035f1eb

+ 0 - 8
src/bin/bind10/bind10_messages.mes

@@ -20,14 +20,6 @@ The boss process is starting up and will now check if the message bus
 daemon is already running. If so, it will not be able to start, as it
 daemon is already running. If so, it will not be able to start, as it
 needs a dedicated message bus.
 needs a dedicated message bus.
 
 
-% BIND10_CONFIGURATION_START_AUTH start authoritative server: %1
-This message shows whether or not the authoritative server should be
-started according to the configuration.
-
-% BIND10_CONFIGURATION_START_RESOLVER start resolver: %1
-This message shows whether or not the resolver should be
-started according to the configuration.
-
 % BIND10_INVALID_STATISTICS_DATA invalid specification of statistics data specified
 % BIND10_INVALID_STATISTICS_DATA invalid specification of statistics data specified
 An error was encountered when the boss module specified
 An error was encountered when the boss module specified
 statistics data which is invalid for the boss specification file.
 statistics data which is invalid for the boss specification file.

+ 17 - 120
src/bin/bind10/bind10_src.py.in

@@ -311,51 +311,14 @@ class BoB:
         # If this is initial update, don't do anything now, leave it to startup
         # If this is initial update, don't do anything now, leave it to startup
         if not self.runnable:
         if not self.runnable:
             return
             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, start, stop):
-            if 'start_' + name in new_config:
-                if new_config['start_' + name]:
-                    start()
-                else:
-                    stop()
-        # These four functions are passed to start_stop (smells like functional
-        # programming little bit)
-        def resolver_on():
-            self.component_config['b10-resolver'] = { 'kind': 'needed',
-                                                      'special': 'resolver' }
-        def resolver_off():
-            if 'b10-resolver' in self.component_config:
-                del self.component_config['b10-resolver']
-        def auth_on():
-            self.component_config['b10-auth'] = { 'kind': 'needed',
-                                                  'special': 'auth' }
-            self.component_config['b10-xfrout'] = { 'kind': 'dispensable',
-                                                    'address': 'Xfrout' }
-            self.component_config['b10-xfrin'] = { 'kind': 'dispensable',
-                                                   'special': 'xfrin' }
-            self.component_config['b10-zonemgr'] = { 'kind': 'dispensable',
-                                                     'address': 'Zonemgr' }
-        def auth_off():
-            if 'b10-zonemgr' in self.component_config:
-                del self.component_config['b10-zonemgr']
-            if 'b10-xfrin' in self.component_config:
-                del self.component_config['b10-xfrin']
-            if 'b10-xfrout' in self.component_config:
-                del self.component_config['b10-xfrout']
-            if 'b10-auth' in self.component_config:
-                del self.component_config['b10-auth']
-
-        # The real code of the config handler function follows here
         logger.debug(DBG_COMMANDS, BIND10_RECEIVED_NEW_CONFIGURATION,
         logger.debug(DBG_COMMANDS, BIND10_RECEIVED_NEW_CONFIGURATION,
                      new_config)
                      new_config)
-        start_stop('resolver', resolver_on, resolver_off)
-        start_stop('auth', auth_on, auth_off)
-        self.__propagate_component_config(self.component_config)
-
-        answer = isc.config.ccsession.create_answer(0)
-        return answer
+        try:
+            if 'components' in new_config:
+                self.__propagate_component_config(new_config['components'])
+            return isc.config.ccsession.create_answer(0)
+        except Exception as e:
+            return isc.config.ccsession.create_answer(1, str(e))
 
 
     def get_processes(self):
     def get_processes(self):
         pids = list(self.components.keys())
         pids = list(self.components.keys())
@@ -424,24 +387,20 @@ class BoB:
             self.components[pid].kill(True)
             self.components[pid].kill(True)
         self.components = {}
         self.components = {}
 
 
-    def read_bind10_config(self):
+    def _read_bind10_config(self):
         """
         """
             Reads the parameters associated with the BoB module itself.
             Reads the parameters associated with the BoB module itself.
 
 
-            At present these are the components to start although arguably this
-            information should be in the configuration for the appropriate
-            module itself. (However, this would cause difficulty in the case of
-            xfrin/xfrout and zone manager as we don't need to start those if we
-            are not running the authoritative server.)
+            This means the list of components we should start now.
+
+            This could easily be combined into start_all_processes, but
+            it stays because of historical reasons and because the tests
+            replace the method sometimes.
         """
         """
         logger.info(BIND10_READING_BOSS_CONFIGURATION)
         logger.info(BIND10_READING_BOSS_CONFIGURATION)
 
 
         config_data = self.ccs.get_full_config()
         config_data = self.ccs.get_full_config()
-        self.cfg_start_auth = config_data.get("start_auth")
-        self.cfg_start_resolver = config_data.get("start_resolver")
-
-        logger.info(BIND10_CONFIGURATION_START_AUTH, self.cfg_start_auth)
-        logger.info(BIND10_CONFIGURATION_START_RESOLVER, self.cfg_start_resolver)
+        self.__propagate_component_config(config_data['components'])
 
 
     def log_starting(self, process, port = None, address = None):
     def log_starting(self, process, port = None, address = None):
         """
         """
@@ -720,60 +679,14 @@ class BoB:
 
 
         # Connect to the msgq. This is not a process, so it's not handled
         # Connect to the msgq. This is not a process, so it's not handled
         # inside the configurator.
         # inside the configurator.
-        c_channel_env = self.c_channel_env
-        self.start_ccsession(c_channel_env)
+        self.start_ccsession(self.c_channel_env)
 
 
         # Extract the parameters associated with Bob.  This can only be
         # Extract the parameters associated with Bob.  This can only be
         # done after the CC Session is started.  Note that the logging
         # done after the CC Session is started.  Note that the logging
         # configuration may override the "-v" switch set on the command line.
         # configuration may override the "-v" switch set on the command line.
-        self.read_bind10_config()
-
-        # Continue starting the components.  The authoritative server (if
-        # selected):
-        component_config = {}
-        if self.cfg_start_auth:
-            component_config['b10-auth'] = { 'kind': 'needed',
-                                             'special': 'auth' }
-            self.__propagate_component_config(component_config)
-
-        # ... and resolver (if selected):
-        if self.cfg_start_resolver:
-            component_config['b10-resolver'] = { 'kind': 'needed',
-                                                 'special': 'resolver' }
-            self.__propagate_component_config(component_config)
-
-        # Everything after the main components can run as non-root.
-        # TODO: this is only temporary - once the privileged socket creator is
-        # fully working, nothing else will run as root.
-        if self.uid is not None:
-            posix.setuid(self.uid)
-
-        # xfrin/xfrout and the zone manager are only meaningful if the
-        # authoritative server has been started.
-        if self.cfg_start_auth:
-            component_config['b10-xfrout'] = { 'kind': 'dispensable',
-                                               'address': 'Xfrout' }
-            component_config['b10-xfrin'] = { 'kind': 'dispensable',
-                                              'special': 'xfrin' }
-            component_config['b10-zonemgr'] = { 'kind': 'dispensable',
-                                              'address': 'Zonemgr' }
-            self.__propagate_component_config(component_config)
-
-        # ... and finally start the remaining components
-        component_config['b10-stats'] = { 'kind': 'dispensable',
-                                          'address': 'Stats' }
-        component_config['b10-stats-httpd'] = { 'kind': 'dispensable',
-                                                'address': 'StatsHttpd' }
-        component_config['b10-cmdctl'] = { 'kind': 'needed',
-                                           'special': 'cmdctl' }
-
-        if self.cfg_start_dhcp6:
-            component_config['b10-dhcp6'] = { 'kind': 'dispensable',
-                                              'address': 'DHCP6' }
-
-        self.__propagate_component_config(component_config)
-
-        self.component_config = component_config
+        self._read_bind10_config()
+
+        # TODO: Return the dropping of privileges
 
 
     def startup(self):
     def startup(self):
         """
         """
@@ -838,22 +751,6 @@ class BoB:
         else:
         else:
             self.runnable = False
             self.runnable = False
 
 
-    # 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."""
         logger.info(BIND10_SHUTDOWN)
         logger.info(BIND10_SHUTDOWN)

+ 59 - 9
src/bin/bind10/bob.spec

@@ -4,16 +4,66 @@
     "module_description": "Master process",
     "module_description": "Master process",
     "config_data": [
     "config_data": [
       {
       {
-        "item_name": "start_auth",
-        "item_type": "boolean",
+        "item_name": "components",
+        "item_type": "named_set",
         "item_optional": false,
         "item_optional": false,
-        "item_default": true
-      },
-      {
-        "item_name": "start_resolver",
-        "item_type": "boolean",
-        "item_optional": false,
-        "item_default": false
+        "item_default": {
+          "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+          "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
+          "b10-auth": { "special": "auth", "kind": "needed" },
+          "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+          "b10-stats": { "address": "Stats", "kind": "dispensable" },
+          "b10-stats-httpd": {
+            "address": "StatsHttpd",
+            "kind": "dispensable"
+          },
+          "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+        },
+        "named_set_item_spec": {
+          "item_name": "component",
+          "item_type": "map",
+          "item_optional": false,
+          "item_default": { },
+          "map_item_spec": [
+            {
+              "item_name": "special",
+              "item_optional": true,
+              "item_type": "string"
+            },
+            {
+              "item_name": "process",
+              "item_optional": true,
+              "item_type": "string"
+            },
+            {
+              "item_name": "kind",
+              "item_optional": false,
+              "item_type": "string",
+              "item_default": "dispensable"
+            },
+            {
+              "item_name": "address",
+              "item_optional": true,
+              "item_type": "string"
+            },
+            {
+              "item_name": "params",
+              "item_optional": true,
+              "item_type": "list",
+              "list_item_spec": {
+                "item_name": "param",
+                "item_optional": false,
+                "item_type": "string",
+                "item_default": ""
+              }
+            },
+            {
+              "item_name": "priority",
+              "item_optional": true,
+              "item_type": "integer"
+            }
+          ]
+        }
       }
       }
     ],
     ],
     "commands": [
     "commands": [

+ 229 - 98
src/bin/bind10/tests/bind10_test.py.in

@@ -244,7 +244,7 @@ class MockBob(BoB):
     def stop_creator(self, kill=False):
     def stop_creator(self, kill=False):
         self.creator = False
         self.creator = False
 
 
-    def read_bind10_config(self):
+    def _read_bind10_config(self):
         # Configuration options are set directly
         # Configuration options are set directly
         pass
         pass
 
 
@@ -278,7 +278,6 @@ class MockBob(BoB):
 
 
     def start_simple(self, name):
     def start_simple(self, name):
         procmap = { 'b10-xfrout': self.start_xfrout,
         procmap = { 'b10-xfrout': self.start_xfrout,
-                    'b10-xfrin': self.start_xfrin,
                     'b10-zonemgr': self.start_zonemgr,
                     'b10-zonemgr': self.start_zonemgr,
                     'b10-stats': self.start_stats,
                     'b10-stats': self.start_stats,
                     'b10-stats-httpd': self.start_stats_httpd,
                     'b10-stats-httpd': self.start_stats_httpd,
@@ -324,13 +323,13 @@ class MockBob(BoB):
         return procinfo
         return procinfo
 
 
     def start_dhcp6(self):
     def start_dhcp6(self):
-        self.stats = True
+        self.dhcp6 = True
         procinfo = ProcessInfo('b10-dhcp6', ['/bin/false'])
         procinfo = ProcessInfo('b10-dhcp6', ['/bin/false'])
         procinfo.pid = 13
         procinfo.pid = 13
         return procinfo
         return procinfo
 
 
     def start_dhcp4(self):
     def start_dhcp4(self):
-        self.stats = True
+        self.dhcp4 = True
         procinfo = ProcessInfo('b10-dhcp4', ['/bin/false'])
         procinfo = ProcessInfo('b10-dhcp4', ['/bin/false'])
         procinfo.pid = 14
         procinfo.pid = 14
         return procinfo
         return procinfo
@@ -346,9 +345,7 @@ class MockBob(BoB):
                     'b10-cmdctl': self.stop_cmdctl }
                     'b10-cmdctl': self.stop_cmdctl }
         procmap[process]()
         procmap[process]()
 
 
-    # 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.
+    # Some functions to pretend we stop processes, use by stop_process
     def stop_msgq(self):
     def stop_msgq(self):
         if self.msgq:
         if self.msgq:
             del self.components[2]
             del self.components[2]
@@ -467,70 +464,61 @@ class TestStartStopProcessesBob(unittest.TestCase):
         """
         """
         Check if proper combinations of DHCPv4 and DHCpv6 can be started
         Check if proper combinations of DHCPv4 and DHCpv6 can be started
         """
         """
-        v4found = 'b10-dhcp4' in bob.component_config
-        v6found = 'b10-dhcp6' in bob.component_config
-
-        # there should be exactly one DHCPv4 daemon (if v4==True)
-        # there should be exactly one DHCPv6 daemon (if v6==True)
-        self.assertEqual(v4==True, v4found==1)
-        self.assertEqual(v6==True, v6found==1)
+        self.assertEqual(v4, bob.dhcp4)
+        self.assertEqual(v6, bob.dhcp6)
         self.check_environment_unchanged()
         self.check_environment_unchanged()
 
 
-    # Checks the components started when starting neither auth nor resolver
-    # is specified.
-    def test_start_none(self):
-        # Create BoB and ensure correct initialization
-        bob = MockBob()
-        self.check_preconditions(bob)
-
-        # Start components and check what was started
-        bob.cfg_start_auth = False
-        bob.cfg_start_resolver = False
-
-        bob.start_all_components()
-        self.check_started_none(bob)
-
-    # Checks the components started when starting only the auth process
-    def test_start_auth(self):
-        # Create BoB and ensure correct initialization
+    def construct_config(self, start_auth, start_resolver):
+        # The things that are common, not turned on an off
+        config = {}
+        config['b10-stats'] = { 'kind': 'dispensable', 'address': 'Stats' }
+        config['b10-stats-httpd'] = { 'kind': 'dispensable',
+                                      'address': 'StatsHttpd' }
+        config['b10-cmdctl'] = { 'kind': 'needed', 'special': 'cmdctl' }
+        if start_auth:
+            config['b10-auth'] = { 'kind': 'needed', 'special': 'auth' }
+            config['b10-xfrout'] = { 'kind': 'dispensable',
+                                     'address': 'Xfrout' }
+            config['b10-xfrin'] = { 'kind': 'dispensable', 'special': 'xfrin' }
+            config['b10-zonemgr'] = { 'kind': 'dispensable',
+                                      'address': 'Zonemgr' }
+        if start_resolver:
+            config['b10-resolver'] = { 'kind': 'needed',
+                                       'special': 'resolver' }
+        return {'components': config}
+
+    def config_start_init(self, start_auth, start_resolver):
+        """
+        Test the configuration is loaded at the startup.
+        """
         bob = MockBob()
         bob = MockBob()
-        self.check_preconditions(bob)
-
-        # Start components and check what was started
-        bob.cfg_start_auth = True
-        bob.cfg_start_resolver = False
-
+        config = self.construct_config(start_auth, start_resolver)
+        class CC:
+            def get_full_config(self):
+                return config
+        # Provide the fake CC with data
+        bob.ccs = CC()
+        # And make sure it's not overwritten
+        def start_ccsession():
+            bob.ccsession = True
+        bob.start_ccsession = lambda _: start_ccsession()
+        # We need to return the original _read_bind10_config
+        bob._read_bind10_config = lambda: BoB._read_bind10_config(bob)
         bob.start_all_components()
         bob.start_all_components()
+        self.check_started(bob, True, start_auth, start_resolver)
+        self.check_environment_unchanged()
 
 
-        self.check_started_auth(bob)
+    def test_start_none(self):
+        self.config_start_init(False, False)
 
 
-    # Checks the components started when starting only the resolver process
     def test_start_resolver(self):
     def test_start_resolver(self):
-        # Create BoB and ensure correct initialization
-        bob = MockBob()
-        self.check_preconditions(bob)
-
-        # Start components and check what was started
-        bob.cfg_start_auth = False
-        bob.cfg_start_resolver = True
-
-        bob.start_all_components()
+        self.config_start_init(False, True)
 
 
-        self.check_started_resolver(bob)
+    def test_start_auth(self):
+        self.config_start_init(True, False)
 
 
-    # Checks the components started when starting both auth and resolver process
     def test_start_both(self):
     def test_start_both(self):
-        # Create BoB and ensure correct initialization
-        bob = MockBob()
-        self.check_preconditions(bob)
-
-        # Start components and check what was started
-        bob.cfg_start_auth = True
-        bob.cfg_start_resolver = True
-
-        bob.start_all_components()
-
-        self.check_started_both(bob)
+        self.config_start_init(True, True)
 
 
     def test_config_start(self):
     def test_config_start(self):
         """
         """
@@ -542,17 +530,14 @@ class TestStartStopProcessesBob(unittest.TestCase):
         bob = MockBob()
         bob = MockBob()
         self.check_preconditions(bob)
         self.check_preconditions(bob)
 
 
-        # Start components (nothing much should be started, as in
-        # test_start_none)
-        bob.cfg_start_auth = False
-        bob.cfg_start_resolver = False
-
         bob.start_all_components()
         bob.start_all_components()
         bob.runnable = True
         bob.runnable = True
+        bob._BoB_started = True
+        bob.config_handler(self.construct_config(False, False))
         self.check_started_none(bob)
         self.check_started_none(bob)
 
 
         # Enable both at once
         # Enable both at once
-        bob.config_handler({'start_auth': True, 'start_resolver': True})
+        bob.config_handler(self.construct_config(True, True))
         self.check_started_both(bob)
         self.check_started_both(bob)
 
 
         # Not touched by empty change
         # Not touched by empty change
@@ -560,11 +545,11 @@ class TestStartStopProcessesBob(unittest.TestCase):
         self.check_started_both(bob)
         self.check_started_both(bob)
 
 
         # Not touched by change to the same configuration
         # Not touched by change to the same configuration
-        bob.config_handler({'start_auth': True, 'start_resolver': True})
+        bob.config_handler(self.construct_config(True, True))
         self.check_started_both(bob)
         self.check_started_both(bob)
 
 
         # Turn them both off again
         # Turn them both off again
-        bob.config_handler({'start_auth': False, 'start_resolver': False})
+        bob.config_handler(self.construct_config(False, False))
         self.check_started_none(bob)
         self.check_started_none(bob)
 
 
         # Not touched by empty change
         # Not touched by empty change
@@ -572,47 +557,46 @@ class TestStartStopProcessesBob(unittest.TestCase):
         self.check_started_none(bob)
         self.check_started_none(bob)
 
 
         # Not touched by change to the same configuration
         # Not touched by change to the same configuration
-        bob.config_handler({'start_auth': False, 'start_resolver': False})
+        bob.config_handler(self.construct_config(False, False))
         self.check_started_none(bob)
         self.check_started_none(bob)
 
 
         # Start and stop auth separately
         # Start and stop auth separately
-        bob.config_handler({'start_auth': True})
+        bob.config_handler(self.construct_config(True, False))
         self.check_started_auth(bob)
         self.check_started_auth(bob)
 
 
-        bob.config_handler({'start_auth': False})
+        bob.config_handler(self.construct_config(False, False))
         self.check_started_none(bob)
         self.check_started_none(bob)
 
 
         # Start and stop resolver separately
         # Start and stop resolver separately
-        bob.config_handler({'start_resolver': True})
+        bob.config_handler(self.construct_config(False, True))
         self.check_started_resolver(bob)
         self.check_started_resolver(bob)
 
 
-        bob.config_handler({'start_resolver': False})
+        bob.config_handler(self.construct_config(False, False))
         self.check_started_none(bob)
         self.check_started_none(bob)
 
 
         # Alternate
         # Alternate
-        bob.config_handler({'start_auth': True})
+        bob.config_handler(self.construct_config(True, False))
         self.check_started_auth(bob)
         self.check_started_auth(bob)
 
 
-        bob.config_handler({'start_auth': False, 'start_resolver': True})
+        bob.config_handler(self.construct_config(False, True))
         self.check_started_resolver(bob)
         self.check_started_resolver(bob)
 
 
-        bob.config_handler({'start_auth': True, 'start_resolver': False})
+        bob.config_handler(self.construct_config(True, False))
         self.check_started_auth(bob)
         self.check_started_auth(bob)
 
 
     def test_config_start_once(self):
     def test_config_start_once(self):
         """
         """
-        Tests that a process is started only once.
+        Tests that a component is started only once.
         """
         """
         # Create BoB and ensure correct initialization
         # Create BoB and ensure correct initialization
         bob = MockBob()
         bob = MockBob()
         self.check_preconditions(bob)
         self.check_preconditions(bob)
 
 
-        # Start components (both)
-        bob.cfg_start_auth = True
-        bob.cfg_start_resolver = True
-
         bob.start_all_components()
         bob.start_all_components()
+
+        bob._BoB_started = True
         bob.runnable = True
         bob.runnable = True
+        bob.config_handler(self.construct_config(True, True))
         self.check_started_both(bob)
         self.check_started_both(bob)
 
 
         bob.start_auth = lambda: self.fail("Started auth again")
         bob.start_auth = lambda: self.fail("Started auth again")
@@ -622,8 +606,7 @@ class TestStartStopProcessesBob(unittest.TestCase):
         bob.start_resolver = lambda: self.fail("Started resolver again")
         bob.start_resolver = lambda: self.fail("Started resolver again")
 
 
         # Send again we want to start them. Should not do it, as they are.
         # 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})
+        bob.config_handler(self.construct_config(True, True))
 
 
     def test_config_not_started_early(self):
     def test_config_not_started_early(self):
         """
         """
@@ -648,29 +631,24 @@ class TestStartStopProcessesBob(unittest.TestCase):
         bob = MockBob()
         bob = MockBob()
         self.check_preconditions(bob)
         self.check_preconditions(bob)
 
 
-        # don't care about DNS stuff
-        bob.cfg_start_auth = False
-        bob.cfg_start_resolver = False
-
-        # v4 and v6 disabled
-        bob.cfg_start_dhcp6 = False
-        bob.cfg_start_dhcp4 = False
         bob.start_all_components()
         bob.start_all_components()
+        bob._BoB_started = True
+        bob.runnable = True
+        bob.config_handler(self.construct_config(False, False))
         self.check_started_dhcp(bob, False, False)
         self.check_started_dhcp(bob, False, False)
 
 
     def test_start_dhcp_v6only(self):
     def test_start_dhcp_v6only(self):
         # Create BoB and ensure correct initialization
         # Create BoB and ensure correct initialization
         bob = MockBob()
         bob = MockBob()
         self.check_preconditions(bob)
         self.check_preconditions(bob)
-
-        # don't care about DNS stuff
-        bob.cfg_start_auth = False
-        bob.cfg_start_resolver = False
-
         # v6 only enabled
         # v6 only enabled
-        bob.cfg_start_dhcp6 = True
-        bob.cfg_start_dhcp4 = False
         bob.start_all_components()
         bob.start_all_components()
+        bob.runnable = True
+        bob._BoB_started = True
+        config = self.construct_config(False, False)
+        config['components']['b10-dhcp6'] = { 'kind': 'needed',
+                                              'address': 'Dhcp6' }
+        bob.config_handler(config)
         self.check_started_dhcp(bob, False, True)
         self.check_started_dhcp(bob, False, True)
 
 
         # uncomment when dhcpv4 becomes implemented
         # uncomment when dhcpv4 becomes implemented
@@ -857,6 +835,159 @@ class TestBrittle(unittest.TestCase):
         sys.stdout = old_stdout
         sys.stdout = old_stdout
         self.assertFalse(bob.runnable)
         self.assertFalse(bob.runnable)
 
 
+class TestBossComponents(unittest.TestCase):
+    """
+    Test the boss propagates component configuration properly to the
+    component configurator and acts sane.
+    """
+    def setUp(self):
+        self.__param = None
+        self.__called = False
+        self.__compconfig = {
+            'comp': {
+                'kind': 'needed',
+                'process': 'cat'
+            }
+        }
+
+    def __unary_hook(self, param):
+        """
+        A hook function that stores the parameter for later examination.
+        """
+        self.__param = param
+
+    def __nullary_hook(self):
+        """
+        A hook function that notes down it was called.
+        """
+        self.__called = True
+
+    def __check_core(self, config):
+        """
+        A function checking that the config contains parts for the valid
+        core component configuration.
+        """
+        self.assertIsNotNone(config)
+        for component in ['sockcreator', 'msgq', 'cfgmgr']:
+            self.assertTrue(component in config)
+            self.assertEqual(component, config[component]['special'])
+            self.assertEqual('core', config[component]['kind'])
+
+    def __check_extended(self, config):
+        """
+        This checks that the config contains the core and one more component.
+        """
+        self.__check_core(config)
+        self.assertTrue('comp' in config)
+        self.assertEqual('cat', config['comp']['process'])
+        self.assertEqual('needed', config['comp']['kind'])
+        self.assertEqual(4, len(config))
+
+    def test_correct_run(self):
+        """
+        Test the situation when we run in usual scenario, nothing fails,
+        we just start, reconfigure and then stop peacefully.
+        """
+        bob = MockBob()
+        # Start it
+        orig = bob._component_configurator.startup
+        bob._component_configurator.startup = self.__unary_hook
+        bob.start_all_components()
+        bob._component_configurator.startup = orig
+        self.__check_core(self.__param)
+        self.assertEqual(3, len(self.__param))
+
+        # Reconfigure it
+        self.__param = None
+        orig = bob._component_configurator.reconfigure
+        bob._component_configurator.reconfigure = self.__unary_hook
+        # Otherwise it does not work
+        bob.runnable = True
+        bob.config_handler({'components': self.__compconfig})
+        self.__check_extended(self.__param)
+        currconfig = self.__param
+        # If we reconfigure it, but it does not contain the components part,
+        # nothing is called
+        bob.config_handler({})
+        self.assertEqual(self.__param, currconfig)
+        self.__param = None
+        bob._component_configurator.reconfigure = orig
+        # Check a configuration that messes up the core components is rejected.
+        compconf = dict(self.__compconfig)
+        compconf['msgq'] = { 'process': 'echo' }
+        result = bob.config_handler({'components': compconf})
+        # Check it rejected it
+        self.assertEqual(1, result['result'][0])
+
+        # We can't call shutdown, that one relies on the stuff in main
+        # We check somewhere else that the shutdown is actually called
+        # from there (the test_kills).
+
+    def test_kills(self):
+        """
+        Test that the boss kills components which don't want to stop.
+        """
+        bob = MockBob()
+        killed = []
+        class ImmortalComponent:
+            """
+            An immortal component. It does not stop when it is told so
+            (anyway it is not told so). It does not die if it is killed
+            the first time. It dies only when killed forcefully.
+            """
+            def kill(self, forcefull=False):
+                killed.append(forcefull)
+                if forcefull:
+                    bob.components = {}
+            def pid(self):
+                return 1
+            def name(self):
+                return "Immortal"
+        bob.components = {}
+        bob.register_process(1, ImmortalComponent())
+
+        # While at it, we check the configurator shutdown is actually called
+        orig = bob._component_configurator.shutdown
+        bob._component_configurator.shutdown = self.__nullary_hook
+        self.__called = False
+
+        bob.shutdown()
+
+        self.assertEqual([False, True], killed)
+        self.assertTrue(self.__called)
+
+        bob._component_configurator.shutdown = orig
+
+    def test_component_shutdown(self):
+        """
+        Test the component_shutdown sets all variables accordingly.
+        """
+        bob = MockBob()
+        self.assertRaises(Exception, bob.component_shutdown, 1)
+        self.assertEqual(1, bob.exitcode)
+        bob._BoB__started = True
+        bob.runnable = True
+        bob.component_shutdown(2)
+        self.assertEqual(2, bob.exitcode)
+        self.assertFalse(bob.runnable)
+
+    def test_init_config(self):
+        """
+        Test initial configuration is loaded.
+        """
+        bob = MockBob()
+        # Start it
+        bob._component_configurator.reconfigure = self.__unary_hook
+        # We need to return the original read_bind10_config
+        bob._read_bind10_config = lambda: BoB._read_bind10_config(bob)
+        # And provide a session to read the data from
+        class CC:
+            pass
+        bob.ccs = CC()
+        bob.ccs.get_full_config = lambda: {'components': self.__compconfig}
+        bob.start_all_components()
+        self.__check_extended(self.__param)
+
 if __name__ == '__main__':
 if __name__ == '__main__':
     # store os.environ for test_unchanged_environment
     # store os.environ for test_unchanged_environment
     original_os_environ = copy.deepcopy(os.environ)
     original_os_environ = copy.deepcopy(os.environ)

+ 5 - 2
tests/system/bindctl/tests.sh

@@ -50,7 +50,9 @@ if [ $status != 0 ]; then echo "I:failed"; fi
 n=`expr $n + 1`
 n=`expr $n + 1`
 
 
 echo "I:Stopping b10-auth and checking that ($n)"
 echo "I:Stopping b10-auth and checking that ($n)"
-echo 'config set Boss/start_auth false
+echo 'config add Boss/components x
+config remove Boss/components b10-auth
+config remove Boss/components x
 config commit
 config commit
 quit
 quit
 ' | $RUN_BINDCTL \
 ' | $RUN_BINDCTL \
@@ -61,7 +63,8 @@ if [ $status != 0 ]; then echo "I:failed"; fi
 n=`expr $n + 1`
 n=`expr $n + 1`
 
 
 echo "I:Restarting b10-auth and checking that ($n)"
 echo "I:Restarting b10-auth and checking that ($n)"
-echo 'config set Boss/start_auth true
+echo 'config add Boss/components b10-auth
+config set Boss/components/b10-auth { "special": "auth", "kind": "needed" }
 config commit
 config commit
 quit
 quit
 ' | $RUN_BINDCTL \
 ' | $RUN_BINDCTL \