bind-cfgd.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import ISC
  2. import signal
  3. import ast
  4. import pprint
  5. import os
  6. from ISC.CC import data
  7. class ConfigManagerData:
  8. CONFIG_VERSION = 1
  9. DB_FILENAME = "/tmp/parkinglot.db"
  10. def __init__(self):
  11. self.data = {}
  12. self.data['version'] = ConfigManagerData.CONFIG_VERSION
  13. def set_data_definition(self, module_name, module_data_definition):
  14. self.zones[module_name] = module_data_definition
  15. self.data_definitions[module_name] = module_data_definition
  16. def read_from_file():
  17. config = ConfigManagerData()
  18. try:
  19. file = open(ConfigManagerData.DB_FILENAME, 'r')
  20. file_config = ast.literal_eval(file.read())
  21. if 'version' in file_config and \
  22. file_config['version'] == ConfigManagerData.CONFIG_VERSION:
  23. config.data = file_config
  24. else:
  25. # of course we can put in a migration path here for old data
  26. print("[bind-cfgd] Old version of data found, starting with empty configuration")
  27. file.close()
  28. except IOError as ioe:
  29. print("No config file found, starting with empty config")
  30. except EOFError as eofe:
  31. print("Config file empty, starting with empty config")
  32. except:
  33. print("Config file unreadable, starting with empty config")
  34. return config
  35. def write_to_file(self):
  36. try:
  37. tmp_filename = self.DB_FILENAME + ".tmp"
  38. file = open(tmp_filename, 'w');
  39. pp = pprint.PrettyPrinter(indent=4)
  40. s = pp.pformat(self.data)
  41. file.write(s)
  42. file.write("\n")
  43. file.close()
  44. os.rename(tmp_filename, self.DB_FILENAME)
  45. except IOError as ioe:
  46. print("Unable to write config file; configuration not stored")
  47. class ConfigManager:
  48. def __init__(self):
  49. self.commands = {}
  50. self.data_definitions = {}
  51. self.config = ConfigManagerData()
  52. self.cc = ISC.CC.Session()
  53. self.cc.group_subscribe("ConfigManager")
  54. self.cc.group_subscribe("Boss", "ConfigManager")
  55. self.running = False
  56. def notify_boss(self):
  57. self.cc.group_sendmsg({"running": "configmanager"}, "Boss")
  58. def set_config(self, module_name, data_specification):
  59. self.data_definitions[module_name] = data_specification
  60. def remove_config(self, module_name):
  61. self.data_definitions[module_name]
  62. def set_commands(self, module_name, commands):
  63. self.commands[module_name] = commands
  64. def remove_commands(self, module_name):
  65. del self.commands[module_name]
  66. def read_config(self):
  67. print("Reading config")
  68. self.config = ConfigManagerData.read_from_file()
  69. def write_config(self):
  70. print("Writing config")
  71. self.config.write_to_file()
  72. def handle_msg(self, msg):
  73. """return answer message"""
  74. answer = {}
  75. if "command" in msg:
  76. cmd = msg["command"]
  77. try:
  78. if cmd[0] == "get_commands":
  79. answer["result"] = [ 0, self.commands ]
  80. elif cmd[0] == "get_data_spec":
  81. if len(cmd) > 1 and cmd[1] != "":
  82. try:
  83. answer["result"] = [0, self.data_definitions[cmd[1]]]
  84. except KeyError as ke:
  85. answer["result"] = [1, "No specification for module " + cmd[1]]
  86. else:
  87. answer["result"] = [0, self.data_definitions]
  88. elif cmd[0] == "get_config":
  89. # we may not have any configuration here
  90. conf_part = None
  91. if len(cmd) > 1:
  92. try:
  93. conf_part = data.find(self.config.data, cmd[1])
  94. except data.DataNotFoundError as dnfe:
  95. pass
  96. else:
  97. conf_part = self.config.data
  98. if conf_part:
  99. answer["result"] = [ 0, conf_part ]
  100. else:
  101. answer["result"] = [ 0 ]
  102. elif cmd[0] == "set_config":
  103. if len(cmd) == 3:
  104. # todo: use api (and check types?)
  105. if cmd[1] != "":
  106. conf_part = data.find_no_exc(self.config.data, cmd[1])
  107. if not conf_part:
  108. conf_part = data.set(self.config.data, cmd[1], "")
  109. else:
  110. conf_part = self.config.data
  111. data.merge(conf_part, cmd[2])
  112. print("[XX bind-cfgd] new config (part):")
  113. print(conf_part)
  114. # send out changed info
  115. self.cc.group_sendmsg({ "config_update": conf_part }, cmd[1])
  116. answer["result"] = [ 0 ]
  117. elif len(cmd) == 2:
  118. print("[XX bind-cfgd] old config:")
  119. print(self.config.data)
  120. print("[XX bind-cfgd] updating with:")
  121. print(cmd[1])
  122. data.merge(self.config.data, cmd[1])
  123. print("[XX bind-cfgd] new config:")
  124. print(self.config.data)
  125. # send out changed info
  126. for module in self.config.data:
  127. self.cc.group_sendmsg({ "config_update": self.config.data[module] }, module)
  128. answer["result"] = [ 0 ]
  129. else:
  130. answer["result"] = [ 1, "Wrong number of arguments" ]
  131. elif cmd[0] == "zone" and cmd[1] == "add":
  132. self.add_zone(cmd[2])
  133. answer["result"] = [ 0 ]
  134. elif cmd[0] == "zone" and cmd[1] == "remove":
  135. try:
  136. self.remove_zone(cmd[2])
  137. answer["result"] = [ 0 ]
  138. except KeyError:
  139. # zone wasn't there, should we make
  140. # a separate exception for that?
  141. answer["result"] = [ 1, "Unknown zone" ]
  142. elif cmd[0] == "zone" and cmd[1] == "list":
  143. answer["result"] = []#list(self.config.zones.keys())
  144. elif cmd == "shutdown":
  145. print("[bind-cfgd] Received shutdown command")
  146. self.running = False
  147. else:
  148. print("[bind-cfgd] unknown command: " + str(cmd))
  149. answer["result"] = [ 1, "Unknown command: " + str(cmd) ]
  150. except IndexError as ie:
  151. print("[bind-cfgd] missing argument")
  152. answer["result"] = [ 1, "Missing argument in command: " + str(ie) ]
  153. raise ie
  154. elif "data_specification" in msg:
  155. # todo: validate? (no direct access to spec as
  156. spec = msg["data_specification"]
  157. if "config_data" in spec:
  158. self.set_config(spec["module_name"], spec["config_data"])
  159. self.cc.group_sendmsg({ "specification_update": [ spec["module_name"], spec["config_data"] ] }, "BigTool")
  160. if "commands" in spec:
  161. self.set_commands(spec["module_name"], spec["commands"])
  162. self.cc.group_sendmsg({ "commands_update": [ spec["module_name"], spec["commands"] ] }, "BigTool")
  163. answer["result"] = [ 0 ]
  164. else:
  165. print("[bind-cfgd] unknown message: " + str(msg))
  166. answer["result"] = [ 1, "Unknown module: " + str(msg) ]
  167. return answer
  168. def run(self):
  169. self.running = True
  170. while (self.running):
  171. msg, env = self.cc.group_recvmsg(False)
  172. if msg:
  173. answer = self.handle_msg(msg);
  174. self.cc.group_reply(env, answer)
  175. else:
  176. self.running = False
  177. cm = None
  178. def signal_handler(signal, frame):
  179. global cm
  180. if cm:
  181. cm.running = False
  182. if __name__ == "__main__":
  183. try:
  184. cm = ConfigManager()
  185. signal.signal(signal.SIGINT, signal_handler)
  186. signal.signal(signal.SIGTERM, signal_handler)
  187. cm.read_config()
  188. cm.notify_boss()
  189. cm.run()
  190. cm.write_config()
  191. except ISC.CC.SessionError as se:
  192. print("[bind-cfgd] Error creating config manager, "
  193. "is the command channel daemon running?")
  194. except KeyboardInterrupt as kie:
  195. print("Got ctrl-c, save config and exit")
  196. cm.write_config()