Example #1
0
 def setUp(self):
     self.data_path = os.environ['CONFIG_TESTDATA_PATH']
     self.writable_data_path = os.environ['CONFIG_WR_TESTDATA_PATH']
     self.fake_session = FakeModuleCCSession()
     self.cm = ConfigManager(self.writable_data_path,
                             database_filename="b10-config.db",
                             session=self.fake_session)
     self.name = "TestModule"
     self.spec = isc.config.module_spec_from_file(self.data_path + os.sep +
                                                  "/spec2.spec")
Example #2
0
 def setUp(self):
     self.data_path = os.environ['CONFIG_TESTDATA_PATH']
     self.writable_data_path = os.environ['CONFIG_WR_TESTDATA_PATH']
     self.fake_session = FakeModuleCCSession()
     self.cm = ConfigManager(self.writable_data_path,
                             database_filename="bundy-config.db",
                             session=self.fake_session)
     self.name = "TestModule"
     self.spec = bundy.config.module_spec_from_file(self.data_path + os.sep + "/spec2.spec")
Example #3
0
class TestConfigManager(unittest.TestCase):

    def setUp(self):
        self.data_path = os.environ['CONFIG_TESTDATA_PATH']
        self.writable_data_path = os.environ['CONFIG_WR_TESTDATA_PATH']
        self.fake_session = FakeModuleCCSession()
        self.cm = ConfigManager(self.writable_data_path,
                                database_filename="bundy-config.db",
                                session=self.fake_session)
        self.name = "TestModule"
        self.spec = bundy.config.module_spec_from_file(self.data_path + os.sep + "/spec2.spec")

    def test_paths(self):
        """
        Test data_path and database filename is passed through to
        underlying ConfigManagerData.
        """
        cm = ConfigManager("datapath", "filename", self.fake_session)
        self.assertEqual("datapath" + os.sep + "filename",
                         cm.config.db_filename)
        # It should preserve it while reading
        cm.read_config()
        self.assertEqual("datapath" + os.sep + "filename",
                         cm.config.db_filename)

    def test_init(self):
        self.assertEqual(self.cm.module_specs, {})
        self.assertEqual(self.cm.data_path, self.writable_data_path)
        self.assertIsNotNone(self.cm.config)
        self.assertTrue(self.fake_session.has_subscription("ConfigManager"))
        self.assertTrue(self.fake_session.has_subscription("Init", "ConfigManager"))
        self.assertFalse(self.cm.running)

    def test_notify_bundy_init(self):
        self.cm.notify_bundy_init()
        msg = self.fake_session.get_message("Init", None)
        self.assertTrue(msg)
        # this one is actually wrong, but 'current status quo'
        self.assertEqual(msg, {"running": "ConfigManager"})

    def test_set_module_spec(self):
        module_spec = bundy.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.cm.set_module_spec(module_spec)
        self.assertIn(module_spec.get_module_name(), self.cm.module_specs)
        self.assertNotIn(module_spec.get_module_name(), self.cm.virtual_modules)

    def test_remove_module_spec(self):
        module_spec = bundy.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.cm.set_module_spec(module_spec)
        self.assertIn(module_spec.get_module_name(), self.cm.module_specs)
        self.cm.remove_module_spec(module_spec.get_module_name())
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.assertNotIn(module_spec.get_module_name(), self.cm.virtual_modules)

    def test_add_remove_virtual_module(self):
        module_spec = bundy.config.module_spec.module_spec_from_file(
            self.data_path + os.sep + "spec1.spec")
        check_func = lambda: True
        # Make sure it's not in there before
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.assertNotIn(module_spec.get_module_name(), self.cm.virtual_modules)
        # Add it there
        self.cm.set_virtual_module(module_spec, check_func)
        # Check it's in there
        self.assertIn(module_spec.get_module_name(), self.cm.module_specs)
        self.assertEqual(self.cm.module_specs[module_spec.get_module_name()],
                      module_spec)
        self.assertEqual(self.cm.virtual_modules[module_spec.get_module_name()],
                      check_func)
        # Remove it again
        self.cm.remove_module_spec(module_spec.get_module_name())
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.assertNotIn(module_spec.get_module_name(), self.cm.virtual_modules)

    def test_get_module_spec(self):
        module_spec = bundy.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.cm.set_module_spec(module_spec)
        self.assertIn(module_spec.get_module_name(), self.cm.module_specs)
        module_spec2 = self.cm.get_module_spec(module_spec.get_module_name())
        self.assertEqual(module_spec.get_full_spec(), module_spec2)

        self.assertEqual({}, self.cm.get_module_spec("nosuchmodule"))

    def test_get_config_spec(self):
        config_spec = self.cm.get_config_spec()
        self.assertEqual(config_spec, {})
        module_spec = bundy.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.cm.set_module_spec(module_spec)
        self.assertIn(module_spec.get_module_name(), self.cm.module_specs)
        config_spec = self.cm.get_config_spec()
        self.assertEqual(config_spec, { 'Spec1': None })
        self.cm.remove_module_spec('Spec1')
        module_spec = bundy.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.cm.set_module_spec(module_spec)
        self.assertIn(module_spec.get_module_name(), self.cm.module_specs)
        config_spec = self.cm.get_config_spec()
        self.assertEqual(config_spec['Spec2'], module_spec.get_config_spec())
        config_spec = self.cm.get_config_spec('Spec2')
        self.assertEqual(config_spec['Spec2'], module_spec.get_config_spec())


    def test_get_commands_spec(self):
        commands_spec = self.cm.get_commands_spec()
        self.assertEqual(commands_spec, {})
        module_spec = bundy.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.cm.set_module_spec(module_spec)
        self.assertIn(module_spec.get_module_name(), self.cm.module_specs)
        commands_spec = self.cm.get_commands_spec()
        self.assertEqual(commands_spec, { 'Spec1': None })
        self.cm.remove_module_spec('Spec1')
        module_spec = bundy.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.cm.set_module_spec(module_spec)
        self.assertIn(module_spec.get_module_name(), self.cm.module_specs)
        commands_spec = self.cm.get_commands_spec()
        self.assertEqual(commands_spec['Spec2'], module_spec.get_commands_spec())
        commands_spec = self.cm.get_commands_spec('Spec2')
        self.assertEqual(commands_spec['Spec2'], module_spec.get_commands_spec())

    def test_get_statistics_spec(self):
        statistics_spec = self.cm.get_statistics_spec()
        self.assertEqual(statistics_spec, {})
        module_spec = bundy.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.cm.set_module_spec(module_spec)
        self.assertIn(module_spec.get_module_name(), self.cm.module_specs)
        statistics_spec = self.cm.get_statistics_spec()
        self.assertEqual(statistics_spec, { 'Spec1': None })
        self.cm.remove_module_spec('Spec1')
        module_spec = bundy.config.module_spec.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.cm.set_module_spec(module_spec)
        self.assertIn(module_spec.get_module_name(), self.cm.module_specs)
        statistics_spec = self.cm.get_statistics_spec()
        self.assertEqual(statistics_spec['Spec2'], module_spec.get_statistics_spec())
        statistics_spec = self.cm.get_statistics_spec('Spec2')
        self.assertEqual(statistics_spec['Spec2'], module_spec.get_statistics_spec())

    def test_read_config(self):
        self.assertEqual(self.cm.config.data, {'version': config_data.BUNDY_CONFIG_DATA_VERSION})
        self.cm.data_path = "/no_such_path"
        self.cm.read_config()
        self.assertEqual(self.cm.config.data, {'version': config_data.BUNDY_CONFIG_DATA_VERSION})

    def test_write_config(self):
        # tested in ConfigManagerData tests
        pass

    def _handle_msg_helper(self, msg, expected_answer):
        answer = self.cm.handle_msg(msg)
        self.assertEqual(expected_answer, answer)

    def test_handle_msg_basic_commands(self):
        # Some basic commands, where not much interaction happens, just
        # check the result
        self._handle_msg_helper({},
            { 'result': [ 1, 'Unknown message format: {}']})
        self._handle_msg_helper("",
            { 'result': [ 1, 'Unknown message format: ']})
        self._handle_msg_helper({ "command": [ "badcommand" ] },
            { 'result': [ 1, "Unknown command: badcommand"]})
        self._handle_msg_helper({ "command": [ "get_commands_spec" ] },
                                { 'result': [ 0, {} ]})
        self._handle_msg_helper({ "command": [ "get_statistics_spec" ] },
                                { 'result': [ 0, {} ]})
        self._handle_msg_helper({ "command": [ "get_module_spec" ] },
                                { 'result': [ 0, {} ]})
        self._handle_msg_helper({ "command": [ "get_module_spec",
                                               { "module_name": "Spec2" } ] },
                                { 'result': [ 0, {} ]})
        self._handle_msg_helper({ "command": [ "get_module_spec", 1 ] },
                                {'result': [1, 'Bad get_module_spec command, '+
                                               'argument not a dict']})
        self._handle_msg_helper({ "command": [ "get_module_spec", { } ] },
                                {'result': [1, 'Bad module_name in '+
                                               'get_module_spec command']})
        self._handle_msg_helper({ "command": [ "get_config" ] },
                                { 'result': [ 0, { 'version':
                                    config_data.BUNDY_CONFIG_DATA_VERSION }]})
        self._handle_msg_helper({ "command": [ "get_config",
                                    { "module_name": "nosuchmodule" } ] },
                                {'result': [0, { 'version':
                                    config_data.BUNDY_CONFIG_DATA_VERSION }]})
        self._handle_msg_helper({ "command": [ "get_config", 1 ] },
                                {'result': [1, 'Bad get_config command, '+
                                               'argument not a dict']})
        self._handle_msg_helper({ "command": [ "get_config", { } ] },
                                {'result': [1, 'Bad module_name in '+
                                               'get_config command']})
        self._handle_msg_helper({ "command": [ "set_config" ] },
                                {'result': [1, 'Wrong number of arguments']})
        self._handle_msg_helper({ "command": [ "set_config", [{}]] },
                                {'result': [0]})

        self.assertEqual(len(self.fake_session.message_queue), 0)

    def test_handle_msg_module_and_stats_commands(self):
        self._handle_msg_helper({ "command":
                                  ["module_spec", self.spec.get_full_spec()]
                                },
                                {'result': [0]})
        # There should be a message on the queue about the 'new' Spec2 module
        # from ConfigManager to Cmdctl, containing its name and full
        # specification
        self.assertEqual(ccsession.create_command(
                            ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE,
                            [ self.spec.get_module_name(),
                              self.spec.get_full_spec()]),
                         self.fake_session.get_message("Cmdctl", None))

        self._handle_msg_helper({ "command": [ "module_spec", { 'foo': 1 } ] },
                                {'result': [1, 'Error in data definition: no '+
                                               'module_name in module_spec']})

        self._handle_msg_helper({ "command": [ "get_module_spec" ] },
                                { 'result': [ 0, { self.spec.get_module_name():
                                                 self.spec.get_full_spec() } ]})
        self._handle_msg_helper({ "command": [ "get_module_spec",
                                               { "module_name" : "Spec2" } ] },
                                { 'result': [ 0, self.spec.get_full_spec() ] })
        self._handle_msg_helper({ "command": [ "get_commands_spec" ] },
                                { 'result': [ 0, { self.spec.get_module_name():
                                              self.spec.get_commands_spec()}]})
        self._handle_msg_helper({ "command": [ "get_statistics_spec" ] },
                                { 'result': [ 0, { self.spec.get_module_name():
                                             self.spec.get_statistics_spec()}]})


    def __test_handle_msg_update_config_helper(self, new_config):
        # Helper function for the common pattern in
        # test_handle_msg_update_config; send 'set config', check for
        # update message, check if config has indeed been updated

        my_ok_answer = { 'result': [ 0 ] }
        # Send the 'ok' that cfgmgr expects back to the fake queue first
        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")

        config_version = config_data.BUNDY_CONFIG_DATA_VERSION
        self._handle_msg_helper({ "command": [ "set_config",
                                               [ { "version": config_version,
                                                 self.name: new_config } ] ] },
                                my_ok_answer)

        # The cfgmgr should have eaten the ok message, and sent out an update
        # message
        self.assertEqual(len(self.fake_session.message_queue), 1)
        self.assertEqual({'command': [ 'config_update', new_config]},
                         self.fake_session.get_message(self.name, None))

        # Config should have been updated
        self.assertEqual(self.cm.config.data, {self.name: new_config,
                            'version': config_version})

        # and the queue should now be empty again
        self.assertEqual(len(self.fake_session.message_queue), 0)

    def test_handle_msg_update_config(self):
        # Update the configuration and check results a few times
        # only work the first time
        self.__test_handle_msg_update_config_helper({ "test": 123 })

        self.__test_handle_msg_update_config_helper({ "test": 124 })

        self.__test_handle_msg_update_config_helper({ "test": 125 })

        self.__test_handle_msg_update_config_helper({ "test": 126 })

        # Now send an error result (i.e. config not accepted)
        my_bad_answer = { 'result': [1, "bad config"] }
        self.fake_session.group_sendmsg(my_bad_answer, "ConfigManager")
        self._handle_msg_helper({ "command": [ "set_config",
                                               [self.name, { "test": 127 }] ] },
                                my_bad_answer )
        self.assertEqual(len(self.fake_session.message_queue), 1)
        self.assertEqual({'command': [ 'config_update', {'test': 127}]},
                         self.fake_session.get_message(self.name, None))
        # Config should not be updated due to the error
        self.cm.read_config()
        self.assertEqual(self.cm.config.data, { self.name: {'test': 126},
                            'version': config_data.BUNDY_CONFIG_DATA_VERSION})

        self.assertEqual(len(self.fake_session.message_queue), 0)

        self.fake_session.group_sendmsg(None, 'ConfigManager')
        self._handle_msg_helper({ "command": [ "set_config", [ ] ] },
                                {'result': [1, 'Wrong number of arguments']} )
        self._handle_msg_helper({ "command": [ "set_config",
                                               [ self.name, { "test": 128 }]]},
                                { 'result': [1, 'No answer message '+
                                                'from TestModule']} )

        # This command should leave a message to the TestModule to update its
        # configuration (since the TestModule did not eat it)
        self.assertEqual(len(self.fake_session.message_queue), 1)
        self.assertEqual(
            ccsession.create_command(ccsession.COMMAND_CONFIG_UPDATE,
                                     { "test": 128 }),
            self.fake_session.get_message("TestModule", None))

        # Make sure queue is empty now
        self.assertEqual(len(self.fake_session.message_queue), 0)

        # Shutdown should result in 'ok' answer
        self._handle_msg_helper({ "command":
                                  ["shutdown"]
                                },
                                {'result': [0]})

    def test_stopping_message(self):
        # Update the system by announcing this module
        self._handle_msg_helper({ "command":
                                  ["module_spec", self.spec.get_full_spec()]
                                },
                                {'result': [0]})

        # This causes a update to be sent from the ConfigManager to the CmdCtl
        # channel, containing the new module's name and full specification
        self.assertEqual(ccsession.create_command(
                            ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE,
                            [ self.spec.get_module_name(),
                              self.spec.get_full_spec()]),
                         self.fake_session.get_message("Cmdctl", None))

        # A stopping message should get no response, but should cause another
        # message to be sent, if it is a known module
        self._handle_msg_helper({ "command": [ "stopping",
                                               { "module_name": "Spec2"}] },
                                None)
        self.assertEqual(len(self.fake_session.message_queue), 1)
        self.assertEqual({'command': [ 'module_specification_update',
                                       ['Spec2', None] ] },
                         self.fake_session.get_message("Cmdctl", None))

        # but if the 'stopping' module is either unknown or not running,
        # no follow-up message should be sent
        self._handle_msg_helper({ "command":
                                  [ "stopping",
                                    { "module_name": "NoSuchModule" } ] },
                                None)
        self.assertEqual(len(self.fake_session.message_queue), 0)

    def test_set_config_virtual(self):
        """Test that if the module is virtual, we don't send it over the
           message bus, but call the checking function.
           """
        # We run the same three times, with different return values
        def single_test(value, returnFunc, expectedResult):
            # Because closures can't assign to closed-in variables, we pass
            # it through self
            self.called_with = None
            def check_test(new_data):
                self.called_with = new_data
                return returnFunc()

            # Register our virtual module
            self.cm.set_virtual_module(self.spec, check_test)
            # The fake session will throw now if it tries to read a response.
            # Handy, we don't need to find a complicated way to check for it.
            result = self.cm.handle_msg(ccsession.create_command(
                        ccsession.COMMAND_SET_CONFIG,
                        [self.spec.get_module_name(), { "item1": value }]))
            # Check the correct result is passed and our function was called
            # With correct data
            self.assertEqual(self.called_with['item1'], value)
            self.assertEqual(result, {'result': expectedResult})
            if expectedResult[0] == 0:
                # Check it provided the correct notification
                self.assertEqual(len(self.fake_session.message_queue), 1)
                self.assertEqual({'command': [ 'config_update',
                                 {'item1': value}]},
                                 self.fake_session.get_message('Spec2', None))
                # and the queue should now be empty again
                self.assertEqual(len(self.fake_session.message_queue), 0)
            else:
                # It shouldn't send anything on error
                self.assertEqual(len(self.fake_session.message_queue), 0)

        # Success
        single_test(5, lambda: None, [0])
        # Graceful error
        single_test(6, lambda: "Just error", [1, "Just error"])
        # Exception from the checker
        def raiser():
            raise Exception("Just exception")
        single_test(7, raiser, [1, "Exception: Just exception"])

    def test_set_config_all(self):
        my_ok_answer = { 'result': [ 0 ] }

        self.assertEqual({"version": config_data.BUNDY_CONFIG_DATA_VERSION},
                         self.cm.config.data)

        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
        self.cm.handle_msg(ccsession.create_command(
            ccsession.COMMAND_SET_CONFIG, ["test", { "value1": 123 }]))
        self.assertEqual({"version": config_data.BUNDY_CONFIG_DATA_VERSION,
                          "test": { "value1": 123 }
                         }, self.cm.config.data)

        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
        self.cm.handle_msg(ccsession.create_command(
            ccsession.COMMAND_SET_CONFIG, ["test", { "value1": 124 }]))
        self.assertEqual({"version": config_data.BUNDY_CONFIG_DATA_VERSION,
                          "test": { "value1": 124 }
                         }, self.cm.config.data)

        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
        self.cm.handle_msg(ccsession.create_command(
            ccsession.COMMAND_SET_CONFIG, ["test", { "value2": True }]))
        self.assertEqual({"version": config_data.BUNDY_CONFIG_DATA_VERSION,
                          "test": { "value1": 124,
                                    "value2": True
                                  }
                         }, self.cm.config.data)

        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
        self.cm.handle_msg(ccsession.create_command(
            ccsession.COMMAND_SET_CONFIG, ["test", { "value3": [ 1, 2, 3 ] }]))
        self.assertEqual({"version": config_data.BUNDY_CONFIG_DATA_VERSION,
                          "test": { "value1": 124,
                                    "value2": True,
                                    "value3": [ 1, 2, 3 ]
                                  }
                         }, self.cm.config.data)

        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
        self.cm.handle_msg(ccsession.create_command(
            ccsession.COMMAND_SET_CONFIG, ["test", { "value2": False }]))
        self.assertEqual({"version": config_data.BUNDY_CONFIG_DATA_VERSION,
                          "test": { "value1": 124,
                                    "value2": False,
                                    "value3": [ 1, 2, 3 ]
                                  }
                         }, self.cm.config.data)

        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
        self.cm.handle_msg(ccsession.create_command(
            ccsession.COMMAND_SET_CONFIG, ["test", { "value1": None }]))
        self.assertEqual({"version": config_data.BUNDY_CONFIG_DATA_VERSION,
                          "test": { "value2": False,
                                    "value3": [ 1, 2, 3 ]
                                  }
                         }, self.cm.config.data)

        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
        self.cm.handle_msg(ccsession.create_command(
            ccsession.COMMAND_SET_CONFIG, ["test", { "value3": [ 1 ] }]))
        self.assertEqual({"version": config_data.BUNDY_CONFIG_DATA_VERSION,
                          "test": { "value2": False,
                                    "value3": [ 1 ]
                                  }
                         }, self.cm.config.data)


    def test_run(self):
        self.fake_session.group_sendmsg({ "command": [ "get_commands_spec" ] }, "ConfigManager")
        self.fake_session.group_sendmsg({ "command": [ "get_statistics_spec" ] }, "ConfigManager")
        self.fake_session.group_sendmsg({ "command": [ "stopping", { "module_name": "FooModule" } ] }, "ConfigManager")
        self.fake_session.group_sendmsg({ "command": [ "shutdown" ] }, "ConfigManager")
        self.assertEqual(len(self.fake_session.message_queue), 4)
        self.cm.run()
        # All commands should have been read out by run()
        # Three of the commands should have been responded to, so the queue
        # should now contain three answers
        self.assertEqual(len(self.fake_session.message_queue), 3)
