config_data.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  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. # Class to store configuration data and data definition
  17. # Used by the config manager and python modules that communicate
  18. # with the configuration manager
  19. #
  20. import isc.cc.data
  21. import isc.config.datadefinition
  22. class ConfigDataError(Exception): pass
  23. def check_type(specification, value):
  24. """Returns true if the value is of the correct type given the
  25. specification part relevant for the value"""
  26. if type(specification) == list:
  27. data_type = "list"
  28. else:
  29. data_type = specification['item_type']
  30. if data_type == "integer" and type(value) != int:
  31. raise isc.cc.data.DataTypeError(str(value) + " is not an integer")
  32. elif data_type == "real" and type(value) != double:
  33. raise isc.cc.data.DataTypeError(str(value) + " is not a real")
  34. elif data_type == "boolean" and type(value) != boolean:
  35. raise isc.cc.data.DataTypeError(str(value) + " is not a boolean")
  36. elif data_type == "string" and type(value) != str:
  37. raise isc.cc.data.DataTypeError(str(value) + " is not a string")
  38. elif data_type == "list":
  39. if type(value) != list:
  40. raise isc.cc.data.DataTypeError(str(value) + " is not a list")
  41. else:
  42. for element in value:
  43. check_type(specification['list_item_spec'], element)
  44. elif data_type == "map" and type(value) != dict:
  45. # todo: check types of map contents too
  46. raise isc.cc.data.DataTypeError(str(value) + " is not a map")
  47. def find_spec(element, identifier):
  48. """find the data definition for the given identifier
  49. returns either a map with 'item_name' etc, or a list of those"""
  50. if identifier == "":
  51. return element
  52. id_parts = identifier.split("/")
  53. id_parts[:] = (value for value in id_parts if value != "")
  54. cur_el = element
  55. for id in id_parts:
  56. if type(cur_el) == dict and id in cur_el.keys():
  57. cur_el = cur_el[id]
  58. elif type(cur_el) == dict and 'item_name' in cur_el.keys() and cur_el['item_name'] == id:
  59. pass
  60. elif type(cur_el) == list:
  61. found = False
  62. for cur_el_item in cur_el:
  63. if cur_el_item['item_name'] == id and 'item_default' in cur_el_item.keys():
  64. cur_el = cur_el_item
  65. found = True
  66. if not found:
  67. raise isc.cc.data.DataNotFoundError(id + " in " + str(cur_el))
  68. else:
  69. raise isc.cc.data.DataNotFoundError(id + " in " + str(cur_el))
  70. return cur_el
  71. def spec_name_list(spec, prefix="", recurse=False):
  72. """Returns a full list of all possible item identifiers in the
  73. specification (part)"""
  74. result = []
  75. if prefix != "" and not prefix.endswith("/"):
  76. prefix += "/"
  77. if type(spec) == dict:
  78. for name in spec:
  79. result.append(prefix + name + "/")
  80. if recurse:
  81. result.extend(spec_name_list(spec[name],name, recurse))
  82. elif type(spec) == list:
  83. for list_el in spec:
  84. if 'item_name' in list_el:
  85. if list_el['item_type'] == dict:
  86. if recurse:
  87. result.extend(spec_name_list(list_el['map_item_spec'], prefix + list_el['item_name'], recurse))
  88. else:
  89. name = list_el['item_name']
  90. if list_el['item_type'] in ["list", "map"]:
  91. name += "/"
  92. result.append(name)
  93. return result
  94. class ConfigData:
  95. """This class stores the datadefinition and the current non-default
  96. config values. It provides functions to get the actual value or
  97. the default value if no non-default value has been set"""
  98. def __init__(self, specification):
  99. """Initialize a ConfigData instance. If specification is not
  100. of type ModuleSpec, a ConfigDataError is raised."""
  101. if type(specification) != isc.config.ModuleSpec:
  102. raise ConfigDataError("specification is of type " + str(type(specification)) + ", not ModuleSpec")
  103. self.specification = specification
  104. self.data = {}
  105. def get_item_list(self, identifier = None, recurse = False):
  106. if identifier:
  107. spec = find_spec(self.specification.get_config_spec(), identifier, recurse)
  108. return spec_name_list(spec, identifier + "/")
  109. return spec_name_list(self.specification.get_config_spec(), "", recurse)
  110. def get_value(self, identifier):
  111. """Returns a tuple where the first item is the value at the
  112. given identifier, and the second item is a bool which is
  113. true if the value is an unset default"""
  114. value = isc.cc.data.find_no_exc(self.data, identifier)
  115. if value:
  116. return value, False
  117. spec = find_spec(self.specification.get_config_spec(), identifier)
  118. if spec and 'item_default' in spec:
  119. return spec['item_default'], True
  120. return None, False
  121. def get_module_spec(self):
  122. """Returns the ModuleSpec object associated with this ConfigData"""
  123. return self.specification
  124. def set_local_config(self, data):
  125. """Set the non-default config values, as passed by cfgmgr"""
  126. self.data = data
  127. def get_local_config(self):
  128. """Returns the non-default config values in a dict"""
  129. return self.data;
  130. def get_full_config(self):
  131. items = self.get_item_list(None, True)
  132. result = {}
  133. for item in items:
  134. value, default = self.get_value(item)
  135. result[item] = value
  136. return result
  137. #def get_identifiers(self):
  138. # Returns a list containing all identifiers
  139. #def
  140. class MultiConfigData:
  141. """This class stores the datadefinitions, current non-default
  142. configuration values and 'local' (uncommitted) changes for
  143. multiple modules"""
  144. LOCAL = 1
  145. CURRENT = 2
  146. DEFAULT = 3
  147. NONE = 4
  148. def __init__(self):
  149. self._specifications = {}
  150. self._current_config = {}
  151. self._local_changes = {}
  152. def set_specification(self, spec):
  153. if type(spec) != isc.config.ModuleSpec:
  154. raise Exception("not a datadef")
  155. self._specifications[spec.get_module_name()] = spec
  156. def get_module_spec(self, module):
  157. if module in self._specifications:
  158. return self._specifications[module]
  159. else:
  160. return None
  161. def find_spec_part(self, identifier):
  162. """returns the specification for the item at the given
  163. identifier, or None if not found"""
  164. if identifier[0] == '/':
  165. identifier = identifier[1:]
  166. module, sep, id = identifier.partition("/")
  167. try:
  168. return find_spec(self._specifications[module].get_config_spec(), id)
  169. except isc.cc.data.DataNotFoundError as dnfe:
  170. return None
  171. def set_current_config(self, config):
  172. self._current_config = config
  173. def get_current_config(self):
  174. """The current config is a dict where the first level is
  175. the module name, and the value is the config values for
  176. that module"""
  177. return self._current_config
  178. def get_local_changes(self):
  179. return self._local_changes
  180. def clear_local_changes(self):
  181. self._local_changes = {}
  182. def get_local_value(self, identifier):
  183. return isc.cc.data.find_no_exc(self._local_changes, identifier)
  184. def get_current_value(self, identifier):
  185. """Returns the current non-default value, or None if not set"""
  186. return isc.cc.data.find_no_exc(self._current_config, identifier)
  187. def get_default_value(self, identifier):
  188. """returns the default value, or None if there is no default"""
  189. if identifier[0] == '/':
  190. identifier = identifier[1:]
  191. module, sep, id = identifier.partition("/")
  192. try:
  193. spec = find_spec(self._specifications[module].get_config_spec(), id)
  194. if 'item_default' in spec:
  195. return spec['item_default']
  196. else:
  197. return None
  198. except isc.cc.data.DataNotFoundError as dnfe:
  199. return None
  200. def get_value(self, identifier):
  201. """Returns a tuple containing value,status. Status is either
  202. LOCAL, CURRENT, DEFAULT or NONE, corresponding to the
  203. source of the value (local change, current setting, default
  204. as specified by the specification, or not found at all)."""
  205. value = self.get_local_value(identifier)
  206. if value:
  207. return value, self.LOCAL
  208. value = self.get_current_value(identifier)
  209. if value:
  210. return value, self.CURRENT
  211. value = self.get_default_value(identifier)
  212. if value:
  213. return value, self.DEFAULT
  214. return None, self.NONE
  215. def get_value_maps(self, identifier = None):
  216. """Returns a list of dicts, containing the following values:
  217. name: name of the entry (string)
  218. type: string containing the type of the value (or 'module')
  219. value: value of the entry if it is a string, int, double or bool
  220. modified: true if the value is a local change
  221. default: true if the value has been changed
  222. Throws DataNotFoundError if the identifier is bad
  223. TODO: use the consts for those last ones
  224. """
  225. result = []
  226. if not identifier:
  227. # No identifier, so we need the list of current modules
  228. for module in self._specifications.keys():
  229. entry = {}
  230. entry['name'] = module
  231. entry['type'] = 'module'
  232. entry['value'] = None
  233. entry['modified'] = False
  234. entry['default'] = False
  235. result.append(entry)
  236. else:
  237. if identifier[0] == '/':
  238. identifier = identifier[1:]
  239. module, sep, id = identifier.partition('/')
  240. spec = self.get_module_spec(module)
  241. if spec:
  242. spec_part = find_spec(spec.get_config_spec(), id)
  243. print(spec_part)
  244. if type(spec_part) == list:
  245. for item in spec_part:
  246. entry = {}
  247. entry['name'] = item['item_name']
  248. entry['type'] = item['item_type']
  249. value, status = self.get_value("/" + identifier + "/" + item['item_name'])
  250. entry['value'] = value
  251. if status == self.LOCAL:
  252. entry['modified'] = True
  253. else:
  254. entry['modified'] = False
  255. if status == self.DEFAULT:
  256. entry['default'] = False
  257. else:
  258. entry['default'] = False
  259. result.append(entry)
  260. else:
  261. item = spec_part
  262. if item['item_type'] == 'list':
  263. li_spec = item['list_item_spec']
  264. l, status = self.get_value("/" + identifier)
  265. if l:
  266. for value in l:
  267. result_part2 = {}
  268. result_part2['name'] = li_spec['item_name']
  269. result_part2['value'] = value
  270. result_part2['type'] = li_spec['item_type']
  271. result_part2['default'] = False
  272. result_part2['modified'] = False
  273. result.append(result_part2)
  274. else:
  275. entry = {}
  276. entry['name'] = item['item_name']
  277. entry['type'] = item['item_type']
  278. #value, status = self.get_value("/" + identifier + "/" + item['item_name'])
  279. value, status = self.get_value("/" + identifier)
  280. entry['value'] = value
  281. if status == self.LOCAL:
  282. entry['modified'] = True
  283. else:
  284. entry['modified'] = False
  285. if status == self.DEFAULT:
  286. entry['default'] = False
  287. else:
  288. entry['default'] = False
  289. result.append(entry)
  290. #print(spec)
  291. return result
  292. def set_value(self, identifier, value):
  293. """Set the local value at the given identifier to value"""
  294. spec_part = self.find_spec_part(identifier)
  295. if check_type(spec_part, value):
  296. isc.cc.data.set(self._local_changes, identifier, value)
  297. def get_config_item_list(self, identifier = None):
  298. """Returns a list of strings containing the item_names of
  299. the child items at the given identifier. If no identifier is
  300. specified, returns a list of module names. The first part of
  301. the identifier (up to the first /) is interpreted as the
  302. module name"""
  303. if identifier:
  304. spec = self.find_spec_part(identifier)
  305. return spec_name_list(spec, identifier + "/")
  306. else:
  307. return self._specifications.keys()
  308. class UIConfigData():
  309. """This class is used in a configuration user interface. It contains
  310. specific functions for getting, displaying, and sending
  311. configuration settings."""
  312. def __init__(self, conn):
  313. self._conn = conn
  314. self._data = MultiConfigData()
  315. self.request_specifications()
  316. self.request_current_config()
  317. a,b = self._data.get_value("/Boss/some_string")
  318. def request_specifications(self):
  319. # this step should be unnecessary but is the current way cmdctl returns stuff
  320. # so changes are needed there to make this clean (we need a command to simply get the
  321. # full specs for everything, including commands etc, not separate gets for that)
  322. specs = self._conn.send_GET('/config_spec')
  323. commands = self._conn.send_GET('/commands')
  324. #print(specs)
  325. #print(commands)
  326. for module in specs.keys():
  327. cur_spec = { 'module_name': module }
  328. if module in specs and specs[module]:
  329. cur_spec['config_data'] = specs[module]
  330. if module in commands and commands[module]:
  331. cur_spec['commands'] = commands[module]
  332. self._data.set_specification(isc.config.ModuleSpec(cur_spec))
  333. def request_current_config(self):
  334. config = self._conn.send_GET('/config_data')
  335. if 'version' not in config or config['version'] != 1:
  336. raise Exception("Bad config version")
  337. self._data.set_current_config(config)
  338. def get_value(self, identifier):
  339. return self._data.get_value(identifier)
  340. def set_value(self, identifier, value):
  341. return self._data.set_value(identifier, value);
  342. def add_value(self, identifier, value_str):
  343. module_spec = self._data.find_spec_part(identifier)
  344. if (type(module_spec) != dict or "list_item_spec" not in module_spec):
  345. raise DataTypeError(identifier + " is not a list")
  346. value = isc.cc.data.parse_value_str(value_str)
  347. cur_list, status = self.get_value(identifier)
  348. if not cur_list:
  349. cur_list = []
  350. if value not in cur_list:
  351. cur_list.append(value)
  352. self.set_value(identifier, cur_list)
  353. def remove_value(self, identifier, value_str):
  354. module_spec = find_spec(self.config.specification, identifier)
  355. if (type(module_spec) != dict or "list_item_spec" not in module_spec):
  356. raise DataTypeError(identifier + " is not a list")
  357. value = parse_value_str(value_str)
  358. check_type(module_spec, [value])
  359. cur_list = isc.cc.data.find_no_exc(self.config_changes, identifier)
  360. if not cur_list:
  361. cur_list = isc.cc.data.find_no_exc(self.config.data, identifier)
  362. if not cur_list:
  363. cur_list = []
  364. if value in cur_list:
  365. cur_list.remove(value)
  366. set(self.config_changes, identifier, cur_list)
  367. def get_value_maps(self, identifier = None):
  368. return self._data.get_value_maps(identifier)
  369. def get_local_changes(self):
  370. return self._data.get_local_changes()
  371. def commit(self):
  372. self._conn.send_POST('/ConfigManager/set_config', self._data.get_local_changes())
  373. # todo: check result
  374. self.request_current_config()
  375. self._data.clear_local_changes()
  376. def get_config_item_list(self, identifier = None):
  377. return self._data.get_config_item_list(identifier)
  378. # remove
  379. class OUIConfigData():
  380. """This class is used in a configuration user interface. It contains
  381. specific functions for getting, displaying, and sending
  382. configuration settings."""
  383. def __init__(self, conn):
  384. # the specs dict contains module: configdata elements
  385. # these should all be replaced by the new stuff
  386. module_spec = self.get_module_spec(conn)
  387. self.config = module_spec
  388. self.get_config_spec(conn)
  389. self.config_changes = {}
  390. #
  391. self.config_
  392. self.specs = self.get_module_specs(conn)
  393. def get_config_spec(self, conn):
  394. data = conn.send_GET('/config_data')
  395. def send_changes(self, conn):
  396. conn.send_POST('/ConfigManager/set_config', self.config_changes)
  397. # Get latest config data
  398. self.get_config_spec(conn)
  399. self.config_changes = {}
  400. def get_module_spec(self, conn):
  401. return conn.send_GET('/config_spec')
  402. def get_module_specs(self, conn):
  403. specs = {}
  404. allspecs = conn.send_GET('/config_spec')
  405. def set(self, identifier, value):
  406. # check against definition
  407. spec = find_spec(identifier)
  408. check_type(spec, value)
  409. set(self.config_changes, identifier, value)
  410. def get_value(self, identifier):
  411. """Returns a three-tuple, where the first item is the value
  412. (or None), the second is a boolean specifying whether
  413. the value is the default value, and the third is a boolean
  414. specifying whether the value is an uncommitted change"""
  415. value = isc.cc.data.find_no_exc(self.config_changes, identifier)
  416. if value:
  417. return value, False, True
  418. value, default = self.config.get_value(identifier)
  419. if value:
  420. return value, default, False
  421. return None, False, False
  422. def get_value_map_single(self, identifier, entry):
  423. """Returns a single entry for a value_map, where the value is
  424. not a part of a bigger map"""
  425. result_part = {}
  426. result_part['name'] = entry['item_name']
  427. result_part['type'] = entry['item_type']
  428. value, default, modified = self.get_value(identifier)
  429. # should we check type and only set int, double, bool and string here?
  430. result_part['value'] = value
  431. result_part['default'] = default
  432. result_part['modified'] = modified
  433. return result_part
  434. def get_value_map(self, identifier, entry):
  435. """Returns a single entry for a value_map, where the value is
  436. a part of a bigger map"""
  437. result_part = {}
  438. result_part['name'] = entry['item_name']
  439. result_part['type'] = entry['item_type']
  440. value, default, modified = self.get_value(identifier + "/" + entry['item_name'])
  441. # should we check type and only set int, double, bool and string here?
  442. result_part['value'] = value
  443. result_part['default'] = default
  444. result_part['modified'] = modified
  445. return result_part
  446. def get_value_maps(self, identifier = None):
  447. """Returns a list of maps, containing the following values:
  448. name: name of the entry (string)
  449. type: string containing the type of the value (or 'module')
  450. value: value of the entry if it is a string, int, double or bool
  451. modified: true if the value is a local change
  452. default: true if the value has been changed
  453. Throws DataNotFoundError if the identifier is bad
  454. """
  455. spec = find_spec(self.config, identifier)
  456. result = []
  457. if type(spec) == dict:
  458. # either the top-level list of modules or a spec map
  459. if 'item_name' in spec:
  460. result_part = self.get_value_map_single(identifier, spec)
  461. if result_part['type'] == "list":
  462. values = self.get_value(identifier)[0]
  463. if values:
  464. for value in values:
  465. result_part2 = {}
  466. li_spec = spec['list_item_spec']
  467. result_part2['name'] = li_spec['item_name']
  468. result_part2['value'] = value
  469. result_part2['type'] = li_spec['item_type']
  470. result_part2['default'] = False
  471. result_part2['modified'] = False
  472. result.append(result_part2)
  473. else:
  474. result.append(result_part)
  475. else:
  476. for name in spec:
  477. result_part = {}
  478. result_part['name'] = name
  479. result_part['type'] = "module"
  480. result_part['value'] = None
  481. result_part['default'] = False
  482. result_part['modified'] = False
  483. result.append(result_part)
  484. elif type(spec) == list:
  485. for entry in spec:
  486. if type(entry) == dict and 'item_name' in entry:
  487. result.append(self.get_value_map(identifier, entry))
  488. return result
  489. def add(self, identifier, value_str):
  490. module_spec = find_spec(self.config.specification, identifier)
  491. if (type(module_spec) != dict or "list_item_spec" not in module_spec):
  492. raise DataTypeError(identifier + " is not a list")
  493. value = parse_value_str(value_str)
  494. check_type(module_spec, [value])
  495. cur_list = isc.cc.data.find_no_exc(self.config_changes, identifier)
  496. if not cur_list:
  497. cur_list = isc.cc.data.find_no_exc(self.config.data, identifier)
  498. if not cur_list:
  499. cur_list = []
  500. if value not in cur_list:
  501. cur_list.append(value)
  502. set(self.config_changes, identifier, cur_list)
  503. def remove(self, identifier, value_str):
  504. module_spec = find_spec(self.config.specification, identifier)
  505. if (type(module_spec) != dict or "list_item_spec" not in module_spec):
  506. raise DataTypeError(identifier + " is not a list")
  507. value = parse_value_str(value_str)
  508. check_type(module_spec, [value])
  509. cur_list = isc.cc.data.find_no_exc(self.config_changes, identifier)
  510. if not cur_list:
  511. cur_list = isc.cc.data.find_no_exc(self.config.data, identifier)
  512. if not cur_list:
  513. cur_list = []
  514. if value in cur_list:
  515. cur_list.remove(value)
  516. set(self.config_changes, identifier, cur_list)
  517. def set(self, identifier, value_str):
  518. module_spec = find_spec(self.config.specification, identifier)
  519. value = parse_value_str(value_str)
  520. check_type(module_spec, value)
  521. set(self.config_changes, identifier, value)
  522. def unset(self, identifier):
  523. # todo: check whether the value is optional?
  524. unset(self.config_changes, identifier)
  525. def revert(self):
  526. self.config_changes = {}
  527. def commit(self, conn):
  528. self.send_changes(conn)