summaryrefslogtreecommitdiff
path: root/python/vyos
diff options
context:
space:
mode:
Diffstat (limited to 'python/vyos')
-rw-r--r--python/vyos/config.py5
-rw-r--r--python/vyos/configdict.py2
-rw-r--r--python/vyos/configquery.py90
-rw-r--r--python/vyos/configverify.py63
-rw-r--r--python/vyos/frr.py18
-rw-r--r--python/vyos/ifconfig/__init__.py2
-rw-r--r--python/vyos/ifconfig/bridge.py6
-rwxr-xr-xpython/vyos/ifconfig/erspan.py170
-rw-r--r--python/vyos/ifconfig/interface.py43
-rw-r--r--python/vyos/ifconfig/tunnel.py13
-rw-r--r--python/vyos/template.py20
-rw-r--r--python/vyos/util.py59
-rw-r--r--python/vyos/xml/kw.py1
-rw-r--r--python/vyos/xml/load.py2
14 files changed, 265 insertions, 229 deletions
diff --git a/python/vyos/config.py b/python/vyos/config.py
index de79a3654..a5c1ad122 100644
--- a/python/vyos/config.py
+++ b/python/vyos/config.py
@@ -214,7 +214,8 @@ class Config(object):
return config_dict
def get_config_dict(self, path=[], effective=False, key_mangling=None,
- get_first_key=False, no_multi_convert=False):
+ get_first_key=False, no_multi_convert=False,
+ no_tag_node_value_mangle=False):
"""
Args:
path (str list): Configuration tree path, can be empty
@@ -247,7 +248,7 @@ class Config(object):
isinstance(key_mangling[1], str)):
raise ValueError("key_mangling must be a tuple of two strings")
- conf_dict = vyos.util.mangle_dict_keys(conf_dict, key_mangling[0], key_mangling[1])
+ conf_dict = vyos.util.mangle_dict_keys(conf_dict, key_mangling[0], key_mangling[1], abs_path=xmlpath, no_tag_node_value_mangle=no_tag_node_value_mangle)
return conf_dict
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index 5acb1fdfe..0969a5353 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -499,7 +499,7 @@ def get_accel_dict(config, base, chap_secrets):
from vyos.util import get_half_cpus
from vyos.template import is_ipv4
- dict = config.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ dict = config.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True)
# We have gathered the dict representation of the CLI, but there are default
# options which we need to update into the dictionary retrived.
diff --git a/python/vyos/configquery.py b/python/vyos/configquery.py
new file mode 100644
index 000000000..ed7346f1f
--- /dev/null
+++ b/python/vyos/configquery.py
@@ -0,0 +1,90 @@
+# Copyright 2021 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+'''
+A small library that allows querying existence or value(s) of config
+settings from op mode, and execution of arbitrary op mode commands.
+'''
+
+from subprocess import STDOUT
+from vyos.util import popen
+
+
+class ConfigQueryError(Exception):
+ pass
+
+class GenericConfigQuery:
+ def __init__(self):
+ pass
+
+ def exists(self, path: list):
+ raise NotImplementedError
+
+ def value(self, path: list):
+ raise NotImplementedError
+
+ def values(self, path: list):
+ raise NotImplementedError
+
+class GenericOpRun:
+ def __init__(self):
+ pass
+
+ def run(self, path: list, **kwargs):
+ raise NotImplementedError
+
+class CliShellApiConfigQuery(GenericConfigQuery):
+ def __init__(self):
+ super().__init__()
+
+ def exists(self, path: list):
+ cmd = ' '.join(path)
+ (_, err) = popen(f'cli-shell-api existsActive {cmd}')
+ if err:
+ return False
+ return True
+
+ def value(self, path: list):
+ cmd = ' '.join(path)
+ (out, err) = popen(f'cli-shell-api returnActiveValue {cmd}')
+ if err:
+ raise ConfigQueryError('No value for given path')
+ return out
+
+ def values(self, path: list):
+ cmd = ' '.join(path)
+ (out, err) = popen(f'cli-shell-api returnActiveValues {cmd}')
+ if err:
+ raise ConfigQueryError('No values for given path')
+ return out
+
+class VbashOpRun(GenericOpRun):
+ def __init__(self):
+ super().__init__()
+
+ def run(self, path: list, **kwargs):
+ cmd = ' '.join(path)
+ (out, err) = popen(f'. /opt/vyatta/share/vyatta-op/functions/interpreter/vyatta-op-run; _vyatta_op_run {cmd}', stderr=STDOUT, **kwargs)
+ if err:
+ raise ConfigQueryError(out)
+ return out
+
+def query_context(config_query_class=CliShellApiConfigQuery,
+ op_run_class=VbashOpRun):
+ query = config_query_class()
+ run = op_run_class()
+ return query, run
+
+
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index 7cf2cb8f9..99c472582 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -80,7 +80,7 @@ def verify_vrf(config):
recurring validation of VRF configuration.
"""
from netifaces import interfaces
- if 'vrf' in config:
+ if 'vrf' in config and config['vrf'] != 'default':
if config['vrf'] not in interfaces():
raise ConfigError('VRF "{vrf}" does not exist'.format(**config))
@@ -337,18 +337,16 @@ def verify_accel_ppp_base_service(config):
def verify_diffie_hellman_length(file, min_keysize):
""" Verify Diffie-Hellamn keypair length given via file. It must be greater
then or equal to min_keysize """
+ import os
+ import re
+ from vyos.util import cmd
try:
keysize = str(min_keysize)
except:
return False
- import os
- import re
- from vyos.util import cmd
-
if os.path.exists(file):
-
out = cmd(f'openssl dhparam -inform PEM -in {file} -text')
prog = re.compile('\d+\s+bit')
if prog.search(out):
@@ -358,26 +356,55 @@ def verify_diffie_hellman_length(file, min_keysize):
return False
-def verify_route_maps(config):
+def verify_common_route_maps(config):
"""
Common helper function used by routing protocol implementations to perform
recurring validation if the specified route-map for either zebra to kernel
installation exists (this is the top-level route_map key) or when a route
is redistributed with a route-map that it exists!
"""
- if 'route_map' in config:
- route_map = config['route_map']
+ # XXX: This function is called in combination with a previous call to:
+ # tmp = conf.get_config_dict(['policy']) - see protocols_ospf.py as example.
+ # We should NOT call this with the key_mangling option as this would rename
+ # route-map hypens '-' to underscores '_' and one could no longer distinguish
+ # what should have been the "proper" route-map name, as foo-bar and foo_bar
+ # are two entire different route-map instances!
+ for route_map in ['route-map', 'route_map']:
+ if route_map not in config:
+ continue
+ tmp = config[route_map]
# Check if the specified route-map exists, if not error out
- if dict_search(f'policy.route_map.{route_map}', config) == None:
- raise ConfigError(f'Specified route-map "{route_map}" does not exist!')
+ if dict_search(f'policy.route-map.{tmp}', config) == None:
+ raise ConfigError(f'Specified route-map "{tmp}" does not exist!')
if 'redistribute' in config:
for protocol, protocol_config in config['redistribute'].items():
if 'route_map' in protocol_config:
- # A hyphen in a route-map name will be converted to _, take care
- # about this effect during validation
- route_map = protocol_config['route_map'].replace('-','_')
- # Check if the specified route-map exists, if not error out
- if dict_search(f'policy.route_map.{route_map}', config) == None:
- raise ConfigError(f'Redistribution route-map "{route_map}" ' \
- f'for "{protocol}" does not exist!')
+ verify_route_map(protocol_config['route_map'], config)
+
+def verify_route_map(route_map_name, config):
+ """
+ Common helper function used by routing protocol implementations to perform
+ recurring validation if a specified route-map exists!
+ """
+ # Check if the specified route-map exists, if not error out
+ if dict_search(f'policy.route-map.{route_map_name}', config) == None:
+ raise ConfigError(f'Specified route-map "{route_map_name}" does not exist!')
+
+def verify_prefix_list(prefix_list, config, version=''):
+ """
+ Common helper function used by routing protocol implementations to perform
+ recurring validation if a specified prefix-list exists!
+ """
+ # Check if the specified prefix-list exists, if not error out
+ if dict_search(f'policy.prefix-list{version}.{prefix_list}', config) == None:
+ raise ConfigError(f'Specified prefix-list{version} "{prefix_list}" does not exist!')
+
+def verify_access_list(access_list, config, version=''):
+ """
+ Common helper function used by routing protocol implementations to perform
+ recurring validation if a specified prefix-list exists!
+ """
+ # Check if the specified ACL exists, if not error out
+ if dict_search(f'policy.access-list{version}.{access_list}', config) == None:
+ raise ConfigError(f'Specified access-list{version} "{access_list}" does not exist!')
diff --git a/python/vyos/frr.py b/python/vyos/frr.py
index 69c7a14ce..df6849472 100644
--- a/python/vyos/frr.py
+++ b/python/vyos/frr.py
@@ -68,6 +68,8 @@ Apply the new configuration:
import tempfile
import re
from vyos import util
+from vyos.util import chown
+from vyos.util import cmd
import logging
from logging.handlers import SysLogHandler
import os
@@ -86,6 +88,7 @@ _frr_daemons = ['zebra', 'bgpd', 'fabricd', 'isisd', 'ospf6d', 'ospfd', 'pbrd',
path_vtysh = '/usr/bin/vtysh'
path_frr_reload = '/usr/lib/frr/frr-reload.py'
+path_config = '/run/frr'
class FrrError(Exception):
@@ -200,13 +203,26 @@ def reload_configuration(config, daemon=None):
for i, e in enumerate(output.split('\n')):
LOG.debug(f'frr-reload output: {i:3} {e}')
if code == 1:
- raise CommitError(f'Configuration FRR failed while commiting code, please enabling debugging to examine logs')
+ raise CommitError('FRR configuration failed while running commit. Please ' \
+ 'enable debugging to examine logs.\n\n\n' \
+ 'To enable debugging run: "touch /tmp/vyos.frr.debug" ' \
+ 'and "sudo systemctl stop vyos-configd"')
elif code:
raise OSError(code, output)
return output
+def save_configuration():
+ """Save FRR configuration to /run/frr/config/frr.conf
+ It save configuration on each commit. T3217
+ """
+
+ cmd(f'{path_vtysh} -n -w')
+
+ return
+
+
def execute(command):
""" Run commands inside vtysh
command: str containing commands to execute inside a vtysh session
diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py
index f5dfa8e05..e9da1e9f5 100644
--- a/python/vyos/ifconfig/__init__.py
+++ b/python/vyos/ifconfig/__init__.py
@@ -32,8 +32,6 @@ from vyos.ifconfig.vtun import VTunIf
from vyos.ifconfig.vti import VTIIf
from vyos.ifconfig.pppoe import PPPoEIf
from vyos.ifconfig.tunnel import TunnelIf
-from vyos.ifconfig.erspan import ERSpanIf
-from vyos.ifconfig.erspan import ER6SpanIf
from vyos.ifconfig.wireless import WiFiIf
from vyos.ifconfig.l2tpv3 import L2TPv3If
from vyos.ifconfig.macsec import MACsecIf
diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py
index 600bd3db8..14f64a8de 100644
--- a/python/vyos/ifconfig/bridge.py
+++ b/python/vyos/ifconfig/bridge.py
@@ -312,9 +312,15 @@ class BridgeIf(Interface):
# not have any addresses configured by CLI so just flush any
# remaining ones
lower.flush_addrs()
+
# enslave interface port to bridge
self.add_port(interface)
+ # always set private-vlan/port isolation
+ tmp = dict_search('isolated', interface_config)
+ value = 'on' if (tmp != None) else 'off'
+ lower.set_port_isolation(value)
+
# set bridge port path cost
if 'cost' in interface_config:
value = interface_config.get('cost')
diff --git a/python/vyos/ifconfig/erspan.py b/python/vyos/ifconfig/erspan.py
deleted file mode 100755
index 03b2acdbf..000000000
--- a/python/vyos/ifconfig/erspan.py
+++ /dev/null
@@ -1,170 +0,0 @@
-# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io>
-#
-# 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 <http://www.gnu.org/licenses/>.
-
-# https://developers.redhat.com/blog/2019/05/17/an-introduction-to-linux-virtual-interfaces-tunnels/#erspan
-# http://vger.kernel.org/lpc_net2018_talks/erspan-linux-presentation.pdf
-
-from copy import deepcopy
-
-from netaddr import EUI
-from netaddr import mac_unix_expanded
-from random import getrandbits
-
-from vyos.util import dict_search
-from vyos.ifconfig.interface import Interface
-from vyos.validate import assert_list
-
-@Interface.register
-class _ERSpan(Interface):
- """
- _ERSpan: private base class for ERSPAN tunnels
- """
- iftype = 'erspan'
- definition = {
- **Interface.definition,
- **{
- 'section': 'erspan',
- 'prefixes': ['ersp',],
- },
- }
-
- def __init__(self,ifname,**config):
- self.config = deepcopy(config) if config else {}
- super().__init__(ifname, **self.config)
-
- def change_options(self):
- pass
-
- def _create(self):
- pass
-
-class ERSpanIf(_ERSpan):
- """
- ERSpanIf: private base class for ERSPAN Over GRE and IPv4 tunnels
- """
-
- def _create(self):
- ifname = self.config['ifname']
- source_address = self.config['source_address']
- remote = self.config['remote']
- key = self.config['parameters']['ip']['key']
- version = self.config['parameters']['version']
- command = f'ip link add dev {ifname} type erspan local {source_address} remote {remote} seq key {key} erspan_ver {version}'
-
- if int(version) == 1:
- idx=dict_search('parameters.erspan.idx',self.config)
- if idx:
- command += f' erspan {idx}'
- elif int(version) == 2:
- direction=dict_search('parameters.erspan.direction',self.config)
- if direction:
- command += f' erspan_dir {direction}'
- hwid=dict_search('parameters.erspan.hwid',self.config)
- if hwid:
- command += f' erspan_hwid {hwid}'
-
- ttl = dict_search('parameters.ip.ttl',self.config)
- if ttl:
- command += f' ttl {ttl}'
- tos = dict_search('parameters.ip.tos',self.config)
- if tos:
- command += f' tos {tos}'
-
- self._cmd(command)
-
- def change_options(self):
- ifname = self.config['ifname']
- source_address = self.config['source_address']
- remote = self.config['remote']
- key = self.config['parameters']['ip']['key']
- version = self.config['parameters']['version']
- command = f'ip link set dev {ifname} type erspan local {source_address} remote {remote} seq key {key} erspan_ver {version}'
-
- if int(version) == 1:
- idx=dict_search('parameters.erspan.idx',self.config)
- if idx:
- command += f' erspan {idx}'
- elif int(version) == 2:
- direction=dict_search('parameters.erspan.direction',self.config)
- if direction:
- command += f' erspan_dir {direction}'
- hwid=dict_search('parameters.erspan.hwid',self.config)
- if hwid:
- command += f' erspan_hwid {hwid}'
-
- ttl = dict_search('parameters.ip.ttl',self.config)
- if ttl:
- command += f' ttl {ttl}'
- tos = dict_search('parameters.ip.tos',self.config)
- if tos:
- command += f' tos {tos}'
-
- self._cmd(command)
-
-class ER6SpanIf(_ERSpan):
- """
- ER6SpanIf: private base class for ERSPAN Over GRE and IPv6 tunnels
- """
-
- def _create(self):
- ifname = self.config['ifname']
- source_address = self.config['source_address']
- remote = self.config['remote']
- key = self.config['parameters']['ip']['key']
- version = self.config['parameters']['version']
- command = f'ip link add dev {ifname} type ip6erspan local {source_address} remote {remote} seq key {key} erspan_ver {version}'
-
- if int(version) == 1:
- idx=dict_search('parameters.erspan.idx',self.config)
- if idx:
- command += f' erspan {idx}'
- elif int(version) == 2:
- direction=dict_search('parameters.erspan.direction',self.config)
- if direction:
- command += f' erspan_dir {direction}'
- hwid=dict_search('parameters.erspan.hwid',self.config)
- if hwid:
- command += f' erspan_hwid {hwid}'
-
- ttl = dict_search('parameters.ip.ttl',self.config)
- if ttl:
- command += f' ttl {ttl}'
- tos = dict_search('parameters.ip.tos',self.config)
- if tos:
- command += f' tos {tos}'
-
- self._cmd(command)
-
- def change_options(self):
- ifname = self.config['ifname']
- source_address = self.config['source_address']
- remote = self.config['remote']
- key = self.config['parameters']['ip']['key']
- version = self.config['parameters']['version']
- command = f'ip link set dev {ifname} type ip6erspan local {source_address} remote {remote} seq key {key} erspan_ver {version}'
-
- if int(version) == 1:
- idx=dict_search('parameters.erspan.idx',self.config)
- if idx:
- command += f' erspan {idx}'
- elif int(version) == 2:
- direction=dict_search('parameters.erspan.direction',self.config)
- if direction:
- command += f' erspan_dir {direction}'
- hwid=dict_search('parameters.erspan.hwid',self.config)
- if hwid:
- command += f' erspan_hwid {hwid}'
-
- self._cmd(command)
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index fe6a3c95e..ff05cab0e 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -113,6 +113,10 @@ class Interface(Control):
'convert': lambda name: name if name else '',
'shellcmd': 'ip link set dev {ifname} alias "{value}"',
},
+ 'bridge_port_isolation': {
+ 'validate': lambda v: assert_list(v, ['on', 'off']),
+ 'shellcmd': 'bridge link set dev {ifname} isolated {value}',
+ },
'mac': {
'validate': assert_mac,
'shellcmd': 'ip link set dev {ifname} address {value}',
@@ -689,6 +693,20 @@ class Interface(Control):
"""
self.set_interface('path_priority', priority)
+ def set_port_isolation(self, on_or_off):
+ """
+ Controls whether a given port will be isolated, which means it will be
+ able to communicate with non-isolated ports only. By default this flag
+ is off.
+
+ Use enable=1 to enable or enable=0 to disable
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth1').set_port_isolation('on')
+ """
+ self.set_interface('bridge_port_isolation', on_or_off)
+
def set_proxy_arp(self, enable):
"""
Set per interface proxy ARP configuration
@@ -1051,6 +1069,10 @@ class Interface(Control):
if not isinstance(state, bool):
raise ValueError("Value out of range")
+ # https://phabricator.vyos.net/T3448 - there is (yet) no RPI support for XDP
+ if not os.path.exists('/usr/sbin/xdp_loader'):
+ return
+
ifname = self.config['ifname']
cmd = f'xdp_loader -d {ifname} -U --auto-mode'
if state:
@@ -1307,27 +1329,6 @@ class VLANIf(Interface):
""" Specific class which abstracts 802.1q and 802.1ad (Q-in-Q) VLAN interfaces """
iftype = 'vlan'
- def remove(self):
- """
- Remove interface from operating system. Removing the interface
- deconfigures all assigned IP addresses and clear possible DHCP(v6)
- client processes.
-
- Example:
- >>> from vyos.ifconfig import Interface
- >>> VLANIf('eth0.10').remove
- """
- # Do we have sub interfaces (VLANs)? As interfaces need to be deleted
- # "in order" starting from Q-in-Q we delete them first.
- for upper in glob(f'/sys/class/net/{self.ifname}/upper*'):
- # an upper interface could be named: upper_bond0.1000.1100, thus
- # we need top drop the upper_ prefix
- vif_c = os.path.basename(upper)
- vif_c = vif_c.replace('upper_', '')
- VLANIf(vif_c).remove()
-
- super().remove()
-
def _create(self):
# bail out early if interface already exists
if self.exists(f'{self.ifname}'):
diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py
index e5e1300b2..2a266fc9f 100644
--- a/python/vyos/ifconfig/tunnel.py
+++ b/python/vyos/ifconfig/tunnel.py
@@ -43,6 +43,7 @@ class TunnelIf(Interface):
**{
'section': 'tunnel',
'prefixes': ['tun',],
+ 'bridgeable': True,
},
}
@@ -63,6 +64,10 @@ class TunnelIf(Interface):
'parameters.ip.no_pmtu_discovery' : 'nopmtudisc',
'parameters.ip.tos' : 'tos',
'parameters.ip.ttl' : 'ttl',
+ 'parameters.erspan.direction' : 'erspan_dir',
+ 'parameters.erspan.hw_id' : 'erspan_hwid',
+ 'parameters.erspan.index' : 'erspan',
+ 'parameters.erspan.version' : 'erspan_ver',
}
mapping_ipv6 = {
'parameters.ipv6.encaplimit' : 'encaplimit',
@@ -113,8 +118,12 @@ class TunnelIf(Interface):
mapping = { **self.mapping, **self.mapping_ipv4 }
cmd = 'ip tunnel add {ifname} mode {encapsulation}'
- if self.iftype in ['gretap', 'ip6gretap']:
+ if self.iftype in ['gretap', 'ip6gretap', 'erspan', 'ip6erspan']:
cmd = 'ip link add name {ifname} type {encapsulation}'
+ # ERSPAN requires the serialisation of packets
+ if self.iftype in ['erspan', 'ip6erspan']:
+ cmd += ' seq'
+
for vyos_key, iproute2_key in mapping.items():
# dict_search will return an empty dict "{}" for valueless nodes like
# "parameters.nolearning" - thus we need to test the nodes existence
@@ -131,7 +140,7 @@ class TunnelIf(Interface):
def _change_options(self):
# gretap interfaces do not support changing any parameter
- if self.iftype in ['gretap', 'ip6gretap']:
+ if self.iftype in ['gretap', 'ip6gretap', 'erspan', 'ip6erspan']:
return
if self.config['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']:
diff --git a/python/vyos/template.py b/python/vyos/template.py
index 85e4d12b3..3fbb33acb 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -207,6 +207,11 @@ def network_from_ipv4(address):
cidr_prefix = ip_interface(f'{address}/{netmask}').network
return address_from_cidr(cidr_prefix)
+@register_filter('is_interface')
+def is_interface(interface):
+ """ Check if parameter is a valid local interface name """
+ return os.path.exists(f'/sys/class/net/{interface}')
+
@register_filter('is_ip')
def is_ip(addr):
""" Check addr if it is an IPv4 or IPv6 address """
@@ -341,3 +346,18 @@ def get_dhcp_router(interface):
if 'option routers' in line:
(_, _, address) = line.split()
return address.rstrip(';')
+
+@register_filter('natural_sort')
+def natural_sort(iterable):
+ import re
+ from jinja2.runtime import Undefined
+
+ if isinstance(iterable, Undefined) or iterable is None:
+ return list()
+
+ def convert(text):
+ return int(text) if text.isdigit() else text.lower()
+ def alphanum_key(key):
+ return [convert(c) for c in re.split('([0-9]+)', str(key))]
+
+ return sorted(iterable, key=alphanum_key)
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 17a7dda91..2a3f6a228 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -364,7 +364,7 @@ def colon_separated_to_dict(data_string, uniquekeys=False):
return data
-def mangle_dict_keys(data, regex, replacement):
+def _mangle_dict_keys(data, regex, replacement, abs_path=[], no_tag_node_value_mangle=False, mod=0):
""" Mangles dict keys according to a regex and replacement character.
Some libraries like Jinja2 do not like certain characters in dict keys.
This function can be used for replacing all offending characters
@@ -375,18 +375,41 @@ def mangle_dict_keys(data, regex, replacement):
Returns: dict
"""
+ from vyos.xml import is_tag
+
new_dict = {}
+
for key in data.keys():
- new_key = re.sub(regex, replacement, key)
+ save_mod = mod
+ save_path = abs_path[:]
+
+ abs_path.append(key)
+
+ if not is_tag(abs_path):
+ new_key = re.sub(regex, replacement, key)
+ else:
+ if mod%2:
+ new_key = key
+ else:
+ new_key = re.sub(regex, replacement, key)
+ if no_tag_node_value_mangle:
+ mod += 1
value = data[key]
+
if isinstance(value, dict):
- new_dict[new_key] = mangle_dict_keys(value, regex, replacement)
+ new_dict[new_key] = _mangle_dict_keys(value, regex, replacement, abs_path=abs_path, mod=mod, no_tag_node_value_mangle=no_tag_node_value_mangle)
else:
new_dict[new_key] = value
+ mod = save_mod
+ abs_path = save_path[:]
+
return new_dict
+def mangle_dict_keys(data, regex, replacement, abs_path=[], no_tag_node_value_mangle=False):
+ return _mangle_dict_keys(data, regex, replacement, abs_path=abs_path, no_tag_node_value_mangle=no_tag_node_value_mangle, mod=0)
+
def _get_sub_dict(d, lpath):
k = lpath[0]
if k not in d.keys():
@@ -630,23 +653,26 @@ def find_device_file(device):
return None
-def dict_search(path, dict):
- """ Traverse Python dictionary (dict) delimited by dot (.).
+def dict_search(path, my_dict):
+ """ Traverse Python dictionary (my_dict) delimited by dot (.).
Return value of key if found, None otherwise.
- This is faster implementation then jmespath.search('foo.bar', dict)"""
+ This is faster implementation then jmespath.search('foo.bar', my_dict)"""
+ if not isinstance(my_dict, dict) or not path:
+ return None
+
parts = path.split('.')
inside = parts[:-1]
if not inside:
- if path not in dict:
+ if path not in my_dict:
return None
- return dict[path]
- c = dict
+ return my_dict[path]
+ c = my_dict
for p in parts[:-1]:
c = c.get(p, {})
return c.get(parts[-1], None)
-def get_json_iface_options(interface):
+def get_interface_config(interface):
""" Returns the used encapsulation protocol for given interface.
If interface does not exist, None is returned.
"""
@@ -655,3 +681,16 @@ def get_json_iface_options(interface):
from json import loads
tmp = loads(cmd(f'ip -d -j link show {interface}'))[0]
return tmp
+
+def get_all_vrfs():
+ """ Return a dictionary of all system wide known VRF instances """
+ from json import loads
+ tmp = loads(cmd('ip -j vrf list'))
+ # Result is of type [{"name":"red","table":1000},{"name":"blue","table":2000}]
+ # so we will re-arrange it to a more nicer representation:
+ # {'red': {'table': 1000}, 'blue': {'table': 2000}}
+ data = {}
+ for entry in tmp:
+ name = entry.pop('name')
+ data[name] = entry
+ return data
diff --git a/python/vyos/xml/kw.py b/python/vyos/xml/kw.py
index 64521c51a..58d47e751 100644
--- a/python/vyos/xml/kw.py
+++ b/python/vyos/xml/kw.py
@@ -27,7 +27,6 @@ def found(word):
# root
-version = '[version]'
tree = '[tree]'
priorities = '[priorities]'
owners = '[owners]'
diff --git a/python/vyos/xml/load.py b/python/vyos/xml/load.py
index 1f463a5b7..0965d4220 100644
--- a/python/vyos/xml/load.py
+++ b/python/vyos/xml/load.py
@@ -115,7 +115,7 @@ def _format_nodes(inside, conf, xml):
nodetype = 'tagNode'
nodename = kw.tagNode
elif 'syntaxVersion' in conf.keys():
- r[kw.version] = conf.pop('syntaxVersion')['@version']
+ conf.pop('syntaxVersion')
continue
else:
_fatal(conf.keys())