summaryrefslogtreecommitdiff
path: root/python/vyos
diff options
context:
space:
mode:
Diffstat (limited to 'python/vyos')
-rw-r--r--python/vyos/configdiff.py31
-rw-r--r--python/vyos/configsession.py14
-rw-r--r--python/vyos/ifconfig/ethernet.py12
-rw-r--r--python/vyos/ifconfig/input.py12
-rw-r--r--python/vyos/opmode.py20
-rw-r--r--python/vyos/qos/base.py46
-rw-r--r--python/vyos/qos/priority.py1
-rw-r--r--python/vyos/qos/roundrobin.py6
-rw-r--r--python/vyos/qos/trafficshaper.py16
-rw-r--r--python/vyos/util.py4
10 files changed, 109 insertions, 53 deletions
diff --git a/python/vyos/configdiff.py b/python/vyos/configdiff.py
index 9185575df..ac86af09c 100644
--- a/python/vyos/configdiff.py
+++ b/python/vyos/configdiff.py
@@ -78,23 +78,34 @@ def get_config_diff(config, key_mangling=None):
isinstance(key_mangling[1], str)):
raise ValueError("key_mangling must be a tuple of two strings")
- diff_t = DiffTree(config._running_config, config._session_config)
+ if hasattr(config, 'cached_diff_tree'):
+ diff_t = getattr(config, 'cached_diff_tree')
+ else:
+ diff_t = DiffTree(config._running_config, config._session_config)
+ setattr(config, 'cached_diff_tree', diff_t)
- return ConfigDiff(config, key_mangling, diff_tree=diff_t)
+ if hasattr(config, 'cached_diff_dict'):
+ diff_d = getattr(config, 'cached_diff_dict')
+ else:
+ diff_d = diff_t.dict
+ setattr(config, 'cached_diff_dict', diff_d)
+
+ return ConfigDiff(config, key_mangling, diff_tree=diff_t,
+ diff_dict=diff_d)
class ConfigDiff(object):
"""
The class of config changes as represented by comparison between the
session config dict and the effective config dict.
"""
- def __init__(self, config, key_mangling=None, diff_tree=None):
+ def __init__(self, config, key_mangling=None, diff_tree=None, diff_dict=None):
self._level = config.get_level()
self._session_config_dict = config.get_cached_root_dict(effective=False)
self._effective_config_dict = config.get_cached_root_dict(effective=True)
self._key_mangling = key_mangling
self._diff_tree = diff_tree
- self._diff_dict = diff_tree.dict if diff_tree else {}
+ self._diff_dict = diff_dict
# mirrored from Config; allow path arguments relative to level
def _make_path(self, path):
@@ -209,9 +220,9 @@ class ConfigDiff(object):
if self._diff_tree is None:
raise NotImplementedError("diff_tree class not available")
else:
- add = get_sub_dict(self._diff_tree.dict, ['add'], get_first_key=True)
- sub = get_sub_dict(self._diff_tree.dict, ['sub'], get_first_key=True)
- inter = get_sub_dict(self._diff_tree.dict, ['inter'], get_first_key=True)
+ add = get_sub_dict(self._diff_dict, ['add'], get_first_key=True)
+ sub = get_sub_dict(self._diff_dict, ['sub'], get_first_key=True)
+ inter = get_sub_dict(self._diff_dict, ['inter'], get_first_key=True)
ret = {}
ret[enum_to_key(Diff.MERGE)] = session_dict
ret[enum_to_key(Diff.DELETE)] = get_sub_dict(sub, self._make_path(path),
@@ -284,9 +295,9 @@ class ConfigDiff(object):
if self._diff_tree is None:
raise NotImplementedError("diff_tree class not available")
else:
- add = get_sub_dict(self._diff_tree.dict, ['add'], get_first_key=True)
- sub = get_sub_dict(self._diff_tree.dict, ['sub'], get_first_key=True)
- inter = get_sub_dict(self._diff_tree.dict, ['inter'], get_first_key=True)
+ add = get_sub_dict(self._diff_dict, ['add'], get_first_key=True)
+ sub = get_sub_dict(self._diff_dict, ['sub'], get_first_key=True)
+ inter = get_sub_dict(self._diff_dict, ['inter'], get_first_key=True)
ret = {}
ret[enum_to_key(Diff.MERGE)] = session_dict
ret[enum_to_key(Diff.DELETE)] = get_sub_dict(sub, self._make_path(path))
diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py
index 3a60f6d92..df44fd8d6 100644
--- a/python/vyos/configsession.py
+++ b/python/vyos/configsession.py
@@ -34,6 +34,8 @@ REMOVE_IMAGE = ['/opt/vyatta/bin/vyatta-boot-image.pl', '--del']
GENERATE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'generate']
SHOW = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'show']
RESET = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'reset']
+OP_CMD_ADD = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'add']
+OP_CMD_DELETE = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'delete']
# Default "commit via" string
APP = "vyos-http-api"
@@ -204,3 +206,15 @@ class ConfigSession(object):
def reset(self, path):
out = self.__run_command(RESET + path)
return out
+
+ def add_container_image(self, name):
+ out = self.__run_command(OP_CMD_ADD + ['container', 'image'] + [name])
+ return out
+
+ def delete_container_image(self, name):
+ out = self.__run_command(OP_CMD_DELETE + ['container', 'image'] + [name])
+ return out
+
+ def show_container_image(self):
+ out = self.__run_command(SHOW + ['container', 'image'])
+ return out
diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py
index 519cfc58c..5080144ff 100644
--- a/python/vyos/ifconfig/ethernet.py
+++ b/python/vyos/ifconfig/ethernet.py
@@ -239,7 +239,7 @@ class EthernetIf(Interface):
if not isinstance(state, bool):
raise ValueError('Value out of range')
- rps_cpus = '0'
+ rps_cpus = 0
queues = len(glob(f'/sys/class/net/{self.ifname}/queues/rx-*'))
if state:
# Enable RPS on all available CPUs except CPU0 which we will not
@@ -248,10 +248,16 @@ class EthernetIf(Interface):
# representation of the CPUs which should participate on RPS, we
# can enable more CPUs that are physically present on the system,
# Linux will clip that internally!
- rps_cpus = 'ffffffff,ffffffff,ffffffff,fffffffe'
+ rps_cpus = (1 << os.cpu_count()) -1
+
+ # XXX: we should probably reserve one core when the system is under
+ # high preasure so we can still have a core left for housekeeping.
+ # This is done by masking out the lowst bit so CPU0 is spared from
+ # receive packet steering.
+ rps_cpus &= ~1
for i in range(0, queues):
- self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_cpus', rps_cpus)
+ self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_cpus', f'{rps_cpus:x}')
# send bitmask representation as hex string without leading '0x'
return True
diff --git a/python/vyos/ifconfig/input.py b/python/vyos/ifconfig/input.py
index db7d2b6b4..3e5f5790d 100644
--- a/python/vyos/ifconfig/input.py
+++ b/python/vyos/ifconfig/input.py
@@ -1,4 +1,4 @@
-# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2023 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
@@ -17,6 +17,16 @@ from vyos.ifconfig.interface import Interface
@Interface.register
class InputIf(Interface):
+ """
+ The Intermediate Functional Block (ifb) pseudo network interface acts as a
+ QoS concentrator for multiple different sources of traffic. Packets from
+ or to other interfaces have to be redirected to it using the mirred action
+ in order to be handled, regularly routed traffic will be dropped. This way,
+ a single stack of qdiscs, classes and filters can be shared between
+ multiple interfaces.
+ """
+
+ iftype = 'ifb'
definition = {
**Interface.definition,
**{
diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py
index 5ff768859..30e893d74 100644
--- a/python/vyos/opmode.py
+++ b/python/vyos/opmode.py
@@ -45,6 +45,10 @@ class PermissionDenied(Error):
"""
pass
+class UnsupportedOperation(Error):
+ """ Requested operation is technically valid but is not implemented yet. """
+ pass
+
class IncorrectValue(Error):
""" Requested operation is valid, but an argument provided has an
incorrect value, preventing successful completion.
@@ -66,13 +70,13 @@ class InternalError(Error):
def _is_op_mode_function_name(name):
- if re.match(r"^(show|clear|reset|restart)", name):
+ if re.match(r"^(show|clear|reset|restart|add|delete|generate)", name):
return True
else:
return False
-def _is_show(name):
- if re.match(r"^show", name):
+def _capture_output(name):
+ if re.match(r"^(show|generate)", name):
return True
else:
return False
@@ -199,14 +203,14 @@ def run(module):
# it would cause an extra argument error when we pass the dict to a function
del args["subcommand"]
- # Show commands must always get the "raw" argument,
- # but other commands (clear/reset/restart) should not,
+ # Show and generate commands must always get the "raw" argument,
+ # but other commands (clear/reset/restart/add/delete) should not,
# because they produce no output and it makes no sense for them.
- if ("raw" not in args) and _is_show(function_name):
+ if ("raw" not in args) and _capture_output(function_name):
args["raw"] = False
- if re.match(r"^show", function_name):
- # Show commands are slightly special:
+ if _capture_output(function_name):
+ # Show and generate commands are slightly special:
# they may return human-formatted output
# or a raw dict that we need to serialize in JSON for printing
res = func(**args)
diff --git a/python/vyos/qos/base.py b/python/vyos/qos/base.py
index d039bbb0f..28635b5e7 100644
--- a/python/vyos/qos/base.py
+++ b/python/vyos/qos/base.py
@@ -13,17 +13,21 @@
# 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/>.
+import os
+
from vyos.base import Warning
from vyos.util import cmd
from vyos.util import dict_search
from vyos.util import read_file
class QoSBase:
- _debug = True
+ _debug = False
_direction = ['egress']
_parent = 0xffff
def __init__(self, interface):
+ if os.path.exists('/tmp/vyos.qos.debug'):
+ self._debug = True
self._interface = interface
def _cmd(self, command):
@@ -41,7 +45,7 @@ class QoSBase:
return tmp[-1]
return None
- def _tmp_qdisc(self, config : dict, cls_id : int):
+ def _build_base_qdisc(self, config : dict, cls_id : int):
"""
Add/replace qdisc for every class (also default is a class). This is
a genetic method which need an implementation "per" queue-type.
@@ -116,11 +120,14 @@ class QoSBase:
'tbit' : 1000000000000,
}
- if rate == 'auto':
+ if rate == 'auto' or rate.endswith('%'):
speed = read_file(f'/sys/class/net/{self._interface}/speed')
if not speed.isnumeric():
Warning('Interface speed cannot be determined (assuming 10 Mbit/s)')
speed = 10
+ if rate.endswith('%'):
+ percent = rate.rstrip('%')
+ speed = int(speed) * int(percent) // 100
return int(speed) *1000000 # convert to MBit/s
rate_numeric = int(''.join([n for n in rate if n.isdigit()]))
@@ -140,8 +147,7 @@ class QoSBase:
if 'class' in config:
for cls, cls_config in config['class'].items():
-
- self._tmp_qdisc(cls_config, int(cls))
+ self._build_base_qdisc(cls_config, int(cls))
if 'match' in cls_config:
for match, match_config in cls_config['match'].items():
@@ -240,11 +246,10 @@ class QoSBase:
self._cmd(filter_cmd)
if 'default' in config:
- class_id_max = self._get_class_max_id(config)
- default_cls_id = int(class_id_max) +1
-
- if 'default' in config:
- self._tmp_qdisc(config['default'], default_cls_id)
+ if 'class' in config:
+ class_id_max = self._get_class_max_id(config)
+ default_cls_id = int(class_id_max) +1
+ self._build_base_qdisc(config['default'], default_cls_id)
filter_cmd = f'tc filter replace dev {self._interface} parent {self._parent:x}: '
filter_cmd += 'prio 255 protocol all basic'
@@ -252,25 +257,26 @@ class QoSBase:
# The police block allows limiting of the byte or packet rate of
# traffic matched by the filter it is attached to.
# https://man7.org/linux/man-pages/man8/tc-police.8.html
- if any(tmp in ['exceed', 'bandwidth', 'burst'] for tmp in cls_config):
+ if any(tmp in ['exceed', 'bandwidth', 'burst'] for tmp in config['default']):
filter_cmd += f' action police'
- if 'exceed' in cls_config:
- action = cls_config['exceed']
+ if 'exceed' in config['default']:
+ action = config['default']['exceed']
filter_cmd += f' conform-exceed {action}'
- if 'not_exceed' in cls_config:
- action = cls_config['not_exceed']
+ if 'not_exceed' in config['default']:
+ action = config['default']['not_exceed']
filter_cmd += f'/{action}'
- if 'bandwidth' in cls_config:
- rate = self._rate_convert(cls_config['bandwidth'])
+ if 'bandwidth' in config['default']:
+ rate = self._rate_convert(config['default']['bandwidth'])
filter_cmd += f' rate {rate}'
- if 'burst' in cls_config:
- burst = cls_config['burst']
+ if 'burst' in config['default']:
+ burst = config['default']['burst']
filter_cmd += f' burst {burst}'
+ if 'class' in config:
+ filter_cmd += f' flowid {self._parent:x}:{default_cls_id:x}'
- filter_cmd += f' flowid {self._parent:x}:{default_cls_id:x}'
self._cmd(filter_cmd)
diff --git a/python/vyos/qos/priority.py b/python/vyos/qos/priority.py
index 72092b7ef..6d4a60a43 100644
--- a/python/vyos/qos/priority.py
+++ b/python/vyos/qos/priority.py
@@ -33,6 +33,7 @@ class Priority(QoSBase):
self._cmd(tmp)
for cls in config['class']:
+ cls = int(cls)
tmp = f'tc qdisc add dev {self._interface} parent {self._parent:x}:{cls:x} pfifo'
self._cmd(tmp)
diff --git a/python/vyos/qos/roundrobin.py b/python/vyos/qos/roundrobin.py
index 4a0cb18aa..80814ddfb 100644
--- a/python/vyos/qos/roundrobin.py
+++ b/python/vyos/qos/roundrobin.py
@@ -26,10 +26,10 @@ class RoundRobin(QoSBase):
if 'class' in config:
for cls in config['class']:
cls = int(cls)
- tmp = f'tc class add dev {self._interface} parent 1:1 classid 1:{cls:x} drr'
+ tmp = f'tc class replace dev {self._interface} parent 1:1 classid 1:{cls:x} drr'
self._cmd(tmp)
- tmp = f'tc qdisc add dev {self._interface} parent 1:{cls:x} pfifo'
+ tmp = f'tc qdisc replace dev {self._interface} parent 1:{cls:x} pfifo'
self._cmd(tmp)
if 'default' in config:
@@ -37,7 +37,7 @@ class RoundRobin(QoSBase):
default_cls_id = int(class_id_max) +1
# class ID via CLI is in range 1-4095, thus 1000 hex = 4096
- tmp = f'tc class add dev {self._interface} parent 1:1 classid 1:{default_cls_id:x} drr'
+ tmp = f'tc class replace dev {self._interface} parent 1:1 classid 1:{default_cls_id:x} drr'
self._cmd(tmp)
# call base class
diff --git a/python/vyos/qos/trafficshaper.py b/python/vyos/qos/trafficshaper.py
index 6d465b38e..f42f4d022 100644
--- a/python/vyos/qos/trafficshaper.py
+++ b/python/vyos/qos/trafficshaper.py
@@ -39,7 +39,6 @@ class TrafficShaper(QoSBase):
# need a bigger r2q if going fast than 16 mbits/sec
if (speed_bps // r2q) >= MAXQUANTUM: # integer division
r2q = ceil(speed_bps // MAXQUANTUM)
- print(r2q)
else:
# if there is a slow class then may need smaller value
if 'class' in config:
@@ -59,10 +58,10 @@ class TrafficShaper(QoSBase):
default_minor_id = int(class_id_max) +1
- tmp = f'tc qdisc add dev {self._interface} root handle {self._parent:x}: htb r2q {r2q} default {default_minor_id:x}' # default is in hex
+ tmp = f'tc qdisc replace dev {self._interface} root handle {self._parent:x}: htb r2q {r2q} default {default_minor_id:x}' # default is in hex
self._cmd(tmp)
- tmp = f'tc class add dev {self._interface} parent {self._parent:x}: classid {self._parent:x}:1 htb rate {speed}'
+ tmp = f'tc class replace dev {self._interface} parent {self._parent:x}: classid {self._parent:x}:1 htb rate {speed}'
self._cmd(tmp)
if 'class' in config:
@@ -75,23 +74,26 @@ class TrafficShaper(QoSBase):
burst = cls_config['burst']
quantum = cls_config['codel_quantum']
- tmp = f'tc class add dev {self._interface} parent {self._parent:x}:1 classid {self._parent:x}:{cls:x} htb rate {rate} burst {burst} quantum {quantum}'
+ tmp = f'tc class replace dev {self._interface} parent {self._parent:x}:1 classid {self._parent:x}:{cls:x} htb rate {rate} burst {burst} quantum {quantum}'
if 'priority' in cls_config:
priority = cls_config['priority']
tmp += f' prio {priority}'
self._cmd(tmp)
- tmp = f'tc qdisc add dev {self._interface} parent {self._parent:x}:{cls:x} sfq'
+ tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{cls:x} sfq'
self._cmd(tmp)
if 'default' in config:
- tmp = f'tc class add dev {self._interface} parent {self._parent:x}:1 classid {self._parent:x}:{default_minor_id:x} htb rate {rate} burst {burst} quantum {quantum}'
+ rate = self._rate_convert(config['default']['bandwidth'])
+ burst = config['default']['burst']
+ quantum = config['default']['codel_quantum']
+ tmp = f'tc class replace dev {self._interface} parent {self._parent:x}:1 classid {self._parent:x}:{default_minor_id:x} htb rate {rate} burst {burst} quantum {quantum}'
if 'priority' in config['default']:
priority = config['default']['priority']
tmp += f' prio {priority}'
self._cmd(tmp)
- tmp = f'tc qdisc add dev {self._interface} parent {self._parent:x}:{default_minor_id:x} sfq'
+ tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{default_minor_id:x} sfq'
self._cmd(tmp)
# call base class
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 6a828c0ac..110da3be5 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -348,9 +348,11 @@ def colon_separated_to_dict(data_string, uniquekeys=False):
l = l.strip()
if l:
match = re.match(key_value_re, l)
- if match:
+ if match and (len(match.groups()) == 2):
key = match.groups()[0].strip()
value = match.groups()[1].strip()
+ else:
+ raise ValueError(f"""Line "{l}" could not be parsed a colon-separated pair """, l)
if key in data.keys():
if uniquekeys:
raise ValueError("Data string has duplicate keys: {0}".format(key))