diff options
Diffstat (limited to 'python')
| -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)) | 
