From 2116c2b62512c7ec9f10a9ac523962f0671e94c1 Mon Sep 17 00:00:00 2001 From: "A. Wilcox" Date: Mon, 31 Aug 2020 11:43:38 -0500 Subject: Add server and configuration modules --- ncserver/config.py | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ ncserver/server.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 2 ++ 3 files changed, 167 insertions(+) create mode 100644 ncserver/config.py create mode 100644 ncserver/server.py diff --git a/ncserver/config.py b/ncserver/config.py new file mode 100644 index 0000000..047e3a5 --- /dev/null +++ b/ncserver/config.py @@ -0,0 +1,85 @@ +""" +NETCONF for APK Distributions server: + Configuration manager module + +Copyright © 2020 Adélie Software in the Public Benefit, Inc. + +Released under the terms of the NCSA license. See the LICENSE file included +with this source distribution for more information. + +SPDX-License-Identifier: NCSA +""" + +import logging +import yaml + +from taillight import Signal + +from ncserver.base.util import _ + + +CONFIG_RELOADED = Signal(('ConfigManager', 'reloaded')) +"""Signal fired when configuration is reloaded.""" + + +class ConfigManager: + """The configuration manager for NETCONF for APK Distributions.""" + + def __init__(self): + """Initialise the configuration manager.""" + self._config = {'server': {'port': 830, 'debug': False, 'modules': [], + 'users': ['netconf']}} + self._logger = logging.getLogger("ConfigManager") + self.reload_config() + + def reload_config(self): + """Reload the configuration from disk.""" + try: + with open('/etc/netconf/netconf.conf', 'r') as conf: + self._config = yaml.safe_load(conf) + except IOError as exc: + self._logger.error(_("Couldn't open configuration file: %s"), exc) + except BaseException as exc: # pylint: disable=W0703 + self._logger.error(_("Couldn't read configuration file: %s"), exc) + else: + CONFIG_RELOADED.call() + + def get(self, clade: str, key: str): + """Retrieve the value for the specified configuration key. + + :param str clade: + The configuration clade (typically 'server'). + + :param str key: + The configuration key desired. + + :returns str: + The value of the specified configuration key. + + :raises KeyError: + The key is not configured or set. + """ + return self._config[clade][key] + + def get_list(self, clade: str, key: str) -> list: + """Retrieve a list value for the specified configuration key. + + :param str clade: + The configuration clade (typically 'server'). + + :param str key: + The configuration key desired. + + :returns list: + The value of the specified configuration key. + + :raises KeyError: + The key is not configured or set. + + :raises TypeError: + The value for this key is not a list. + """ + value = self._config[clade][key] + if not isinstance(value, list): + raise TypeError(_("{}/{} is not a list value").format(clade, key)) + return value diff --git a/ncserver/server.py b/ncserver/server.py new file mode 100644 index 0000000..a0eed8e --- /dev/null +++ b/ncserver/server.py @@ -0,0 +1,80 @@ +""" +NETCONF for APK Distributions server: + Server module + +Copyright © 2020 Adélie Software in the Public Benefit, Inc. + +Released under the terms of the NCSA license. See the LICENSE file included +with this source distribution for more information. + +SPDX-License-Identifier: NCSA +""" + +import logging + +from netconf import util +from netconf.server import NetconfSSHServer, SSHAuthorizedKeysController + +from ncserver.base.modman import ModuleManager +from ncserver.config import ConfigManager + + +class Server: + """The NETCONF server component.""" + + def __init__(self, port=830): + """Create the NETCONF server. + + :param int port: + The port number to listen on. Typically 830. + """ + self.config = ConfigManager() + self.auth = SSHAuthorizedKeysController( + users=self.config.get_list('server', 'users') + ) + + self.debug = self.config.get('server', 'debug') + if self.debug: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.INFO) + + self.server = NetconfSSHServer( + self.auth, self, port, self.config.get('server', 'host_key'), + self.debug + ) + self.modman = ModuleManager() + for module in self.config.get_list('server', 'modules'): + self.modman.load_module(module) + + def close(self): + """Close all connections.""" + self.server.close() + + def nc_append_capabilities(self, capabilities): + """List all capabilities of this NETCONF server.""" + for module in self.modman.modules.values(): + util.subelm(capabilities, 'capability').text = module.M_NS + + def rpc_get(self, session, rpc, filter_or_none): # pylint: disable=W0613 + """Handle the RPC.""" + root = util.elm('nc:data') + self.modman.collect_running(root) + self.modman.collect_operational(root) + + return util.filter_results(rpc, root, filter_or_none, self.debug) + + # pylint: disable=W0613 + def rpc_get_config(self, session, rpc, source, filter_or_none): + """Handle the RPC.""" + root = util.elm('nc:data') + self.modman.collect_running(root) + + return util.filter_results(rpc, root, filter_or_none, self.debug) + +if __name__ == "__main__": + s = Server() + try: + s.server.join() + except KeyboardInterrupt: + s.close() diff --git a/requirements.txt b/requirements.txt index 959bb53..57d28ca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,3 @@ netconf>=2.1.0 +taillight +PyYAML -- cgit v1.2.3-70-g09d2