Parcourir la source

working on ccsession tests
updated command callback fingerprint; the function a module provides should now
take 2 arguments; the first is a string with the name of the command, the second
an ElementPtr containing argument(s)


git-svn-id: svn://bind10.isc.org/svn/bind10/trunk@999 e5f2f494-b856-4b98-b285-d166d9295462

Jelte Jansen il y a 15 ans
Parent
commit
718c75b966

+ 5 - 5
src/bin/auth/main.cc

@@ -63,15 +63,15 @@ my_config_handler(isc::data::ElementPtr config)
 }
 
 isc::data::ElementPtr
-my_command_handler(isc::data::ElementPtr command) {
+my_command_handler(const std::string& command, const isc::data::ElementPtr args) {
     isc::data::ElementPtr answer = isc::config::createAnswer(0);
 
-    cout << "[XX] Handle command: " << endl << command->str() << endl;
-    if (command->get(0)->stringValue() == "print_message") 
+    cout << "[XX] Handle command: " << endl << command << endl;
+    if (command == "print_message") 
     {
-        cout << command->get(1)->get("message") << endl;
+        cout << args << endl;
         /* let's add that message to our answer as well */
-        answer->get("result")->add(command->get(1));
+        answer->get("result")->add(args);
     }
     return answer;
 }

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

@@ -119,24 +119,23 @@ class BoB:
         return answer
         # TODO
 
-    def command_handler(self, command):
-        # a command is of the form [ "command", { "arg1": arg1, "arg2": arg2 } ]
+    def command_handler(self, command, args):
         if self.verbose:
             print("[XX] Boss got command:")
             print(command)
         answer = [ 1, "Command not implemented" ]
-        if type(command) != list or len(command) == 0:
+        if type(command) != str:
             answer = isc.config.ccsession.create_answer(1, "bad command")
         else:
-            cmd = command[0]
+            cmd = command
             if cmd == "shutdown":
                 print("[XX] got shutdown command")
                 self.runnable = False
                 answer = isc.config.ccsession.create_answer(0)
             elif cmd == "print_message":
-                if len(command) > 1 and type(command[1]) == dict and "message" in command[1]:
-                    print(command[1]["message"])
-                answer = isc.config.ccsession.create_answer(0)
+                if args:
+                    print(args)
+                answer = isc.config.ccsession.create_answer(0, args)
             elif cmd == "print_settings":
                 print("Full Config:")
                 full_config = self.ccs.get_full_config()

+ 8 - 8
src/lib/config/cpp/ccsession.cc

@@ -164,7 +164,7 @@ ModuleCCSession::read_module_specification(const std::string& filename) {
 
 ModuleCCSession::ModuleCCSession(std::string spec_file_name,
                                isc::data::ElementPtr(*config_handler)(isc::data::ElementPtr new_config),
-                               isc::data::ElementPtr(*command_handler)(isc::data::ElementPtr command)
+                               isc::data::ElementPtr(*command_handler)(const std::string& command, const isc::data::ElementPtr args)
                               ) throw (isc::cc::SessionError):
     session_(isc::cc::Session())
 {
@@ -249,16 +249,16 @@ ModuleCCSession::check_command()
         if (!data->getType() == Element::map || data->contains("result")) {
             return 0;
         }
+        ElementPtr arg;
+        std::string cmd_str = parseCommand(arg, data);
         ElementPtr answer;
-        if (data->contains("config_update")) {
-            ElementPtr new_config = data->get("config_update");
-            answer = handleConfigUpdate(new_config);
-        }
-        if (data->contains("command")) {
+        if (cmd_str == "config_update") {
+            answer = handleConfigUpdate(arg);
+        } else {
             if (command_handler_) {
-                answer = command_handler_(data->get("command"));
+                answer = command_handler_(cmd_str, arg);
             } else {
-                answer = Element::createFromString("{ \"result\": [0] }");
+                answer = createAnswer(1, "Command given but no command handler for module");
             }
         }
         session_.reply(routing, answer);

+ 3 - 3
src/lib/config/cpp/ccsession.h

@@ -54,7 +54,7 @@ public:
      */
     ModuleCCSession(std::string spec_file_name,
                     isc::data::ElementPtr(*config_handler)(isc::data::ElementPtr new_config) = NULL,
-                    isc::data::ElementPtr(*command_handler)(isc::data::ElementPtr command) = NULL
+                    isc::data::ElementPtr(*command_handler)(const std::string& command, const isc::data::ElementPtr args) = NULL
                     ) throw (isc::cc::SessionError);
     int getSocket();
 
@@ -87,7 +87,7 @@ public:
      *
      * This protocol is very likely to change.
      */
-    void set_command_handler(isc::data::ElementPtr(*command_handler)(isc::data::ElementPtr command)) { command_handler_ = command_handler; };
+    void set_command_handler(isc::data::ElementPtr(*command_handler)(const std::string& command, const isc::data::ElementPtr args)) { command_handler_ = command_handler; };
 
     const ElementPtr getConfig() { return config_; }
 private:
@@ -100,7 +100,7 @@ private:
     ElementPtr handleConfigUpdate(ElementPtr new_config);
 
     isc::data::ElementPtr(*config_handler_)(isc::data::ElementPtr new_config);
-    isc::data::ElementPtr(*command_handler_)(isc::data::ElementPtr command);
+    isc::data::ElementPtr(*command_handler_)(const std::string& command, const isc::data::ElementPtr args);
 };
 
 ElementPtr createAnswer(const int rcode);

+ 31 - 18
src/lib/config/python/isc/config/ccsession.py

@@ -58,6 +58,8 @@ def parse_answer(msg):
         raise ModuleCCSessionError("wrong rcode type in answer message")
     else:
         if len(msg['result']) > 1:
+            if (msg['result'][0] != 0 and type(msg['result'][1]) != str):
+                raise ModuleCCSessionError("rcode in answer message is non-zero, value is not a string")
             return msg['result'][0], msg['result'][1]
         else:
             return msg['result'][0], None
@@ -78,6 +80,7 @@ def create_answer(rcode, arg = None):
 
 # 'fixed' commands
 """Fixed names for command and configuration messages"""
+COMMAND_CONFIG_UPDATE = "config_update"
 COMMAND_COMMANDS_UPDATE = "commands_update"
 COMMAND_SPECIFICATION_UPDATE = "specification_update"
 
@@ -94,9 +97,9 @@ def parse_command(msg):
     if type(msg) == dict and len(msg.items()) == 1:
         cmd, value = msg.popitem()
         if cmd == "command" and type(value) == list:
-            if len(value) == 1:
+            if len(value) == 1 and type(value[0]) == str:
                 return value[0], None
-            elif len(value) > 1:
+            elif len(value) > 1 and type(value[0]) == str:
                 return value[0], value[1]
     return None, None
 
@@ -105,6 +108,8 @@ def create_command(command_name, params = None):
        specified in the module's specification, and an optional params
        object"""
     # TODO: validate_command with spec
+    if type(command_name) != str:
+        raise ModuleCCSessionError("command in create_command() not a string")
     cmd = [ command_name ]
     if params:
         cmd.append(params)
@@ -121,7 +126,7 @@ class ModuleCCSession(ConfigData):
        callbacks are called when 'check_command' is called on the
        ModuleCCSession"""
        
-    def __init__(self, spec_file_name, config_handler, command_handler):
+    def __init__(self, spec_file_name, config_handler, command_handler, cc_session = None):
         """Initialize a ModuleCCSession. This does *NOT* send the
            specification and request the configuration yet. Use start()
            for that once the ModuleCCSession has been initialized.
@@ -137,7 +142,10 @@ class ModuleCCSession(ConfigData):
         self.set_config_handler(config_handler)
         self.set_command_handler(command_handler)
 
-        self._session = Session()
+        if not cc_session:
+            self._session = Session()
+        else:
+            self._session = cc_session
         self._session.group_subscribe(self._module_name, "*")
 
     def start(self):
@@ -172,17 +180,18 @@ class ModuleCCSession(ConfigData):
         if msg:
             answer = None
             try:
-                if "config_update" in msg:
-                    new_config = msg["config_update"]
+                cmd, arg = isc.config.ccsession.parse_command(msg)
+                if cmd == COMMAND_CONFIG_UPDATE:
+                    new_config = arg
                     errors = []
                     if not self._config_handler:
                         answer = create_answer(2, self._module_name + " has no config handler")
                     elif not self.get_module_spec().validate_config(False, new_config, errors):
                         answer = create_answer(1, " ".join(errors))
                     else:
-                        answer = self._config_handler(msg["config_update"])
-                if "command" in msg and self._command_handler:
-                    answer = self._command_handler(msg["command"])
+                        answer = self._config_handler(new_config)
+                else:
+                    answer = self._command_handler(cmd, arg)
             except Exception as exc:
                 answer = create_answer(1, str(exc))
             if answer:
@@ -208,18 +217,22 @@ class ModuleCCSession(ConfigData):
         answer, env = self._session.group_recvmsg(False)
         
     def __request_config(self):
-        """Asks the configuration manager for the current configuration, and call the config handler if set"""
+        """Asks the configuration manager for the current configuration, and call the config handler if set.
+           Raises a ModuleCCSessionError if there is no answer from the configuration manager"""
         self._session.group_sendmsg({ "command": [ "get_config", { "module_name": self._module_name } ] }, "ConfigManager")
         answer, env = self._session.group_recvmsg(False)
-        rcode, value = parse_answer(answer)
-        if rcode == 0:
-            if value != None and self.get_module_spec().validate_config(False, value):
-                self.set_local_config(value);
-                if self._config_handler:
-                    self._config_handler(value)
+        if answer:
+            rcode, value = parse_answer(answer)
+            if rcode == 0:
+                if value != None and self.get_module_spec().validate_config(False, value):
+                    self.set_local_config(value);
+                    if self._config_handler:
+                        self._config_handler(value)
+            else:
+                # log error
+                print("Error requesting configuration: " + value)
         else:
-            # log error
-            print("Error requesting configuration: " + value)
+            raise ModuleCCSessionError("No answer from configuration manager")
 
 class UIModuleCCSession(MultiConfigData):
     """This class is used in a configuration user interface. It contains

+ 169 - 0
src/lib/config/python/isc/config/ccsession_test.py

@@ -0,0 +1,169 @@
+# Copyright (C) 2010  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+#
+# Tests for the ConfigData and MultiConfigData classes
+#
+
+import unittest
+import os
+from isc.config.ccsession import *
+from unittest_fakesession import FakeModuleCCSession
+
+class TestHelperFunctions(unittest.TestCase):
+    def test_parse_answer(self):
+        self.assertRaises(ModuleCCSessionError, parse_answer, 1)
+        self.assertRaises(ModuleCCSessionError, parse_answer, { 'just a dict': 1 })
+        self.assertRaises(ModuleCCSessionError, parse_answer, { 'result': 1 })
+        self.assertRaises(ModuleCCSessionError, parse_answer, { 'result': [] })
+        self.assertRaises(ModuleCCSessionError, parse_answer, { 'result': [ 'not_an_rcode' ] })
+        self.assertRaises(ModuleCCSessionError, parse_answer, { 'result': [ 1, 2 ] })
+        
+        rcode, val = parse_answer({ 'result': [ 0 ] })
+        self.assertEqual(0, rcode)
+        self.assertEqual(None, val)
+        rcode, val = parse_answer({ 'result': [ 0, "something" ] })
+        self.assertEqual(0, rcode)
+        self.assertEqual("something", val)
+        rcode, val = parse_answer({ 'result': [ 1, "some error" ] })
+        self.assertEqual(1, rcode)
+        self.assertEqual("some error", val)
+
+    def test_create_answer(self):
+        self.assertRaises(ModuleCCSessionError, create_answer, 'not_an_int')
+        self.assertRaises(ModuleCCSessionError, create_answer, 1, 2)
+        self.assertRaises(ModuleCCSessionError, create_answer, 1)
+        self.assertEqual({ 'result': [ 0 ] }, create_answer(0))
+        self.assertEqual({ 'result': [ 1, 'something bad' ] }, create_answer(1, 'something bad'))
+        self.assertEqual({ 'result': [ 0, 'something good' ] }, create_answer(0, 'something good'))
+        self.assertEqual({ 'result': [ 0, ['some', 'list' ] ] }, create_answer(0, ['some', 'list']))
+        self.assertEqual({ 'result': [ 0, {'some': 'map' } ] }, create_answer(0, {'some': 'map'}))
+
+    def test_parse_command(self):
+        cmd, arg = parse_command(1)
+        self.assertEqual(None, cmd)
+        self.assertEqual(None, arg)
+
+        cmd, arg = parse_command({})
+        self.assertEqual(None, cmd)
+        self.assertEqual(None, arg)
+
+        cmd, arg = parse_command({ 'not a command': 1})
+        self.assertEqual(None, cmd)
+        self.assertEqual(None, arg)
+
+        cmd, arg = parse_command({ 'command': 1})
+        self.assertEqual(None, cmd)
+        self.assertEqual(None, arg)
+
+        cmd, arg = parse_command({ 'command': []})
+        self.assertEqual(None, cmd)
+        self.assertEqual(None, arg)
+
+        cmd, arg = parse_command({ 'command': [ 1 ]})
+        self.assertEqual(None, cmd)
+        self.assertEqual(None, arg)
+
+        cmd, arg = parse_command({ 'command': [ 'command' ]})
+        self.assertEqual('command', cmd)
+        self.assertEqual(None, arg)
+
+        cmd, arg = parse_command({ 'command': [ 'command', 1 ]})
+        self.assertEqual('command', cmd)
+        self.assertEqual(1, arg)
+
+        cmd, arg = parse_command({ 'command': [ 'command', ['some', 'argument', 'list'] ]})
+        self.assertEqual('command', cmd)
+        self.assertEqual(['some', 'argument', 'list'], arg)
+
+    def test_create_command(self):
+        self.assertRaises(ModuleCCSessionError, create_command, 1)
+        self.assertEqual({'command': [ 'my_command' ]}, create_command('my_command'))
+        self.assertEqual({'command': [ 'my_command', 1 ]}, create_command('my_command', 1))
+        self.assertEqual({'command': [ 'my_command', [ 'some', 'list' ] ]}, create_command('my_command', [ 'some', 'list' ]))
+        self.assertEqual({'command': [ 'my_command', { 'some': 'map' } ]}, create_command('my_command', { 'some': 'map' }))
+
+class TestModuleCCSession(unittest.TestCase):
+    def setUp(self):
+        if 'CONFIG_TESTDATA_PATH' in os.environ:
+            self.data_path = os.environ['CONFIG_TESTDATA_PATH']
+        else:
+            self.data_path = "../../../testdata"
+
+    def spec_file(self, file):
+        return self.data_path + os.sep + file
+        
+    def create_session(self, spec_file_name, config_handler = None, command_handler = None, cc_session = None):
+        return ModuleCCSession(self.spec_file(spec_file_name), config_handler, command_handler, cc_session)
+
+    def test_init(self):
+        fake_session = FakeModuleCCSession()
+        mccs = self.create_session("spec1.spec", None, None, fake_session)
+        self.assertEqual(isc.config.module_spec_from_file(self.spec_file("spec1.spec"))._module_spec, mccs.specification._module_spec)
+        self.assertEqual(None, mccs._config_handler)
+        self.assertEqual(None, mccs._command_handler)
+
+    def test_start1(self):
+        fake_session = FakeModuleCCSession()
+        mccs = self.create_session("spec1.spec", None, None, fake_session)
+        self.assertEqual(len(fake_session.message_queue), 0)
+        self.assertRaises(ModuleCCSessionError, mccs.start)
+        self.assertEqual(len(fake_session.message_queue), 2)
+        self.assertEqual({'command': ['module_spec', {'module_name': 'Spec1'}]},
+                         fake_session.get_message('ConfigManager', None))
+        self.assertEqual({'command': ['get_config', {'module_name': 'Spec1'}]},
+                         fake_session.get_message('ConfigManager', None))
+
+        self.assertEqual(len(fake_session.message_queue), 0)
+        fake_session.group_sendmsg({'result': [ 0 ]}, "Spec1")
+        fake_session.group_sendmsg({'result': [ 0 ]}, "Spec1")
+        mccs.start()
+        self.assertEqual(len(fake_session.message_queue), 2)
+
+        self.assertEqual({'command': ['module_spec', {'module_name': 'Spec1'}]},
+                         fake_session.get_message('ConfigManager', None))
+        self.assertEqual({'command': ['get_config', {'module_name': 'Spec1'}]},
+                         fake_session.get_message('ConfigManager', None))
+
+    def test_get_socket(self):
+        fake_session = FakeModuleCCSession()
+        mccs = self.create_session("spec1.spec", None, None, fake_session)
+        self.assertNotEqual(None, mccs.get_socket())
+
+    def test_get_session(self):
+        fake_session = FakeModuleCCSession()
+        mccs = self.create_session("spec1.spec", None, None, fake_session)
+        self.assertEqual(fake_session, mccs.get_session())
+
+    def test_close(self):
+        fake_session = FakeModuleCCSession()
+        mccs = self.create_session("spec1.spec", None, None, fake_session)
+        mccs.close()
+        self.assertEqual("closed", fake_session._socket)
+
+    def test_check_command(self):
+        fake_session = FakeModuleCCSession()
+        mccs = self.create_session("spec1.spec", None, None, fake_session)
+
+        mccs.check_command()
+        self.assertEqual(len(fake_session.message_queue), 0)
+
+        fake_session.group_sendmsg({'result': [ 0 ]}, "Spec1")
+        mccs.check_command()
+        self.assertEqual(len(fake_session.message_queue), 0)
+        
+if __name__ == '__main__':
+    unittest.main()
+

+ 6 - 3
src/lib/config/python/isc/config/cfgmgr.py

@@ -241,13 +241,15 @@ class ConfigManager:
             conf_part = data.find_no_exc(self.config.data, module_name)
             if conf_part:
                 data.merge(conf_part, cmd[1])
-                self.cc.group_sendmsg({ "config_update": conf_part }, module_name)
+                update_cmd = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_CONFIG_UPDATE, conf_part)
+                self.cc.group_sendmsg(update_cmd, module_name)
                 answer, env = self.cc.group_recvmsg(False)
             else:
                 conf_part = data.set(self.config.data, module_name, {})
                 data.merge(conf_part[module_name], cmd[1])
                 # send out changed info
-                self.cc.group_sendmsg({ "config_update": conf_part[module_name] }, module_name)
+                update_cmd = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_CONFIG_UPDATE, conf_part[module_name])
+                self.cc.group_sendmsg(update_cmd, module_name)
                 # replace 'our' answer with that of the module
                 answer, env = self.cc.group_recvmsg(False)
             if answer:
@@ -263,7 +265,8 @@ class ConfigManager:
             err_list = []
             for module in self.config.data:
                 if module != "version":
-                    self.cc.group_sendmsg({ "config_update": self.config.data[module] }, module)
+                    update_cmd = isc.config.ccsession.create_command(isc.config.ccsession.COMMAND_CONFIG_UPDATE, self.config.data[module])
+                    self.cc.group_sendmsg(update_cmd, module)
                     answer, env = self.cc.group_recvmsg(False)
                     if answer == None:
                         got_error = True

+ 7 - 51
src/lib/config/python/isc/config/cfgmgr_test.py

@@ -20,6 +20,7 @@
 import unittest
 import os
 from isc.config.cfgmgr import *
+from unittest_fakesession import FakeModuleCCSession
 
 class TestConfigManagerData(unittest.TestCase):
     def setUp(self):
@@ -71,51 +72,6 @@ class TestConfigManagerData(unittest.TestCase):
         cfd2.data['test'] = { 'a': [ 1, 2, 3]}
         self.assertNotEqual(cfd1, cfd2)
         
-#
-# We can probably use a more general version of this
-#
-class FakeModuleCCSession:
-    def __init__(self):
-        self.subscriptions = {}
-        # each entry is of the form [ channel, instance, message ]
-        self.message_queue = []
-
-    def group_subscribe(self, group_name, instance_name = None):
-        if not group_name in self.subscriptions:
-            self.subscriptions[group_name] = []
-        if instance_name:
-            self.subscriptions[group_name].append(instance_name)
-            
-
-    def has_subscription(self, group_name, instance_name = None):
-        if group_name in self.subscriptions:
-            if instance_name:
-                return instance_name in self.subscriptions[group_name]
-            else:
-                return True
-        else:
-            return False
-
-    def group_sendmsg(self, msg, channel, target = None):
-        self.message_queue.append([ channel, target, msg ])
-
-    def group_reply(self, env, msg):
-        pass
-
-    def group_recvmsg(self, blocking):
-        for qm in self.message_queue:
-            if qm[0] in self.subscriptions and (qm[1] == None or qm[1] in self.subscriptions[qm[0]]):
-                self.message_queue.remove(qm)
-                return qm[2], {}
-        return None, None
-
-    def get_message(self, channel, target = None):
-        for qm in self.message_queue:
-            if qm[0] == channel and qm[1] == target:
-                self.message_queue.remove(qm)
-                return qm[2]
-        return None
-        
 
 class TestConfigManager(unittest.TestCase):
 
@@ -257,7 +213,7 @@ class TestConfigManager(unittest.TestCase):
                                 my_ok_answer)
         # The cfgmgr should have eaten the ok message, and sent out an update again
         self.assertEqual(len(self.fake_session.message_queue), 1)
