Commit 275dbabd authored by Jelte Jansen's avatar Jelte Jansen

renamed CCSession to ModuleCCSession

renamed UIConfigData to UICCSession
That last one is now a subclass of MultiConfigData, since half the functions it needed turned out to be nothing but passtroughs
(next is to do the same for ModuleCCSession, only that will be a subclass of ConfigData, and make the two as similar as possible, from a certain point of view they to the same, albeit from another 'direction')


git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-configuration@842 e5f2f494-b856-4b98-b285-d166d9295462
parent 2884a118
...@@ -116,7 +116,7 @@ class BoB: ...@@ -116,7 +116,7 @@ class BoB:
print("[XX] handling new config:") print("[XX] handling new config:")
print(new_config) print(new_config)
errors = [] errors = []
if self.ccs.get_config_spec().get_module_spec().validate(False, new_config, errors): if self.ccs.get_module_spec().validate(False, new_config, errors):
print("[XX] new config validated") print("[XX] new config validated")
self.ccs.set_config(new_config) self.ccs.set_config(new_config)
answer = isc.config.ccsession.create_answer(0) answer = isc.config.ccsession.create_answer(0)
...@@ -209,7 +209,7 @@ class BoB: ...@@ -209,7 +209,7 @@ class BoB:
time.sleep(1) time.sleep(1)
if self.verbose: if self.verbose:
print("[XX] starting ccsession") print("[XX] starting ccsession")
self.ccs = isc.config.CCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler) self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler)
self.ccs.start() self.ccs.start()
if self.verbose: if self.verbose:
print("[XX] ccsession started") print("[XX] ccsession started")
......
...@@ -86,7 +86,7 @@ class BindCmdInterpreter(Cmd): ...@@ -86,7 +86,7 @@ class BindCmdInterpreter(Cmd):
return False return False
# Get all module information from cmd-ctrld # Get all module information from cmd-ctrld
self.config_data = isc.config.UIConfigData(self) self.config_data = isc.config.UIModuleCCSession(self)
self.update_commands() self.update_commands()
self.cmdloop() self.cmdloop()
except KeyboardInterrupt: except KeyboardInterrupt:
......
...@@ -21,27 +21,28 @@ ...@@ -21,27 +21,28 @@
# modeled after ccsession.h/cc 'protocol' changes here need to be # modeled after ccsession.h/cc 'protocol' changes here need to be
# made there as well # made there as well
"""This module provides the CCSession class, as well as a set of """This module provides the ModuleCCSession class, as well as a set of
utility functions to create and parse messages related to commands utility functions to create and parse messages related to commands
and configuration""" and configuration"""
from isc.cc import Session from isc.cc import Session
from isc.config.config_data import ConfigData, MultiConfigData
import isc import isc
class CCSessionError(Exception): pass class ModuleCCSessionError(Exception): pass
def parse_answer(msg): def parse_answer(msg):
"""Returns a tuple (rcode, value), where value depends on the """Returns a tuple (rcode, value), where value depends on the
command that was called. If rcode != 0, value is a string command that was called. If rcode != 0, value is a string
containing an error message""" containing an error message"""
if 'result' not in msg: if 'result' not in msg:
raise CCSessionError("answer message does not contain 'result' element") raise ModuleCCSessionError("answer message does not contain 'result' element")
elif type(msg['result']) != list: elif type(msg['result']) != list:
raise CCSessionError("wrong result type in answer message") raise ModuleCCSessionError("wrong result type in answer message")
elif len(msg['result']) < 1: elif len(msg['result']) < 1:
raise CCSessionError("empty result list in answer message") raise ModuleCCSessionError("empty result list in answer message")
elif type(msg['result'][0]) != int: elif type(msg['result'][0]) != int:
raise CCSessionError("wrong rcode type in answer message") raise ModuleCCSessionError("wrong rcode type in answer message")
else: else:
if len(msg['result']) > 1: if len(msg['result']) > 1:
return msg['result'][0], msg['result'][1] return msg['result'][0], msg['result'][1]
...@@ -54,28 +55,28 @@ def create_answer(rcode, arg = None): ...@@ -54,28 +55,28 @@ def create_answer(rcode, arg = None):
on what the command or option was. If rcode != 0, arg must be on what the command or option was. If rcode != 0, arg must be
a string containing an error message""" a string containing an error message"""
if type(rcode) != int: if type(rcode) != int:
raise CCSessionError("rcode in create_answer() must be an integer") raise ModuleCCSessionError("rcode in create_answer() must be an integer")
if rcode != 0 and type(arg) != str: if rcode != 0 and type(arg) != str:
raise CCSessionError("arg in create_answer for rcode != 0 must be a string describing the error") raise ModuleCCSessionError("arg in create_answer for rcode != 0 must be a string describing the error")
if arg != None: if arg != None:
return { 'result': [ rcode, arg ] } return { 'result': [ rcode, arg ] }
else: else:
return { 'result': [ rcode ] } return { 'result': [ rcode ] }
class CCSession: class ModuleCCSession:
"""This class maintains a connection to the command channel, as """This class maintains a connection to the command channel, as
well as configuration options for modules. The module provides well as configuration options for modules. The module provides
a specification file that contains the module name, configuration a specification file that contains the module name, configuration
options, and commands. It also gives the CCSession two callback options, and commands. It also gives the ModuleCCSession two callback
functions, one to call when there is a direct command to the functions, one to call when there is a direct command to the
module, and one to update the configuration run-time. These module, and one to update the configuration run-time. These
callbacks are called when 'check_command' is called on the callbacks are called when 'check_command' is called on the
CCSession""" ModuleCCSession"""
def __init__(self, spec_file_name, config_handler, command_handler): def __init__(self, spec_file_name, config_handler, command_handler):
"""Initialize a CCSession. This does *NOT* send the """Initialize a ModuleCCSession. This does *NOT* send the
specification and request the configuration yet. Use start() specification and request the configuration yet. Use start()
for that once the CCSession has been initialized. for that once the ModuleCCSession has been initialized.
specfile_name is the path to the specification file specfile_name is the path to the specification file
config_handler and command_handler are callback functions, config_handler and command_handler are callback functions,
see set_config_handler and set_command_handler for more see set_config_handler and set_command_handler for more
...@@ -137,8 +138,6 @@ class CCSession: ...@@ -137,8 +138,6 @@ class CCSession:
if msg: if msg:
answer = None answer = None
try: try:
print("[XX] got msg: ")
print(msg)
if "config_update" in msg and self._config_handler: if "config_update" in msg and self._config_handler:
answer = self._config_handler(msg["config_update"]) answer = self._config_handler(msg["config_update"])
if "command" in msg and self._command_handler: if "command" in msg and self._command_handler:
...@@ -163,11 +162,8 @@ class CCSession: ...@@ -163,11 +162,8 @@ class CCSession:
def __send_spec(self): def __send_spec(self):
"""Sends the data specification to the configuration manager""" """Sends the data specification to the configuration manager"""
print("[XX] send spec for " + self._module_name + " to ConfigManager")
self._session.group_sendmsg({ "module_spec": self._config_data.get_module_spec().get_full_spec() }, "ConfigManager") self._session.group_sendmsg({ "module_spec": self._config_data.get_module_spec().get_full_spec() }, "ConfigManager")
answer, env = self._session.group_recvmsg(False) answer, env = self._session.group_recvmsg(False)
print("[XX] got answer from cfgmgr:")
print(answer)
def __request_config(self): 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"""
...@@ -175,11 +171,75 @@ class CCSession: ...@@ -175,11 +171,75 @@ class CCSession:
answer, env = self._session.group_recvmsg(False) answer, env = self._session.group_recvmsg(False)
rcode, value = parse_answer(answer) rcode, value = parse_answer(answer)
if rcode == 0: if rcode == 0:
if self._config_data.get_module_spec().validate(False, value): if value != None and self._config_data.get_module_spec().validate(False, value):
self._config_data.set_local_config(value); self._config_data.set_local_config(value);
if self._config_handler: if self._config_handler:
self._config_handler(value) self._config_handler(value)
else: else:
# log error # log error
print("Error requesting configuration: " + value) print("Error requesting configuration: " + value)
class UIModuleCCSession(MultiConfigData):
"""This class is used in a configuration user interface. It contains
specific functions for getting, displaying, and sending
configuration settings."""
def __init__(self, conn):
MultiConfigData.__init__(self)
self._conn = conn
self.request_specifications()
self.request_current_config()
def request_specifications(self):
# this step should be unnecessary but is the current way cmdctl returns stuff
# so changes are needed there to make this clean (we need a command to simply get the
# full specs for everything, including commands etc, not separate gets for that)
specs = self._conn.send_GET('/config_spec')
commands = self._conn.send_GET('/commands')
for module in specs.keys():
cur_spec = { 'module_name': module }
if module in specs and specs[module]:
cur_spec['config_data'] = specs[module]
if module in commands and commands[module]:
cur_spec['commands'] = commands[module]
self.set_specification(isc.config.ModuleSpec(cur_spec))
def request_current_config(self):
config = self._conn.send_GET('/config_data')
if 'version' not in config or config['version'] != 1:
raise Exception("Bad config version")
self.set_local_config(config)
def add_value(self, identifier, value_str):
module_spec = self.find_spec_part(identifier)
if (type(module_spec) != dict or "list_item_spec" not in module_spec):
raise DataTypeError(identifier + " is not a list")
value = isc.cc.data.parse_value_str(value_str)
cur_list, status = self.get_value(identifier)
if not cur_list:
cur_list = []
if value not in cur_list:
cur_list.append(value)
self.set_value(identifier, cur_list)
def remove_value(self, identifier, value_str):
module_spec = find_spec(self.config.specification, identifier)
if (type(module_spec) != dict or "list_item_spec" not in module_spec):
raise DataTypeError(identifier + " is not a list")
value = parse_value_str(value_str)
check_type(module_spec, [value])
cur_list = isc.cc.data.find_no_exc(self.config_changes, identifier)
if not cur_list:
cur_list = isc.cc.data.find_no_exc(self.config.data, identifier)
if not cur_list:
cur_list = []
if value in cur_list:
cur_list.remove(value)
set(self.config_changes, identifier, cur_list)
def commit(self):
if self.get_local_changes():
self._conn.send_POST('/ConfigManager/set_config', self.get_local_changes())
# todo: check result
self.request_current_config()
self.clear_local_changes()
...@@ -62,7 +62,7 @@ class TestConfigManagerData(unittest.TestCase): ...@@ -62,7 +62,7 @@ class TestConfigManagerData(unittest.TestCase):
# #
# We can probably use a more general version of this # We can probably use a more general version of this
# #
class FakeCCSession: class FakeModuleCCSession:
def __init__(self): def __init__(self):
self.subscriptions = {} self.subscriptions = {}
# each entry is of the form [ channel, instance, message ] # each entry is of the form [ channel, instance, message ]
...@@ -106,7 +106,7 @@ class TestConfigManager(unittest.TestCase): ...@@ -106,7 +106,7 @@ class TestConfigManager(unittest.TestCase):
def setUp(self): def setUp(self):
self.data_path = os.environ['CONFIG_TESTDATA_PATH'] self.data_path = os.environ['CONFIG_TESTDATA_PATH']
self.fake_session = FakeCCSession() self.fake_session = FakeModuleCCSession()
self.cm = ConfigManager(self.data_path, self.fake_session) self.cm = ConfigManager(self.data_path, self.fake_session)
self.name = "TestModule" self.name = "TestModule"
self.spec = isc.config.module_spec_from_file(self.data_path + os.sep + "/spec2.spec") self.spec = isc.config.module_spec_from_file(self.data_path + os.sep + "/spec2.spec")
......
...@@ -14,9 +14,10 @@ ...@@ -14,9 +14,10 @@
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
# #
# Class to store configuration data and data definition # Classes to store configuration data and data specifications
# Used by the config manager and python modules that communicate #
# with the configuration manager # Used by the config manager, (python) modules, and UI's (those last
# two through the classes in ccsession)
# #
...@@ -173,19 +174,23 @@ class MultiConfigData: ...@@ -173,19 +174,23 @@ class MultiConfigData:
self._local_changes = {} self._local_changes = {}
def set_specification(self, spec): def set_specification(self, spec):
"""Add or update a ModuleSpec"""
if type(spec) != isc.config.ModuleSpec: if type(spec) != isc.config.ModuleSpec:
raise Exception("not a datadef") raise Exception("not a datadef")
self._specifications[spec.get_module_name()] = spec self._specifications[spec.get_module_name()] = spec
def get_module_spec(self, module): def get_module_spec(self, module):
"""Returns the ModuleSpec for the module with the given name"""
if module in self._specifications: if module in self._specifications:
return self._specifications[module] return self._specifications[module]
else: else:
return None return None
def find_spec_part(self, identifier): def find_spec_part(self, identifier):
"""returns the specification for the item at the given """Returns the specification for the item at the given
identifier, or None if not found""" identifier, or None if not found. The first part of the
identifier (up to the first /) is interpreted as the module
name."""
if identifier[0] == '/': if identifier[0] == '/':
identifier = identifier[1:] identifier = identifier[1:]
module, sep, id = identifier.partition("/") module, sep, id = identifier.partition("/")
...@@ -194,30 +199,52 @@ class MultiConfigData: ...@@ -194,30 +199,52 @@ class MultiConfigData:
except isc.cc.data.DataNotFoundError as dnfe: except isc.cc.data.DataNotFoundError as dnfe:
return None return None
def set_current_config(self, config): # this function should only be called by __request_config
def __set_current_config(self, config):
"""Replace the full current config values."""
self._current_config = config self._current_config = config
def get_current_config(self): def get_current_config(self):
"""The current config is a dict where the first level is """Returns the current configuration as it is known by the
configuration manager. It is a dict where the first level is
the module name, and the value is the config values for the module name, and the value is the config values for
that module""" that module"""
return self._current_config return self._current_config
def get_local_changes(self): def get_local_changes(self):
"""Returns the local config changes, i.e. those that have not
been committed yet and are not known by the configuration
manager or the modules."""
return self._local_changes return self._local_changes
def clear_local_changes(self): def clear_local_changes(self):
"""Reverts all local changes"""
self._local_changes = {} self._local_changes = {}
def get_local_value(self, identifier): def get_local_value(self, identifier):
"""Returns a specific local (uncommitted) configuration value,
as specified by the identifier. If the local changes do not
contain a new setting for this identifier, or if the
identifier cannot be found, None is returned. See
get_value() for a general way to find a configuration value
"""
return isc.cc.data.find_no_exc(self._local_changes, identifier) return isc.cc.data.find_no_exc(self._local_changes, identifier)
def get_current_value(self, identifier): def get_current_value(self, identifier):
"""Returns the current non-default value, or None if not set""" """Returns the current non-default value as known by the
configuration manager, or None if it is not set.
See get_value() for a general way to find a configuration
value
"""
return isc.cc.data.find_no_exc(self._current_config, identifier) return isc.cc.data.find_no_exc(self._current_config, identifier)
def get_default_value(self, identifier): def get_default_value(self, identifier):
"""returns the default value, or None if there is no default""" """Returns the default value for the given identifier as
specified by the module specification, or None if there is
no default or the identifier could not be found.
See get_value() for a general way to find a configuration
value
"""
if identifier[0] == '/': if identifier[0] == '/':
identifier = identifier[1:] identifier = identifier[1:]
module, sep, id = identifier.partition("/") module, sep, id = identifier.partition("/")
...@@ -231,10 +258,12 @@ class MultiConfigData: ...@@ -231,10 +258,12 @@ class MultiConfigData:
return None return None
def get_value(self, identifier): def get_value(self, identifier):
"""Returns a tuple containing value,status. Status is either """Returns a tuple containing value,status.
LOCAL, CURRENT, DEFAULT or NONE, corresponding to the The value contains the configuration value for the given
source of the value (local change, current setting, default identifier. The status reports where this value came from;
as specified by the specification, or not found at all).""" it is one of: LOCAL, CURRENT, DEFAULT or NONE, corresponding
(local change, current setting, default as specified by the
specification, or not found at all)."""
value = self.get_local_value(identifier) value = self.get_local_value(identifier)
if value: if value:
return value, self.LOCAL return value, self.LOCAL
...@@ -253,8 +282,8 @@ class MultiConfigData: ...@@ -253,8 +282,8 @@ class MultiConfigData:
value: value of the entry if it is a string, int, double or bool value: value of the entry if it is a string, int, double or bool
modified: true if the value is a local change modified: true if the value is a local change
default: true if the value has been changed default: true if the value has been changed
Throws DataNotFoundError if the identifier is bad
TODO: use the consts for those last ones TODO: use the consts for those last ones
Throws DataNotFoundError if the identifier is bad
""" """
result = [] result = []
if not identifier: if not identifier:
...@@ -327,8 +356,8 @@ class MultiConfigData: ...@@ -327,8 +356,8 @@ class MultiConfigData:
def set_value(self, identifier, value): def set_value(self, identifier, value):
"""Set the local value at the given identifier to value""" """Set the local value at the given identifier to value"""
spec_part = self.find_spec_part(identifier) spec_part = self.find_spec_part(identifier)
if check_type(spec_part, value): check_type(spec_part, value)
isc.cc.data.set(self._local_changes, identifier, value) isc.cc.data.set(self._local_changes, identifier, value)
def get_config_item_list(self, identifier = None): def get_config_item_list(self, identifier = None):
"""Returns a list of strings containing the item_names of """Returns a list of strings containing the item_names of
...@@ -343,254 +372,3 @@ class MultiConfigData: ...@@ -343,254 +372,3 @@ class MultiConfigData:
return self._specifications.keys() return self._specifications.keys()
class UIConfigData():
"""This class is used in a configuration user interface. It contains
specific functions for getting, displaying, and sending
configuration settings."""
def __init__(self, conn):
self._conn = conn
self._data = MultiConfigData()
self.request_specifications()
self.request_current_config()
a,b = self._data.get_value("/Boss/some_string")
def request_specifications(self):
# this step should be unnecessary but is the current way cmdctl returns stuff
# so changes are needed there to make this clean (we need a command to simply get the
# full specs for everything, including commands etc, not separate gets for that)
specs = self._conn.send_GET('/config_spec')
commands = self._conn.send_GET('/commands')
#print(specs)
#print(commands)
for module in specs.keys():
cur_spec = { 'module_name': module }
if module in specs and specs[module]:
cur_spec['config_data'] = specs[module]
if module in commands and commands[module]:
cur_spec['commands'] = commands[module]
self._data.set_specification(isc.config.ModuleSpec(cur_spec))
def request_current_config(self):
config = self._conn.send_GET('/config_data')
if 'version' not in config or config['version'] != 1:
raise Exception("Bad config version")
self._data.set_current_config(config)
def get_value(self, identifier):
return self._data.get_value(identifier)
def set_value(self, identifier, value):
return self._data.set_value(identifier, value);
def add_value(self, identifier, value_str):
module_spec = self._data.find_spec_part(identifier)
if (type(module_spec) != dict or "list_item_spec" not in module_spec):
raise DataTypeError(identifier + " is not a list")
value = isc.cc.data.parse_value_str(value_str)
cur_list, status = self.get_value(identifier)
if not cur_list:
cur_list = []
if value not in cur_list:
cur_list.append(value)
self.set_value(identifier, cur_list)
def remove_value(self, identifier, value_str):
module_spec = find_spec(self.config.specification, identifier)
if (type(module_spec) != dict or "list_item_spec" not in module_spec):
raise DataTypeError(identifier + " is not a list")
value = parse_value_str(value_str)
check_type(module_spec, [value])
cur_list = isc.cc.data.find_no_exc(self.config_changes, identifier)
if not cur_list:
cur_list = isc.cc.data.find_no_exc(self.config.data, identifier)
if not cur_list:
cur_list = []
if value in cur_list:
cur_list.remove(value)
set(self.config_changes, identifier, cur_list)
def get_value_maps(self, identifier = None):
return self._data.get_value_maps(identifier)
def get_local_changes(self):
return self._data.get_local_changes()
def commit(self):
self._conn.send_POST('/ConfigManager/set_config', self._data.get_local_changes())
# todo: check result
self.request_current_config()
self._data.clear_local_changes()
def get_config_item_list(self, identifier = None):
return self._data.get_config_item_list(identifier)
# remove
class OUIConfigData():
"""This class is used in a configuration user interface. It contains
specific functions for getting, displaying, and sending
configuration settings."""
def __init__(self, conn):
# the specs dict contains module: configdata elements
# these should all be replaced by the new stuff
module_spec = self.get_module_spec(conn)
self.config = module_spec
self.get_config_spec(conn)
self.config_changes = {}
#
self.config_
self.specs = self.get_module_specs(conn)
def get_config_spec(self, conn):
data = conn.send_GET('/config_data')
def send_changes(self, conn):
conn.send_POST('/ConfigManager/set_config', self.config_changes)
# Get latest config data
self.get_config_spec(conn)
self.config_changes = {}
def get_module_spec(self, conn):
return conn.send_GET('/config_spec')
def get_module_specs(self, conn):
specs = {}
allspecs = conn.send_GET('/config_spec')
def set(self, identifier, value):
# check against definition
spec = find_spec(identifier)
check_type(spec, value)
set(self.config_changes, identifier, value)