Example #4
0
class TestConfigManager(unittest.TestCase):
    def setUp(self):
        self.data_path = os.environ['CONFIG_TESTDATA_PATH']
        self.writable_data_path = os.environ['CONFIG_WR_TESTDATA_PATH']
        self.fake_session = FakeModuleCCSession()
        self.cm = ConfigManager(self.writable_data_path,
                                database_filename="b10-config.db",
                                session=self.fake_session)
        self.name = "TestModule"
        self.spec = isc.config.module_spec_from_file(self.data_path + os.sep +
                                                     "/spec2.spec")

    def test_paths(self):
        """
        Test data_path and database filename is passed through to
        underlying ConfigManagerData.
        """
        cm = ConfigManager("datapath", "filename", self.fake_session)
        self.assertEqual("datapath" + os.sep + "filename",
                         cm.config.db_filename)
        # It should preserve it while reading
        cm.read_config()
        self.assertEqual("datapath" + os.sep + "filename",
                         cm.config.db_filename)

    def test_init(self):
        self.assertEqual(self.cm.module_specs, {})
        self.assertEqual(self.cm.data_path, self.writable_data_path)
        self.assertIsNotNone(self.cm.config)
        self.assertTrue(self.fake_session.has_subscription("ConfigManager"))
        self.assertTrue(
            self.fake_session.has_subscription("Init", "ConfigManager"))
        self.assertFalse(self.cm.running)

    def test_notify_b10_init(self):
        self.cm.notify_b10_init()
        msg = self.fake_session.get_message("Init", None)
        self.assertTrue(msg)
        # this one is actually wrong, but 'current status quo'
        self.assertEqual(msg, {"running": "ConfigManager"})

    def test_set_module_spec(self):
        module_spec = isc.config.module_spec.module_spec_from_file(
            self.data_path + os.sep + "spec1.spec")
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.cm.set_module_spec(module_spec)
        self.assertIn(module_spec.get_module_name(), self.cm.module_specs)
        self.assertNotIn(module_spec.get_module_name(),
                         self.cm.virtual_modules)

    def test_remove_module_spec(self):
        module_spec = isc.config.module_spec.module_spec_from_file(
            self.data_path + os.sep + "spec1.spec")
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.cm.set_module_spec(module_spec)
        self.assertIn(module_spec.get_module_name(), self.cm.module_specs)
        self.cm.remove_module_spec(module_spec.get_module_name())
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.assertNotIn(module_spec.get_module_name(),
                         self.cm.virtual_modules)

    def test_add_remove_virtual_module(self):
        module_spec = isc.config.module_spec.module_spec_from_file(
            self.data_path + os.sep + "spec1.spec")
        check_func = lambda: True
        # Make sure it's not in there before
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.assertNotIn(module_spec.get_module_name(),
                         self.cm.virtual_modules)
        # Add it there
        self.cm.set_virtual_module(module_spec, check_func)
        # Check it's in there
        self.assertIn(module_spec.get_module_name(), self.cm.module_specs)
        self.assertEqual(self.cm.module_specs[module_spec.get_module_name()],
                         module_spec)
        self.assertEqual(
            self.cm.virtual_modules[module_spec.get_module_name()], check_func)
        # Remove it again
        self.cm.remove_module_spec(module_spec.get_module_name())
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.assertNotIn(module_spec.get_module_name(),
                         self.cm.virtual_modules)

    def test_get_module_spec(self):
        module_spec = isc.config.module_spec.module_spec_from_file(
            self.data_path + os.sep + "spec1.spec")
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.cm.set_module_spec(module_spec)
        self.assertIn(module_spec.get_module_name(), self.cm.module_specs)
        module_spec2 = self.cm.get_module_spec(module_spec.get_module_name())
        self.assertEqual(module_spec.get_full_spec(), module_spec2)

        self.assertEqual({}, self.cm.get_module_spec("nosuchmodule"))

    def test_get_config_spec(self):
        config_spec = self.cm.get_config_spec()
        self.assertEqual(config_spec, {})
        module_spec = isc.config.module_spec.module_spec_from_file(
            self.data_path + os.sep + "spec1.spec")
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.cm.set_module_spec(module_spec)
        self.assertIn(module_spec.get_module_name(), self.cm.module_specs)
        config_spec = self.cm.get_config_spec()
        self.assertEqual(config_spec, {'Spec1': None})
        self.cm.remove_module_spec('Spec1')
        module_spec = isc.config.module_spec.module_spec_from_file(
            self.data_path + os.sep + "spec2.spec")
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.cm.set_module_spec(module_spec)
        self.assertIn(module_spec.get_module_name(), self.cm.module_specs)
        config_spec = self.cm.get_config_spec()
        self.assertEqual(config_spec['Spec2'], module_spec.get_config_spec())
        config_spec = self.cm.get_config_spec('Spec2')
        self.assertEqual(config_spec['Spec2'], module_spec.get_config_spec())

    def test_get_commands_spec(self):
        commands_spec = self.cm.get_commands_spec()
        self.assertEqual(commands_spec, {})
        module_spec = isc.config.module_spec.module_spec_from_file(
            self.data_path + os.sep + "spec1.spec")
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.cm.set_module_spec(module_spec)
        self.assertIn(module_spec.get_module_name(), self.cm.module_specs)
        commands_spec = self.cm.get_commands_spec()
        self.assertEqual(commands_spec, {'Spec1': None})
        self.cm.remove_module_spec('Spec1')
        module_spec = isc.config.module_spec.module_spec_from_file(
            self.data_path + os.sep + "spec2.spec")
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.cm.set_module_spec(module_spec)
        self.assertIn(module_spec.get_module_name(), self.cm.module_specs)
        commands_spec = self.cm.get_commands_spec()
        self.assertEqual(commands_spec['Spec2'],
                         module_spec.get_commands_spec())
        commands_spec = self.cm.get_commands_spec('Spec2')
        self.assertEqual(commands_spec['Spec2'],
                         module_spec.get_commands_spec())

    def test_get_statistics_spec(self):
        statistics_spec = self.cm.get_statistics_spec()
        self.assertEqual(statistics_spec, {})
        module_spec = isc.config.module_spec.module_spec_from_file(
            self.data_path + os.sep + "spec1.spec")
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.cm.set_module_spec(module_spec)
        self.assertIn(module_spec.get_module_name(), self.cm.module_specs)
        statistics_spec = self.cm.get_statistics_spec()
        self.assertEqual(statistics_spec, {'Spec1': None})
        self.cm.remove_module_spec('Spec1')
        module_spec = isc.config.module_spec.module_spec_from_file(
            self.data_path + os.sep + "spec2.spec")
        self.assertNotIn(module_spec.get_module_name(), self.cm.module_specs)
        self.cm.set_module_spec(module_spec)
        self.assertIn(module_spec.get_module_name(), self.cm.module_specs)
        statistics_spec = self.cm.get_statistics_spec()
        self.assertEqual(statistics_spec['Spec2'],
                         module_spec.get_statistics_spec())
        statistics_spec = self.cm.get_statistics_spec('Spec2')
        self.assertEqual(statistics_spec['Spec2'],
                         module_spec.get_statistics_spec())

    def test_read_config(self):
        self.assertEqual(self.cm.config.data,
                         {'version': config_data.BIND10_CONFIG_DATA_VERSION})
        self.cm.data_path = "/no_such_path"
        self.cm.read_config()
        self.assertEqual(self.cm.config.data,
                         {'version': config_data.BIND10_CONFIG_DATA_VERSION})

    def test_write_config(self):
        # tested in ConfigManagerData tests
        pass

    def _handle_msg_helper(self, msg, expected_answer):
        answer = self.cm.handle_msg(msg)
        self.assertEqual(expected_answer, answer)

    def test_handle_msg_basic_commands(self):
        # Some basic commands, where not much interaction happens, just
        # check the result
        self._handle_msg_helper({},
                                {'result': [1, 'Unknown message format: {}']})
        self._handle_msg_helper("",
                                {'result': [1, 'Unknown message format: ']})
        self._handle_msg_helper({"command": ["badcommand"]},
                                {'result': [1, "Unknown command: badcommand"]})
        self._handle_msg_helper({"command": ["get_commands_spec"]},
                                {'result': [0, {}]})
        self._handle_msg_helper({"command": ["get_statistics_spec"]},
                                {'result': [0, {}]})
        self._handle_msg_helper({"command": ["get_module_spec"]},
                                {'result': [0, {}]})
        self._handle_msg_helper(
            {"command": ["get_module_spec", {
                "module_name": "Spec2"
            }]}, {'result': [0, {}]})
        self._handle_msg_helper({"command": ["get_module_spec", 1]}, {
            'result':
            [1, 'Bad get_module_spec command, ' + 'argument not a dict']
        })
        self._handle_msg_helper(
            {"command": ["get_module_spec", {}]},
            {'result': [1, 'Bad module_name in ' + 'get_module_spec command']})
        self._handle_msg_helper({"command": ["get_config"]}, {
            'result': [0, {
                'version': config_data.BIND10_CONFIG_DATA_VERSION
            }]
        })
        self._handle_msg_helper(
            {"command": ["get_config", {
                "module_name": "nosuchmodule"
            }]}, {
                'result':
                [0, {
                    'version': config_data.BIND10_CONFIG_DATA_VERSION
                }]
            })
        self._handle_msg_helper({"command": ["get_config", 1]}, {
            'result': [1, 'Bad get_config command, ' + 'argument not a dict']
        })
        self._handle_msg_helper(
            {"command": ["get_config", {}]},
            {'result': [1, 'Bad module_name in ' + 'get_config command']})
        self._handle_msg_helper({"command": ["set_config"]},
                                {'result': [1, 'Wrong number of arguments']})
        self._handle_msg_helper({"command": ["set_config", [{}]]},
                                {'result': [0]})

        self.assertEqual(len(self.fake_session.message_queue), 0)

    def test_handle_msg_module_and_stats_commands(self):
        self._handle_msg_helper(
            {"command": ["module_spec",
                         self.spec.get_full_spec()]}, {'result': [0]})
        # There should be a message on the queue about the 'new' Spec2 module
        # from ConfigManager to Cmdctl, containing its name and full
        # specification
        self.assertEqual(
            ccsession.create_command(
                ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE,
                [self.spec.get_module_name(),
                 self.spec.get_full_spec()]),
            self.fake_session.get_message("Cmdctl", None))

        self._handle_msg_helper({"command": ["module_spec", {
            'foo': 1
        }]}, {
            'result': [
                1,
                'Error in data definition: no ' + 'module_name in module_spec'
            ]
        })

        self._handle_msg_helper({"command": ["get_module_spec"]}, {
            'result':
            [0, {
                self.spec.get_module_name(): self.spec.get_full_spec()
            }]
        })
        self._handle_msg_helper(
            {"command": ["get_module_spec", {
                "module_name": "Spec2"
            }]}, {'result': [0, self.spec.get_full_spec()]})
        self._handle_msg_helper({"command": ["get_commands_spec"]}, {
            'result':
            [0, {
                self.spec.get_module_name(): self.spec.get_commands_spec()
            }]
        })
        self._handle_msg_helper({"command": ["get_statistics_spec"]}, {
            'result': [
                0, {
                    self.spec.get_module_name():
                    self.spec.get_statistics_spec()
                }
            ]
        })

    def __test_handle_msg_update_config_helper(self, new_config):
        # Helper function for the common pattern in
        # test_handle_msg_update_config; send 'set config', check for
        # update message, check if config has indeed been updated

        my_ok_answer = {'result': [0]}
        # Send the 'ok' that cfgmgr expects back to the fake queue first
        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")

        config_version = config_data.BIND10_CONFIG_DATA_VERSION
        self._handle_msg_helper(
            {
                "command": [
                    "set_config",
                    [{
                        "version": config_version,
                        self.name: new_config
                    }]
                ]
            }, my_ok_answer)

        # The cfgmgr should have eaten the ok message, and sent out an update
        # message
        self.assertEqual(len(self.fake_session.message_queue), 1)
        self.assertEqual({'command': ['config_update', new_config]},
                         self.fake_session.get_message(self.name, None))

        # Config should have been updated
        self.assertEqual(self.cm.config.data, {
            self.name: new_config,
            'version': config_version
        })

        # and the queue should now be empty again
        self.assertEqual(len(self.fake_session.message_queue), 0)

    def test_handle_msg_update_config(self):
        # Update the configuration and check results a few times
        # only work the first time
        self.__test_handle_msg_update_config_helper({"test": 123})

        self.__test_handle_msg_update_config_helper({"test": 124})

        self.__test_handle_msg_update_config_helper({"test": 125})

        self.__test_handle_msg_update_config_helper({"test": 126})

        # Now send an error result (i.e. config not accepted)
        my_bad_answer = {'result': [1, "bad config"]}
        self.fake_session.group_sendmsg(my_bad_answer, "ConfigManager")
        self._handle_msg_helper(
            {"command": ["set_config", [self.name, {
                "test": 127
            }]]}, my_bad_answer)
        self.assertEqual(len(self.fake_session.message_queue), 1)
        self.assertEqual({'command': ['config_update', {
            'test': 127
        }]}, self.fake_session.get_message(self.name, None))
        # Config should not be updated due to the error
        self.cm.read_config()
        self.assertEqual(
            self.cm.config.data, {
                self.name: {
                    'test': 126
                },
                'version': config_data.BIND10_CONFIG_DATA_VERSION
            })

        self.assertEqual(len(self.fake_session.message_queue), 0)

        self.fake_session.group_sendmsg(None, 'ConfigManager')
        self._handle_msg_helper({"command": ["set_config", []]},
                                {'result': [1, 'Wrong number of arguments']})
        self._handle_msg_helper(
            {"command": ["set_config", [self.name, {
                "test": 128
            }]]}, {'result': [1, 'No answer message ' + 'from TestModule']})

        # This command should leave a message to the TestModule to update its
        # configuration (since the TestModule did not eat it)
        self.assertEqual(len(self.fake_session.message_queue), 1)
        self.assertEqual(
            ccsession.create_command(ccsession.COMMAND_CONFIG_UPDATE,
                                     {"test": 128}),
            self.fake_session.get_message("TestModule", None))

        # Make sure queue is empty now
        self.assertEqual(len(self.fake_session.message_queue), 0)

        # Shutdown should result in 'ok' answer
        self._handle_msg_helper({"command": ["shutdown"]}, {'result': [0]})

    def test_stopping_message(self):
        # Update the system by announcing this module
        self._handle_msg_helper(
            {"command": ["module_spec",
                         self.spec.get_full_spec()]}, {'result': [0]})

        # This causes a update to be sent from the ConfigManager to the CmdCtl
        # channel, containing the new module's name and full specification
        self.assertEqual(
            ccsession.create_command(
                ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE,
                [self.spec.get_module_name(),
                 self.spec.get_full_spec()]),
            self.fake_session.get_message("Cmdctl", None))

        # A stopping message should get no response, but should cause another
        # message to be sent, if it is a known module
        self._handle_msg_helper(
            {"command": ["stopping", {
                "module_name": "Spec2"
            }]}, None)
        self.assertEqual(len(self.fake_session.message_queue), 1)
        self.assertEqual(
            {'command': ['module_specification_update', ['Spec2', None]]},
            self.fake_session.get_message("Cmdctl", None))

        # but if the 'stopping' module is either unknown or not running,
        # no follow-up message should be sent
        self._handle_msg_helper(
            {"command": ["stopping", {
                "module_name": "NoSuchModule"
            }]}, None)
        self.assertEqual(len(self.fake_session.message_queue), 0)

    def test_set_config_virtual(self):
        """Test that if the module is virtual, we don't send it over the
           message bus, but call the checking function.
           """

        # We run the same three times, with different return values
        def single_test(value, returnFunc, expectedResult):
            # Because closures can't assign to closed-in variables, we pass
            # it through self
            self.called_with = None

            def check_test(new_data):
                self.called_with = new_data
                return returnFunc()

            # Register our virtual module
            self.cm.set_virtual_module(self.spec, check_test)
            # The fake session will throw now if it tries to read a response.
            # Handy, we don't need to find a complicated way to check for it.
            result = self.cm.handle_msg(
                ccsession.create_command(
                    ccsession.COMMAND_SET_CONFIG,
                    [self.spec.get_module_name(), {
                        "item1": value
                    }]))
            # Check the correct result is passed and our function was called
            # With correct data
            self.assertEqual(self.called_with['item1'], value)
            self.assertEqual(result, {'result': expectedResult})
            if expectedResult[0] == 0:
                # Check it provided the correct notification
                self.assertEqual(len(self.fake_session.message_queue), 1)
                self.assertEqual(
                    {'command': ['config_update', {
                        'item1': value
                    }]}, self.fake_session.get_message('Spec2', None))
                # and the queue should now be empty again
                self.assertEqual(len(self.fake_session.message_queue), 0)
            else:
                # It shouldn't send anything on error
                self.assertEqual(len(self.fake_session.message_queue), 0)

        # Success
        single_test(5, lambda: None, [0])
        # Graceful error
        single_test(6, lambda: "Just error", [1, "Just error"])

        # Exception from the checker
        def raiser():
            raise Exception("Just exception")

        single_test(7, raiser, [1, "Exception: Just exception"])

    def test_set_config_all(self):
        my_ok_answer = {'result': [0]}

        self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION},
                         self.cm.config.data)

        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
        self.cm.handle_msg(
            ccsession.create_command(ccsession.COMMAND_SET_CONFIG,
                                     ["test", {
                                         "value1": 123
                                     }]))
        self.assertEqual(
            {
                "version": config_data.BIND10_CONFIG_DATA_VERSION,
                "test": {
                    "value1": 123
                }
            }, self.cm.config.data)

        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
        self.cm.handle_msg(
            ccsession.create_command(ccsession.COMMAND_SET_CONFIG,
                                     ["test", {
                                         "value1": 124
                                     }]))
        self.assertEqual(
            {
                "version": config_data.BIND10_CONFIG_DATA_VERSION,
                "test": {
                    "value1": 124
                }
            }, self.cm.config.data)

        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
        self.cm.handle_msg(
            ccsession.create_command(ccsession.COMMAND_SET_CONFIG,
                                     ["test", {
                                         "value2": True
                                     }]))
        self.assertEqual(
            {
                "version": config_data.BIND10_CONFIG_DATA_VERSION,
                "test": {
                    "value1": 124,
                    "value2": True
                }
            }, self.cm.config.data)

        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
        self.cm.handle_msg(
            ccsession.create_command(ccsession.COMMAND_SET_CONFIG,
                                     ["test", {
                                         "value3": [1, 2, 3]
                                     }]))
        self.assertEqual(
            {
                "version": config_data.BIND10_CONFIG_DATA_VERSION,
                "test": {
                    "value1": 124,
                    "value2": True,
                    "value3": [1, 2, 3]
                }
            }, self.cm.config.data)

        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
        self.cm.handle_msg(
            ccsession.create_command(ccsession.COMMAND_SET_CONFIG,
                                     ["test", {
                                         "value2": False
                                     }]))
        self.assertEqual(
            {
                "version": config_data.BIND10_CONFIG_DATA_VERSION,
                "test": {
                    "value1": 124,
                    "value2": False,
                    "value3": [1, 2, 3]
                }
            }, self.cm.config.data)

        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
        self.cm.handle_msg(
            ccsession.create_command(ccsession.COMMAND_SET_CONFIG,
                                     ["test", {
                                         "value1": None
                                     }]))
        self.assertEqual(
            {
                "version": config_data.BIND10_CONFIG_DATA_VERSION,
                "test": {
                    "value2": False,
                    "value3": [1, 2, 3]
                }
            }, self.cm.config.data)

        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
        self.cm.handle_msg(
            ccsession.create_command(ccsession.COMMAND_SET_CONFIG,
                                     ["test", {
                                         "value3": [1]
                                     }]))
        self.assertEqual(
            {
                "version": config_data.BIND10_CONFIG_DATA_VERSION,
                "test": {
                    "value2": False,
                    "value3": [1]
                }
            }, self.cm.config.data)

    def test_run(self):
        self.fake_session.group_sendmsg({"command": ["get_commands_spec"]},
                                        "ConfigManager")
        self.fake_session.group_sendmsg({"command": ["get_statistics_spec"]},
                                        "ConfigManager")
        self.fake_session.group_sendmsg(
            {"command": ["stopping", {
                "module_name": "FooModule"
            }]}, "ConfigManager")
        self.fake_session.group_sendmsg({"command": ["shutdown"]},
                                        "ConfigManager")
        self.assertEqual(len(self.fake_session.message_queue), 4)
        self.cm.run()
        # All commands should have been read out by run()
        # Three of the commands should have been responded to, so the queue
        # should now contain three answers
        self.assertEqual(len(self.fake_session.message_queue), 3)