-        self.assertEqual({'config_update': {'test': 123}},
+        self.assertEqual({'command': [ 'config_update', {'test': 123}]},
                          self.fake_session.get_message(self.name, None))
         # and the queue should now be empty again
         self.assertEqual(len(self.fake_session.message_queue), 0)
@@ -267,7 +223,7 @@ class TestConfigManager(unittest.TestCase):
         self._handle_msg_helper({ "command": [ "set_config", [self.name, { "test": 124 }] ] },
                                 my_ok_answer)
         self.assertEqual(len(self.fake_session.message_queue), 1)
-        self.assertEqual({'config_update': {'test': 124}},
+        self.assertEqual({'command': [ 'config_update', {'test': 124}]},
                          self.fake_session.get_message(self.name, None))
         self.assertEqual(len(self.fake_session.message_queue), 0)
 
@@ -277,7 +233,7 @@ class TestConfigManager(unittest.TestCase):
         self._handle_msg_helper({ "command": [ "set_config", [ { self.name: { "test": 125 } }] ] },
                                 my_ok_answer )
         self.assertEqual(len(self.fake_session.message_queue), 1)
-        self.assertEqual({'config_update': {'test': 125}},
+        self.assertEqual({'command': [ 'config_update', {'test': 125}]},
                          self.fake_session.get_message(self.name, None))
         self.assertEqual(len(self.fake_session.message_queue), 0)
 
@@ -286,7 +242,7 @@ class TestConfigManager(unittest.TestCase):
                           self.cm.handle_msg,
                           { "command": [ "set_config", [ { self.name: { "test": 125 } }] ] } )
         self.assertEqual(len(self.fake_session.message_queue), 1)
-        self.assertEqual({'config_update': {'test': 125}},
+        self.assertEqual({'command': [ 'config_update', {'test': 125}]},
                          self.fake_session.get_message(self.name, None))
         self.assertEqual(len(self.fake_session.message_queue), 0)
 
@@ -295,7 +251,7 @@ class TestConfigManager(unittest.TestCase):
         self._handle_msg_helper({ "command": [ "set_config", [ { self.name: { "test": 125 } }] ] },
                                 my_bad_answer )
         self.assertEqual(len(self.fake_session.message_queue), 1)
-        self.assertEqual({'config_update': {'test': 125}},
+        self.assertEqual({'command': [ 'config_update', {'test': 125}]},
                          self.fake_session.get_message(self.name, None))
         self.assertEqual(len(self.fake_session.message_queue), 0)
 
@@ -304,7 +260,7 @@ class TestConfigManager(unittest.TestCase):
         self._handle_msg_helper({ "command": [ "set_config", [ self.name, { "test": 125 }] ] },
                                 my_bad_answer )
         self.assertEqual(len(self.fake_session.message_queue), 1)
-        self.assertEqual({'config_update': {'test': 125}},
+        self.assertEqual({'command': [ 'config_update', {'test': 125}]},
                          self.fake_session.get_message(self.name, None))
         self.assertEqual(len(self.fake_session.message_queue), 0)
 

+ 2 - 0
src/lib/config/python/isc/config/config_test.in

@@ -16,4 +16,6 @@ ${PYTHON_EXEC} -O ${CONFIG_PATH}/config_data_test.py $*
 
 ${PYTHON_EXEC} -O ${CONFIG_PATH}/module_spec_test.py $*
 
+${PYTHON_EXEC} -O ${CONFIG_PATH}/ccsession_test.py $*
+
 ${PYTHON_EXEC} -O ${CONFIG_PATH}/cfgmgr_test.py $*

+ 52 - 0
src/lib/config/python/isc/config/unittest_fakesession.py

@@ -0,0 +1,52 @@
+
+
+#
+# We can probably use a more general version of this
+#
+class FakeModuleCCSession:
+    def __init__(self):
+        self.subscriptions = {}
+        # each entry is of the form [ channel, instance, message ]
+        self.message_queue = []
+        self._socket = "ok we just need something not-None here atm"
+
+    def group_subscribe(self, group_name, instance_name = None):
+        if not group_name in self.subscriptions:
+            self.subscriptions[group_name] = []
+        if instance_name:
+            self.subscriptions[group_name].append(instance_name)
+            
+
+    def has_subscription(self, group_name, instance_name = None):
+        if group_name in self.subscriptions:
+            if instance_name:
+                return instance_name in self.subscriptions[group_name]
+            else:
+                return True
+        else:
+            return False
+
+    def group_sendmsg(self, msg, channel, target = None):
+        self.message_queue.append([ channel, target, msg ])
+
+    def group_reply(self, env, msg):
+        pass
+
+    def group_recvmsg(self, blocking):
+        for qm in self.message_queue:
+            if qm[0] in self.subscriptions and (qm[1] == None or qm[1] in self.subscriptions[qm[0]]):
+                self.message_queue.remove(qm)
+                return qm[2], {}
+        return None, None
+
+    def get_message(self, channel, target = None):
+        for qm in self.message_queue:
+            if qm[0] == channel and qm[1] == target:
+                self.message_queue.remove(qm)
+                return qm[2]
+        return None
+
+    def close(self):
+        # need to pass along somehow that this function has been called,
+        self._socket = "closed"
+        pass