summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/vyos/configdict.py5
-rw-r--r--python/vyos/configverify.py74
-rw-r--r--python/vyos/ifconfig/interface.py39
-rw-r--r--python/vyos/ifconfig/loopback.py25
-rw-r--r--python/vyos/ifconfig/vrrp.py5
-rw-r--r--python/vyos/xml/__init__.py11
-rw-r--r--python/vyos/xml/definition.py56
7 files changed, 188 insertions, 27 deletions
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index ce086872e..0dc7578d8 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -22,7 +22,6 @@ from enum import Enum
from copy import deepcopy
from vyos import ConfigError
-from vyos.ifconfig import Interface
from vyos.validate import is_member
from vyos.util import ifname_from_config
@@ -97,6 +96,8 @@ def dict_merge(source, destination):
for key, value in source.items():
if key not in tmp.keys():
tmp[key] = value
+ elif isinstance(source[key], dict):
+ tmp[key] = dict_merge(source[key], tmp[key])
return tmp
@@ -214,6 +215,8 @@ def disable_state(conf, check=[3,5,7]):
def intf_to_dict(conf, default):
+ from vyos.ifconfig import Interface
+
"""
Common used function which will extract VLAN related information from config
and represent the result as Python dictionary.
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
new file mode 100644
index 000000000..e2fffeca7
--- /dev/null
+++ b/python/vyos/configverify.py
@@ -0,0 +1,74 @@
+# Copyright 2020 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/>.
+
+# The sole purpose of this module is to hold common functions used in
+# all kinds of implementations to verify the CLI configuration.
+# It is started by migrating the interfaces to the new get_config_dict()
+# approach which will lead to a lot of code that can be reused.
+
+# NOTE: imports should be as local as possible to the function which
+# makes use of it!
+
+from vyos import ConfigError
+
+def verify_bridge_vrf(config):
+ """
+ Common helper function used by interface implementations to
+ perform recurring validation of VRF configuration
+ """
+ from netifaces import interfaces
+ if 'vrf' in config.keys():
+ if config['vrf'] not in interfaces():
+ raise ConfigError('VRF "{vrf}" does not exist'.format(**config))
+
+ if 'is_bridge_member' in config.keys():
+ raise ConfigError(
+ 'Interface "{ifname}" cannot be both a member of VRF "{vrf}" '
+ 'and bridge "{is_bridge_member}"!'.format(**config))
+
+
+def verify_bridge_address(config):
+ """
+ Common helper function used by interface implementations to
+ perform recurring validation of IP address assignmenr
+ when interface also is part of a bridge.
+ """
+ if {'is_bridge_member', 'address'} <= set(config):
+ raise ConfigError(
+ f'Cannot assign address to interface "{ifname}" as it is a '
+ f'member of bridge "{is_bridge_member}"!'.format(**config))
+
+
+def verify_bridge_delete(config):
+ """
+ Common helper function used by interface implementations to
+ perform recurring validation of IP address assignmenr
+ when interface also is part of a bridge.
+ """
+ if 'is_bridge_member' in config.keys():
+ raise ConfigError(
+ 'Interface "{ifname}" cannot be deleted as it is a '
+ 'member of bridge "{is_bridge_member}"!'.format(**config))
+
+
+def verify_source_interface(config):
+ """
+ Common helper function used by interface implementations to
+ perform recurring validation of the existence of a source-interface
+ required by e.g. peth/MACvlan, MACsec ...
+ """
+ if not 'source_interface' in config.keys():
+ raise ConfigError('Physical source-interface required for '
+ 'interface "{ifname}"'.format(**config))
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 2c2396440..1819ffc82 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -27,6 +27,7 @@ from netifaces import AF_INET
from netifaces import AF_INET6
from vyos import ConfigError
+from vyos.configdict import list_diff
from vyos.util import mac2eui64
from vyos.validate import is_ipv4
from vyos.validate import is_ipv6
@@ -757,3 +758,41 @@ class Interface(Control):
# TODO: port config (STP)
return True
+
+ def update(self, config):
+ """ General helper function which works on a dictionary retrived by
+ get_config_dict(). It's main intention is to consolidate the scattered
+ interface setup code and provide a single point of entry when workin
+ on any interface. """
+
+ # Update interface description
+ self.set_alias(config.get('description', None))
+
+ # Configure assigned interface IP addresses. No longer
+ # configured addresses will be removed first
+ new_addr = config.get('address', [])
+
+ # XXX workaround for T2636, convert IP address string to a list
+ # with one element
+ if isinstance(new_addr, str):
+ new_addr = [new_addr]
+
+ # determine IP addresses which are assigned to the interface and build a
+ # list of addresses which are no longer in the dict so they can be removed
+ cur_addr = self.get_addr()
+ for addr in list_diff(cur_addr, new_addr):
+ self.del_addr(addr)
+
+ for addr in new_addr:
+ self.add_addr(addr)
+
+ # There are some items in the configuration which can only be applied
+ # if this instance is not bound to a bridge. This should be checked
+ # by the caller but better save then sorry!
+ if not config.get('is_bridge_member', False):
+ # Bind interface instance into VRF
+ self.set_vrf(config.get('vrf', ''))
+
+ # Interface administrative state
+ state = 'down' if 'disable' in config.keys() else 'up'
+ self.set_admin_state(state)
diff --git a/python/vyos/ifconfig/loopback.py b/python/vyos/ifconfig/loopback.py
index 8e4438662..7ebd13b54 100644
--- a/python/vyos/ifconfig/loopback.py
+++ b/python/vyos/ifconfig/loopback.py
@@ -23,7 +23,7 @@ class LoopbackIf(Interface):
The loopback device is a special, virtual network interface that your router
uses to communicate with itself.
"""
-
+ _persistent_addresses = ['127.0.0.1/8', '::1/128']
default = {
'type': 'loopback',
}
@@ -49,10 +49,31 @@ class LoopbackIf(Interface):
"""
# remove all assigned IP addresses from interface
for addr in self.get_addr():
- if addr in ["127.0.0.1/8", "::1/128"]:
+ if addr in self._persistent_addresses:
# Do not allow deletion of the default loopback addresses as
# this will cause weird system behavior like snmp/ssh no longer
# operating as expected, see https://phabricator.vyos.net/T2034.
continue
self.del_addr(addr)
+
+ def update(self, config):
+ """ General helper function which works on a dictionary retrived by
+ get_config_dict(). It's main intention is to consolidate the scattered
+ interface setup code and provide a single point of entry when workin
+ on any interface. """
+
+ addr = config.get('address', [])
+ # XXX workaround for T2636, convert IP address string to a list
+ # with one element
+ if isinstance(addr, str):
+ addr = [addr]
+
+ # We must ensure that the loopback addresses are never deleted from the system
+ addr += self._persistent_addresses
+
+ # Update IP address entry in our dictionary
+ config.update({'address' : addr})
+
+ # now call the regular function from within our base class
+ super().update(config)
diff --git a/python/vyos/ifconfig/vrrp.py b/python/vyos/ifconfig/vrrp.py
index a872725b2..5e6387881 100644
--- a/python/vyos/ifconfig/vrrp.py
+++ b/python/vyos/ifconfig/vrrp.py
@@ -28,6 +28,9 @@ from vyos import util
class VRRPError(Exception):
pass
+class VRRPNoData(VRRPError):
+ pass
+
class VRRP(object):
_vrrp_prefix = '00:00:5E:00:01:'
location = {
@@ -96,6 +99,8 @@ class VRRP(object):
# shoud look for file size change ?
sleep(0.2)
return util.read_file(fname)
+ except FileNotFoundError:
+ raise VRRPNoData("VRRP data is not available (process not running or no active groups)")
except Exception:
name = cls._name[what]
raise VRRPError(f'VRRP {name} is not available')
diff --git a/python/vyos/xml/__init__.py b/python/vyos/xml/__init__.py
index 52f5bfb38..6e0e73b1b 100644
--- a/python/vyos/xml/__init__.py
+++ b/python/vyos/xml/__init__.py
@@ -9,7 +9,7 @@
# 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+# if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from vyos.xml import definition
@@ -35,5 +35,10 @@ def load_configuration(cache=[]):
return xml
-def defaults(lpath):
- return load_configuration().defaults(lpath)
+def defaults(lpath, flat=False):
+ return load_configuration().defaults(lpath, flat)
+
+
+if __name__ == '__main__':
+ print(defaults(['service'], flat=True))
+ print(defaults(['service'], flat=False))
diff --git a/python/vyos/xml/definition.py b/python/vyos/xml/definition.py
index c5f6b0fc7..5421007e0 100644
--- a/python/vyos/xml/definition.py
+++ b/python/vyos/xml/definition.py
@@ -11,7 +11,6 @@
# You should have received a copy of the GNU Lesser General Public License along with this library;
# if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
from vyos.xml import kw
# As we index by key, the name is first and then the data:
@@ -228,8 +227,9 @@ class XML(dict):
inner = self.tree[option]
prefix = '+> ' if inner.get(kw.node, '') != kw.leafNode else ' '
if kw.help in inner:
- h = inner[kw.help]
- yield (prefix + option, h.get(kw.summary), '')
+ yield (prefix + option, inner[kw.help].get(kw.summary), '')
+ else:
+ yield (prefix + option, '(no help available)', '')
def debug(self):
print('------')
@@ -245,36 +245,48 @@ class XML(dict):
# @lru_cache(maxsize=100)
# XXX: need to use cachetool instead - for later
- def defaults(self, lpath):
+ def defaults(self, lpath, flat):
d = self[kw.default]
for k in lpath:
d = d[k]
- r = {}
- def _flatten(inside, index, d, r):
+ if not flat:
+ r = {}
+ for k in d:
+ under = k.replace('-','_')
+ if isinstance(d[k],dict):
+ r[under] = self.defaults(lpath + [k], flat)
+ continue
+ r[under] = d[k]
+ return r
+
+ def _flatten(inside, index, d):
+ r = {}
local = inside[index:]
prefix = '_'.join(_.replace('-','_') for _ in local) + '_' if local else ''
for k in d:
under = prefix + k.replace('-','_')
level = inside + [k]
if isinstance(d[k],dict):
- _flatten(level, index, d[k], r)
+ r.update(_flatten(level, index, d[k]))
continue
- if self.is_multi(level):
+ if self.is_multi(level, with_tag=False):
r[under] = [_.strip() for _ in d[k].split(',')]
continue
r[under] = d[k]
+ return r
- _flatten(lpath, len(lpath), d, r)
- return r
+ return _flatten(lpath, len(lpath), d)
# from functools import lru_cache
# @lru_cache(maxsize=100)
# XXX: need to use cachetool instead - for later
- def _tree(self, lpath):
+ def _tree(self, lpath, with_tag=True):
"""
returns the part of the tree searched or None if it does not exists
+ if with_tag is set, this is a configuration path (with tagNode names)
+ and tag name will be removed from the path when traversing the tree
"""
tree = self[kw.tree]
spath = lpath.copy()
@@ -283,19 +295,21 @@ class XML(dict):
if p not in tree:
return None
tree = tree[p]
+ if with_tag and spath and tree[kw.node] == kw.tagNode:
+ spath.pop(0)
return tree
- def _get(self, lpath, tag):
- return self._tree(lpath + [tag])
+ def _get(self, lpath, tag, with_tag=True):
+ return self._tree(lpath + [tag], with_tag)
- def is_multi(self, lpath):
- return self._get(lpath, kw.multi) is True
+ def is_multi(self, lpath, with_tag=True):
+ return self._get(lpath, kw.multi, with_tag) is True
- def is_tag(self, lpath):
- return self._get(lpath, kw.node) == kw.tagNode
+ def is_tag(self, lpath, with_tag=True):
+ return self._get(lpath, kw.node, with_tag) == kw.tagNode
- def is_leaf(self, lpath):
- return self._get(lpath, kw.node) == kw.leafNode
+ def is_leaf(self, lpath, with_tag=True):
+ return self._get(lpath, kw.node, with_tag) == kw.leafNode
- def exists(self, lpath):
- return self._get(lpath, kw.node) is not None
+ def exists(self, lpath, with_tag=True):
+ return self._get(lpath, kw.node, with_tag) is not None