config_data.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  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. class ConfigDataError(Exception): pass
  24. BIND10_CONFIG_DATA_VERSION = 2
  25. def check_type(spec_part, value):
  26. """Does nothing if the value is of the correct type given the
  27. specification part relevant for the value. Raises an
  28. isc.cc.data.DataTypeError exception if not. spec_part can be
  29. retrieved with find_spec_part()"""
  30. if type(spec_part) == dict and 'item_type' in spec_part:
  31. data_type = spec_part['item_type']
  32. else:
  33. raise isc.cc.data.DataTypeError(str("Incorrect specification part for type checking"))
  34. if data_type == "integer" and type(value) != int:
  35. raise isc.cc.data.DataTypeError(str(value) + " is not an integer")
  36. elif data_type == "real" and type(value) != float:
  37. raise isc.cc.data.DataTypeError(str(value) + " is not a real")
  38. elif data_type == "boolean" and type(value) != bool:
  39. raise isc.cc.data.DataTypeError(str(value) + " is not a boolean")
  40. elif data_type == "string" and type(value) != str:
  41. raise isc.cc.data.DataTypeError(str(value) + " is not a string")
  42. elif data_type == "list":
  43. if type(value) != list:
  44. raise isc.cc.data.DataTypeError(str(value) + " is not a list")
  45. else:
  46. for element in value:
  47. check_type(spec_part['list_item_spec'], element)
  48. elif data_type == "map" and type(value) != dict:
  49. # todo: check types of map contents too
  50. raise isc.cc.data.DataTypeError(str(value) + " is not a map")
  51. def convert_type(spec_part, value):
  52. """Convert the given value(type is string) according specification
  53. part relevant for the value. Raises an isc.cc.data.DataTypeError
  54. exception if conversion failed.
  55. """
  56. if type(spec_part) == dict and 'item_type' in spec_part:
  57. data_type = spec_part['item_type']
  58. else:
  59. raise isc.cc.data.DataTypeError(str("Incorrect specification part for type conversion"))
  60. try:
  61. if data_type == "integer":
  62. return int(value)
  63. elif data_type == "real":
  64. return float(value)
  65. elif data_type == "boolean":
  66. return str.lower(str(value)) != 'false'
  67. elif data_type == "string":
  68. return str(value)
  69. elif data_type == "list":
  70. ret = []
  71. if type(value) == list:
  72. for item in value:
  73. ret.append(convert_type(spec_part['list_item_spec'], item))
  74. elif type(value) == str:
  75. value = value.split(',')
  76. for item in value:
  77. sub_value = item.split()
  78. for sub_item in sub_value:
  79. ret.append(convert_type(spec_part['list_item_spec'],
  80. sub_item))
  81. if ret == []:
  82. raise isc.cc.data.DataTypeError(str(value) + " is not a list")
  83. return ret
  84. elif data_type == "map":
  85. map = ast.literal_eval(value)
  86. if type(map) == dict:
  87. # todo: check types of map contents too
  88. return map
  89. else:
  90. raise isc.cc.data.DataTypeError(
  91. "Value in convert_type not a string "
  92. "specifying a dict")
  93. else:
  94. return value
  95. except ValueError as err:
  96. raise isc.cc.data.DataTypeError(str(err))
  97. except TypeError as err:
  98. raise isc.cc.data.DataTypeError(str(err))
  99. def _get_map_or_list(spec_part):
  100. """Returns the list or map specification if this is a list or a
  101. map specification part. If not, returns the given spec_part
  102. itself"""
  103. if "map_item_spec" in spec_part:
  104. return spec_part["map_item_spec"]
  105. elif "list_item_spec" in spec_part:
  106. return spec_part["list_item_spec"]
  107. else:
  108. return spec_part
  109. def _find_spec_part_single(cur_spec, id_part):
  110. """Find the spec part for the given (partial) name. This partial
  111. name does not contain separators ('/'), and the specification
  112. part should be a direct child of the given specification part.
  113. id_part may contain list selectors, which will be ignored.
  114. Returns the child part.
  115. Raises DataNotFoundError if it was not found."""
  116. # strip list selector part
  117. # don't need it for the spec part, so just drop it
  118. id, list_indices = isc.cc.data.split_identifier_list_indices(id_part)
  119. # The specification we want a sub-part for should be either a
  120. # list or a map, which is internally represented by a dict with
  121. # an element 'map_item_spec', a dict with an element 'list_item_spec',
  122. # or a list (when it is the 'main' config_data element of a module).
  123. if type(cur_spec) == dict and 'map_item_spec' in cur_spec.keys():
  124. for cur_spec_item in cur_spec['map_item_spec']:
  125. if cur_spec_item['item_name'] == id:
  126. return cur_spec_item
  127. # not found
  128. raise isc.cc.data.DataNotFoundError(id + " not found")
  129. elif type(cur_spec) == dict and 'list_item_spec' in cur_spec.keys():
  130. if cur_spec['item_name'] == id:
  131. return cur_spec['list_item_spec']
  132. # not found
  133. raise isc.cc.data.DataNotFoundError(id + " not found")
  134. elif type(cur_spec) == dict and 'named_map_item_spec' in cur_spec.keys():
  135. return cur_spec['named_map_item_spec']
  136. elif type(cur_spec) == list:
  137. for cur_spec_item in cur_spec:
  138. if cur_spec_item['item_name'] == id:
  139. return cur_spec_item
  140. # not found
  141. raise isc.cc.data.DataNotFoundError(id + " not found")
  142. else:
  143. raise isc.cc.data.DataNotFoundError("Not a correct config specification")
  144. def find_spec_part(element, identifier):
  145. """find the data definition for the given identifier
  146. returns either a map with 'item_name' etc, or a list of those"""
  147. if identifier == "":
  148. return element
  149. id_parts = identifier.split("/")
  150. id_parts[:] = (value for value in id_parts if value != "")
  151. cur_el = element
  152. # up to the last element, if the result is a map or a list,
  153. # we want its subspecification (i.e. list_item_spec or
  154. # map_item_spec). For the last element in the identifier we
  155. # always want the 'full' spec of the item
  156. for id_part in id_parts[:-1]:
  157. cur_el = _find_spec_part_single(cur_el, id_part)
  158. cur_el = _get_map_or_list(cur_el)
  159. cur_el = _find_spec_part_single(cur_el, id_parts[-1])
  160. return cur_el
  161. def spec_name_list(spec, prefix="", recurse=False):
  162. """Returns a full list of all possible item identifiers in the
  163. specification (part). Raises a ConfigDataError if spec is not
  164. a correct spec (as returned by ModuleSpec.get_config_spec()"""
  165. result = []
  166. if prefix != "" and not prefix.endswith("/"):
  167. prefix += "/"
  168. if type(spec) == dict:
  169. if 'map_item_spec' in spec:
  170. for map_el in spec['map_item_spec']:
  171. name = map_el['item_name']
  172. if map_el['item_type'] == 'map':
  173. name += "/"
  174. if recurse and 'map_item_spec' in map_el:
  175. result.extend(spec_name_list(map_el['map_item_spec'], prefix + map_el['item_name'], recurse))
  176. else:
  177. result.append(prefix + name)
  178. else:
  179. for name in spec:
  180. result.append(prefix + name + "/")
  181. if recurse:
  182. result.extend(spec_name_list(spec[name],name, recurse))
  183. elif type(spec) == list:
  184. for list_el in spec:
  185. if 'item_name' in list_el:
  186. if list_el['item_type'] == "map" and recurse:
  187. result.extend(spec_name_list(list_el['map_item_spec'], prefix + list_el['item_name'], recurse))
  188. else:
  189. name = list_el['item_name']
  190. result.append(prefix + name)
  191. else:
  192. raise ConfigDataError("Bad specification")
  193. else:
  194. raise ConfigDataError("Bad specication")
  195. return result
  196. class ConfigData:
  197. """This class stores the module specs and the current non-default
  198. config values. It provides functions to get the actual value or
  199. the default value if no non-default value has been set"""
  200. def __init__(self, specification):
  201. """Initialize a ConfigData instance. If specification is not
  202. of type ModuleSpec, a ConfigDataError is raised."""
  203. if type(specification) != isc.config.ModuleSpec:
  204. raise ConfigDataError("specification is of type " + str(type(specification)) + ", not ModuleSpec")
  205. self.specification = specification
  206. self.data = {}
  207. def get_value(self, identifier):
  208. """Returns a tuple where the first item is the value at the
  209. given identifier, and the second item is a bool which is
  210. true if the value is an unset default. Raises an
  211. isc.cc.data.DataNotFoundError if the identifier is bad"""
  212. value = isc.cc.data.find_no_exc(self.data, identifier)
  213. if value != None:
  214. return value, False
  215. spec = find_spec_part(self.specification.get_config_spec(), identifier)
  216. if spec and 'item_default' in spec:
  217. return spec['item_default'], True
  218. return None, False
  219. def get_default_value(self, identifier):
  220. """Returns the default from the specification, or None if there
  221. is no default"""
  222. spec = find_spec_part(self.specification.get_config_spec(), identifier)
  223. if spec and 'item_default' in spec:
  224. return spec['item_default']
  225. else:
  226. return None
  227. def get_module_spec(self):
  228. """Returns the ModuleSpec object associated with this ConfigData"""
  229. return self.specification
  230. def set_local_config(self, data):
  231. """Set the non-default config values, as passed by cfgmgr"""
  232. self.data = data
  233. def get_local_config(self):
  234. """Returns the non-default config values in a dict"""
  235. return self.data;
  236. def get_item_list(self, identifier = None, recurse = False):
  237. """Returns a list of strings containing the full identifiers of
  238. all 'sub'options at the given identifier. If recurse is True,
  239. it will also add all identifiers of all children, if any"""
  240. if identifier:
  241. spec = find_spec_part(self.specification.get_config_spec(), identifier)
  242. return spec_name_list(spec, identifier + "/")
  243. return spec_name_list(self.specification.get_config_spec(), "", recurse)
  244. def get_full_config(self):
  245. """Returns a dict containing identifier: value elements, for
  246. all configuration options for this module. If there is
  247. a local setting, that will be used. Otherwise the value
  248. will be the default as specified by the module specification.
  249. If there is no default and no local setting, the value will
  250. be None"""
  251. items = self.get_item_list(None, True)
  252. result = {}
  253. for item in items:
  254. value, default = self.get_value(item)
  255. result[item] = value
  256. return result
  257. # should we just make a class for these?
  258. def _create_value_map_entry(name, type, value, status = None):
  259. entry = {}
  260. entry['name'] = name
  261. entry['type'] = type
  262. entry['value'] = value
  263. entry['modified'] = False
  264. entry['default'] = False
  265. if status == MultiConfigData.LOCAL:
  266. entry['modified'] = True
  267. if status == MultiConfigData.DEFAULT:
  268. entry['default'] = True
  269. return entry
  270. class MultiConfigData:
  271. """This class stores the module specs, current non-default
  272. configuration values and 'local' (uncommitted) changes for
  273. multiple modules"""
  274. LOCAL = 1
  275. CURRENT = 2
  276. DEFAULT = 3
  277. NONE = 4
  278. def __init__(self):
  279. self._specifications = {}
  280. self._current_config = {}
  281. self._local_changes = {}
  282. def set_specification(self, spec):
  283. """Add or update a ModuleSpec. Raises a ConfigDataError is spec is not a ModuleSpec"""
  284. if type(spec) != isc.config.ModuleSpec:
  285. raise ConfigDataError("not a datadef: " + str(type(spec)))
  286. self._specifications[spec.get_module_name()] = spec
  287. def remove_specification(self, module_name):
  288. """Removes the specification with the given module name. Does nothing if it wasn't there."""
  289. if module_name in self._specifications:
  290. del self._specifications[module_name]
  291. def have_specification(self, module_name):
  292. """Returns True if we have a specification for the module with the given name.
  293. Returns False if we do not."""
  294. return module_name in self._specifications
  295. def get_module_spec(self, module):
  296. """Returns the ModuleSpec for the module with the given name.
  297. If there is no such module, it returns None"""
  298. if module in self._specifications:
  299. return self._specifications[module]
  300. else:
  301. return None
  302. def find_spec_part(self, identifier):
  303. """Returns the specification for the item at the given
  304. identifier, or None if not found. The first part of the
  305. identifier (up to the first /) is interpreted as the module
  306. name. Returns None if not found, or if identifier is not a
  307. string."""
  308. if type(identifier) != str or identifier == "":
  309. return None
  310. if identifier[0] == '/':
  311. identifier = identifier[1:]
  312. module, sep, id = identifier.partition("/")
  313. try:
  314. return find_spec_part(self._specifications[module].get_config_spec(), id)
  315. except isc.cc.data.DataNotFoundError as dnfe:
  316. return None
  317. except KeyError as ke:
  318. return None
  319. # this function should only be called by __request_config
  320. def _set_current_config(self, config):
  321. """Replace the full current config values."""
  322. self._current_config = config
  323. def get_current_config(self):
  324. """Returns the current configuration as it is known by the
  325. configuration manager. It is a dict where the first level is
  326. the module name, and the value is the config values for
  327. that module"""
  328. return self._current_config
  329. def get_local_changes(self):
  330. """Returns the local config changes, i.e. those that have not
  331. been committed yet and are not known by the configuration
  332. manager or the modules."""
  333. return self._local_changes
  334. def clear_local_changes(self):
  335. """Reverts all local changes"""
  336. self._local_changes = {}
  337. def get_local_value(self, identifier):
  338. """Returns a specific local (uncommitted) configuration value,
  339. as specified by the identifier. If the local changes do not
  340. contain a new setting for this identifier, or if the
  341. identifier cannot be found, None is returned. See
  342. get_value() for a general way to find a configuration value
  343. """
  344. return isc.cc.data.find_no_exc(self._local_changes, identifier)
  345. def get_current_value(self, identifier):
  346. """Returns the current non-default value as known by the
  347. configuration manager, or None if it is not set.
  348. See get_value() for a general way to find a configuration
  349. value
  350. """
  351. return isc.cc.data.find_no_exc(self._current_config, identifier)
  352. def get_default_value(self, identifier):
  353. """Returns the default value for the given identifier as
  354. specified by the module specification, or None if there is
  355. no default or the identifier could not be found.
  356. See get_value() for a general way to find a configuration
  357. value
  358. """
  359. try:
  360. if identifier[0] == '/':
  361. identifier = identifier[1:]
  362. module, sep, id = identifier.partition("/")
  363. # if there is a 'higher-level' list index specified, we need
  364. # to check if that list specification has a default that
  365. # overrides the more specific default in the final spec item
  366. # (ie. list_default = [1, 2, 3], list_item_spec=int, default=0)
  367. # def default list[1] should return 2, not 0
  368. id_parts = isc.cc.data.split_identifier(id)
  369. id_prefix = ""
  370. while len(id_parts) > 0:
  371. id_part = id_parts.pop(0)
  372. item_id, list_indices = isc.cc.data.split_identifier_list_indices(id_part)
  373. id_list = module + "/" + id_prefix + "/" + item_id
  374. id_prefix += "/" + id_part
  375. part_spec = find_spec_part(self._specifications[module].get_config_spec(), id_prefix)
  376. if part_spec['item_type'] == 'named_map':
  377. if len(id_parts) == 0:
  378. if 'item_default' in part_spec:
  379. return part_spec['item_default']
  380. else:
  381. return None
  382. id_part = id_parts.pop(0)
  383. named_map_value, type = self.get_value(id_list)
  384. if id_part in named_map_value:
  385. if len(id_parts) > 0:
  386. # we are looking for the *default* value.
  387. # so if not present in here, we need to
  388. # lookup the one from the spec
  389. rest_of_id = "/".join(id_parts)
  390. result = isc.cc.data.find_no_exc(named_map_value[id_part], rest_of_id)
  391. if result is None:
  392. spec_part = self.find_spec_part(identifier)
  393. if 'item_default' in spec_part:
  394. return spec_part['item_default']
  395. return result
  396. else:
  397. return named_map_value[id_part]
  398. else:
  399. return None
  400. elif list_indices is not None:
  401. # there's actually two kinds of default here for
  402. # lists; they can have a default value (like an
  403. # empty list), but their elements can also have
  404. # default values.
  405. # So if the list item *itself* is a default,
  406. # we need to get the value out of that. If not, we
  407. # need to find the default for the specific element.
  408. list_value, type = self.get_value(id_list)
  409. list_spec = find_spec_part(self._specifications[module].get_config_spec(), id_prefix)
  410. if type == self.DEFAULT:
  411. if 'item_default' in list_spec:
  412. list_value = list_spec['item_default']
  413. for i in list_indices:
  414. if i < len(list_value):
  415. list_value = list_value[i]
  416. else:
  417. # out of range, return None
  418. return None
  419. if len(id_parts) > 0:
  420. rest_of_id = "/".join(id_parts)
  421. return isc.cc.data.find(list_value, rest_of_id)
  422. else:
  423. return list_value
  424. else:
  425. # we do have a non-default list, see if our indices
  426. # exist
  427. for i in list_indices:
  428. if i < len(list_value):
  429. list_value = list_value[i]
  430. else:
  431. # out of range, return None
  432. return None
  433. spec = find_spec_part(self._specifications[module].get_config_spec(), id)
  434. if 'item_default' in spec:
  435. # one special case, named_map
  436. if spec['item_type'] == 'named_map':
  437. print("is " + id_part + " in named map?")
  438. return spec['item_default']
  439. else:
  440. return spec['item_default']
  441. else:
  442. return None
  443. except isc.cc.data.DataNotFoundError as dnfe:
  444. return None
  445. def get_value(self, identifier, default = True):
  446. """Returns a tuple containing value,status.
  447. The value contains the configuration value for the given
  448. identifier. The status reports where this value came from;
  449. it is one of: LOCAL, CURRENT, DEFAULT or NONE, corresponding
  450. (local change, current setting, default as specified by the
  451. specification, or not found at all). Does not check and
  452. set DEFAULT if the argument 'default' is False (default
  453. defaults to True)"""
  454. value = self.get_local_value(identifier)
  455. if value != None:
  456. return value, self.LOCAL
  457. value = self.get_current_value(identifier)
  458. if value != None:
  459. return value, self.CURRENT
  460. if default:
  461. value = self.get_default_value(identifier)
  462. if value != None:
  463. return value, self.DEFAULT
  464. return None, self.NONE
  465. def _append_value_item(self, result, spec_part, identifier, all, first = False):
  466. # Look at the spec; it is a list of items, or a map containing 'item_name' etc
  467. if type(spec_part) == list:
  468. for spec_part_element in spec_part:
  469. spec_part_element_name = spec_part_element['item_name']
  470. self._append_value_item(result, spec_part_element, identifier + "/" + spec_part_element_name, all)
  471. elif type(spec_part) == dict:
  472. # depending on item type, and the value of argument 'all'
  473. # we need to either add an item, or recursively go on
  474. # In the case of a list that is empty, we do need to show that
  475. item_name = spec_part['item_name']
  476. item_type = spec_part['item_type']
  477. if item_type == "list" and (all or first):
  478. spec_part_list = spec_part['list_item_spec']
  479. list_value, status = self.get_value(identifier)
  480. if list_value is None:
  481. raise isc.cc.data.DataNotFoundError(identifier + " not found")
  482. if type(list_value) != list:
  483. # the identifier specified a single element
  484. self._append_value_item(result, spec_part_list, identifier, all)
  485. else:
  486. list_len = len(list_value)
  487. if len(list_value) == 0 and (all or first):
  488. entry = _create_value_map_entry(identifier,
  489. item_type,
  490. [], status)
  491. result.append(entry)
  492. else:
  493. for i in range(len(list_value)):
  494. self._append_value_item(result, spec_part_list, "%s[%d]" % (identifier, i), all)
  495. elif item_type == "map":
  496. value, status = self.get_value(identifier)
  497. # just show the specific contents of a map, we are
  498. # almost never interested in just its name
  499. spec_part_map = spec_part['map_item_spec']
  500. self._append_value_item(result, spec_part_map, identifier, all)
  501. elif item_type == "named_map":
  502. value, status = self.get_value(identifier)
  503. # show just the one entry, when either the map is empty,
  504. # or when this is element is not requested specifically
  505. if len(value.keys()) == 0:
  506. entry = _create_value_map_entry(identifier,
  507. item_type,
  508. {}, status)
  509. result.append(entry)
  510. elif not first and not all:
  511. entry = _create_value_map_entry(identifier,
  512. item_type,
  513. None, status)
  514. result.append(entry)
  515. else:
  516. spec_part_named_map = spec_part['named_map_item_spec']
  517. for entry in value:
  518. self._append_value_item(result,
  519. spec_part_named_map,
  520. identifier + "/" + entry,
  521. all)
  522. else:
  523. value, status = self.get_value(identifier)
  524. if status == self.NONE and not spec_part['item_optional']:
  525. raise isc.cc.data.DataNotFoundError(identifier + " not found")
  526. entry = _create_value_map_entry(identifier,
  527. item_type,
  528. value, status)
  529. result.append(entry)
  530. return
  531. def get_value_maps(self, identifier = None, all = False):
  532. """Returns a list of dicts, containing the following values:
  533. name: name of the entry (string)
  534. type: string containing the type of the value (or 'module')
  535. value: value of the entry if it is a string, int, double or bool
  536. modified: true if the value is a local change that has not
  537. been committed
  538. default: true if the value has not been changed (i.e. the
  539. value is the default from the specification)
  540. TODO: use the consts for those last ones
  541. Throws DataNotFoundError if the identifier is bad
  542. """
  543. result = []
  544. if not identifier:
  545. # No identifier, so we need the list of current modules
  546. for module in self._specifications.keys():
  547. if all:
  548. spec = self.get_module_spec(module)
  549. if spec:
  550. spec_part = spec.get_config_spec()
  551. self._append_value_item(result, spec_part, module, all, True)
  552. else:
  553. entry = _create_value_map_entry(module, 'module', None)
  554. result.append(entry)
  555. else:
  556. if identifier[0] == '/':
  557. identifier = identifier[1:]
  558. module, sep, id = identifier.partition('/')
  559. spec = self.get_module_spec(module)
  560. if spec:
  561. spec_part = find_spec_part(spec.get_config_spec(), id)
  562. self._append_value_item(result, spec_part, identifier, all, True)
  563. return result
  564. def set_value(self, identifier, value):
  565. """Set the local value at the given identifier to value. If
  566. there is a specification for the given identifier, the type
  567. is checked."""
  568. spec_part = self.find_spec_part(identifier)
  569. if spec_part is not None:
  570. if value is not None:
  571. id, list_indices = isc.cc.data.split_identifier_list_indices(identifier)
  572. if list_indices is not None \
  573. and spec_part['item_type'] == 'list':
  574. spec_part = spec_part['list_item_spec']
  575. check_type(spec_part, value)
  576. else:
  577. raise isc.cc.data.DataNotFoundError(identifier + " not found")
  578. # Since we do not support list diffs (yet?), we need to
  579. # copy the currently set list of items to _local_changes
  580. # if we want to modify an element in there
  581. # (for any list indices specified in the full identifier)
  582. id_parts = isc.cc.data.split_identifier(identifier)
  583. cur_id_part = '/'
  584. for id_part in id_parts:
  585. id, list_indices = isc.cc.data.split_identifier_list_indices(id_part)
  586. cur_value, status = self.get_value(cur_id_part + id)
  587. # Check if the value was there in the first place
  588. if status == MultiConfigData.NONE and cur_id_part != "/":
  589. raise isc.cc.data.DataNotFoundError(id_part +
  590. " not found in " +
  591. cur_id_part)
  592. if list_indices is not None:
  593. # And check if we don't set something outside of any
  594. # list
  595. cur_list = cur_value
  596. for list_index in list_indices:
  597. if list_index >= len(cur_list):
  598. raise isc.cc.data.DataNotFoundError("No item " +
  599. str(list_index) + " in " + id_part)
  600. else:
  601. cur_list = cur_list[list_index]
  602. if status != MultiConfigData.LOCAL:
  603. isc.cc.data.set(self._local_changes,
  604. cur_id_part + id,
  605. cur_value)
  606. cur_id_part = cur_id_part + id_part + "/"
  607. isc.cc.data.set(self._local_changes, identifier, value)
  608. def get_config_item_list(self, identifier = None, recurse = False):
  609. """Returns a list of strings containing the item_names of
  610. the child items at the given identifier. If no identifier is
  611. specified, returns a list of module names. The first part of
  612. the identifier (up to the first /) is interpreted as the
  613. module name"""
  614. if identifier and identifier != "/":
  615. if identifier.startswith("/"):
  616. identifier = identifier[1:]
  617. spec = self.find_spec_part(identifier)
  618. return spec_name_list(spec, identifier + "/", recurse)
  619. else:
  620. if recurse:
  621. id_list = []
  622. for module in self._specifications.keys():
  623. id_list.extend(spec_name_list(self.find_spec_part(module), module, recurse))
  624. return id_list
  625. else:
  626. return list(self._specifications.keys())