bind-cfgd.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  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. return config
  33. def write_to_file(self):
  34. try:
  35. tmp_filename = self.DB_FILENAME + ".tmp"
  36. file = open(tmp_filename, 'w');
  37. pp = pprint.PrettyPrinter(indent=4)
  38. s = pp.pformat(self.data)
  39. file.write(s)
  40. file.write("\n")
  41. file.close()
  42. os.rename(tmp_filename, self.DB_FILENAME)
  43. except IOError as ioe:
  44. print("Unable to write config file; configuration not stored")
  45. class ConfigManager:
  46. def __init__(self):
  47. self.commands = {}
  48. self.data_definitions = {}
  49. self.config = ConfigManagerData()
  50. self.cc = ISC.CC.Session()
  51. self.cc.group_subscribe("ConfigManager")
  52. self.cc.group_subscribe("Boss", "ConfigManager")
  53. self.running = False
  54. def notify_boss(self):
  55. self.cc.group_sendmsg({"running": "configmanager"}, "Boss")
  56. def set_config(self, module_name, data_specification):
  57. self.data_definitions[module_name] = data_specification
  58. def remove_config(self, module_name):
  59. self.data_definitions[module_name]
  60. def set_commands(self, module_name, commands):
  61. self.commands[module_name] = commands
  62. def remove_commands(self, module_name):
  63. del self.commands[module_name]
  64. def add_zone(self, zone_name):
  65. self.config.add_zone(zone_name, "todo")
  66. self.write_config()
  67. print("sending update zone add")
  68. self.cc.group_sendmsg({"zone_added": zone_name }, "ParkingLot")
  69. def remove_zone(self, zone_name):
  70. self.config.remove_zone(zone_name)
  71. print("sending update zone del")
  72. self.cc.group_sendmsg({"zone_deleted": zone_name }, "ParkingLot")
  73. def read_config(self):
  74. print("Reading config")
  75. self.config = ConfigManagerData.read_from_file()
  76. def write_config(self):
  77. print("Writing config")
  78. self.config.write_to_file()
  79. def handle_msg(self, msg):
  80. """return answer message"""
  81. answer = {}
  82. if "command" in msg:
  83. cmd = msg["command"]
  84. try:
  85. if cmd[0] == "get_commands":
  86. answer["result"] = [ 0, self.commands ]
  87. elif cmd[0] == "get_data_spec":
  88. if len(cmd) > 1 and cmd[1] != "":
  89. try:
  90. answer["result"] = [0, self.data_definitions[cmd[1]]]
  91. except KeyError as ke:
  92. answer["result"] = [1, "No specification for module " + cmd[1]]
  93. else:
  94. answer["result"] = [0, self.data_definitions]
  95. elif cmd[0] == "get_config":
  96. # we may not have any configuration here
  97. conf_part = None
  98. if len(cmd) > 1:
  99. try:
  100. conf_part = data.find(self.config.data, cmd[1])
  101. except data.DataNotFoundError as dnfe:
  102. pass
  103. else:
  104. conf_part = self.config.data
  105. answer["result"] = [ 0, conf_part ]
  106. elif cmd[0] == "set_config":
  107. if len(cmd) == 3:
  108. # todo: use api (and check types?)
  109. if cmd[1] != "":
  110. conf_part = data.find_no_exc(self.config.data, cmd[1])
  111. if not conf_part:
  112. conf_part = data.set(self.config.data, cmd[1], "")
  113. else:
  114. conf_part = self.config.data
  115. conf_part.update(cmd[2])
  116. # send out changed info
  117. self.cc.group_sendmsg({ "config_update": conf_part }, cmd[1])
  118. answer["result"] = [ 0 ]
  119. elif len(cmd) == 2:
  120. self.config.data.update(cmd[1])
  121. # send out changed info
  122. for module in self.config.data:
  123. self.cc.group_sendmsg({ "config_update": self.config.data[module] }, module)
  124. answer["result"] = [ 0 ]
  125. else:
  126. answer["result"] = [ 1, "Wrong number of arguments" ]
  127. elif cmd[0] == "zone" and cmd[1] == "add":
  128. self.add_zone(cmd[2])
  129. answer["result"] = [ 0 ]
  130. elif cmd[0] == "zone" and cmd[1] == "remove":
  131. self.remove_zone(cmd[2])
  132. answer["result"] = [ 0 ]
  133. elif cmd[0] == "zone" and cmd[1] == "list":
  134. answer["result"] = []#list(self.config.zones.keys())
  135. elif len(cmd) > 1 and cmd[1] == "shutdown":
  136. print("Received shutdown command")
  137. self.running = False
  138. else:
  139. print("unknown command: " + str(cmd))
  140. answer["result"] = [ 1, "Unknown command: " + str(cmd) ]
  141. except IndexError as ie:
  142. print("missing argument")
  143. answer["result"] = [ 1, "Missing argument in command: " + str(ie) ]
  144. raise ie
  145. elif "data_specification" in msg:
  146. # todo: validate? (no direct access to spec as
  147. spec = msg["data_specification"]
  148. if "config_data" in spec:
  149. self.set_config(spec["module_name"], spec["config_data"])
  150. if "commands" in spec:
  151. self.set_commands(spec["module_name"], spec["commands"])
  152. answer["result"] = [ 0 ]
  153. else:
  154. print("unknown message: " + str(msg))
  155. answer["result"] = [ 1, "Unknown module: " + str(msg) ]
  156. return answer
  157. def run(self):
  158. self.running = True
  159. while (self.running):
  160. msg, env = self.cc.group_recvmsg(False)
  161. if msg:
  162. answer = self.handle_msg(msg);
  163. self.cc.group_reply(env, answer)
  164. else:
  165. self.running = False
  166. cm = None
  167. def signal_handler(signal, frame):
  168. global cm
  169. if cm:
  170. cm.running = False
  171. if __name__ == "__main__":
  172. print("Hello, BIND10 world!")
  173. try:
  174. cm = ConfigManager()
  175. signal.signal(signal.SIGINT, signal_handler)
  176. signal.signal(signal.SIGTERM, signal_handler)
  177. cm.read_config()
  178. cm.notify_boss()
  179. cm.run()
  180. cm.write_config()
  181. except ISC.CC.SessionError as se:
  182. print("Error creating config manager, "
  183. "is the command channel daemon running?")
  184. except KeyboardInterrupt as kie:
  185. print("Got ctrl-c, save config and exit")
  186. cm.write_config()