Commit 718c75b9 authored by Jelte Jansen's avatar Jelte Jansen
Browse files

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
parent 8988bd54
......@@ -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;
}
......
......@@ -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()
......
......@@ -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);
......
......@@ -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);
......
......@@ -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
......
# 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()
......@@ -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
......
......@@ -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)
......
......@@ -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 $*
#
# 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