|
@@ -1,3 +1,24 @@
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+"""This is the BIND 10 configuration manager, run by b10-cfgmgr.
|
|
|
+
|
|
|
+ It stores the system configuration, and sends updates of the
|
|
|
+ configuration to the modules that need them.
|
|
|
+"""
|
|
|
+
|
|
|
import isc
|
|
|
import signal
|
|
|
import ast
|
|
@@ -5,21 +26,42 @@ import pprint
|
|
|
import os
|
|
|
from isc.cc import data
|
|
|
|
|
|
+class ConfigManagerDataReadError(Exception):
|
|
|
+ """This exception is thrown when there is an error while reading
|
|
|
+ the current configuration on startup."""
|
|
|
+ pass
|
|
|
+
|
|
|
+class ConfigManagerDataEmpty(Exception):
|
|
|
+ """This exception is thrown when the currently stored configuration
|
|
|
+ is not found, or appears empty."""
|
|
|
+ pass
|
|
|
+
|
|
|
class ConfigManagerData:
|
|
|
+ """This class hold the actual configuration information, and
|
|
|
+ reads it from and writes it to persistent storage"""
|
|
|
+
|
|
|
CONFIG_VERSION = 1
|
|
|
|
|
|
- def __init__(self, data_path):
|
|
|
+ def __init__(self, data_path, file_name = "b10-config.db"):
|
|
|
+ """Initialize the data for the configuration manager, and
|
|
|
+ set the version and path for the data store. Initializing
|
|
|
+ this does not yet read the database, a call to
|
|
|
+ read_from_file is needed for that."""
|
|
|
self.data = {}
|
|
|
self.data['version'] = ConfigManagerData.CONFIG_VERSION
|
|
|
self.data_path = data_path
|
|
|
- self.db_filename = data_path + "/b10-config.db"
|
|
|
+ self.db_filename = data_path + os.sep + file_name
|
|
|
|
|
|
- def set_data_definition(self, module_name, module_data_definition):
|
|
|
- self.zones[module_name] = module_data_definition
|
|
|
- self.data_definitions[module_name] = module_data_definition
|
|
|
-
|
|
|
- def read_from_file(data_path):
|
|
|
- config = ConfigManagerData(data_path)
|
|
|
+ def read_from_file(data_path, file_name = "b10-config.db"):
|
|
|
+ """Read the current configuration found in the file at
|
|
|
+ data_path. If the file does not exist, a
|
|
|
+ ConfigManagerDataEmpty exception is raised. If there is a
|
|
|
+ parse error, or if the data in the file has the wrong
|
|
|
+ version, a ConfigManagerDataReadError is raised. In the first
|
|
|
+ case, it is probably safe to log and ignore. In the case of
|
|
|
+ the second exception, the best way is probably to report the
|
|
|
+ error and stop loading the system."""
|
|
|
+ config = ConfigManagerData(data_path, file_name)
|
|
|
try:
|
|
|
file = open(config.db_filename, 'r')
|
|
|
file_config = ast.literal_eval(file.read())
|
|
@@ -27,19 +69,20 @@ class ConfigManagerData:
|
|
|
file_config['version'] == ConfigManagerData.CONFIG_VERSION:
|
|
|
config.data = file_config
|
|
|
else:
|
|
|
-
|
|
|
- print("[bind-cfgd] Old version of data found, starting with empty configuration")
|
|
|
+
|
|
|
+ raise ConfigManagerDataReadError("[bind-cfgd] Old version of data found")
|
|
|
file.close()
|
|
|
except IOError as ioe:
|
|
|
- print("No config file found, starting with empty config")
|
|
|
- except EOFError as eofe:
|
|
|
- print("Config file empty, starting with empty config")
|
|
|
+ raise ConfigManagerDataEmpty("No config file found")
|
|
|
except:
|
|
|
- print("Config file unreadable, starting with empty config")
|
|
|
+ raise ConfigManagerDataReadError("Config file unreadable")
|
|
|
|
|
|
return config
|
|
|
|
|
|
- def write_to_file(self):
|
|
|
+ def write_to_file(self, output_file_name = None):
|
|
|
+ """Writes the current configuration data to a file. If
|
|
|
+ output_file_name is not specified, the file used in
|
|
|
+ read_from_file is used."""
|
|
|
try:
|
|
|
tmp_filename = self.db_filename + ".tmp"
|
|
|
file = open(tmp_filename, 'w');
|
|
@@ -52,141 +95,235 @@ class ConfigManagerData:
|
|
|
except IOError as ioe:
|
|
|
print("Unable to write config file; configuration not stored")
|
|
|
|
|
|
+ def __eq__(self, other):
|
|
|
+ """Returns True if the data contained is equal. data_path and
|
|
|
+ db_filename may be different."""
|
|
|
+ if type(other) != type(self):
|
|
|
+ return False
|
|
|
+ return self.data == other.data
|
|
|
+
|
|
|
class ConfigManager:
|
|
|
- def __init__(self, data_path):
|
|
|
- self.commands = {}
|
|
|
- self.data_definitions = {}
|
|
|
+ """Creates a configuration manager. The data_path is the path
|
|
|
+ to the directory containing the b10-config.db file.
|
|
|
+ If session is set, this will be used as the communication
|
|
|
+ channel session. If not, a new session will be created.
|
|
|
+ The ability to specify a custom session is for testing purposes
|
|
|
+ and should not be needed for normal usage."""
|
|
|
+ def __init__(self, data_path, session = None):
|
|
|
+ """Initialize the configuration manager. The data_path string
|
|
|
+ is the path to the directory where the configuration is
|
|
|
+ stored (in <data_path>/b10-config.db). Session is an optional
|
|
|
+ cc-channel session. If this is not given, a new one is
|
|
|
+ created"""
|
|
|
self.data_path = data_path
|
|
|
+ self.module_specs = {}
|
|
|
self.config = ConfigManagerData(data_path)
|
|
|
- self.cc = isc.cc.Session()
|
|
|
+ if session:
|
|
|
+ self.cc = session
|
|
|
+ else:
|
|
|
+ self.cc = isc.cc.Session()
|
|
|
self.cc.group_subscribe("ConfigManager")
|
|
|
self.cc.group_subscribe("Boss", "ConfigManager")
|
|
|
self.running = False
|
|
|
|
|
|
def notify_boss(self):
|
|
|
+ """Notifies the Boss module that the Config Manager is running"""
|
|
|
self.cc.group_sendmsg({"running": "configmanager"}, "Boss")
|
|
|
|
|
|
- def set_config(self, module_name, data_specification):
|
|
|
- self.data_definitions[module_name] = data_specification
|
|
|
-
|
|
|
- def remove_config(self, module_name):
|
|
|
- self.data_definitions[module_name]
|
|
|
+ def set_module_spec(self, spec):
|
|
|
+ """Adds a ModuleSpec"""
|
|
|
+ self.module_specs[spec.get_module_name()] = spec
|
|
|
|
|
|
- def set_commands(self, module_name, commands):
|
|
|
- self.commands[module_name] = commands
|
|
|
+ def remove_module_spec(self, module_name):
|
|
|
+ """Removes the full ModuleSpec for the given module_name.
|
|
|
+ Does nothing if the module was not present."""
|
|
|
+ if module_name in self.module_specs:
|
|
|
+ del self.module_specs[module_name]
|
|
|
|
|
|
- def remove_commands(self, module_name):
|
|
|
- del self.commands[module_name]
|
|
|
+ def get_module_spec(self, module_name):
|
|
|
+ """Returns the full ModuleSpec for the module with the given
|
|
|
+ module_name"""
|
|
|
+ if module_name in self.module_specs:
|
|
|
+ return self.module_specs[module_name]
|
|
|
+
|
|
|
+ def get_config_spec(self, name = None):
|
|
|
+ """Returns a dict containing 'module_name': config_spec for
|
|
|
+ all modules. If name is specified, only that module will
|
|
|
+ be included"""
|
|
|
+ config_data = {}
|
|
|
+ if name:
|
|
|
+ if name in self.module_specs:
|
|
|
+ config_data[name] = self.module_specs[name].get_data
|
|
|
+ else:
|
|
|
+ for module_name in self.module_specs.keys():
|
|
|
+ config_data[module_name] = self.module_specs[module_name].get_config_spec()
|
|
|
+ return config_data
|
|
|
+
|
|
|
+ def get_commands_spec(self, name = None):
|
|
|
+ """Returns a dict containing 'module_name': commands_spec for
|
|
|
+ all modules. If name is specified, only that module will
|
|
|
+ be included"""
|
|
|
+ commands = {}
|
|
|
+ if name:
|
|
|
+ if name in self.module_specs:
|
|
|
+ commands[name] = self.module_specs[name].get_commands_spec
|
|
|
+ else:
|
|
|
+ for module_name in self.module_specs.keys():
|
|
|
+ commands[module_name] = self.module_specs[module_name].get_commands_spec()
|
|
|
+ return commands
|
|
|
|
|
|
def read_config(self):
|
|
|
- print("Reading config")
|
|
|
- self.config = ConfigManagerData.read_from_file(self.data_path)
|
|
|
+ """Read the current configuration from the b10-config.db file
|
|
|
+ at the path specificied at init()"""
|
|
|
+ try:
|
|
|
+ self.config = ConfigManagerData.read_from_file(self.data_path)
|
|
|
+ except ConfigManagerDataEmpty:
|
|
|
+
|
|
|
+ self.config = ConfigManagerData(self.data_path)
|
|
|
|
|
|
def write_config(self):
|
|
|
- print("Writing config")
|
|
|
+ """Write the current configuration to the b10-config.db file
|
|
|
+ at the path specificied at init()"""
|
|
|
self.config.write_to_file()
|
|
|
|
|
|
+ def _handle_get_module_spec(self, cmd):
|
|
|
+ """Private function that handles the 'get_module_spec' command"""
|
|
|
+ answer = {}
|
|
|
+ if len(cmd) > 1:
|
|
|
+ if type(cmd[1]) == dict:
|
|
|
+ if 'module_name' in cmd[1] and cmd[1]['module_name'] != '':
|
|
|
+ module_name = cmd[1]['module_name']
|
|
|
+ answer = isc.config.ccsession.create_answer(0, self.get_config_spec(module_name))
|
|
|
+ else:
|
|
|
+ answer = isc.config.ccsession.create_answer(1, "Bad module_name in get_module_spec command")
|
|
|
+ else:
|
|
|
+ answer = isc.config.ccsession.create_answer(1, "Bad get_module_spec command, argument not a dict")
|
|
|
+ else:
|
|
|
+ answer = isc.config.ccsession.create_answer(0, self.get_config_spec())
|
|
|
+ return answer
|
|
|
+
|
|
|
+ def _handle_get_config(self, cmd):
|
|
|
+ """Private function that handles the 'get_config' command"""
|
|
|
+ answer = {}
|
|
|
+ if len(cmd) > 1:
|
|
|
+ if type(cmd[1]) == dict:
|
|
|
+ if 'module_name' in cmd[1] and cmd[1]['module_name'] != '':
|
|
|
+ module_name = cmd[1]['module_name']
|
|
|
+ try:
|
|
|
+ answer = isc.config.ccsession.create_answer(0, data.find(self.config.data, module_name))
|
|
|
+ except data.DataNotFoundError as dnfe:
|
|
|
+
|
|
|
+
|
|
|
+ answer = isc.config.ccsession.create_answer(0, {})
|
|
|
+ else:
|
|
|
+ answer = isc.config.ccsession.create_answer(1, "Bad module_name in get_config command")
|
|
|
+ else:
|
|
|
+ answer = isc.config.ccsession.create_answer(1, "Bad get_config command, argument not a dict")
|
|
|
+ else:
|
|
|
+ answer = isc.config.ccsession.create_answer(0, self.config.data)
|
|
|
+ return answer
|
|
|
+
|
|
|
+ def _handle_set_config(self, cmd):
|
|
|
+ """Private function that handles the 'set_config' command"""
|
|
|
+ answer = None
|
|
|
+ if len(cmd) == 3:
|
|
|
+
|
|
|
+ module_name = cmd[1]
|
|
|
+ conf_part = data.find_no_exc(self.config.data, module_name)
|
|
|
+ if conf_part:
|
|
|
+ data.merge(conf_part, cmd[2])
|
|
|
+ self.cc.group_sendmsg({ "config_update": conf_part }, module_name)
|
|
|
+ answer, env = self.cc.group_recvmsg(False)
|
|
|
+ else:
|
|
|
+ conf_part = data.set(self.config.data, module_name, {})
|
|
|
+ data.merge(conf_part[module_name], cmd[2])
|
|
|
+
|
|
|
+ self.cc.group_sendmsg({ "config_update": conf_part[module_name] }, module_name)
|
|
|
+
|
|
|
+ answer, env = self.cc.group_recvmsg(False)
|
|
|
+ rcode, val = isc.config.ccsession.parse_answer(answer)
|
|
|
+ if rcode == 0:
|
|
|
+ self.write_config()
|
|
|
+ elif len(cmd) == 2:
|
|
|
+
|
|
|
+ data.merge(self.config.data, cmd[1])
|
|
|
+
|
|
|
+ got_error = False
|
|
|
+ err_list = []
|
|
|
+ for module in self.config.data:
|
|
|
+ if module != "version":
|
|
|
+ self.cc.group_sendmsg({ "config_update": self.config.data[module] }, module)
|
|
|
+ answer, env = self.cc.group_recvmsg(False)
|
|
|
+ rcode, val = isc.config.ccsession.parse_answer(answer)
|
|
|
+ if rcode != 0:
|
|
|
+ got_error = True
|
|
|
+ err_list.append(val)
|
|
|
+ if not got_error:
|
|
|
+ self.write_config()
|
|
|
+ answer = isc.config.ccsession.create_answer(0)
|
|
|
+ else:
|
|
|
+
|
|
|
+
|
|
|
+ answer = isc.config.ccsession.create_answer(1, " ".join(err_list))
|
|
|
+ else:
|
|
|
+ answer = isc.config.ccsession.create_answer(1, "Wrong number of arguments")
|
|
|
+ if not answer:
|
|
|
+ answer = isc.config.ccsession.create_answer(1, "Error handling set_config command")
|
|
|
+
|
|
|
+ return answer
|
|
|
+
|
|
|
+ def _handle_module_spec(self, spec):
|
|
|
+ """Private function that handles the 'module_spec' command"""
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ answer = {}
|
|
|
+ self.set_module_spec(spec)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ self.cc.group_sendmsg({ "specification_update": [ spec.get_module_name(), spec.get_config_spec() ] }, "Cmd-Ctrld")
|
|
|
+ self.cc.group_sendmsg({ "commands_update": [ spec.get_module_name(), spec.get_commands_spec() ] }, "Cmd-Ctrld")
|
|
|
+ answer = isc.config.ccsession.create_answer(0)
|
|
|
+ return answer
|
|
|
+
|
|
|
def handle_msg(self, msg):
|
|
|
- """return answer message"""
|
|
|
+ """Handle a command from the cc channel to the configuration manager"""
|
|
|
answer = {}
|
|
|
if "command" in msg:
|
|
|
cmd = msg["command"]
|
|
|
try:
|
|
|
- if cmd[0] == "get_commands":
|
|
|
- print("[XX] bind-cfgd got get_commands");
|
|
|
- print("[XX] sending:")
|
|
|
- print(self.commands)
|
|
|
- answer["result"] = [ 0, self.commands ]
|
|
|
- elif cmd[0] == "get_data_spec":
|
|
|
- if len(cmd) > 1 and cmd[1]['module_name'] != '':
|
|
|
- module_name = cmd[1]['module_name']
|
|
|
- try:
|
|
|
- answer["result"] = [0, self.data_definitions[module_name]]
|
|
|
- except KeyError as ke:
|
|
|
- answer["result"] = [1, "No specification for module " + module_name]
|
|
|
- else:
|
|
|
- answer["result"] = [0, self.data_definitions]
|
|
|
+ if cmd[0] == "get_commands_spec":
|
|
|
+ answer = isc.config.ccsession.create_answer(0, self.get_commands_spec())
|
|
|
+ elif cmd[0] == "get_module_spec":
|
|
|
+ answer = self._handle_get_module_spec(cmd)
|
|
|
elif cmd[0] == "get_config":
|
|
|
-
|
|
|
- conf_part = None
|
|
|
- print("[XX] bind-cfgd got command:")
|
|
|
- print(cmd)
|
|
|
- if len(cmd) > 1:
|
|
|
- try:
|
|
|
- conf_part = data.find(self.config.data, cmd[1]['module_name'])
|
|
|
- except data.DataNotFoundError as dnfe:
|
|
|
- pass
|
|
|
- else:
|
|
|
- conf_part = self.config.data
|
|
|
- if conf_part:
|
|
|
- answer["result"] = [ 0, conf_part ]
|
|
|
- else:
|
|
|
- answer["result"] = [ 0 ]
|
|
|
-
|
|
|
+ answer = self._handle_get_config(cmd)
|
|
|
elif cmd[0] == "set_config":
|
|
|
- if len(cmd) == 3:
|
|
|
-
|
|
|
- if cmd[1] != "":
|
|
|
- conf_part = data.find_no_exc(self.config.data, cmd[1])
|
|
|
- if not conf_part:
|
|
|
- conf_part = data.set(self.config.data, cmd[1], "")
|
|
|
- else:
|
|
|
- conf_part = self.config.data
|
|
|
- data.merge(conf_part, cmd[2])
|
|
|
- print("[XX bind-cfgd] new config (part):")
|
|
|
- print(conf_part)
|
|
|
-
|
|
|
- self.cc.group_sendmsg({ "config_update": conf_part }, cmd[1])
|
|
|
- self.write_config()
|
|
|
- answer["result"] = [ 0 ]
|
|
|
- elif len(cmd) == 2:
|
|
|
- print("[XX bind-cfgd] old config:")
|
|
|
- print(self.config.data)
|
|
|
- print("[XX bind-cfgd] updating with:")
|
|
|
- print(cmd[1])
|
|
|
-
|
|
|
- data.merge(self.config.data, cmd[1])
|
|
|
- print("[XX bind-cfgd] new config:")
|
|
|
- print(self.config.data)
|
|
|
-
|
|
|
- for module in self.config.data:
|
|
|
- self.cc.group_sendmsg({ "config_update": self.config.data[module] }, module)
|
|
|
- self.write_config()
|
|
|
- answer["result"] = [ 0 ]
|
|
|
- else:
|
|
|
- answer["result"] = [ 1, "Wrong number of arguments" ]
|
|
|
-
|
|
|
+ answer = self._handle_set_config(cmd)
|
|
|
elif cmd[0] == "shutdown":
|
|
|
print("[bind-cfgd] Received shutdown command")
|
|
|
self.running = False
|
|
|
+ answer = isc.config.ccsession.create_answer(0)
|
|
|
else:
|
|
|
- print("[bind-cfgd] unknown command: " + str(cmd))
|
|
|
- answer["result"] = [ 1, "Unknown command: " + str(cmd) ]
|
|
|
+ answer = isc.config.ccsession.create_answer(1, "Unknown command: " + str(cmd))
|
|
|
except IndexError as ie:
|
|
|
- print("[bind-cfgd] missing argument")
|
|
|
- answer["result"] = [ 1, "Missing argument in command: " + str(ie) ]
|
|
|
+ answer = isc.config.ccsession.create_answer(1, "Missing argument in command: " + str(ie))
|
|
|
raise ie
|
|
|
- elif "data_specification" in msg:
|
|
|
-
|
|
|
-
|
|
|
- print("[XX] bind-cfgd got specification:")
|
|
|
- print(msg["data_specification"])
|
|
|
- spec = msg["data_specification"]
|
|
|
- if "config_data" in spec:
|
|
|
- self.set_config(spec["module_name"], spec["config_data"])
|
|
|
- self.cc.group_sendmsg({ "specification_update": [ spec["module_name"], spec["config_data"] ] }, "Cmd-Ctrld")
|
|
|
- if "commands" in spec:
|
|
|
- self.set_commands(spec["module_name"], spec["commands"])
|
|
|
- self.cc.group_sendmsg({ "commands_update": [ spec["module_name"], spec["commands"] ] }, "Cmd-Ctrld")
|
|
|
- answer["result"] = [ 0 ]
|
|
|
+ elif "module_spec" in msg:
|
|
|
+ try:
|
|
|
+ answer = self._handle_module_spec(isc.config.ModuleSpec(msg["module_spec"]))
|
|
|
+ except isc.config.ModuleSpecError as dde:
|
|
|
+ answer = isc.config.ccsession.create_answer(1, "Error in data definition: " + str(dde))
|
|
|
elif 'result' in msg:
|
|
|
- answer['result'] = [0]
|
|
|
+
|
|
|
+ answer = isc.config.ccsession.create_answer(0)
|
|
|
else:
|
|
|
- print("[bind-cfgd] unknown message: " + str(msg))
|
|
|
- answer["result"] = [ 1, "Unknown module: " + str(msg) ]
|
|
|
+ answer = isc.config.ccsession.create_answer(1, "Unknown message format: " + str(msg))
|
|
|
return answer
|
|
|
|
|
|
def run(self):
|
|
|
+ """Runs the configuration manager."""
|
|
|
self.running = True
|
|
|
while (self.running):
|
|
|
msg, env = self.cc.group_recvmsg(False)
|
|
@@ -195,10 +332,3 @@ class ConfigManager:
|
|
|
self.cc.group_reply(env, answer)
|
|
|
else:
|
|
|
self.running = False
|
|
|
-
|
|
|
-cm = None
|
|
|
-
|
|
|
-def signal_handler(signal, frame):
|
|
|
- global cm
|
|
|
- if cm:
|
|
|
- cm.running = False
|