summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorA. Wilcox <AWilcox@Wilcox-Tech.com>2020-08-31 08:07:12 -0500
committerA. Wilcox <AWilcox@Wilcox-Tech.com>2020-08-31 08:07:12 -0500
commit0c52c51a33a5db9dfa7f2514b7bc72e608b25ef2 (patch)
tree5db0e6448d28acb0f6176c3bdbacc20e1d5fa102
parentbf48d49a976067cc93ecf79ac20514d3d631feb5 (diff)
downloadnetconfapk-0c52c51a33a5db9dfa7f2514b7bc72e608b25ef2.tar.gz
netconfapk-0c52c51a33a5db9dfa7f2514b7bc72e608b25ef2.tar.bz2
netconfapk-0c52c51a33a5db9dfa7f2514b7bc72e608b25ef2.tar.xz
netconfapk-0c52c51a33a5db9dfa7f2514b7bc72e608b25ef2.zip
Module management system: Initial impl finished
-rw-r--r--doc/roadmap.rst6
-rw-r--r--ncserver/base/modman.py131
2 files changed, 117 insertions, 20 deletions
diff --git a/doc/roadmap.rst b/doc/roadmap.rst
index 0a752ed..19a1b2f 100644
--- a/doc/roadmap.rst
+++ b/doc/roadmap.rst
@@ -6,7 +6,7 @@
1.0 (2020 Q3)
=============
-[/] Module management system
+[X] Module management system
----------------------------
* [X] Track name/revision/namespace, and register with NSMAP
@@ -14,9 +14,9 @@
* Namespace includes prefix, since this is standardised. i.e.
ns "urn:ietf:params:xml:ns:yang:ietf-interfaces" -> prefix "if:".
-* [ ] Track features implemented
+* [X] Track features implemented
-* [ ] Handle import-only modules
+* [X] Handle import-only modules
* [ ] Submodule support? (possibly post-1.0)
diff --git a/ncserver/base/modman.py b/ncserver/base/modman.py
index 551c1cd..1423be1 100644
--- a/ncserver/base/modman.py
+++ b/ncserver/base/modman.py
@@ -24,6 +24,75 @@ MODULE_SET_CHANGE_SIGNAL = Signal(('base/ModuleManager', 'module_set_changed'))
yang-library-change notification should be fired, when this is supported."""
+def _import_compatible(existing, improps):
+ """Determine if an imported module is compatible with the existing
+ module that has the same name/revision.
+
+ :param existing:
+ The dictionary of properties for the existing module.
+
+ :param improps:
+ The dictionary of properties for the candidate module.
+
+ :returns bool:
+ True if compatible, False otherwise.
+ """
+ return (improps.keys() == existing.keys() and
+ all([existing[key] == improps[key] for key in existing.keys()]))
+
+
+def _gen_modnode(parent, module):
+ """Generate a yanglib:module node.
+
+ The generated node is in the NMDA format. With some massaging, it
+ could be used for the deprecated non-NMDA format.
+
+ :param parent:
+ The parent XML node.
+
+ :param module:
+ The module.
+
+ :returns:
+ The generated module node.
+ """
+ modnode = util.subelm(parent, '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))
+ if getattr(module, 'M_FEATURES', None) is not None:
+ for feature in module.M_FEATURES:
+ modnode.append(util.leaf_elm('yanglib:feature', feature))
+ return modnode
+
+
+def _gen_modnode_import(parent, nodename, imname, improps):
+ """Generate a node for an import-only module.
+
+ :param parent:
+ The parent XML node.
+
+ :param nodename:
+ The name of the XML node to create. For NMDA library contents, use
+ 'yanglib:import-only-module'; otherwise, use 'yanglib:module'.
+
+ :param imname:
+ The name of the imported module in M_IMPORTS syntax.
+
+ :param improps:
+ The M_IMPORTS property dictionary for this imported module.
+
+ :returns:
+ The generated module node.
+ """
+ imn_name, imn_rev = imname.split('@')
+ modnode = util.subelm(parent, nodename)
+ modnode.append(util.leaf_elm('yanglib:name', imn_name))
+ modnode.append(util.leaf_elm('yanglib:revision', imn_rev))
+ modnode.append(util.leaf_elm('yanglib:namespace', improps['ns']))
+ return modnode
+
+
class ModuleManager:
"""Manages the modules for the NETCONF for APK Distributions server.
@@ -39,9 +108,16 @@ class ModuleManager:
M_NS = "urn:ietf:params:xml:ns:yang:ietf-yang-library"
M_NAME = "ietf-yang-library"
M_REVISION = "2019-01-04"
+ M_IMPORTS = {'ietf-yang-types@2013-07-15':
+ {'ns': "urn:ietf:params:xml:ns:yang:ietf-yang-types",
+ 'prefix': "yang"},
+ 'ietf-inet-types@2013-07-16':
+ {'ns': "urn:ietf:params:xml:ns:yang:ietf-inet-types",
+ 'prefix': "inet"}}
def __init__(self):
self.modules = {'ncserver.base.modman': self}
+ self.imports = dict(self.M_IMPORTS)
self.library = {'ncserver.base.modman':
'{}@{}'.format(self.M_NAME, self.M_REVISION)}
nsmap_add(self.M_PREFIX, self.M_NS)
@@ -80,34 +156,47 @@ class ModuleManager:
mod.M_ABI_VERSION)
return None
+ if getattr(mod, 'M_IMPORTS', None) is not None:
+ for imname, improps in mod.M_IMPORTS:
+ if imname in self.imports:
+ if not _import_compatible(self.imports[imname], improps):
+ self.logger.error(_("Module '%s' has incompatible "
+ "imported module '%s'"), name,
+ imname)
+ return None
+ else:
+ self.imports[imname] = improps
+
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)
+ MODULE_SET_CHANGE_SIGNAL.call(name, mod)
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()
+ def _gen_yang_library(self, node, content_id):
+ """Generate yanglib:yang-library node.
- # NDMA-enabled yang-library node.
+ :param node:
+ The XML node to append to.
+
+ :param content_id:
+ The unique Content ID for this library.
+ """
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))
+ _gen_modnode(modset, module)
+ for imname, improps in self.imports.items():
+ _gen_modnode_import(modset, 'yanglib:import-only-module', imname,
+ improps)
schema = util.subelm(lib, 'yanglib:schema')
schema.append(util.leaf_elm('yanglib:name', 'apkschema'))
schema.append(util.leaf_elm('yanglib:module-set', 'netconfapk'))
@@ -118,19 +207,27 @@ class ModuleManager:
dsnode.append(util.leaf_elm('schema', 'apkschema'))
lib.append(util.leaf_elm('yanglib:content-id', content_id))
+ return lib
+
+ 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.
+ self._gen_yang_library(node, 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 = _gen_modnode(modstate, module)
modnode.append(util.leaf_elm('yanglib:conformance-type',
'implement'))
+ for imname, improps in self.imports.items():
+ modnode = _gen_modnode_import(modstate, 'yanglib:module', imname,
+ improps)
+ modnode.append(util.leaf_elm('yanglib:conformance-type', 'import'))
def collect_running(self, node):
"""Collect all available information for the <running> datastore."""