From b6098ddf2e634b95f0255fbba78ae047199da4d4 Mon Sep 17 00:00:00 2001 From: "A. Wilcox" Date: Wed, 18 Nov 2020 12:50:53 -0600 Subject: ncserver: Add draft NMSA mod for ifupdown-ng --- ncserver/module/interfaces.py | 60 ++++++------ ncserver/module/nms_ifupdownng.py | 201 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 233 insertions(+), 28 deletions(-) create mode 100644 ncserver/module/nms_ifupdownng.py diff --git a/ncserver/module/interfaces.py b/ncserver/module/interfaces.py index b244bfc..959436c 100644 --- a/ncserver/module/interfaces.py +++ b/ncserver/module/interfaces.py @@ -11,12 +11,11 @@ SPDX-License-Identifier: NCSA """ import logging -import os import pathlib import subprocess from lxml import etree -from netconf import error, util +from netconf import util from ncserver.base.util import _, yang_dt_for_timestamp @@ -107,11 +106,8 @@ def running(node): _add_running_contents(ifaces) -def operational(node): - """Retrieve the service state for this device.""" - ifaces = util.subelm(node, 'if:interfaces') - _add_running_contents(ifaces) - +def _add_stats_to(iface, name: str): + """Add the statistics node to +iface+.""" counter_tags = { 'rx.octets': 'if:in-octets', 'rx.discard': 'if:in-discards', @@ -121,6 +117,33 @@ def operational(node): 'tx.errors': 'if:out-errors' } + stats = util.subelm(iface, 'if:statistics') + # XXX BAD vvv + stats.append(util.leaf_elm('if:discontinuity-time', '2020-01-01T01:01:01.011Z')) + # XXX BAD ^^^ + + result = subprocess.run(['/sbin/ifupdown', 'ifctrstat', name], + stdout=subprocess.PIPE, check=False) + if result.returncode != 0: + LOGGER.error(_('ifctrstat failed for %s: %s'), + name, result.returncode) + return + + counters = result.stdout.decode('utf-8').split('\n') + counters.pop() + for counter, value in [data.split(': ') for data in counters]: + if counter not in counter_tags.keys(): + LOGGER.warning(_('unhandled ifctrstat counter for %s: %s'), + name, counter) + continue + stats.append(util.leaf_elm(counter_tags[counter], value)) + + +def operational(node): + """Retrieve the service state for this device.""" + ifaces = util.subelm(node, 'if:interfaces') + _add_running_contents(ifaces) + for iface in ifaces.iterchildren(): name = iface.find('{'+M_NS+'}name').text ifpath = pathlib.Path("/sys/class/net/" + name) @@ -151,7 +174,7 @@ def operational(node): pass # if the interface does not have measurable speed, omit except ValueError: LOGGER.warning("%s has non-integral speed; kernel bug?", name) - + # phys-address if (ifpath / "address").exists(): addr_file = open(ifpath / "address", 'r') @@ -160,26 +183,7 @@ def operational(node): addr_file.close() # statistics - stats = util.subelm(iface, 'if:statistics') - # XXX BAD vvv - stats.append(util.leaf_elm('if:discontinuity-time', '2020-01-01T01:01:01.011Z')) - # XXX BAD ^^^ - - result = subprocess.run(['/sbin/ifupdown', 'ifctrstat', name], - stdout=subprocess.PIPE) - if result.returncode != 0: - LOGGER.error(_('ifctrstat failed for %s: %s'), - name, result.returncode) - continue - - counters = result.stdout.decode('utf-8').split('\n') - counters.pop() - for counter, value in [data.split(': ') for data in counters]: - if counter not in counter_tags.keys(): - LOGGER.warning(_('unhandled ifctrstat counter for %s: %s'), - name, counter) - continue - stats.append(util.leaf_elm(counter_tags[counter], value)) + _add_stats_to(iface, name) def edit(session, rpc, node, def_op): diff --git a/ncserver/module/nms_ifupdownng.py b/ncserver/module/nms_ifupdownng.py new file mode 100644 index 0000000..9066a18 --- /dev/null +++ b/ncserver/module/nms_ifupdownng.py @@ -0,0 +1,201 @@ +""" +NETCONF for APK Distributions server: + Network Management System abstraction module for ifupdown-ng + +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 subprocess +import yaml + +from ncserver.base.util import _ + + +LOGGER = logging.getLogger(__name__) +"""The object used for logging informational messages.""" + + +M_ABI_VERSION = 1 +"""The ABI version of this NETCONF module.""" + + +M_PREFIX = "nmsa" +"""The XML tag prefix for this module's tags.""" + + +M_NS = "http://netconf.adelielinux.org/ns/netmgmt" +"""The XML namespace for this module.""" + + +M_NAME = "adelie-nms-abstract" +"""The YANG model name for this module.""" + + +M_REVISION = "2020-11-18" +"""The YANG revision date for this module.""" + + +_CONFIG = dict() +"""The internal configuration handle.""" + + +_TRANSACTION = False +"""Determines if a transaction is in progress.""" + + +def _load_config(): + """Load the current active configuration from /e/n/i.""" + global _CONFIG # pylint: disable=W0603 + + # Won't load during a transaction. + if _TRANSACTION: + LOGGER.warning(_("attempted to load eni config during transaction")) + return + + result = None + try: + result = subprocess.run(['/sbin/ifupdown', 'ifparse', '-AF', + 'yaml-raw'], + stdout=subprocess.PIPE, check=False) + except OSError: + LOGGER.error(_("ifupdown-ng may not be installed properly")) + return + + if result.returncode != 0: + LOGGER.error(_("ifparse returned error %d"), result.returncode) + return + + rawyaml = result.stdout.decode('utf-8') + _CONFIG = yaml.safe_load(rawyaml) + + +def _save(): + """Save changes to the configuration.""" + eni = "" + for iface in _CONFIG.keys(): + buf = "iface " + iface + "\n" + for item in _CONFIG[iface]: + for key, val in item.items(): + if key == 'auto' and \ + val == True or val.lower()[0] == 't': + buf = "auto " + iface + "\n" + buf + continue + if isinstance(val, bool): + val = str(val).lower() + buf += " " + buf += str(key) + " " + str(val) + buf += "\n" + eni += buf + "\n" + + with open('/etc/network/interfaces', 'w') as conf_file: + # snip last double-\n off + conf_file.write(eni[:-1]) + + +def interface_list(): + """Return a list of configured interfaces.""" + _load_config() + return tuple(_CONFIG.keys()) + + +def begin_transaction(): + """Begin a transaction.""" + global _TRANSACTION # pylint: disable=W0603 + + if _TRANSACTION: + LOGGER.error(_("attempt to nest transactions")) + return + + _TRANSACTION = True + + +def commit(): + """Commit any outstanding operations.""" + global _TRANSACTION # pylint: disable=W0603 + + if not _TRANSACTION: + LOGGER.warning(_("commit when no transaction is in progress")) + + _save() + _TRANSACTION = False + + +def get_param(iface: str, parameter: str): + """Retrieve the parameter for the specified interface.""" + if iface not in _CONFIG.keys(): + LOGGER.warning( + _("requested parameter %s for non-existant interface %s"), + parameter, iface + ) + return None + + # implement this. + raise NotImplementedError + + +def set_param(iface: str, parameter: str, value): + """Set the parameter for the specified interface.""" + if iface not in _CONFIG.keys(): + LOGGER.warning( + _("attempted to set parameter %s for non-existant interface %s"), + parameter, iface + ) + return + + # implement this. + raise NotImplementedError + + +def unset_param(iface: str, parameter: str): + """Unset the parameter for the specified interface.""" + if iface not in _CONFIG.keys(): + LOGGER.warning( + _("attempted to unset parameter %s for non-existant interface %s"), + parameter, iface + ) + return + + # implement this. + raise NotImplementedError + + +def list_addresses(iface: str) -> list: + """Retrieve all configured addresses for the specified interface.""" + if iface not in _CONFIG.keys(): + LOGGER.warning(_("requested addresses for non-existant interface %s"), + iface) + return list() + + # implement this. + raise NotImplementedError + + +def add_address(iface: str, _type, addr: str, prefix): + """Add an address of the specified ``type`` to the specified interface.""" + if iface not in _CONFIG.keys(): + LOGGER.warning( + _("attempted to add address to non-existant interface %s"), iface + ) + return + + # implement this. + raise NotImplementedError + + +def remove_address(iface: str, addr: str): + """Remove an address from the specified interface.""" + if iface not in _CONFIG.keys(): + LOGGER.warning( + _("attempted to remove address from non-existant interface %s"), + iface + ) + return + + # implement this. + raise NotImplementedError -- cgit v1.2.3-70-g09d2