config_data.py 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  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. import ast
  23. import copy
  24. class ConfigDataError(Exception): pass
  25. BIND10_CONFIG_DATA_VERSION = 2
  26. # Helper functions
  27. def spec_part_is_list(spec_part):
  28. """Returns True if the given spec_part is a dict that contains a
  29. list specification, and False otherwise."""
  30. return (type(spec_part) == dict and 'list_item_spec' in spec_part)
  31. def spec_part_is_map(spec_part):
  32. """Returns True if the given spec_part is a dict that contains a
  33. map specification, and False otherwise."""
  34. return (type(spec_part) == dict and 'map_item_spec' in spec_part)
  35. def spec_part_is_named_set(spec_part):
  36. """Returns True if the given spec_part is a dict that contains a
  37. named_set specification, and False otherwise."""
  38. return (type(spec_part) == dict and 'named_set_item_spec' in spec_part)
  39. def spec_part_is_any(spec_part):
  40. """Returns true if the given spec_part specifies an element of type
  41. any, and False otherwise.
  42. """
  43. return (type(spec_part) == dict and 'item_type' in spec_part and
  44. spec_part['item_type'] == "any")
  45. def check_type(spec_part, value):
  46. """Does nothing if the value is of the correct type given the
  47. specification part relevant for the value. Raises an
  48. isc.cc.data.DataTypeError exception if not. spec_part can be
  49. retrieved with find_spec_part()"""
  50. if type(spec_part) == dict and 'item_type' in spec_part:
  51. data_type = spec_part['item_type']
  52. else:
  53. raise isc.cc.data.DataTypeError(str("Incorrect specification part for type checking"))
  54. if data_type == "integer" and type(value) != int:
  55. raise isc.cc.data.DataTypeError(str(value) + " is not an integer")
  56. elif data_type == "real" and type(value) != float:
  57. raise isc.cc.data.DataTypeError(str(value) + " is not a real")
  58. elif data_type == "boolean" and type(value) != bool:
  59. raise isc.cc.data.DataTypeError(str(value) + " is not a boolean")
  60. elif data_type == "string" and type(value) != str:
  61. raise isc.cc.data.DataTypeError(str(value) + " is not a string")
  62. elif data_type == "list":
  63. if type(value) != list:
  64. raise isc.cc.data.DataTypeError(str(value) + " is not a list")
  65. else:
  66. for element in value:
  67. check_type(spec_part['list_item_spec'], element)
  68. elif data_type == "map" and type(value) != dict:
  69. # todo: check types of map contents too
  70. raise isc.cc.data.DataTypeError(str(value) + " is not a map")
  71. def convert_type(spec_part, value):
  72. """Convert the given value(type is string) according specification
  73. part relevant for the value. Raises an isc.cc.data.DataTypeError
  74. exception if conversion failed.
  75. """
  76. if type(spec_part) == dict and 'item_type' in spec_part:
  77. data_type = spec_part['item_type']
  78. else:
  79. raise isc.cc.data.DataTypeError(str("Incorrect specification part for type conversion"))
  80. try:
  81. if data_type == "integer":
  82. return int(value)
  83. elif data_type == "real":
  84. return float(value)
  85. elif data_type == "boolean":
  86. return str.lower(str(value)) != 'false'
  87. elif data_type == "string":
  88. return str(value)
  89. elif data_type == "list":
  90. ret = []
  91. if type(value) == list:
  92. for item in value:
  93. ret.append(convert_type(spec_part['list_item_spec'], item))
  94. elif type(value) == str:
  95. value = value.split(',')
  96. for item in value:
  97. sub_value = item.split()
  98. for sub_item in sub_value:
  99. ret.append(convert_type(spec_part['list_item_spec'],
  100. sub_item))
  101. if ret == []:
  102. raise isc.cc.data.DataTypeError(str(value) + " is not a list")
  103. return ret
  104. elif data_type == "map":
  105. try:
  106. map = ast.literal_eval(value)
  107. if type(map) == dict:
  108. # todo: check types of map contents too
  109. return map
  110. else:
  111. raise isc.cc.data.DataTypeError(
  112. "Value in convert_type not a string "
  113. "specifying a dict")
  114. except SyntaxError as se:
  115. raise isc.cc.data.DataTypeError("Error parsing map: " + str(se))
  116. else:
  117. return value
  118. except ValueError as err:
  119. raise isc.cc.data.DataTypeError(str(err))
  120. except TypeError as err:
  121. raise isc.cc.data.DataTypeError(str(err))
  122. def _get_map_or_list(spec_part):
  123. """Returns the list or map specification if this is a list or a
  124. map specification part. If not, returns the given spec_part
  125. itself"""
  126. if spec_part_is_map(spec_part):
  127. return spec_part["map_item_spec"]
  128. elif spec_part_is_list(spec_part):
  129. return spec_part["list_item_spec"]
  130. else:
  131. return spec_part
  132. def _find_spec_part_single(cur_spec, id_part):
  133. """Find the spec part for the given (partial) name. This partial
  134. name does not contain separators ('/'), and the specification
  135. part should be a direct child of the given specification part.
  136. id_part may contain list selectors, which will be ignored.
  137. Returns the child part.
  138. Raises DataNotFoundError if it was not found."""
  139. # strip list selector part
  140. # don't need it for the spec part, so just drop it
  141. id, list_indices = isc.cc.data.split_identifier_list_indices(id_part)
  142. # The specification we want a sub-part for should be either a
  143. # list or a map, which is internally represented by a dict with
  144. # an element 'map_item_spec', a dict with an element 'list_item_spec',
  145. # or a list (when it is the 'main' config_data element of a module).
  146. if spec_part_is_map(cur_spec):
  147. for cur_spec_item in cur_spec['map_item_spec']:
  148. if cur_spec_item['item_name'] == id:
  149. return cur_spec_item
  150. # not found
  151. raise isc.cc.data.DataNotFoundError(id + " not found")
  152. elif spec_part_is_list(cur_spec):
  153. if cur_spec['item_name'] == id:
  154. return cur_spec['list_item_spec']
  155. # not found
  156. raise isc.cc.data.DataNotFoundError(id + " not found")
  157. elif type(cur_spec) == dict and 'named_set_item_spec' in cur_spec.keys():
  158. return cur_spec['named_set_item_spec']
  159. elif type(cur_spec) == list:
  160. for cur_spec_item in cur_spec:
  161. if cur_spec_item['item_name'] == id:
  162. return cur_spec_item
  163. # not found
  164. raise isc.cc.data.DataNotFoundError(id + " not found")
  165. else:
  166. raise isc.cc.data.DataNotFoundError("Not a correct config specification")
  167. def find_spec_part(element, identifier, strict_identifier = True):
  168. """find the data definition for the given identifier
  169. returns either a map with 'item_name' etc, or a list of those
  170. Parameters:
  171. element: The specification element to start the search in
  172. identifier: The element to find (relative to element above)
  173. strict_identifier: If True (the default), additional checking occurs.
  174. Currently the only check is whether a list index is
  175. specified (except for the last part of the
  176. identifier)
  177. Raises a DataNotFoundError if the data is not found, or if
  178. strict_identifier is True and any non-final identifier parts
  179. (i.e. before the last /) identify a list element and do not contain
  180. an index.
  181. Returns the spec element identified by the given identifier.
  182. """
  183. if identifier == "":
  184. return element
  185. id_parts = identifier.split("/")
  186. id_parts[:] = (value for value in id_parts if value != "")
  187. cur_el = element
  188. # up to the last element, if the result is a map or a list,
  189. # we want its subspecification (i.e. list_item_spec or
  190. # map_item_spec). For the last element in the identifier we
  191. # always want the 'full' spec of the item
  192. for id_part in id_parts[:-1]:
  193. cur_el = _find_spec_part_single(cur_el, id_part)
  194. # As soon as we find 'any', return that
  195. if cur_el["item_type"] == "any":
  196. return cur_el
  197. if strict_identifier and spec_part_is_list(cur_el) and\
  198. not isc.cc.data.identifier_has_list_index(id_part):
  199. raise isc.cc.data.DataNotFoundError(id_part +
  200. " is a list and needs an index")
  201. cur_el = _get_map_or_list(cur_el)
  202. cur_el = _find_spec_part_single(cur_el, id_parts[-1])
  203. # Due to the raw datatypes we use, it is safer to return a deep copy here
  204. return copy.deepcopy(cur_el)
  205. def spec_name_list(spec, prefix="", recurse=False):
  206. """Returns a full list of all possible item identifiers in the
  207. specification (part). Raises a ConfigDataError if spec is not
  208. a correct spec (as returned by ModuleSpec.get_config_spec()"""
  209. result = []
  210. if prefix != "" and not prefix.endswith("/"):
  211. prefix += "/"
  212. if type(spec) == dict:
  213. if spec_part_is_map(spec):
  214. for map_el in spec['map_item_spec']:
  215. name = map_el['item_name']
  216. if map_el['item_type'] == 'map':
  217. name += "/"
  218. if recurse and spec_part_is_map(map_el):
  219. result.extend(spec_name_list(map_el['map_item_spec'], prefix + map_el['item_name'], recurse))
  220. else:
  221. result.append(prefix + name)
  222. elif 'named_set_item_spec' in spec:
  223. # we added a '/' above, but in this one case we don't want it
  224. result.append(prefix[:-1])
  225. # ignore any
  226. elif not spec_part_is_any(spec):
  227. for name in spec:
  228. result.append(prefix + name + "/")
  229. if recurse:
  230. result.extend(spec_name_list(spec[name], name, recurse))
  231. elif type(spec) == list:
  232. for list_el in spec:
  233. if 'item_name' in list_el:
  234. if list_el['item_type'] == "map" and recurse:
  235. result.extend(spec_name_list(list_el['map_item_spec'], prefix + list_el['item_name'], recurse))
  236. else:
  237. name = list_el['item_name']
  238. result.append(prefix + name)
  239. else:
  240. raise ConfigDataError("Bad specification")
  241. else:
  242. raise ConfigDataError("Bad specification")
  243. return result
  244. class ConfigData:
  245. """This class stores the module specs and the current non-default
  246. config values. It provides functions to get the actual value or
  247. the default value if no non-default value has been set"""
  248. def __init__(self, specification):
  249. """Initialize a ConfigData instance. If specification is not
  250. of type ModuleSpec, a ConfigDataError is raised."""
  251. if type(specification) != isc.config.ModuleSpec:
  252. raise ConfigDataError("specification is of type " + str(type(specification)) + ", not ModuleSpec")
  253. self.specification = specification
  254. self.data = {}
  255. def get_value(self, identifier):
  256. """Returns a tuple where the first item is the value at the
  257. given identifier, and the second item is a bool which is
  258. true if the value is an unset default. Raises an
  259. isc.cc.data.DataNotFoundError if the identifier is bad"""
  260. value = isc.cc.data.find_no_exc(self.data, identifier)
  261. if value != None:
  262. return value, False
  263. spec = find_spec_part(self.specification.get_config_spec(), identifier)
  264. if spec and 'item_default' in spec:
  265. return spec['item_default'], True
  266. return None, False
  267. def get_default_value(self, identifier):
  268. """Returns the default from the specification, or None if there
  269. is no default"""
  270. # We are searching for the default value, so we can set
  271. # strict_identifier to false (in fact, we need to; we may not know
  272. # some list indices, or they may not exist, we are looking for
  273. # a default value for a reason here).
  274. spec = find_spec_part(self.specification.get_config_spec(),
  275. identifier, False)
  276. if spec and 'item_default' in spec:
  277. return spec['item_default']
  278. else:
  279. return None
  280. def get_module_spec(self):
  281. """Returns the ModuleSpec object associated with this ConfigData"""
  282. return self.specification
  283. def set_local_config(self, data):
  284. """Set the non-default config values, as passed by cfgmgr"""
  285. self.data = data
  286. def get_local_config(self):
  287. """Returns the non-default config values in a dict"""
  288. return self.data
  289. def get_item_list(self, identifier = None, recurse = False):
  290. """Returns a list of strings containing the full identifiers of
  291. all 'sub'options at the given identifier. If recurse is True,
  292. it will also add all identifiers of all children, if any"""
  293. if identifier:
  294. spec = find_spec_part(self.specification.get_config_spec(), identifier)
  295. return spec_name_list(spec, identifier + "/")
  296. return spec_name_list(self.specification.get_config_spec(), "", recurse)
  297. def get_full_config(self):
  298. """Returns a dict containing identifier: value elements, for
  299. all configuration options for this module. If there is
  300. a local setting, that will be used. Otherwise the value
  301. will be the default as specified by the module specification.
  302. If there is no default and no local setting, the value will
  303. be None"""
  304. items = self.get_item_list(None, True)
  305. result = {}
  306. for item in items:
  307. value, default = self.get_value(item)
  308. result[item] = value
  309. return result
  310. # should we just make a class for these?
  311. def _create_value_map_entry(name, type, value, status = None):
  312. entry = {}
  313. entry['name'] = name
  314. entry['type'] = type
  315. entry['value'] = value
  316. entry['modified'] = False
  317. entry['default'] = False
  318. if status == MultiConfigData.LOCAL:
  319. entry['modified'] = True
  320. if status == MultiConfigData.DEFAULT:
  321. entry['default'] = True
  322. return entry
  323. class MultiConfigData:
  324. """This class stores the module specs, current non-default
  325. configuration values and 'local' (uncommitted) changes for
  326. multiple modules"""
  327. LOCAL = 1
  328. CURRENT = 2
  329. DEFAULT = 3
  330. NONE = 4
  331. def __init__(self):
  332. self._specifications = {}
  333. self._current_config = {}
  334. self._local_changes = {}
  335. def clear_specifications(self):
  336. """Remove all known module specifications"""
  337. self._specifications = {}
  338. def set_specification(self, spec):
  339. """Add or update a ModuleSpec. Raises a ConfigDataError is spec is not a ModuleSpec"""
  340. if type(spec) != isc.config.ModuleSpec:
  341. raise ConfigDataError("not a datadef: " + str(type(spec)))
  342. self._specifications[spec.get_module_name()] = spec
  343. def remove_specification(self, module_name):
  344. """Removes the specification with the given module name. Does nothing if it wasn't there."""
  345. if module_name in self._specifications:
  346. del self._specifications[module_name]
  347. def have_specification(self, module_name):
  348. """Returns True if we have a specification for the module with the given name.
  349. Returns False if we do not."""
  350. return module_name in self._specifications
  351. def get_module_spec(self, module):
  352. """Returns the ModuleSpec for the module with the given name.
  353. If there is no such module, it returns None"""
  354. if module in self._specifications:
  355. return self._specifications[module]
  356. else:
  357. return None
  358. def find_spec_part(self, identifier):
  359. """Returns the specification for the item at the given
  360. identifier, or None if not found. The first part of the
  361. identifier (up to the first /) is interpreted as the module
  362. name. Returns None if not found, or if identifier is not a
  363. string.
  364. If an index is given for a List-type element, it returns
  365. the specification of the list elements, not of the list itself
  366. """
  367. if type(identifier) != str or identifier == "":
  368. return None
  369. if identifier[0] == '/':
  370. identifier = identifier[1:]
  371. module, sep, id = identifier.partition("/")
  372. if id != "":
  373. id, indices = isc.cc.data.split_identifier_list_indices(id)
  374. else:
  375. indices = None
  376. try:
  377. spec_part = find_spec_part(self._specifications[module].get_config_spec(), id)
  378. if indices is not None and spec_part_is_list(spec_part):
  379. return spec_part['list_item_spec']
  380. else:
  381. return spec_part
  382. except isc.cc.data.DataNotFoundError as dnfe:
  383. return None
  384. except KeyError as ke:
  385. return None
  386. # this function should only be called by __request_config
  387. def _set_current_config(self, config):
  388. """Replace the full current config values."""
  389. self._current_config = config
  390. def get_current_config(self):
  391. """Returns the current configuration as it is known by the
  392. configuration manager. It is a dict where the first level is
  393. the module name, and the value is the config values for
  394. that module"""
  395. return self._current_config
  396. def get_local_changes(self):
  397. """Returns the local config changes, i.e. those that have not
  398. been committed yet and are not known by the configuration
  399. manager or the modules."""
  400. return self._local_changes
  401. def set_local_changes(self, new_local_changes):
  402. """Sets the entire set of local changes, used when reverting
  403. changes done automatically in case there was a problem (e.g.
  404. when executing commands from a script that fails halfway
  405. through).
  406. """
  407. self._local_changes = new_local_changes
  408. def clear_local_changes(self):
  409. """Reverts all local changes"""
  410. self._local_changes = {}
  411. def get_local_value(self, identifier):
  412. """Returns a specific local (uncommitted) configuration value,
  413. as specified by the identifier. If the local changes do not
  414. contain a new setting for this identifier, or if the
  415. identifier cannot be found, None is returned. See
  416. get_value() for a general way to find a configuration value
  417. """
  418. return isc.cc.data.find_no_exc(self._local_changes, identifier)
  419. def get_current_value(self, identifier):
  420. """Returns the current non-default value as known by the
  421. configuration manager, or None if it is not set.
  422. See get_value() for a general way to find a configuration
  423. value
  424. """
  425. return isc.cc.data.find_no_exc(self._current_config, identifier)
  426. def get_default_value(self, identifier):
  427. """Returns the default value for the given identifier as
  428. specified by the module specification, or None if there is
  429. no default or the identifier could not be found.
  430. See get_value() for a general way to find a configuration
  431. value
  432. """
  433. try:
  434. if identifier[0] == '/':
  435. identifier = identifier[1:]
  436. module, sep, id = identifier.partition("/")
  437. # if there is a 'higher-level' list index specified, we need
  438. # to check if that list specification has a default that
  439. # overrides the more specific default in the final spec item
  440. # (ie. list_default = [1, 2, 3], list_item_spec=int, default=0)
  441. # def default list[1] should return 2, not 0
  442. id_parts = isc.cc.data.split_identifier(id)
  443. id_prefix = ""
  444. while len(id_parts) > 0:
  445. id_part = id_parts.pop(0)
  446. item_id, list_indices = isc.cc.data.split_identifier_list_indices(id_part)
  447. id_list = module + "/" + id_prefix + "/" + item_id
  448. id_prefix += "/" + id_part
  449. part_spec = find_spec_part(self._specifications[module].get_config_spec(), id_prefix)
  450. if part_spec['item_type'] == 'named_set':
  451. # For named sets, the identifier is partly defined
  452. # by which values are actually present, and not
  453. # purely by the specification.
  454. # So if there is a part of the identifier left,
  455. # we need to look up the value, then see if that
  456. # contains the next part of the identifier we got
  457. if len(id_parts) == 0:
  458. if 'item_default' in part_spec:
  459. return part_spec['item_default']
  460. else:
  461. return None
  462. id_part = id_parts.pop(0)
  463. item_id, list_indices =\
  464. isc.cc.data.split_identifier_list_indices(id_part)
  465. named_set_value, type = self.get_value(id_list)
  466. if item_id in named_set_value.keys():
  467. result = named_set_value[item_id]
  468. # If the item is a list and we have indices in the
  469. # identifier part, continue with the item pointed to
  470. # by those indices
  471. if list_indices is not None:
  472. while len(list_indices) > 0:
  473. result = result[list_indices.pop(0)]
  474. if len(id_parts) > 0:
  475. # we are looking for the *default* value.
  476. # so if not present in here, we need to
  477. # lookup the one from the spec
  478. rest_of_id = "/".join(id_parts)
  479. result = isc.cc.data.find_no_exc(result, rest_of_id)
  480. if result is None:
  481. spec_part = self.find_spec_part(identifier)
  482. if 'item_default' in spec_part:
  483. return spec_part['item_default']
  484. return result
  485. else:
  486. return result
  487. else:
  488. return None
  489. elif list_indices is not None:
  490. # there's actually two kinds of default here for
  491. # lists; they can have a default value (like an
  492. # empty list), but their elements can also have
  493. # default values.
  494. # So if the list item *itself* is a default,
  495. # we need to get the value out of that. If not, we
  496. # need to find the default for the specific element.
  497. list_value, type = self.get_value(id_list)
  498. list_spec = find_spec_part(self._specifications[module].get_config_spec(), id_prefix)
  499. if type == self.DEFAULT:
  500. if 'item_default' in list_spec:
  501. list_value = list_spec['item_default']
  502. for i in list_indices:
  503. if i < len(list_value):
  504. list_value = list_value[i]
  505. else:
  506. # out of range, return None
  507. return None
  508. if len(id_parts) > 0:
  509. rest_of_id = "/".join(id_parts)
  510. return isc.cc.data.find(list_value, rest_of_id)
  511. else:
  512. return list_value
  513. else:
  514. # we do have a non-default list, see if our indices
  515. # exist
  516. for i in list_indices:
  517. if i < len(list_value):
  518. list_value = list_value[i]
  519. else:
  520. # out of range, return None
  521. return None
  522. spec = find_spec_part(self._specifications[module].get_config_spec(), id)
  523. if 'item_default' in spec:
  524. # one special case, named_set
  525. if spec['item_type'] == 'named_set':
  526. return spec['item_default']
  527. else:
  528. return spec['item_default']
  529. else:
  530. return None
  531. except isc.cc.data.DataNotFoundError as dnfe:
  532. return None
  533. def get_value(self, identifier, default = True):
  534. """Returns a tuple containing value,status.
  535. The value contains the configuration value for the given
  536. identifier. The status reports where this value came from;
  537. it is one of: LOCAL, CURRENT, DEFAULT or NONE, corresponding
  538. (local change, current setting, default as specified by the
  539. specification, or not found at all). Does not check and
  540. set DEFAULT if the argument 'default' is False (default
  541. defaults to True)"""
  542. value = self.get_local_value(identifier)
  543. if value != None:
  544. return value, self.LOCAL
  545. value = self.get_current_value(identifier)
  546. if value != None:
  547. return value, self.CURRENT
  548. if default:
  549. value = self.get_default_value(identifier)
  550. if value is not None:
  551. return value, self.DEFAULT
  552. else:
  553. # get_default_value returns None for both
  554. # the cases where there is no default, and where
  555. # it is set to null, so we need to catch the latter
  556. spec_part = self.find_spec_part(identifier)
  557. if spec_part and 'item_default' in spec_part and\
  558. spec_part['item_default'] is None:
  559. return None, self.DEFAULT
  560. return None, self.NONE
  561. def _append_value_item(self, result, spec_part, identifier, all, first = False):
  562. # Look at the spec; it is a list of items, or a map containing 'item_name' etc
  563. if type(spec_part) == list:
  564. for spec_part_element in spec_part:
  565. spec_part_element_name = spec_part_element['item_name']
  566. self._append_value_item(result, spec_part_element, identifier + "/" + spec_part_element_name, all)
  567. elif type(spec_part) == dict:
  568. # depending on item type, and the value of argument 'all'
  569. # we need to either add an item, or recursively go on
  570. # In the case of a list that is empty, we do need to show that
  571. item_name = spec_part['item_name']
  572. item_type = spec_part['item_type']
  573. if item_type == "list" and (all or first):
  574. spec_part_list = spec_part['list_item_spec']
  575. list_value, status = self.get_value(identifier)
  576. # If not set, and no default, lists will show up as 'None',
  577. # but it's better to treat it as an empty list then
  578. if list_value is None:
  579. list_value = []
  580. if type(list_value) != list:
  581. # the identifier specified a single element
  582. self._append_value_item(result, spec_part_list, identifier, all)
  583. else:
  584. list_len = len(list_value)
  585. if len(list_value) == 0 and (all or first):
  586. entry = _create_value_map_entry(identifier,
  587. item_type,
  588. [], status)
  589. result.append(entry)
  590. else:
  591. for i in range(len(list_value)):
  592. self._append_value_item(result, spec_part_list, "%s[%d]" % (identifier, i), all)
  593. elif item_type == "map":
  594. value, status = self.get_value(identifier)
  595. # just show the specific contents of a map, we are
  596. # almost never interested in just its name
  597. spec_part_map = spec_part['map_item_spec']
  598. self._append_value_item(result, spec_part_map, identifier, all)
  599. elif item_type == "named_set":
  600. value, status = self.get_value(identifier)
  601. # show just the one entry, when either the map is empty,
  602. # or when this is element is not requested specifically
  603. if len(value.keys()) == 0:
  604. entry = _create_value_map_entry(identifier,
  605. item_type,
  606. {}, status)
  607. result.append(entry)
  608. elif not first and not all:
  609. entry = _create_value_map_entry(identifier,
  610. item_type,
  611. None, status)
  612. result.append(entry)
  613. else:
  614. spec_part_named_set = spec_part['named_set_item_spec']
  615. for entry in value:
  616. self._append_value_item(result,
  617. spec_part_named_set,
  618. identifier + "/" + entry,
  619. all)
  620. else:
  621. value, status = self.get_value(identifier)
  622. if status == self.NONE and not spec_part['item_optional']:
  623. raise isc.cc.data.DataNotFoundError(identifier + " not found")
  624. entry = _create_value_map_entry(identifier,
  625. item_type,
  626. value, status)
  627. result.append(entry)
  628. return
  629. def get_value_maps(self, identifier = None, all = False):
  630. """Returns a list of dicts, containing the following values:
  631. name: name of the entry (string)
  632. type: string containing the type of the value (or 'module')
  633. value: value of the entry if it is a string, int, double or bool
  634. modified: true if the value is a local change that has not
  635. been committed
  636. default: true if the value has not been changed (i.e. the
  637. value is the default from the specification)
  638. TODO: use the consts for those last ones
  639. Throws DataNotFoundError if the identifier is bad
  640. """
  641. result = []
  642. if not identifier or identifier == "/":
  643. # No identifier, so we need the list of current modules
  644. for module in self._specifications.keys():
  645. if all:
  646. spec = self.get_module_spec(module)
  647. if spec:
  648. spec_part = spec.get_config_spec()
  649. self._append_value_item(result, spec_part, module, all, True)
  650. else:
  651. entry = _create_value_map_entry(module, 'module', None)
  652. result.append(entry)
  653. else:
  654. # Strip off start and end slashes, if they are there
  655. if len(identifier) > 0 and identifier[0] == '/':
  656. identifier = identifier[1:]
  657. if len(identifier) > 0 and identifier[-1] == '/':
  658. identifier = identifier[:-1]
  659. module, sep, id = identifier.partition('/')
  660. spec = self.get_module_spec(module)
  661. if spec:
  662. spec_part = find_spec_part(spec.get_config_spec(), id)
  663. self._append_value_item(result, spec_part, identifier, all, True)
  664. return result
  665. def unset(self, identifier):
  666. """
  667. Reset the value to default.
  668. """
  669. spec_part = self.find_spec_part(identifier)
  670. if spec_part is not None:
  671. isc.cc.data.unset(self._local_changes, identifier)
  672. else:
  673. raise isc.cc.data.DataNotFoundError(identifier + "not found")
  674. def set_value(self, identifier, value):
  675. """Set the local value at the given identifier to value. If
  676. there is a specification for the given identifier, the type
  677. is checked."""
  678. spec_part = self.find_spec_part(identifier)
  679. if spec_part is not None:
  680. if value is not None:
  681. id, list_indices = isc.cc.data.split_identifier_list_indices(identifier)
  682. if list_indices is not None \
  683. and spec_part['item_type'] == 'list':
  684. spec_part = spec_part['list_item_spec']
  685. check_type(spec_part, value)
  686. else:
  687. raise isc.cc.data.DataNotFoundError(identifier + " not found")
  688. # Since we do not support list diffs (yet?), we need to
  689. # copy the currently set list of items to _local_changes
  690. # if we want to modify an element in there
  691. # (for any list indices specified in the full identifier)
  692. id_parts = isc.cc.data.split_identifier(identifier)
  693. cur_id_part = '/'
  694. for id_part in id_parts:
  695. id, list_indices = isc.cc.data.split_identifier_list_indices(id_part)
  696. cur_value, status = self.get_value(cur_id_part + id)
  697. # Check if the value was there in the first place
  698. # If we are at the final element, we do not care whether we found
  699. # it, since if we have reached this point and it did not exist,
  700. # it was apparently an optional value without a default.
  701. if status == MultiConfigData.NONE and cur_id_part != "/" and\
  702. cur_id_part + id != identifier:
  703. raise isc.cc.data.DataNotFoundError(id_part +
  704. " not found in " +
  705. cur_id_part)
  706. if list_indices is not None:
  707. # And check if we don't set something outside of any
  708. # list
  709. cur_list = cur_value
  710. for list_index in list_indices:
  711. if type(cur_list) != list:
  712. raise isc.cc.data.DataTypeError(id + " is not a list")
  713. if list_index >= len(cur_list):
  714. raise isc.cc.data.DataNotFoundError("No item " +
  715. str(list_index) + " in " + id_part)
  716. else:
  717. cur_list = cur_list[list_index]
  718. if status != MultiConfigData.LOCAL:
  719. isc.cc.data.set(self._local_changes,
  720. cur_id_part + id,
  721. cur_value)
  722. cur_id_part = cur_id_part + id_part + "/"
  723. # We also need to copy to local if we are changing a named set,
  724. # so that the other items in the set do not disappear
  725. if spec_part_is_named_set(self.find_spec_part(cur_id_part)):
  726. ns_value, ns_status = self.get_value(cur_id_part)
  727. if ns_status != MultiConfigData.LOCAL:
  728. isc.cc.data.set(self._local_changes,
  729. cur_id_part,
  730. ns_value)
  731. isc.cc.data.set(self._local_changes, identifier, value)
  732. def _get_list_items(self, item_name):
  733. """This method is used in get_config_item_list, to add list
  734. indices and named_set names to the completion list. If
  735. the given item_name is for a list or named_set, it'll
  736. return a list of those (appended to item_name), otherwise
  737. the list will only contain the item_name itself.
  738. If the item is a named set, and it's contents are maps
  739. or named_sets as well, a / is appended to the result
  740. strings.
  741. Parameters:
  742. item_name (string): the (full) identifier for the list or
  743. named_set to enumerate.
  744. Returns a list of strings with item names
  745. Examples:
  746. _get_list_items("Module/some_item")
  747. where item is not a list of a named_set, or where
  748. said list or named set is empty, returns
  749. ["Module/some_item"]
  750. _get_list_items("Module/named_set")
  751. where the named_set contains items with names 'a'
  752. and 'b', returns
  753. [ "Module/named_set/a", "Module/named_set/b" ]
  754. _get_list_items("Module/named_set_of_maps")
  755. where the named_set contains items with names 'a'
  756. and 'b', and those items are maps themselves
  757. (or other named_sets), returns
  758. [ "Module/named_set/a/", "Module/named_set/b/" ]
  759. _get_list_items("Module/list")
  760. where the list contains 2 elements, returns
  761. [ "Module/list[0]", "Module/list[1]" ]
  762. """
  763. spec_part = self.find_spec_part(item_name)
  764. if spec_part_is_named_set(spec_part):
  765. values, status = self.get_value(item_name)
  766. if values is not None and len(values) > 0:
  767. subslash = ""
  768. if spec_part['named_set_item_spec']['item_type'] == 'map' or\
  769. spec_part['named_set_item_spec']['item_type'] == 'named_set':
  770. subslash = "/"
  771. return [ item_name + "/" + v + subslash for v in values.keys() ]
  772. else:
  773. return [ item_name ]
  774. elif spec_part_is_list(spec_part):
  775. values, status = self.get_value(item_name)
  776. if values is not None and len(values) > 0:
  777. result = []
  778. for i in range(len(values)):
  779. name = item_name + '[%d]' % i
  780. result.extend(self._get_list_items(name))
  781. return result
  782. else:
  783. return [ item_name ]
  784. else:
  785. return [ item_name ]
  786. def get_config_item_list(self, identifier = None, recurse = False):
  787. """Returns a list of strings containing the item_names of
  788. the child items at the given identifier. If no identifier is
  789. specified, returns a list of module names. The first part of
  790. the identifier (up to the first /) is interpreted as the
  791. module name"""
  792. if identifier and identifier != "/":
  793. if identifier.startswith("/"):
  794. identifier = identifier[1:]
  795. spec = self.find_spec_part(identifier)
  796. spec_list = spec_name_list(spec, identifier + "/", recurse)
  797. result_list = []
  798. for spec_name in spec_list:
  799. result_list.extend(self._get_list_items(spec_name))
  800. return result_list
  801. else:
  802. if recurse:
  803. id_list = []
  804. for module in self._specifications.keys():
  805. id_list.extend(spec_name_list(self.find_spec_part(module), module, recurse))
  806. return id_list
  807. else:
  808. return list(self._specifications.keys())