From 44685f1b07eabd70ada8504d6c77321c5a4de726 Mon Sep 17 00:00:00 2001 From: "A. Wilcox" Date: Fri, 28 Aug 2020 00:37:24 -0500 Subject: Implement rudimentary module manager This implements requirements 1 and 5 for the module management system. --- doc/roadmap.rst | 6 +- ncserver/base/__init__.py | 11 ++++ ncserver/base/modman.py | 143 ++++++++++++++++++++++++++++++++++++++++++++++ ncserver/base/util.py | 18 ++++++ print_operational.py | 7 +++ 5 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 ncserver/base/__init__.py create mode 100644 ncserver/base/modman.py create mode 100644 ncserver/base/util.py create mode 100644 print_operational.py diff --git a/doc/roadmap.rst b/doc/roadmap.rst index 8bed427..d78f36d 100644 --- a/doc/roadmap.rst +++ b/doc/roadmap.rst @@ -6,10 +6,10 @@ 1.0 (2020 Q3) ============= -[ ] Module management system +[/] Module management system ---------------------------- -* [ ] Track name/revision/namespace, and register with NSMAP +* [X] Track name/revision/namespace, and register with NSMAP * Namespace includes prefix, since this is standardised. i.e. ns "urn:ietf:params:xml:ns:yang:ietf-interfaces" -> prefix "if:". @@ -20,7 +20,7 @@ * [ ] Submodule support? (possibly post-1.0) -* [ ] Respond to state requests +* [X] Respond to state requests The module management system will be the heart of ``ncserver``. Similar to diff --git a/ncserver/base/__init__.py b/ncserver/base/__init__.py new file mode 100644 index 0000000..41c96db --- /dev/null +++ b/ncserver/base/__init__.py @@ -0,0 +1,11 @@ +""" +NETCONF for APK Distributions server: + Base functionality. + +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 +""" diff --git a/ncserver/base/modman.py b/ncserver/base/modman.py new file mode 100644 index 0000000..551c1cd --- /dev/null +++ b/ncserver/base/modman.py @@ -0,0 +1,143 @@ +""" +NETCONF for APK Distributions server: + Module Management System + +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 hashlib + +from logging import getLogger +from netconf import nsmap_add, util +from taillight import Signal + +from ncserver.base.util import _ + + +MODULE_SET_CHANGE_SIGNAL = Signal(('base/ModuleManager', 'module_set_changed')) +"""Signal fired when the module set changes. This implies the +yang-library-change notification should be fired, when this is supported.""" + + +class ModuleManager: + """Manages the modules for the NETCONF for APK Distributions server. + + :ivar modules: + Dictionary of loaded modules. + + :ivar library: + Dictionary of loaded YANG models. + """ + + M_ABI_VERSION = 1 + M_PREFIX = "yanglib" + M_NS = "urn:ietf:params:xml:ns:yang:ietf-yang-library" + M_NAME = "ietf-yang-library" + M_REVISION = "2019-01-04" + + def __init__(self): + self.modules = {'ncserver.base.modman': self} + self.library = {'ncserver.base.modman': + '{}@{}'.format(self.M_NAME, self.M_REVISION)} + nsmap_add(self.M_PREFIX, self.M_NS) + self.logger = getLogger('base/ModuleManager') + + def load_module(self, name): + """Load a module. + + :param str name: + The name of the module to load. + + :returns: + Either the name of the module (if loaded), or None (on error). + """ + if name in self.modules: + self.logger.warning(_("Module '%s' is already loaded; skipping"), + name) + return name + + self.logger.debug(_("Discovering module '%s'..."), name) + + mod = None + try: + mod = __import__(name, globals(), locals(), [name], 0) + except ImportError: + self.logger.error(_("Module '%s' was not found."), name) + return None + + if any([getattr(mod, attr, None) is None + for attr in ['M_ABI_VERSION', 'M_NS', 'M_PREFIX', 'M_NAME']]): + self.logger.error(_("'%s' is not a valid module."), name) + return None + + if mod.M_ABI_VERSION != 1: + self.logger.error(_("Module '%s' requires ABI version %d."), name, + mod.M_ABI_VERSION) + return None + + self.logger.info(_("Loading module '%s' with ABI %d for namespace %s"), + name, mod.M_ABI_VERSION, mod.M_NS) + + self.modules[name] = mod + self.library[name] = '{}@{}'.format(mod.M_NAME, mod.M_REVISION) + nsmap_add(mod.M_PREFIX, mod.M_NS) + return name + + def running(self, node): + """Return running configuration information.""" + # ietf-yang-library is state-only. + + def operational(self, node): + """Return configuration and device state information.""" + module_set_id = hashlib.sha256(','.join(self.modules.keys()).encode()) + content_id = module_set_id.hexdigest() + + # NDMA-enabled yang-library node. + lib = util.subelm(node, 'yanglib:yang-library') + modset = util.subelm(lib, 'yanglib:module-set') + modset.append(util.leaf_elm('yanglib:name', 'netconfapk')) + for module in self.modules.values(): + # XXX or import-only-module, if it's import-only + modnode = util.subelm(modset, 'yanglib:module') + modnode.append(util.leaf_elm('yanglib:name', module.M_NAME)) + modnode.append(util.leaf_elm('yanglib:revision', + module.M_REVISION)) + modnode.append(util.leaf_elm('yanglib:namespace', module.M_NS)) + schema = util.subelm(lib, 'yanglib:schema') + schema.append(util.leaf_elm('yanglib:name', 'apkschema')) + schema.append(util.leaf_elm('yanglib:module-set', 'netconfapk')) + + for store in ['running', 'operational']: + dsnode = util.subelm(lib, 'yanglib:datastore') + dsnode.append(util.leaf_elm('name', store)) + dsnode.append(util.leaf_elm('schema', 'apkschema')) + + lib.append(util.leaf_elm('yanglib:content-id', content_id)) + + # Deprecated modules-state node. + modstate = util.subelm(node, 'yanglib:modules-state') + modstate.append(util.leaf_elm('yanglib:module-set-id', content_id)) + for module in self.modules.values(): + modnode = util.subelm(modstate, 'yanglib:module') + modnode.append(util.leaf_elm('yanglib:name', module.M_NAME)) + modnode.append(util.leaf_elm('yanglib:revision', + module.M_REVISION)) + modnode.append(util.leaf_elm('yanglib:namespace', module.M_NS)) + # XXX import-only module support: use 'import' + modnode.append(util.leaf_elm('yanglib:conformance-type', + 'implement')) + + def collect_running(self, node): + """Collect all available information for the datastore.""" + for mod in self.modules.values(): + mod.running(node) + + def collect_operational(self, node): + """Collect all available information for the datastore""" + for mod in self.modules.values(): + mod.operational(node) diff --git a/ncserver/base/util.py b/ncserver/base/util.py new file mode 100644 index 0000000..3df076c --- /dev/null +++ b/ncserver/base/util.py @@ -0,0 +1,18 @@ +""" +NETCONF for APK Distributions server: + Base utility functions. + +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 +""" + + +def _(the_string): + """Translate a user-facing string. + + Currently not implemented, but allows us to implement translation later.""" + return the_string diff --git a/print_operational.py b/print_operational.py new file mode 100644 index 0000000..828075f --- /dev/null +++ b/print_operational.py @@ -0,0 +1,7 @@ +from lxml import etree as et +from ncserver.base.modman import ModuleManager +from netconf.util import elm +mgr = ModuleManager() +node = elm('nc:data') +mgr.collect_operational(node) +print(et.tostring(node, pretty_print=True).decode()) -- cgit v1.2.3-70-g09d2