123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596 |
- # 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.
- #
- # Class to store configuration data and data definition
- # Used by the config manager and python modules that communicate
- # with the configuration manager
- #
- import isc.cc.data
- import isc.config.datadefinition
- class ConfigDataError(Exception): pass
- def check_type(specification, value):
- """Returns true if the value is of the correct type given the
- specification part relevant for the value"""
- if type(specification) == list:
- data_type = "list"
- else:
- data_type = specification['item_type']
- if data_type == "integer" and type(value) != int:
- raise isc.cc.data.DataTypeError(str(value) + " is not an integer")
- elif data_type == "real" and type(value) != double:
- raise isc.cc.data.DataTypeError(str(value) + " is not a real")
- elif data_type == "boolean" and type(value) != boolean:
- raise isc.cc.data.DataTypeError(str(value) + " is not a boolean")
- elif data_type == "string" and type(value) != str:
- raise isc.cc.data.DataTypeError(str(value) + " is not a string")
- elif data_type == "list":
- if type(value) != list:
- raise isc.cc.data.DataTypeError(str(value) + " is not a list")
- else:
- for element in value:
- check_type(specification['list_item_spec'], element)
- elif data_type == "map" and type(value) != dict:
- # todo: check types of map contents too
- raise isc.cc.data.DataTypeError(str(value) + " is not a map")
- 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"""
- if identifier == "":
- return element
- 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 isc.cc.data.DataNotFoundError(id + " in " + str(cur_el))
- else:
- raise isc.cc.data.DataNotFoundError(id + " in " + str(cur_el))
- return cur_el
- 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:
- """This class stores the datadefinition and the current non-default
- config values. It provides functions to get the actual value or
- the default value if no non-default value has been set"""
-
- def __init__(self, specification):
- """Initialize a ConfigData instance. If specification is not
- of type ModuleSpec, a ConfigDataError is raised."""
- if type(specification) != isc.config.ModuleSpec:
- raise ConfigDataError("specification is of type " + str(type(specification)) + ", not ModuleSpec")
- self.specification = specification
- self.data = {}
- def get_item_list(self, identifier = None, recurse = False):
- if identifier:
- spec = find_spec(self.specification.get_config_spec(), identifier, recurse)
- return spec_name_list(spec, identifier + "/")
- return spec_name_list(self.specification.get_config_spec(), "", recurse)
- 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 = isc.cc.data.find_no_exc(self.data, identifier)
- if value:
- return value, False
- spec = find_spec(self.specification.get_config_spec(), identifier)
- if spec and 'item_default' in spec:
- return spec['item_default'], True
- return None, False
- def get_module_spec(self):
- """Returns the ModuleSpec object associated with this ConfigData"""
- return self.specification
- def set_local_config(self, data):
- """Set the non-default config values, as passed by cfgmgr"""
- self.data = data
- def get_local_config(self):
- """Returns the non-default config values in a dict"""
- return self.data;
- def get_full_config(self):
- items = self.get_item_list(None, True)
- result = {}
- for item in items:
- value, default = self.get_value(item)
- result[item] = value
- return result
- #def get_identifiers(self):
- # Returns a list containing all identifiers
- #def
- class MultiConfigData:
- """This class stores the datadefinitions, current non-default
- configuration values and 'local' (uncommitted) changes for
- multiple modules"""
- LOCAL = 1
- CURRENT = 2
- DEFAULT = 3
- NONE = 4
-
- def __init__(self):
- self._specifications = {}
- self._current_config = {}
- self._local_changes = {}
- def set_specification(self, spec):
- if type(spec) != isc.config.ModuleSpec:
- raise Exception("not a datadef")
- self._specifications[spec.get_module_name()] = spec
- def get_module_spec(self, module):
- if module in self._specifications:
- return self._specifications[module]
- else:
- return None
- def find_spec_part(self, identifier):
- """returns the specification for the item at the given
- identifier, or None if not found"""
- if identifier[0] == '/':
- identifier = identifier[1:]
- module, sep, id = identifier.partition("/")
- try:
- return find_spec(self._specifications[module].get_config_spec(), id)
- except isc.cc.data.DataNotFoundError as dnfe:
- return None
- def set_current_config(self, config):
- self._current_config = config
- def get_current_config(self):
- """The current config is a dict where the first level is
- the module name, and the value is the config values for
- that module"""
- return self._current_config
-
- def get_local_changes(self):
- return self._local_changes
- def clear_local_changes(self):
- self._local_changes = {}
- def get_local_value(self, identifier):
- return isc.cc.data.find_no_exc(self._local_changes, identifier)
-
- def get_current_value(self, identifier):
- """Returns the current non-default value, or None if not set"""
- return isc.cc.data.find_no_exc(self._current_config, identifier)
-
- def get_default_value(self, identifier):
- """returns the default value, or None if there is no default"""
- if identifier[0] == '/':
- identifier = identifier[1:]
- module, sep, id = identifier.partition("/")
- try:
- spec = find_spec(self._specifications[module].get_config_spec(), id)
- if 'item_default' in spec:
- return spec['item_default']
- else:
- return None
- except isc.cc.data.DataNotFoundError as dnfe:
- return None
- def get_value(self, identifier):
- """Returns a tuple containing value,status. Status is either
- LOCAL, CURRENT, DEFAULT or NONE, corresponding to the
- source of the value (local change, current setting, default
- as specified by the specification, or not found at all)."""
- value = self.get_local_value(identifier)
- if value:
- return value, self.LOCAL
- value = self.get_current_value(identifier)
- if value:
- return value, self.CURRENT
- value = self.get_default_value(identifier)
- if value:
- return value, self.DEFAULT
- return None, self.NONE
- def get_value_maps(self, identifier = None):
- """Returns a list of dicts, 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
- TODO: use the consts for those last ones
- """
- result = []
- if not identifier:
- # No identifier, so we need the list of current modules
- for module in self._specifications.keys():
- entry = {}
- entry['name'] = module
- entry['type'] = 'module'
- entry['value'] = None
- entry['modified'] = False
- entry['default'] = False
- result.append(entry)
- else:
- if identifier[0] == '/':
- identifier = identifier[1:]
- module, sep, id = identifier.partition('/')
- spec = self.get_module_spec(module)
- if spec:
- spec_part = find_spec(spec.get_config_spec(), id)
- print(spec_part)
- if type(spec_part) == list:
- for item in spec_part:
- entry = {}
- entry['name'] = item['item_name']
- entry['type'] = item['item_type']
- value, status = self.get_value("/" + identifier + "/" + item['item_name'])
- entry['value'] = value
- if status == self.LOCAL:
- entry['modified'] = True
- else:
- entry['modified'] = False
- if status == self.DEFAULT:
- entry['default'] = False
- else:
- entry['default'] = False
- result.append(entry)
- else:
- item = spec_part
- if item['item_type'] == 'list':
- li_spec = item['list_item_spec']
- l, status = self.get_value("/" + identifier)
- if l:
- for value in l:
- result_part2 = {}
- 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:
- entry = {}
- entry['name'] = item['item_name']
- entry['type'] = item['item_type']
- #value, status = self.get_value("/" + identifier + "/" + item['item_name'])
- value, status = self.get_value("/" + identifier)
- entry['value'] = value
- if status == self.LOCAL:
- entry['modified'] = True
- else:
- entry['modified'] = False
- if status == self.DEFAULT:
- entry['default'] = False
- else:
- entry['default'] = False
- result.append(entry)
- #print(spec)
- return result
- def set_value(self, identifier, value):
- """Set the local value at the given identifier to value"""
- spec_part = self.find_spec_part(identifier)
- if check_type(spec_part, value):
- isc.cc.data.set(self._local_changes, identifier, value)
-
- def get_config_item_list(self, identifier = None):
- """Returns a list of strings containing the item_names of
- the child items at the given identifier. If no identifier is
- specified, returns a list of module names. The first part of
- the identifier (up to the first /) is interpreted as the
- module name"""
- if identifier:
- spec = self.find_spec_part(identifier)
- return spec_name_list(spec, identifier + "/")
- else:
- 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)
- 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 = isc.cc.data.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_single(self, identifier, entry):
- """Returns a single entry for a value_map, where the value is
- not a part of a bigger map"""
- result_part = {}
- result_part['name'] = entry['item_name']
- result_part['type'] = entry['item_type']
- value, default, modified = self.get_value(identifier)
- # 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_map(self, identifier, entry):
- """Returns a single entry for a value_map, where the value is
- a part of a bigger map"""
- 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, 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_single(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):
- 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 not in cur_list:
- cur_list.append(value)
- set(self.config_changes, identifier, cur_list)
- def remove(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 set(self, identifier, value_str):
- module_spec = find_spec(self.config.specification, identifier)
- value = parse_value_str(value_str)
- check_type(module_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, conn):
- self.send_changes(conn)
|