Commit 57979d6e authored by Jelte Jansen's avatar Jelte Jansen
Browse files

added a data.py class with a few classes and function usable by configuration...

added a data.py class with a few classes and function usable by configuration handlers. bigtool now has a fixed config 'module' which takes configuration commands. The completion function completes configuration identifiers, though i haven't been able to get rid of the space yet, which would not always be ideal

config commands are kind of based on the message i sent last week to -dev; set,unset,add (to list), remove (from list), revert, and commit

changed configuration is stored, but not sent to and used by parkinglot etc. yet


git-svn-id: svn://bind10.isc.org/svn/bind10/branches/jelte-datadef@343 e5f2f494-b856-4b98-b285-d166d9295462
parent ee7c74fb
......@@ -28,7 +28,7 @@ def _prepare_fake_data(bigtool):
bigtool.add_module_info(zone_module)
bigtool.add_module_info(boss_module)
def prepare_data_module(bigtool, module_name, module_commands):
def prepare_module_commands(bigtool, module_name, module_commands):
module = ModuleInfo(name = module_name,
desc = "same here")
for command in module_commands:
......@@ -45,10 +45,52 @@ def prepare_data_module(bigtool, module_name, module_commands):
module.add_command(cmd)
bigtool.add_module_info(module)
def prepare_data(bigtool, command_spec):
def prepare_commands(bigtool, command_spec):
for module_name in command_spec.keys():
prepare_data_module(bigtool, module_name, command_spec[module_name])
prepare_module_commands(bigtool, module_name, command_spec[module_name])
def prepare_config_commands(bigtool):
module = ModuleInfo(name = "config", desc = "Configuration commands")
cmd = CommandInfo(name = "show", desc = "Show configuration", need_inst_param = False)
param = ParamInfo(name = "identifier", type = "string", optional=True)
cmd.add_param(param)
module.add_command(cmd)
cmd = CommandInfo(name = "add", desc = "Add entry to configuration list", need_inst_param = False)
param = ParamInfo(name = "identifier", type = "string", optional=True)
cmd.add_param(param)
param = ParamInfo(name = "value", type = "string", optional=True)
cmd.add_param(param)
module.add_command(cmd)
cmd = CommandInfo(name = "remove", desc = "Remove entry from configuration list", need_inst_param = False)
param = ParamInfo(name = "identifier", type = "string", optional=True)
cmd.add_param(param)
param = ParamInfo(name = "value", type = "string", optional=True)
cmd.add_param(param)
module.add_command(cmd)
cmd = CommandInfo(name = "set", desc = "Set a configuration value", need_inst_param = False)
param = ParamInfo(name = "identifier", type = "string", optional=True)
cmd.add_param(param)
param = ParamInfo(name = "value", type = "string", optional=True)
cmd.add_param(param)
module.add_command(cmd)
cmd = CommandInfo(name = "unset", desc = "Unset a configuration value", need_inst_param = False)
param = ParamInfo(name = "identifier", type = "string", optional=True)
cmd.add_param(param)
module.add_command(cmd)
cmd = CommandInfo(name = "revert", desc = "Revert all local changes", need_inst_param = False)
module.add_command(cmd)
cmd = CommandInfo(name = "commit", desc = "Commit all local changes", need_inst_param = False)
module.add_command(cmd)
bigtool.add_module_info(module)
if __name__ == '__main__':
try:
cc = ISC.CC.Session()
......@@ -59,7 +101,8 @@ if __name__ == '__main__':
tool = BigTool(cc)
cc.group_sendmsg({ "command": ["get_commands"] }, "ConfigManager")
command_spec, env = cc.group_recvmsg(False)
prepare_data(tool, command_spec["result"])
prepare_commands(tool, command_spec["result"])
prepare_config_commands(tool)
_prepare_fake_data(tool)
tool.cmdloop()
except ISC.CC.SessionError:
......
......@@ -7,6 +7,7 @@ from moduleinfo import ParamInfo
from command import BigToolCmd
from xml.dom import minidom
import ISC
import ISC.CC.data
try:
from collections import OrderedDict
......@@ -34,7 +35,7 @@ class BigTool(Cmd):
self.modules = OrderedDict()
self.add_module_info(ModuleInfo("help", desc = "Get help for bigtool"))
self.cc = session
self.config_data = ISC.CC.data.UIConfigData("", session)
def validate_cmd(self, cmd):
if not cmd.module in self.modules:
......@@ -85,13 +86,15 @@ class BigTool(Cmd):
for name in manda_params:
if not name in params and not param_nr in params:
raise CmdMissParamSyntaxError(cmd.module, cmd.command, name)
param_nr += 1
def _handle_cmd(self, cmd):
#to do, consist xml package and send to bind10
if cmd.command == "help" or ("help" in cmd.params.keys()):
self._handle_help(cmd)
elif cmd.module == "config":
self.apply_config_cmd(cmd)
else:
self.apply_cmd(cmd)
......@@ -141,6 +144,11 @@ class BigTool(Cmd):
else:
hints = self._get_param_startswith(cmd.module, cmd.command,
text)
if cmd.module == "config":
# grm text has been stripped of slashes...
my_text = cur_line.rpartition(" ")[2]
list = self.config_data.config.get_item_list(my_text.rpartition("/")[0])
hints.extend([val for val in list if val.startswith(text)])
except CmdModuleNameFormatError:
if not text:
hints = list(self.modules.keys())
......@@ -160,9 +168,9 @@ class BigTool(Cmd):
except BigToolException:
hints = []
self.hint = hints
self._append_space_to_hint()
#self._append_space_to_hint()
if state < len(self.hint):
return self.hint[state]
......@@ -240,6 +248,43 @@ class BigTool(Cmd):
self.modules[cmd.module].command_help(cmd.command)
def apply_config_cmd(self, cmd):
identifier = ""
try:
if 'identifier' in cmd.params:
identifier = cmd.params['identifier']
if cmd.command == "show":
values = self.config_data.get_value_maps(identifier)
for value_map in values:
line = value_map['name']
if value_map['type'] in [ 'module', 'map', 'list' ]:
line += "/"
else:
line += ":\t" + str(value_map['value'])
line += "\t" + value_map['type']
line += "\t"
if value_map['default']:
line += "(default)"
if value_map['modified']:
line += "(modified)"
print(line)
elif cmd.command == "add":
self.config_data.add(identifier, cmd.params['value'])
elif cmd.command == "remove":
self.config_data.remove(identifier, cmd.params['value'])
elif cmd.command == "set":
self.config_data.set(identifier, cmd.params['value'])
elif cmd.command == "unset":
self.config_data.unset(identifier)
elif cmd.command == "revert":
self.config_data.revert()
elif cmd.command == "commit":
self.config_data.commit(self.cc)
except ISC.CC.data.DataTypeError as dte:
print("Error: " + str(dte))
except ISC.CC.data.DataNotFoundError as dnfe:
print("Error: " + identifier + " not found")
def apply_cmd(self, cmd):
if not self.cc:
return
......
import ISC
import pickle
import signal
from ISC.CC import data
class ConfigData:
def __init__(self):
self.zones = {}
self.data = {}
def add_zone(self, zone_name, zone_file):
self.zones[zone_name] = zone_file
......@@ -85,6 +87,47 @@ class ConfigManager:
try:
if cmd[0] == "get_commands":
answer["result"] = self.commands
elif cmd[0] == "get_data_spec":
if len(cmd) > 1 and cmd[1] != "":
try:
answer["result"] = [0, self.data_definitions[cmd[1]]]
except KeyError as ke:
answer["result"] = [1, "No specification for module " + cmd[1]]
else:
answer["result"] = [0, self.data_definitions]
elif cmd[0] == "get_config":
# we may not have any configuration here
conf_part = None
if len(cmd) > 1:
try:
conf_part = data.find(self.config.data, cmd[1])
except data.DataNotFoundError as dnfe:
pass
else:
conf_part = self.config.data
answer["result"] = [ 0, conf_part ]
elif cmd[0] == "set_config":
print("[XX] cmd len: " + str(len(cmd)))
print("[XX] cmd 0: " + str(cmd[0]))
print("[XX] cmd 1: " + str(cmd[1]))
print("[XX] cmd 2: " + str(cmd[2]))
if len(cmd) == 3:
# todo: use api (and check types?)
if cmd[1] != "":
conf_part = data.find_no_exc(self.config.data, cmd[1])
if not conf_part:
conf_part = data.set(self.config.data, cmd[1], "")
else:
conf_part = self.config.data
conf_part.update(cmd[2])
# send out changed info
answer["result"] = [ 0 ]
elif len(cmd) == 2:
self.config.data.update(cmd[1])
# send out changed info
answer["result"] = [ 0 ]
else:
answer["result"] = [ 1, "Wrong number of arguments" ]
elif cmd[0] == "zone" and cmd[1] == "add":
self.add_zone(cmd[2])
answer["result"] = [ 0 ]
......@@ -101,7 +144,7 @@ class ConfigManager:
answer["result"] = [ 1, "Unknown command: " + str(cmd) ]
except IndexError as ie:
print("missing argument")
answer["result"] = [ 1, "Missing argument in command" ]
answer["result"] = [ 1, "Missing argument in command: " + str(ie) ]
elif "data_specification" in msg:
# todo: validate? (no direct access to spec as
spec = msg["data_specification"]
......
# data, data_definition, config_data, module_config_data and ui_config_data classes
# we might want to split these up :)
import ast
class DataNotFoundError(Exception): pass
class DataTypeError(Exception): pass
def find(element, identifier):
"""Returns the subelement in the given data element, raises DataNotFoundError if not found"""
id_parts = identifier.split("/")
id_parts[:] = (value for value in id_parts if value != "")
cur_el = element
for id in id_parts:
if type(cur_el) == dict and id in cur_el.keys():
cur_el = cur_el[id]
else:
raise DataNotFoundError(identifier + " in " + str(element))
return cur_el
def set(element, identifier, value):
id_parts = identifier.split("/")
id_parts[:] = (value for value in id_parts if value != "")
cur_el = element
for id in id_parts[:-1]:
if id in cur_el.keys():
cur_el = cur_el[id]
else:
cur_el[id] = {}
cur_el = cur_el[id]
cur_el[id_parts[-1]] = value
return element
def unset(element, identifier):
id_parts = identifier.split("/")
id_parts[:] = (value for value in id_parts if value != "")
cur_el = element
for id in id_parts[:-1]:
if id in cur_el.keys():
cur_el = cur_el[id]
else:
cur_el[id] = {}
cur_el = cur_el[id]
cur_el[id_parts[-1]] = None
return element
def find_no_exc(element, identifier):
"""Returns the subelement in the given data element, returns None if not found"""
id_parts = identifier.split("/")
id_parts[:] = (value for value in id_parts if value != "")
cur_el = element
for id in id_parts:
if type(cur_el) == dict and id in cur_el.keys():
cur_el = cur_el[id]
else:
return None
return cur_el
def find_spec(element, identifier):
"""find the data definition for the given identifier
returns either a map with 'item_name' etc, or a list of those"""
id_parts = identifier.split("/")
id_parts[:] = (value for value in id_parts if value != "")
cur_el = element
for id in id_parts:
if type(cur_el) == dict and id in cur_el.keys():
cur_el = cur_el[id]
elif type(cur_el) == dict and 'item_name' in cur_el.keys() and cur_el['item_name'] == id:
pass
elif type(cur_el) == list:
found = False
for cur_el_item in cur_el:
if cur_el_item['item_name'] == id and 'item_default' in cur_el_item.keys():
cur_el = cur_el_item
found = True
if not found:
raise DataNotFoundError(id + " in " + str(cur_el))
else:
raise DataNotFoundError(id + " in " + str(cur_el))
return cur_el
def check_type(specification, value):
"""Returns true if the value is of the correct type given the
specification"""
if type(specification) == list:
data_type = "list"
else:
data_type = specification['item_type']
if data_type == "integer" and type(value) != int:
raise DataTypeError(str(value) + " should be an integer")
elif data_type == "real" and type(value) != double:
raise DataTypeError(str(value) + " should be a real")
elif data_type == "boolean" and type(value) != boolean:
raise DataTypeError(str(value) + " should be a boolean")
elif data_type == "string" and type(value) != str:
raise DataTypeError(str(value) + " should be a string")
elif data_type == "list":
if type(value) != list:
raise DataTypeError(str(value) + " should be a list, not a " + str(value.__class__.__name__))
else:
# todo: check subtypes etc
for element in value:
check_type(specification['list_item_spec'], element)
elif data_type == "map" and type(value) != dict:
# todo: check subtypes etc
raise DataTypeError(str(value) + " should be a map")
def spec_name_list(spec, prefix="", recurse=False):
"""Returns a full list of all possible item identifiers in the
specification (part)"""
result = []
if prefix != "" and not prefix.endswith("/"):
prefix += "/"
if type(spec) == dict:
for name in spec:
result.append(prefix + name + "/")
if recurse:
result.extend(spec_name_list(spec[name],name, recurse))
elif type(spec) == list:
for list_el in spec:
if 'item_name' in list_el:
if list_el['item_type'] == dict:
if recurse:
result.extend(spec_name_list(list_el['map_item_spec'], prefix + list_el['item_name'], recurse))
else:
name = list_el['item_name']
if list_el['item_type'] in ["list", "map"]:
name += "/"
result.append(name)
return result
class ConfigData:
def __init__(self, specification):
self.specification = specification
self.data = {}
def get_item_list(self, identifier = None):
if identifier:
spec = find_spec(self.specification, identifier)
return spec_name_list(spec, identifier + "/")
return spec_name_list(self.specification)
def get_value(self, identifier):
"""Returns a tuple where the first item is the value at the
given identifier, and the second item is a bool which is
true if the value is an unset default"""
value = find_no_exc(self.data, identifier)
if value:
return value, False
spec = find_spec(self.specification, identifier)
if spec and 'item_default' in spec:
return spec['item_default'], True
return None, False
class UIConfigData():
def __init__(self, name, cc):
self.module_name = name
data_spec = self.get_data_specification(cc)
self.config = ConfigData(data_spec)
self.get_config_data(cc)
self.config_changes = {}
def get_config_data(self, cc):
cc.group_sendmsg({ "command": ["get_config", self.module_name] }, "ConfigManager")
answer, env = cc.group_recvmsg(False)
if 'result' in answer.keys() and type(answer['result']) == list:
# TODO: with the new cc implementation, replace "1" by 1
if answer['result'][0] == "1":
# todo: exception
print("Error: " + str(answer['result'][1]))
else:
self.config.data = answer['result'][1]
else:
# XX todo: raise exc
print("Error: unexpected answer from config manager:")
print(answer)
def send_changes(self, cc):
"""Sends the changes configuration values to the config manager.
If the command succeeds, the changes are re-requested and
the changed list is reset"""
cc.group_sendmsg({ "command": [ "set_config", self.module_name, self.config_changes ]}, "ConfigManager")
answer, env = cc.group_recvmsg(False)
if 'result' in answer and type(answer['result']) == list:
# TODO: with the new cc implementation, replace "0" by 0
if answer['result'][0] == "0":
# ok
self.get_config_data(cc)
self.config_changes = {}
else:
print("Error committing changes: " + answer['result'][1])
else:
print("Error: unexpected answer: " + str(answer))
def get_data_specification(self, cc):
cc.group_sendmsg({ "command": ["get_data_spec", self.module_name] }, "ConfigManager")
answer, env = cc.group_recvmsg(False)
if 'result' in answer.keys() and type(answer['result']) == list:
# TODO: with the new cc implementation, replace "1" by 1
if answer['result'][0] == "1":
# todo: exception
print("Error: " + str(answer['result'][1]))
return None
else:
return answer['result'][1]
else:
# XX todo: raise exc
print("Error: unexpected answer from config manager:")
print(answer)
return None
def set(self, identifier, value):
# check against definition
spec = find_spec(identifier)
check_type(spec, value)
set(self.config_changes, identifier, value)
def get_value(self, identifier):
"""Returns a three-tuple, where the first item is the value
(or None), the second is a boolean specifying whether
the value is the default value, and the third is a boolean
specifying whether the value is an uncommitted change"""
value = find_no_exc(self.config_changes, identifier)
if value:
return value, False, True
value, default = self.config.get_value(identifier)
if value:
return value, default, False
return None, False, False
def get_value_map(self, identifier, entry):
result_part = {}
result_part['name'] = entry['item_name']
result_part['type'] = entry['item_type']
value, default, modified = self.get_value(identifier + "/" + entry['item_name'])
# should we check type and only set int, double, bool and string here?
result_part['value'] = value
result_part['default'] = default
result_part['modified'] = modified
return result_part
def get_value_maps(self, identifier = None):
"""Returns a list of maps, containing the following values:
name: name of the entry (string)
type: string containing the type of the value (or 'module')
value: value of the entry if it is a string, int, double or bool
modified: true if the value is a local change
default: true if the value has been changed
Throws DataNotFoundError if the identifier is bad
"""
spec = find_spec(self.config.specification, identifier)
result = []
if type(spec) == dict:
# either the top-level list of modules or a spec map
if 'item_name' in spec:
result_part = self.get_value_map(identifier, spec)
if result_part['type'] == "list":
values = self.get_value(identifier)[0]
if values:
for value in values:
result_part2 = {}
li_spec = spec['list_item_spec']
result_part2['name'] = li_spec['item_name']
result_part2['value'] = value
result_part2['type'] = li_spec['item_type']
result_part2['default'] = False
result_part2['modified'] = False
result.append(result_part2)
else:
result.append(result_part)
else:
for name in spec:
result_part = {}
result_part['name'] = name
result_part['type'] = "module"
result_part['value'] = None
result_part['default'] = False
result_part['modified'] = False
result.append(result_part)
elif type(spec) == list:
for entry in spec:
if type(entry) == dict and 'item_name' in entry:
result.append(self.get_value_map(identifier, entry))
return result
def add(self, identifier, value_str):
data_spec = find_spec(self.config.specification, identifier)
value = ast.literal_eval(value_str)
check_type(data_spec, [value])
cur_list = find_no_exc(self.config_changes, identifier)
if not cur_list:
cur_list = find_no_exc(self.config.data, identifier)
if not cur_list:
cur_list = []
if value not in cur_list:
cur_list.append(value)
set(self.config_changes, identifier, cur_list)
def remove(self, identifier, value_str):
data_spec = find_spec(self.config.specification, identifier)
value = ast.literal_eval(value_str)
check_type(data_spec, [value])
cur_list = find_no_exc(self.config_changes, identifier)
if not cur_list:
cur_list = 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 set(self, identifier, value_str):
data_spec = find_spec(self.config.specification, identifier)
value = ast.literal_eval(value_str)
check_type(data_spec, value)
set(self.config_changes, identifier, value)
def unset(self, identifier):
# todo: check whether the value is optional?
unset(self.config_changes, identifier)
def revert(self):
self.config_changes = {}
def commit(self, cc):
self.send_changes(cc)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment