config_data.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. # Copyright (C) 2010 Internet Systems Consortium.
  2. #
  3. # Permission to use, copy, modify, and distribute this software for any
  4. # purpose with or without fee is hereby granted, provided that the above
  5. # copyright notice and this permission notice appear in all copies.
  6. #
  7. # THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
  8. # DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
  9. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
  10. # INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
  11. # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
  12. # FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  13. # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
  14. # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. """
  16. Classes to store configuration data and module specifications
  17. Used by the config manager, (python) modules, and UI's (those last
  18. two through the classes in ccsession)
  19. """
  20. import isc.cc.data
  21. import isc.config.module_spec
  22. class ConfigDataError(Exception): pass
  23. def check_type(spec_part, value):
  24. """Does nothing if the value is of the correct type given the
  25. specification part relevant for the value. Raises an
  26. isc.cc.data.DataTypeError exception if not. spec_part can be
  27. retrieved with find_spec_part()"""
  28. if type(spec_part) == list:
  29. data_type = "list"
  30. else:
  31. data_type = spec_part['item_type']
  32. if data_type == "integer" and type(value) != int:
  33. raise isc.cc.data.DataTypeError(str(value) + " is not an integer")
  34. elif data_type == "real" and type(value) != float:
  35. raise isc.cc.data.DataTypeError(str(value) + " is not a real")
  36. elif data_type == "boolean" and type(value) != bool:
  37. raise isc.cc.data.DataTypeError(str(value) + " is not a boolean")
  38. elif data_type == "string" and type(value) != str:
  39. raise isc.cc.data.DataTypeError(str(value) + " is not a string")
  40. elif data_type == "list":
  41. if type(value) != list:
  42. raise isc.cc.data.DataTypeError(str(value) + " is not a list")
  43. else:
  44. for element in value:
  45. check_type(spec_part['list_item_spec'], element)
  46. elif data_type == "map" and type(value) != dict:
  47. # todo: check types of map contents too
  48. raise isc.cc.data.DataTypeError(str(value) + " is not a map")
  49. def find_spec_part(element, identifier):
  50. """find the data definition for the given identifier
  51. returns either a map with 'item_name' etc, or a list of those"""
  52. if identifier == "":
  53. return element
  54. id_parts = identifier.split("/")
  55. id_parts[:] = (value for value in id_parts if value != "")
  56. cur_el = element
  57. for id in id_parts:
  58. if type(cur_el) == dict and id in cur_el.keys():
  59. cur_el = cur_el[id]
  60. elif type(cur_el) == dict and 'item_name' in cur_el.keys() and cur_el['item_name'] == id:
  61. pass
  62. elif type(cur_el) == dict and 'map_item_spec' in cur_el.keys():
  63. found = False
  64. for cur_el_item in cur_el['map_item_spec']:
  65. if cur_el_item['item_name'] == id:
  66. cur_el = cur_el_item
  67. found = True
  68. if not found:
  69. raise isc.cc.data.DataNotFoundError(id + " in " + str(cur_el))
  70. elif type(cur_el) == list:
  71. found = False
  72. for cur_el_item in cur_el:
  73. if cur_el_item['item_name'] == id and 'item_default' in cur_el_item.keys():
  74. cur_el = cur_el_item
  75. found = True
  76. if not found:
  77. raise isc.cc.data.DataNotFoundError(id + " in " + str(cur_el))
  78. else:
  79. raise isc.cc.data.DataNotFoundError(id + " in " + str(cur_el))
  80. return cur_el
  81. def spec_name_list(spec, prefix="", recurse=False):
  82. """Returns a full list of all possible item identifiers in the
  83. specification (part)"""
  84. result = []
  85. if prefix != "" and not prefix.endswith("/"):
  86. prefix += "/"
  87. if type(spec) == dict:
  88. if 'map_item_spec' in spec:
  89. for map_el in spec['map_item_spec']:
  90. name = map_el['item_name']
  91. if map_el['item_type'] == 'map':
  92. name += "/"
  93. result.append(prefix + name)
  94. else:
  95. for name in spec:
  96. result.append(prefix + name + "/")
  97. if recurse:
  98. print("[XX] recurse1")
  99. result.extend(spec_name_list(spec[name],name, recurse))
  100. elif type(spec) == list:
  101. for list_el in spec:
  102. if 'item_name' in list_el:
  103. if list_el['item_type'] == "map" and recurse:
  104. result.extend(spec_name_list(list_el['map_item_spec'], prefix + list_el['item_name'], recurse))
  105. else:
  106. name = list_el['item_name']
  107. if list_el['item_type'] in ["list", "map"]:
  108. name += "/"
  109. result.append(prefix + name)
  110. return result
  111. class ConfigData:
  112. """This class stores the module specs and the current non-default
  113. config values. It provides functions to get the actual value or
  114. the default value if no non-default value has been set"""
  115. def __init__(self, specification):
  116. """Initialize a ConfigData instance. If specification is not
  117. of type ModuleSpec, a ConfigDataError is raised."""
  118. if type(specification) != isc.config.ModuleSpec:
  119. raise ConfigDataError("specification is of type " + str(type(specification)) + ", not ModuleSpec")
  120. self.specification = specification
  121. self.data = {}
  122. def get_value(self, identifier):
  123. """Returns a tuple where the first item is the value at the
  124. given identifier, and the second item is a bool which is
  125. true if the value is an unset default. Raises an
  126. isc.cc.data.DataNotFoundError if the identifier is bad"""
  127. value = isc.cc.data.find_no_exc(self.data, identifier)
  128. if value != None:
  129. return value, False
  130. spec = find_spec_part(self.specification.get_config_spec(), identifier)
  131. if spec and 'item_default' in spec:
  132. return spec['item_default'], True
  133. return None, False
  134. def get_module_spec(self):
  135. """Returns the ModuleSpec object associated with this ConfigData"""
  136. return self.specification
  137. def set_local_config(self, data):
  138. """Set the non-default config values, as passed by cfgmgr"""
  139. self.data = data
  140. def get_local_config(self):
  141. """Returns the non-default config values in a dict"""
  142. return self.data;
  143. def get_item_list(self, identifier = None, recurse = False):
  144. """Returns a list of strings containing the full identifiers of
  145. all 'sub'options at the given identifier. If recurse is True,
  146. it will also add all identifiers of all children, if any"""
  147. if identifier:
  148. spec = find_spec_part(self.specification.get_config_spec(), identifier)
  149. return spec_name_list(spec, identifier + "/")
  150. return spec_name_list(self.specification.get_config_spec(), "", recurse)
  151. def get_full_config(self):
  152. """Returns a dict containing identifier: value elements, for
  153. all configuration options for this module. If there is
  154. a local setting, that will be used. Otherwise the value
  155. will be the default as specified by the module specification.
  156. If there is no default and no local setting, the value will
  157. be None"""
  158. items = self.get_item_list(None, True)
  159. result = {}
  160. for item in items:
  161. value, default = self.get_value(item)
  162. result[item] = value
  163. return result
  164. class MultiConfigData:
  165. """This class stores the module specs, current non-default
  166. configuration values and 'local' (uncommitted) changes for
  167. multiple modules"""
  168. LOCAL = 1
  169. CURRENT = 2
  170. DEFAULT = 3
  171. NONE = 4
  172. def __init__(self):
  173. self._specifications = {}
  174. self._current_config = {}
  175. self._local_changes = {}
  176. def set_specification(self, spec):
  177. """Add or update a ModuleSpec"""
  178. if type(spec) != isc.config.ModuleSpec:
  179. raise Exception("not a datadef")
  180. self._specifications[spec.get_module_name()] = spec
  181. def get_module_spec(self, module):
  182. """Returns the ModuleSpec for the module with the given name.
  183. If there is no such module, it returns None"""
  184. if module in self._specifications:
  185. return self._specifications[module]
  186. else:
  187. return None
  188. def find_spec_part(self, identifier):
  189. """Returns the specification for the item at the given
  190. identifier, or None if not found. The first part of the
  191. identifier (up to the first /) is interpreted as the module
  192. name. Returns None if not found."""
  193. if identifier[0] == '/':
  194. identifier = identifier[1:]
  195. module, sep, id = identifier.partition("/")
  196. try:
  197. return find_spec_part(self._specifications[module].get_config_spec(), id)
  198. except isc.cc.data.DataNotFoundError as dnfe:
  199. return None
  200. except KeyError as ke:
  201. return None
  202. # this function should only be called by __request_config
  203. def _set_current_config(self, config):
  204. """Replace the full current config values."""
  205. self._current_config = config
  206. def get_current_config(self):
  207. """Returns the current configuration as it is known by the
  208. configuration manager. It is a dict where the first level is
  209. the module name, and the value is the config values for
  210. that module"""
  211. return self._current_config
  212. def get_local_changes(self):
  213. """Returns the local config changes, i.e. those that have not
  214. been committed yet and are not known by the configuration
  215. manager or the modules."""
  216. return self._local_changes
  217. def clear_local_changes(self):
  218. """Reverts all local changes"""
  219. self._local_changes = {}
  220. def get_local_value(self, identifier):
  221. """Returns a specific local (uncommitted) configuration value,
  222. as specified by the identifier. If the local changes do not
  223. contain a new setting for this identifier, or if the
  224. identifier cannot be found, None is returned. See
  225. get_value() for a general way to find a configuration value
  226. """
  227. return isc.cc.data.find_no_exc(self._local_changes, identifier)
  228. def get_current_value(self, identifier):
  229. """Returns the current non-default value as known by the
  230. configuration manager, or None if it is not set.
  231. See get_value() for a general way to find a configuration
  232. value
  233. """
  234. return isc.cc.data.find_no_exc(self._current_config, identifier)
  235. def get_default_value(self, identifier):
  236. """Returns the default value for the given identifier as
  237. specified by the module specification, or None if there is
  238. no default or the identifier could not be found.
  239. See get_value() for a general way to find a configuration
  240. value
  241. """
  242. if identifier[0] == '/':
  243. identifier = identifier[1:]
  244. module, sep, id = identifier.partition("/")
  245. try:
  246. spec = find_spec_part(self._specifications[module].get_config_spec(), id)
  247. if 'item_default' in spec:
  248. return spec['item_default']
  249. else:
  250. return None
  251. except isc.cc.data.DataNotFoundError as dnfe:
  252. return None
  253. def get_value(self, identifier):
  254. """Returns a tuple containing value,status.
  255. The value contains the configuration value for the given
  256. identifier. The status reports where this value came from;
  257. it is one of: LOCAL, CURRENT, DEFAULT or NONE, corresponding
  258. (local change, current setting, default as specified by the
  259. specification, or not found at all)."""
  260. value = self.get_local_value(identifier)
  261. if value != None:
  262. return value, self.LOCAL
  263. value = self.get_current_value(identifier)
  264. if value != None:
  265. return value, self.CURRENT
  266. value = self.get_default_value(identifier)
  267. if value != None:
  268. return value, self.DEFAULT
  269. return None, self.NONE
  270. def get_value_maps(self, identifier = None):
  271. """Returns a list of dicts, containing the following values:
  272. name: name of the entry (string)
  273. type: string containing the type of the value (or 'module')
  274. value: value of the entry if it is a string, int, double or bool
  275. modified: true if the value is a local change
  276. default: true if the value has been changed
  277. TODO: use the consts for those last ones
  278. Throws DataNotFoundError if the identifier is bad
  279. """
  280. result = []
  281. if not identifier:
  282. # No identifier, so we need the list of current modules
  283. for module in self._specifications.keys():
  284. entry = {}
  285. entry['name'] = module
  286. entry['type'] = 'module'
  287. entry['value'] = None
  288. entry['modified'] = False
  289. entry['default'] = False
  290. result.append(entry)
  291. else:
  292. if identifier[0] == '/':
  293. identifier = identifier[1:]
  294. module, sep, id = identifier.partition('/')
  295. spec = self.get_module_spec(module)
  296. if spec:
  297. spec_part = find_spec_part(spec.get_config_spec(), id)
  298. print(spec_part)
  299. if type(spec_part) == list:
  300. for item in spec_part:
  301. entry = {}
  302. entry['name'] = item['item_name']
  303. entry['type'] = item['item_type']
  304. value, status = self.get_value("/" + identifier + "/" + item['item_name'])
  305. entry['value'] = value
  306. if status == self.LOCAL:
  307. entry['modified'] = True
  308. else:
  309. entry['modified'] = False
  310. if status == self.DEFAULT:
  311. entry['default'] = False
  312. else:
  313. entry['default'] = False
  314. result.append(entry)
  315. else:
  316. item = spec_part
  317. if item['item_type'] == 'list':
  318. li_spec = item['list_item_spec']
  319. l, status = self.get_value("/" + identifier)
  320. if l:
  321. for value in l:
  322. result_part2 = {}
  323. result_part2['name'] = li_spec['item_name']
  324. result_part2['value'] = value
  325. result_part2['type'] = li_spec['item_type']
  326. result_part2['default'] = False
  327. result_part2['modified'] = False
  328. result.append(result_part2)
  329. else:
  330. entry = {}
  331. entry['name'] = item['item_name']
  332. entry['type'] = item['item_type']
  333. #value, status = self.get_value("/" + identifier + "/" + item['item_name'])
  334. value, status = self.get_value("/" + identifier)
  335. entry['value'] = value
  336. if status == self.LOCAL:
  337. entry['modified'] = True
  338. else:
  339. entry['modified'] = False
  340. if status == self.DEFAULT:
  341. entry['default'] = False
  342. else:
  343. entry['default'] = False
  344. result.append(entry)
  345. #print(spec)
  346. return result
  347. def set_value(self, identifier, value):
  348. """Set the local value at the given identifier to value. If
  349. there is a specification for the given identifier, the type
  350. is checked."""
  351. spec_part = self.find_spec_part(identifier)
  352. if spec_part != None:
  353. check_type(spec_part, value)
  354. isc.cc.data.set(self._local_changes, identifier, value)
  355. def get_config_item_list(self, identifier = None, recurse = False):
  356. """Returns a list of strings containing the item_names of
  357. the child items at the given identifier. If no identifier is
  358. specified, returns a list of module names. The first part of
  359. the identifier (up to the first /) is interpreted as the
  360. module name"""
  361. if identifier:
  362. spec = self.find_spec_part(identifier)
  363. return spec_name_list(spec, identifier + "/", recurse)
  364. else:
  365. if recurse:
  366. id_list = []
  367. for module in self._specifications:
  368. id_list.extend(spec_name_list(self._specifications[module], module, recurse))
  369. return id_list
  370. else:
  371. return list(self._specifications.keys())