summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorA. Wilcox <AWilcox@Wilcox-Tech.com>2020-08-28 00:37:24 -0500
committerA. Wilcox <AWilcox@Wilcox-Tech.com>2020-08-28 00:37:24 -0500
commit44685f1b07eabd70ada8504d6c77321c5a4de726 (patch)
treee36f976d032a10ee0f78e03893e5e446d2bc5c21
parentb99bcb0660958c109e1efb601790d4de1c6bf86d (diff)
downloadnetconfapk-44685f1b07eabd70ada8504d6c77321c5a4de726.tar.gz
netconfapk-44685f1b07eabd70ada8504d6c77321c5a4de726.tar.bz2
netconfapk-44685f1b07eabd70ada8504d6c77321c5a4de726.tar.xz
netconfapk-44685f1b07eabd70ada8504d6c77321c5a4de726.zip
Implement rudimentary module manager
This implements requirements 1 and 5 for the module management system.
-rw-r--r--doc/roadmap.rst6
-rw-r--r--ncserver/base/__init__.py11
-rw-r--r--ncserver/base/modman.py143
-rw-r--r--ncserver/base/util.py18
-rw-r--r--print_operational.py7
5 files changed, 182 insertions, 3 deletions
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 <running> datastore."""
+ for mod in self.modules.values():
+ mod.running(node)
+
+ def collect_operational(self, node):
+ """Collect all available information for the <operational> 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())