From 091f68baec1b732bc28a203419be04b8e9b985e4 Mon Sep 17 00:00:00 2001 From: kroy Date: Tue, 22 Oct 2019 14:22:09 -0500 Subject: T1759: Migrating interfaces --- python/vyos/interface.py | 131 ++++++++++++++++++++++++++++++++++++++++++++++ python/vyos/interfaces.py | 45 +++++++++++++++- 2 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 python/vyos/interface.py (limited to 'python') diff --git a/python/vyos/interface.py b/python/vyos/interface.py new file mode 100644 index 000000000..6d57d972b --- /dev/null +++ b/python/vyos/interface.py @@ -0,0 +1,131 @@ +# Copyright 2019 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see . + +import vyos +from vyos.config import Config +import vyos.interfaces + +import re +import json + +import subprocess +import time +from datetime import timedelta +import glob +from os.path import isfile +from tabulate import tabulate +from hurry.filesize import size,alternative + +class Interface(): + + intf = None + intf_type = None + + def __init__(self,intf): + self.intf = intf + self.intf_type = vyos.interfaces.get_type_of_interface(self.intf) + + def print_interface(self): + if (self.intf_type == 'wireguard'): + self.print_wireguard_interface() + + self.print_interface_stats() + + def print_interface_stats(self): + stats = self.get_interface_stats() + rx = [['bytes','packets','errors','dropped','overrun','mcast'],[stats['rx_bytes'],stats['rx_packets'],stats['rx_errors'],stats['rx_dropped'],stats['rx_over_errors'],stats['multicast']]] + tx = [['bytes','packets','errors','dropped','carrier','collisions'],[stats['tx_bytes'],stats['tx_packets'],stats['tx_errors'],stats['tx_dropped'],stats['tx_carrier_errors'],stats['collisions']]] + output = "RX: \n" + output += tabulate(rx,headers="firstrow",numalign="right",tablefmt="plain") + output += "\n\nTX: \n" + output += tabulate(tx,headers="firstrow",numalign="right",tablefmt="plain") + print(' '.join(('\n'+output.lstrip()).splitlines(True))) + + def get_interface_stats(self): + interface_stats = dict() + devices = [f for f in glob.glob("/sys/class/net/**/statistics")] + for dev_path in devices: + metrics = [f for f in glob.glob(dev_path +"/**")] + dev = re.findall(r"/sys/class/net/(.*)/statistics",dev_path)[0] + dev_dict = dict() + for metric_path in metrics: + metric = metric_path.replace(dev_path+"/","") + if isfile(metric_path): + data = open(metric_path, 'r').read()[:-1] + dev_dict[metric] = int(data) + interface_stats[dev] = dev_dict + return interface_stats[self.intf] + + def print_wireguard_interface(self): + output = subprocess.check_output(["wg", "show", self.intf], universal_newlines=True) + wgdump = vyos.interfaces.wireguard_dump()[self.intf] + c = Config() + c.set_level("interfaces wireguard {}".format(self.intf)) + description = c.return_effective_value("description") + + print ("interface: {}".format(self.intf)) + """ if the interface has a description, modify the output to include it """ + if (description): + print (" description: {}".format(description)) + output = re.sub(r"interface: {}".format(re.escape(self.intf)),"interface: {}\n Description: {}".format(self.intf,description),output) + + print (" public key: {}".format(wgdump['public_key'])) + print (" private key: (hidden)") + print (" listening port: {}".format(wgdump['listen_port'])) + print () + + for peer in c.list_effective_nodes(' peer'): + if wgdump['peers']: + pubkey = c.return_effective_value("peer {} pubkey".format(peer)) + if pubkey in wgdump['peers']: + wgpeer = wgdump['peers'][pubkey] + + print (" peer: {}".format(peer)) + print (" public key: {}".format(pubkey)) + + """ figure out if the tunnel is recently active or not """ + status = "inactive" + if (wgpeer['latest_handshake'] is None): + """ no handshake ever """ + status = "inactive" + else: + if int(wgpeer['latest_handshake']) > 0: + delta = timedelta(seconds=int(time.time() - wgpeer['latest_handshake'])) + print (" latest handshake: {}".format(delta)) + if (time.time() - int(wgpeer['latest_handshake']) < (60*5)): + """ Five minutes and the tunnel is still active """ + status = "active" + else: + """ it's been longer than 5 minutes """ + status = "inactive" + elif int(wgpeer['latest_handshake']) == 0: + """ no handshake ever """ + status = "inactive" + print (" status: {}".format(status)) + + if wgpeer['endpoint'] is not None: + print (" endpoint: {}".format(wgpeer['endpoint'])) + + if wgpeer['allowed_ips'] is not None: + print (" allowed ips: {}".format(",".join(wgpeer['allowed_ips']).replace(",",", "))) + + if wgpeer['transfer_rx'] > 0 or wgpeer['transfer_tx'] > 0: + rx_size =size(wgpeer['transfer_rx'],system=alternative) + tx_size =size(wgpeer['transfer_tx'],system=alternative) + print (" transfer: {} received, {} sent".format(rx_size,tx_size)) + + if wgpeer['persistent_keepalive'] is not None: + print (" persistent keepalive: every {} seconds".format(wgpeer['persistent_keepalive'])) + print() diff --git a/python/vyos/interfaces.py b/python/vyos/interfaces.py index d69ce9d04..ecf061d17 100644 --- a/python/vyos/interfaces.py +++ b/python/vyos/interfaces.py @@ -16,9 +16,9 @@ import re import json +import subprocess import netifaces - intf_type_data_file = '/usr/share/vyos/interface-types.json' def list_interfaces(): @@ -54,3 +54,46 @@ def get_type_of_interface(intf): return key raise ValueError("No type found for interface name: {0}".format(intf)) + +def wireguard_dump(): + """Dump wireguard data in a python friendly way.""" + last_device=None + output = {} + + # Dump wireguard connection data + _f = subprocess.check_output(["wg", "show", "all", "dump"]).decode() + for line in _f.split('\n'): + if not line: + # Skip empty lines and last line + continue + items = line.split('\t') + + if last_device != items[0]: + # We are currently entering a new node + device, private_key, public_key, listen_port, fw_mark = items + last_device = device + + output[device] = { + 'private_key': None if private_key == '(none)' else private_key, + 'public_key': None if public_key == '(none)' else public_key, + 'listen_port': int(listen_port), + 'fw_mark': None if fw_mark == 'off' else int(fw_mark), + 'peers': {}, + } + else: + # We are entering a peer + device, public_key, preshared_key, endpoint, allowed_ips, latest_handshake, transfer_rx, transfer_tx, persistent_keepalive = items + if allowed_ips == '(none)': + allowed_ips = [] + else: + allowed_ips = allowed_ips.split('\t') + output[device]['peers'][public_key] = { + 'preshared_key': None if preshared_key == '(none)' else preshared_key, + 'endpoint': None if endpoint == '(none)' else endpoint, + 'allowed_ips': allowed_ips, + 'latest_handshake': None if latest_handshake == '0' else int(latest_handshake), + 'transfer_rx': int(transfer_rx), + 'transfer_tx': int(transfer_tx), + 'persistent_keepalive': None if persistent_keepalive == 'off' else int(persistent_keepalive), + } + return output -- cgit v1.2.3 From 4b3c01ceac65082a5e202d88c995c04d8c80e3ce Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 23 Oct 2019 10:31:16 -0500 Subject: [vyos.config] T1758: check that config setup has completed before calling showConfig, else, default to config.boot --- python/vyos/config.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'python') diff --git a/python/vyos/config.py b/python/vyos/config.py index d70093531..2232d900c 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -63,6 +63,7 @@ In operational mode, all functions return values from the running config. """ +import os import re import subprocess @@ -96,7 +97,11 @@ class Config(object): # Running config can be obtained either from op or conf mode, it always succeeds # (if config system is initialized at all). - running_config_text = self._run([self._cli_shell_api, '--show-active-only', '--show-show-defaults', 'showConfig']) + if os.path.isfile('/tmp/vyos-config-status'): + running_config_text = self._run([self._cli_shell_api, '--show-active-only', '--show-show-defaults', 'showConfig']) + else: + with open('/opt/vyatta/etc/config/config.boot') as f: + running_config_text = f.read() # Session config ("active") only exists in conf mode. # Trying to obtain it from op mode will cause a fatal cli-shell-api error. -- cgit v1.2.3 From 3f8884587ab1291c13f633b079058879241ac3c1 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Wed, 23 Oct 2019 15:41:58 +0200 Subject: [HTTP API] Add endpoints for config file and image management. --- python/vyos/configsession.py | 18 +++++++++ src/conf_mode/https.py | 2 +- src/services/vyos-http-api-server | 77 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 95 insertions(+), 2 deletions(-) (limited to 'python') diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index 09fae78a1..ed6288939 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -25,6 +25,9 @@ COMMIT = '/opt/vyatta/sbin/my_commit' DISCARD = '/opt/vyatta/sbin/my_discard' SHOW_CONFIG = ['/bin/cli-shell-api', 'showConfig'] LOAD_CONFIG = ['/bin/cli-shell-api', 'loadFile'] +SAVE_CONFIG = ['/opt/vyatta/sbin/vyatta-save-config.pl'] +INSTALL_IMAGE = ['/opt/vyatta/sbin/install-image'] +REMOVE_IMAGE = ['/opt/vyatta/bin/vyatta-boot-image.pl', '--del'] # Default "commit via" string APP = "vyos-http-api" @@ -36,6 +39,8 @@ APP = "vyos-http-api" def inject_vyos_env(env): env['VYATTA_CFG_GROUP_NAME'] = 'vyattacfg' env['VYATTA_USER_LEVEL_DIR'] = '/opt/vyatta/etc/shell/level/admin' + env['VYATTA_PROCESS_CLIENT'] = 'gui2_rest' + env['VYOS_HEADLESS_CLIENT'] = 'vyos_http_api' env['vyatta_bindir']= '/opt/vyatta/bin' env['vyatta_cfg_templates'] = '/opt/vyatta/share/vyatta-cfg/templates' env['vyatta_configdir'] = '/opt/vyatta/config' @@ -160,3 +165,16 @@ class ConfigSession(object): def load_config(self, file_path): out = self.__run_command(LOAD_CONFIG + [file_path]) return out + + def save_config(self, file_path): + out = self.__run_command(SAVE_CONFIG + [file_path]) + return out + + def install_image(self, url): + out = self.__run_command(INSTALL_IMAGE + [url]) + return out + + def remove_image(self, name): + out = self.__run_command(REMOVE_IMAGE + [name]) + return out + diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py index f948063e9..233c815bc 100755 --- a/src/conf_mode/https.py +++ b/src/conf_mode/https.py @@ -67,7 +67,7 @@ server { {% endif %} # proxy settings for HTTP API, if enabled; 503, if not - location ~ /(retrieve|configure) { + location ~ /(retrieve|configure|config-file|image) { {% if api %} proxy_pass http://localhost:{{ api.port }}; proxy_buffering off; diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index 63e67e855..04c44c2be 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -34,7 +34,6 @@ from vyos.config import VyOSError DEFAULT_CONFIG_FILE = '/etc/vyos/http-api.conf' - CFG_GROUP = 'vyattacfg' app = bottle.default_app() @@ -223,6 +222,82 @@ def get_value(): return success(res) +@app.route('/config-file', method='POST') +@auth_required +def config_file_op(): + config = app.config['vyos_config'] + session = app.config['vyos_session'] + + command = bottle.request.forms.get("data") + command = json.loads(command) + + try: + op = command['op'] + except KeyError: + return error(400, "Missing required field \"op\"") + + try: + if op == 'save': + try: + path = command['file'] + except KeyError: + path = '/config/config.boot' + res = session.save_config(path) + elif op == 'load': + try: + path = command['file'] + except KeyError: + return error(400, "Missing required field \"file\"") + res = session.load_config(path) + res = session.commit() + else: + return error(400, "\"{0}\" is not a valid operation".format(op)) + except VyOSError as e: + return error(400, str(e)) + except Exception as e: + print(traceback.format_exc(), file=sys.stderr) + return error(500, "An internal error occured. Check the logs for details.") + + return success(res) + +@app.route('/image', method='POST') +@auth_required +def config_file_op(): + config = app.config['vyos_config'] + session = app.config['vyos_session'] + + command = bottle.request.forms.get("data") + command = json.loads(command) + + try: + op = command['op'] + except KeyError: + return error(400, "Missing required field \"op\"") + + try: + if op == 'add': + try: + url = command['url'] + except KeyError: + return error(400, "Missing required field \"url\"") + res = session.install_image(url) + elif op == 'delete': + try: + name = command['name'] + except KeyError: + return error(400, "Missing required field \"name\"") + res = session.remove_image(name) + else: + return error(400, "\"{0}\" is not a valid operation".format(op)) + except VyOSError as e: + return error(400, str(e)) + except Exception as e: + print(traceback.format_exc(), file=sys.stderr) + return error(500, "An internal error occured. Check the logs for details.") + + return success(res) + + if __name__ == '__main__': # systemd's user and group options don't work, do it by hand here, # else no one else will be able to commit -- cgit v1.2.3 From d9ee0b95d1020b6d5412dd011ebb1ef7f6ef3fc7 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Tue, 22 Oct 2019 15:53:04 +0200 Subject: [vyos.config] T1758: use vyos.configtree for reading values, instead of calling cli-shell-api. --- python/vyos/config.py | 224 +++++++++++++++++++++++++------------------------- 1 file changed, 110 insertions(+), 114 deletions(-) (limited to 'python') diff --git a/python/vyos/config.py b/python/vyos/config.py index c9c73b971..d70093531 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -1,4 +1,4 @@ -# Copyright 2017 VyOS maintainers and contributors +# Copyright 2017, 2019 VyOS maintainers and contributors # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -17,7 +17,7 @@ A library for reading VyOS running config data. This library is used internally by all config scripts of VyOS, -but its API should be considered stable and it is safe to use +but its API should be considered stable and safe to use in user scripts. Note that this module will not work outside VyOS. @@ -43,8 +43,8 @@ For example, under "system", the names of all valid child nodes are predefined To the contrary, children of the "system task-scheduler task" node can have arbitrary names. Such nodes are called *tag nodes*. This terminology is confusing but we keep using it for lack -of a better word. The knowledge of whether in "task Foo" the "tag" is "task" or "Foo" is lost -in time, luckily, the distinction is irrelevant in practice. +of a better word. No one remembers if the "tag" in "task Foo" is "task" or "Foo", +but the distinction is irrelevant in practice. Configuration modes ################### @@ -53,21 +53,20 @@ VyOS has two distinct modes: operational mode and configuration mode. When a use the CLI is in the operational mode. In this mode, only the running (effective) config is accessible for reading. When a user enters the "configure" command, a configuration session is setup. Every config session -has its *proposed* config built on top of the current running config. When changes are commited, if commit succeeds, +has its *proposed* (or *session*) config built on top of the current running config. When changes are commited, if commit succeeds, the proposed config is merged into the running config. -For this reason, this library has two sets of functions. The base versions, such as ``exists`` or ``return_value`` -are only usable in configuration mode. They take all nodes into account, in both proposed and running configs. -Configuration scripts require access to uncommited changes for obvious reasons. Configuration mode completion helpers -should also use these functions because not having nodes you've just created in completion is annoying. +In configuration mode, "base" functions like `exists`, `return_value` return values from the session config, +while functions prefixed "effective" return values from the running config. + +In operational mode, all functions return values from the running config. -However, in operational mode, only the running config is available. Currently, you need to use special functions -for reading it from operational mode scripts, they can be distinguished by the word "effective" in their names. -In the future base versions may be made to detect if they are called from a config session or not. """ -import subprocess import re +import subprocess + +import vyos.configtree class VyOSError(Exception): @@ -89,17 +88,42 @@ class Config(object): """ def __init__(self, session_env=None): self._cli_shell_api = "/bin/cli-shell-api" - self._level = "" + self._level = [] if session_env: self.__session_env = session_env else: self.__session_env = None + # Running config can be obtained either from op or conf mode, it always succeeds + # (if config system is initialized at all). + running_config_text = self._run([self._cli_shell_api, '--show-active-only', '--show-show-defaults', 'showConfig']) + + # Session config ("active") only exists in conf mode. + # Trying to obtain it from op mode will cause a fatal cli-shell-api error. + # If that happens, we assume that a script is running from op mode and use the running config + # for the "session config" variable as well. + try: + session_config_text = self._run([self._cli_shell_api, '--show-working-only', '--show-show-defaults', 'showConfig']) + except VyOSError: + session_config_text = running_config_text + + self._session_config = vyos.configtree.ConfigTree(session_config_text) + self._running_config = vyos.configtree.ConfigTree(running_config_text) + def _make_command(self, op, path): args = path.split() cmd = [self._cli_shell_api, op] + args return cmd + def _make_path(self, path): + # Backwards-compatibility stuff: original implementation used string paths + # libvyosconfig paths are lists, but since node names cannot contain whitespace, + # splitting at whitespace is reasonably safe. + # It may cause problems with exists() when it's used for checking values, + # since values may contain whitespace. + path = re.split(r'\s*', path) + return (self._level + path) + def _run(self, cmd): if self.__session_env: p = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=self.__session_env) @@ -126,7 +150,7 @@ class Config(object): # Make sure there's always a space between default path (level) # and path supplied as method argument # XXX: for small strings in-place concatenation is not a problem - self._level = path + " " + self._level = re.split(r'\s*', path) def get_level(self): """ @@ -135,7 +159,7 @@ class Config(object): Returns: str: current edit level """ - return(self._level.strip()) + return(self._level) def exists(self, path): """ @@ -148,11 +172,20 @@ class Config(object): This function cannot be used outside a configuration sessions. In operational mode scripts, use ``exists_effective``. """ - try: - self._run(self._make_command('exists', self._level + path)) + if self._session_config.exists(self._make_path(path)): return True - except VyOSError: - return False + else: + # libvyosconfig exists() works only for _nodes_, not _values_ + # libvyattacfg one also worked for values, so we emulate that case here + path = re.split(r'\s*', path) + path_without_value = path[:-1] + path_str = " ".join(path_without_value) + try: + value = self._session_config.return_value(self._make_path(path_str)) + return (value == path[-1]) + except vyos.configtree.ConfigTreeError: + # node doesn't exist at all + return False def session_changed(self): """ @@ -203,7 +236,8 @@ class Config(object): It also returns False if node doesn't exist. """ try: - self._run(self._make_command('isMulti', self._level + path)) + path = " ".join(self._level) + " " + path + self._run(self._make_command('isMulti', path)) return True except VyOSError: return False @@ -220,7 +254,8 @@ class Config(object): It also returns False if node doesn't exist. """ try: - self._run(self._make_command('isTag', self._level + path)) + path = " ".join(self._level) + " " + path + self._run(self._make_command('isTag', path)) return True except VyOSError: return False @@ -237,7 +272,8 @@ class Config(object): It also returns False if node doesn't exist. """ try: - self._run(self._make_command('isLeaf', self._level + path)) + path = " ".join(self._level) + " " + path + self._run(self._make_command('isLeaf', path)) return True except VyOSError: return False @@ -254,9 +290,6 @@ class Config(object): str: Node value, if it has any None: if node is valueless *or* if it doesn't exist - Raises: - VyOSError: if node is not a single-value leaf node - Note: Due to the issue with treatment of valueless nodes by this function, valueless nodes should be checked with ``exists`` instead. @@ -264,17 +297,15 @@ class Config(object): This function cannot be used outside a configuration session. In operational mode scripts, use ``return_effective_value``. """ - full_path = self._level + path - if self.is_multi(path): - raise VyOSError("Cannot use return_value on multi node: {0}".format(full_path)) - elif not self.is_leaf(path): - raise VyOSError("Cannot use return_value on non-leaf node: {0}".format(full_path)) + try: + value = self._session_config.return_value(self._make_path(path)) + except vyos.configtree.ConfigTreeError: + value = None + + if not value: + return(default) else: - try: - out = self._run(self._make_command('returnValue', full_path)) - return out - except VyOSError: - return(default) + return(value) def return_values(self, path, default=[]): """ @@ -285,27 +316,21 @@ class Config(object): Returns: str list: Node values, if it has any - None: if node does not exist - - Raises: - VyOSError: if node is not a multi-value leaf node + []: if node does not exist Note: This function cannot be used outside a configuration session. In operational mode scripts, use ``return_effective_values``. """ - full_path = self._level + path - if not self.is_multi(path): - raise VyOSError("Cannot use return_values on non-multi node: {0}".format(full_path)) - elif not self.is_leaf(path): - raise VyOSError("Cannot use return_values on non-leaf node: {0}".format(full_path)) + try: + values = self._session_config.return_values(self._make_path(path)) + except vyos.configtree.ConfigTreeError: + values = [] + + if not values: + return(default) else: - try: - out = self._run(self._make_command('returnValues', full_path)) - values = re.findall(r"\'(.*?)\'", out) - return values - except VyOSError: - return(default) + return(values) def list_nodes(self, path, default=[]): """ @@ -317,26 +342,16 @@ class Config(object): Returns: string list: child node names - Raises: - VyOSError: if the node is not a tag node - - Note: - There is no way to list all children of a non-tag node in - the current config backend. - - This function cannot be used outside a configuration session. - In operational mode scripts, use ``list_effective_nodes``. """ - full_path = self._level + path - if self.is_tag(path): - try: - out = self._run(self._make_command('listNodes', full_path)) - values = re.findall(r"\'(.*?)\'", out) - return values - except VyOSError: - return(default) + try: + nodes = self._session_config.list_nodes(self._make_path(path)) + except vyos.configtree.ConfigTreeError: + nodes = [] + + if not nodes: + return(default) else: - raise VyOSError("Cannot use list_nodes on a non-tag node: {0}".format(full_path)) + return(nodes) def exists_effective(self, path): """ @@ -352,11 +367,7 @@ class Config(object): This function is safe to use in operational mode. In configuration mode, it ignores uncommited changes. """ - try: - self._run(self._make_command('existsEffective', self._level + path)) - return True - except VyOSError: - return False + return(self._running_config.exists(self._make_path(path))) def return_effective_value(self, path, default=None): """ @@ -368,21 +379,17 @@ class Config(object): Returns: str: Node value - - Raises: - VyOSError: if node is not a multi-value leaf node """ - full_path = self._level + path - if self.is_multi(path): - raise VyOSError("Cannot use return_effective_value on multi node: {0}".format(full_path)) - elif not self.is_leaf(path): - raise VyOSError("Cannot use return_effective_value on non-leaf node: {0}".format(full_path)) + try: + value = self._running_config.return_value(self._make_path(path)) + except vyos.configtree.ConfigTreeError: + value = None + + if not value: + return(default) else: - try: - out = self._run(self._make_command('returnEffectiveValue', full_path)) - return out - except VyOSError: - return(default) + return(value) + def return_effective_values(self, path, default=[]): """ @@ -393,22 +400,16 @@ class Config(object): Returns: str list: A list of values - - Raises: - VyOSError: if node is not a multi-value leaf node """ - full_path = self._level + path - if not self.is_multi(path): - raise VyOSError("Cannot use return_effective_values on non-multi node: {0}".format(full_path)) - elif not self.is_leaf(path): - raise VyOSError("Cannot use return_effective_values on non-leaf node: {0}".format(full_path)) + try: + values = self._running_config.return_values(self._make_path(path)) + except vyos.configtree.ConfigTreeError: + values = [] + + if not values: + return(default) else: - try: - out = self._run(self._make_command('returnEffectiveValues', full_path)) - values = re.findall(r"\'(.*?)\'", out) - return values - except VyOSError: - return(default) + return(values) def list_effective_nodes(self, path, default=[]): """ @@ -422,18 +423,13 @@ class Config(object): Raises: VyOSError: if the node is not a tag node - - Note: - There is no way to list all children of a non-tag node in - the current config backend. """ - full_path = self._level + path - if self.is_tag(path): - try: - out = self._run(self._make_command('listEffectiveNodes', full_path)) - values = out.split() - return list(map(lambda x: re.sub(r'^\'(.*)\'$', r'\1',x), values)) - except VyOSError: - return(default) + try: + nodes = self._running_config.list_nodes(self._make_path(path)) + except vyos.configtree.ConfigTreeError: + nodes = [] + + if not nodes: + return(default) else: - raise VyOSError("Cannot use list_effective_nodes on a non-tag node: {0}".format(full_path)) + return(nodes) -- cgit v1.2.3 From 517fa8da7260c753776505b069cd44c622ec02b2 Mon Sep 17 00:00:00 2001 From: kroy Date: Wed, 23 Oct 2019 10:29:18 -0500 Subject: T1759: bug fixes, missing interface IP --- python/vyos/interface.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'python') diff --git a/python/vyos/interface.py b/python/vyos/interface.py index 6d57d972b..1aae6db60 100644 --- a/python/vyos/interface.py +++ b/python/vyos/interface.py @@ -32,12 +32,19 @@ class Interface(): intf = None intf_type = None + valid = False def __init__(self,intf): self.intf = intf self.intf_type = vyos.interfaces.get_type_of_interface(self.intf) + self.valid = (self.intf in vyos.interfaces.list_interfaces()) def print_interface(self): + + if not self.valid: + print("Invalid interface [{}]".format(self.intf)) + return + if (self.intf_type == 'wireguard'): self.print_wireguard_interface() @@ -66,21 +73,24 @@ class Interface(): data = open(metric_path, 'r').read()[:-1] dev_dict[metric] = int(data) interface_stats[dev] = dev_dict + return interface_stats[self.intf] def print_wireguard_interface(self): - output = subprocess.check_output(["wg", "show", self.intf], universal_newlines=True) - wgdump = vyos.interfaces.wireguard_dump()[self.intf] + + wgdump = vyos.interfaces.wireguard_dump().get(self.intf,None) + c = Config() c.set_level("interfaces wireguard {}".format(self.intf)) description = c.return_effective_value("description") + ips = c.return_effective_values("address") print ("interface: {}".format(self.intf)) - """ if the interface has a description, modify the output to include it """ if (description): print (" description: {}".format(description)) - output = re.sub(r"interface: {}".format(re.escape(self.intf)),"interface: {}\n Description: {}".format(self.intf,description),output) + if (ips): + print (" address: {}".format(", ".join(ips))) print (" public key: {}".format(wgdump['public_key'])) print (" private key: (hidden)") print (" listening port: {}".format(wgdump['listen_port'])) -- cgit v1.2.3 From 8bf1b924055cac270d931666ad2b7fdb82fdebac Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Thu, 24 Oct 2019 11:32:05 +0200 Subject: [vyos.config] T1764: support both string and list arguments in config functions. --- python/vyos/config.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'python') diff --git a/python/vyos/config.py b/python/vyos/config.py index 2232d900c..3a340b2da 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -126,7 +126,12 @@ class Config(object): # splitting at whitespace is reasonably safe. # It may cause problems with exists() when it's used for checking values, # since values may contain whitespace. - path = re.split(r'\s*', path) + if isinstance(path, str): + path = re.split(r'\s*', path) + elif isinstance(path, list): + pass + else: + raise TypeError("Path must be a whitespace-separated string or a list") return (self._level + path) def _run(self, cmd): @@ -155,7 +160,12 @@ class Config(object): # Make sure there's always a space between default path (level) # and path supplied as method argument # XXX: for small strings in-place concatenation is not a problem - self._level = re.split(r'\s*', path) + if isinstance(path, str): + self._level = re.split(r'\s*', path) + elif isinstance(path, list): + self._level = path + else: + raise TypeError("Level path must be either a whitespace-separated string or a list") def get_level(self): """ -- cgit v1.2.3 From 3400b1dd79702553ebbd40516bf454f3fe47885b Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Thu, 24 Oct 2019 11:33:06 +0200 Subject: T1762: adjust the set_level() calls to use the new list representation. --- python/vyos/configdict.py | 6 +++--- src/conf_mode/interfaces-ethernet.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'python') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 983906923..e8e52b33d 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -106,7 +106,7 @@ def vlan_to_dict(conf): Function call's itself recursively if a vif-s/vif-c pair is detected. """ vlan = { - 'id': conf.get_level().split()[-1], # get the '100' in 'interfaces bonding bond0 vif-s 100' + 'id': conf.get_level()[-1], # get the '100' in 'interfaces bonding bond0 vif-s 100' 'address': [], 'address_remove': [], 'description': '', @@ -192,7 +192,7 @@ def vlan_to_dict(conf): # ethertype is mandatory on vif-s nodes and only exists here! # check if this is a vif-s node at all: - if conf.get_level().split()[-2] == 'vif-s': + if conf.get_level()[-2] == 'vif-s': vlan['vif_c'] = [] vlan['vif_c_remove'] = [] @@ -215,7 +215,7 @@ def vlan_to_dict(conf): # add new key (vif-c) to dictionary for vif in conf.list_nodes('vif-c'): # set config level to vif interface - conf.set_level(cfg_level + ' vif-c ' + vif) + conf.set_level(cfg_level + ['vif-c', vif]) vlan['vif_c'].append(vlan_to_dict(conf)) return vlan diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index cd40aff3e..a9ed6bfb6 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -130,7 +130,7 @@ def get_config(): print("Interface not specified") # check if ethernet interface has been removed - cfg_base = 'interfaces ethernet ' + eth['intf'] + cfg_base = ['interfaces', 'ethernet', eth['intf']] if not conf.exists(cfg_base): eth['deleted'] = True # we can not bail out early as ethernet interface can not be removed @@ -249,7 +249,7 @@ def get_config(): if conf.exists('vif-s'): for vif_s in conf.list_nodes('vif-s'): # set config level to vif-s interface - conf.set_level(cfg_base + ' vif-s ' + vif_s) + conf.set_level(cfg_base + ['vif-s', vif_s]) eth['vif_s'].append(vlan_to_dict(conf)) # re-set configuration level to parse new nodes @@ -263,7 +263,7 @@ def get_config(): if conf.exists('vif'): for vif in conf.list_nodes('vif'): # set config level to vif interface - conf.set_level(cfg_base + ' vif ' + vif) + conf.set_level(cfg_base + ['vif', vif]) eth['vif'].append(vlan_to_dict(conf)) return eth -- cgit v1.2.3