diff options
Diffstat (limited to 'python/vyos')
-rw-r--r-- | python/vyos/configdiff.py | 31 | ||||
-rw-r--r-- | python/vyos/configsession.py | 14 | ||||
-rw-r--r-- | python/vyos/ifconfig/ethernet.py | 12 | ||||
-rw-r--r-- | python/vyos/ifconfig/input.py | 12 | ||||
-rw-r--r-- | python/vyos/opmode.py | 20 | ||||
-rw-r--r-- | python/vyos/qos/base.py | 46 | ||||
-rw-r--r-- | python/vyos/qos/priority.py | 1 | ||||
-rw-r--r-- | python/vyos/qos/roundrobin.py | 6 | ||||
-rw-r--r-- | python/vyos/qos/trafficshaper.py | 16 | ||||
-rw-r--r-- | python/vyos/util.py | 4 |
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)) |