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
 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
 An error was encountered when the boss module specified
 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 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, 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,
                      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):
         pids = list(self.components.keys())
@@ -424,24 +387,20 @@ class BoB:
             self.components[pid].kill(True)
         self.components = {}
 
-    def read_bind10_config(self):
+    def _read_bind10_config(self):
         """
             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)
 
         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):
         """
@@ -720,60 +679,14 @@ class BoB:
 
         # Connect to the msgq. This is not a process, so it's not handled
         # 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
         # done after the CC Session is started.  Note that the logging
         # 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):
         """
@@ -838,22 +751,6 @@ class BoB:
         else:
             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):
         """Stop the BoB instance."""
         logger.info(BIND10_SHUTDOWN)

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

@@ -4,16 +4,66 @@
     "module_description": "Master process",
     "config_data": [
       {
-        "item_name": "start_auth",
-        "item_type": "boolean",
+        "item_name": "components",
+        "item_type": "named_set",
         "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": [

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

@@ -244,7 +244,7 @@ class MockBob(BoB):
     def stop_creator(self, kill=False):
         self.creator = False
 
-    def read_bind10_config(self):
+    def _read_bind10_config(self):
         # Configuration options are set directly
         pass
 
@@ -278,7 +278,6 @@ class MockBob(BoB):
 
     def start_simple(self, name):
         procmap = { 'b10-xfrout': self.start_xfrout,
-                    'b10-xfrin': self.start_xfrin,
                     'b10-zonemgr': self.start_zonemgr,
                     'b10-stats': self.start_stats,
                     'b10-stats-httpd': self.start_stats_httpd,
@@ -324,13 +323,13 @@ class MockBob(BoB):
         return procinfo
 
     def start_dhcp6(self):
-        self.stats = True
+        self.dhcp6 = True
         procinfo = ProcessInfo('b10-dhcp6', ['/bin/false'])
         procinfo.pid = 13
         return procinfo
 
     def start_dhcp4(self):
-        self.stats = True
+        self.dhcp4 = True
         procinfo = ProcessInfo('b10-dhcp4', ['/bin/false'])
         procinfo.pid = 14
         return procinfo
@@ -346,9 +345,7 @@ class MockBob(BoB):
                     'b10-cmdctl': self.stop_cmdctl }
         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):
         if self.msgq:
             del self.components[2]
@@ -467,70 +464,61 @@ class TestStartStopProcessesBob(unittest.TestCase):
         """
         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()
 
-    # 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()
-        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()
+        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):
-        # 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):
-        # 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):
         """
@@ -542,17 +530,14 @@ class TestStartStopProcessesBob(unittest.TestCase):
         bob = MockBob()
         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.runnable = True
+        bob._BoB_started = True
+        bob.config_handler(self.construct_config(False, False))
         self.check_started_none(bob)
 
         # 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)
 
         # Not touched by empty change
@@ -560,11 +545,11 @@ class TestStartStopProcessesBob(unittest.TestCase):
         self.check_started_both(bob)
 
         # 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)
 
         # 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)
 
         # Not touched by empty change
@@ -572,47 +557,46 @@ class TestStartStopProcessesBob(unittest.TestCase):
         self.check_started_none(bob)
 
         # 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)
 
         # Start and stop auth separately
-        bob.config_handler({'start_auth': True})
+        bob.config_handler(self.construct_config(True, False))
         self.check_started_auth(bob)
 
-        bob.config_handler({'start_auth': False})
+        bob.config_handler(self.construct_config(False, False))
         self.check_started_none(bob)
 
         # Start and stop resolver separately
-        bob.config_handler({'start_resolver': True})
+        bob.config_handler(self.construct_config(False, True))
         self.check_started_resolver(bob)
 
-        bob.config_handler({'start_resolver': False})
+        bob.config_handler(self.construct_config(False, False))
         self.check_started_none(bob)
 
         # Alternate
-        bob.config_handler({'start_auth': True})
+        bob.config_handler(self.construct_config(True, False))
         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)
 
-        bob.config_handler({'start_auth': True, 'start_resolver': False})
+        bob.config_handler(self.construct_config(True, False))
         self.check_started_auth(bob)
 
     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
         bob = MockBob()
         self.check_preconditions(bob)
 
-        # Start components (both)
-        bob.cfg_start_auth = True
-        bob.cfg_start_resolver = True
-
         bob.start_all_components()
+
+        bob._BoB_started = True
         bob.runnable = True
+        bob.config_handler(self.construct_config(True, True))
         self.check_started_both(bob)
 
         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")
 
         # 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):
         """
@@ -648,29 +631,24 @@ class TestStartStopProcessesBob(unittest.TestCase):
         bob = MockBob()
         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._BoB_started = True
+        bob.runnable = True
+        bob.config_handler(self.construct_config(False, False))
         self.check_started_dhcp(bob, False, False)
 
     def test_start_dhcp_v6only(self):
         # Create BoB and ensure correct initialization
         bob = MockBob()
         self.check_preconditions(bob)
-
-        # don't care about DNS stuff
-        bob.cfg_start_auth = False
-        bob.cfg_start_resolver = False
-
         # v6 only enabled
-        bob.cfg_start_dhcp6 = True
-        bob.cfg_start_dhcp4 = False
         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)
 
         # uncomment when dhcpv4 becomes implemented
@@ -857,6 +835,159 @@ class TestBrittle(unittest.TestCase):
         sys.stdout = old_stdout
         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__':
     # store os.environ for test_unchanged_environment
     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`
 
 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
 quit
 ' | $RUN_BINDCTL \
@@ -61,7 +63,8 @@ if [ $status != 0 ]; then echo "I:failed"; fi
 n=`expr $n + 1`
 
 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
 quit
 ' | $RUN_BINDCTL \