summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorA. Wilcox <AWilcox@Wilcox-Tech.com>2020-11-18 12:50:53 -0600
committerA. Wilcox <AWilcox@Wilcox-Tech.com>2020-11-18 12:50:53 -0600
commitb6098ddf2e634b95f0255fbba78ae047199da4d4 (patch)
tree8c639535d9a9aa221a3fd7888dce9da47c3ec0f0
parent340a6a9c4198ff0d2907768580088fa6486c1a08 (diff)
downloadnetconfapk-b6098ddf2e634b95f0255fbba78ae047199da4d4.tar.gz
netconfapk-b6098ddf2e634b95f0255fbba78ae047199da4d4.tar.bz2
netconfapk-b6098ddf2e634b95f0255fbba78ae047199da4d4.tar.xz
netconfapk-b6098ddf2e634b95f0255fbba78ae047199da4d4.zip
ncserver: Add draft NMSA mod for ifupdown-ng
-rw-r--r--ncserver/module/interfaces.py60
-rw-r--r--ncserver/module/nms_ifupdownng.py201
2 files changed, 233 insertions, 28 deletions
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