diff options
Diffstat (limited to 'python')
-rw-r--r-- | python/vyos/configdep.py | 2 | ||||
-rw-r--r-- | python/vyos/configdict.py | 14 | ||||
-rw-r--r-- | python/vyos/ifconfig/bond.py | 13 | ||||
-rw-r--r-- | python/vyos/ifconfig/ethernet.py | 34 | ||||
-rw-r--r-- | python/vyos/nat.py | 2 | ||||
-rw-r--r-- | python/vyos/progressbar.py | 70 | ||||
-rw-r--r-- | python/vyos/qos/trafficshaper.py | 9 | ||||
-rw-r--r-- | python/vyos/remote.py | 44 | ||||
-rw-r--r-- | python/vyos/template.py | 3 | ||||
-rw-r--r-- | python/vyos/utils/dict.py | 59 | ||||
-rw-r--r-- | python/vyos/utils/io.py | 39 |
11 files changed, 217 insertions, 72 deletions
diff --git a/python/vyos/configdep.py b/python/vyos/configdep.py index 05d9a3fa3..8a28811eb 100644 --- a/python/vyos/configdep.py +++ b/python/vyos/configdep.py @@ -43,7 +43,7 @@ def canon_name_of_path(path: str) -> str: return canon_name(script) def caller_name() -> str: - return stack()[-1].filename + return stack()[2].filename def read_dependency_dict(dependency_dir: str = dependency_dir) -> dict: res = {} diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 71a06b625..075ffe466 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -258,10 +258,10 @@ def has_address_configured(conf, intf): old_level = conf.get_level() conf.set_level([]) - intfpath = 'interfaces ' + Section.get_config_path(intf) - if ( conf.exists(f'{intfpath} address') or - conf.exists(f'{intfpath} ipv6 address autoconf') or - conf.exists(f'{intfpath} ipv6 address eui64') ): + intfpath = ['interfaces', Section.get_config_path(intf)] + if (conf.exists([intfpath, 'address']) or + conf.exists([intfpath, 'ipv6', 'address', 'autoconf']) or + conf.exists([intfpath, 'ipv6', 'address', 'eui64'])): ret = True conf.set_level(old_level) @@ -279,8 +279,7 @@ def has_vrf_configured(conf, intf): old_level = conf.get_level() conf.set_level([]) - tmp = ['interfaces', Section.get_config_path(intf), 'vrf'] - if conf.exists(tmp): + if conf.exists(['interfaces', Section.get_config_path(intf), 'vrf']): ret = True conf.set_level(old_level) @@ -298,8 +297,7 @@ def has_vlan_subinterface_configured(conf, intf): ret = False intfpath = ['interfaces', Section.section(intf), intf] - if ( conf.exists(intfpath + ['vif']) or - conf.exists(intfpath + ['vif-s'])): + if (conf.exists(intfpath + ['vif']) or conf.exists(intfpath + ['vif-s'])): ret = True return ret diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py index d1d7d48c4..45e6e4c16 100644 --- a/python/vyos/ifconfig/bond.py +++ b/python/vyos/ifconfig/bond.py @@ -92,6 +92,19 @@ class BondIf(Interface): } }} + @staticmethod + def get_inherit_bond_options() -> list: + """ + Returns list of option + which are inherited from bond interface to member interfaces + :return: List of interface options + :rtype: list + """ + options = [ + 'mtu' + ] + return options + def remove(self): """ Remove interface from operating system. Removing the interface diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 285542057..aa1e87744 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -75,6 +75,40 @@ class EthernetIf(Interface): }, }} + @staticmethod + def get_bond_member_allowed_options() -> list: + """ + Return list of options which are allowed for changing, + when interface is a bond member + :return: List of interface options + :rtype: list + """ + bond_allowed_sections = [ + 'description', + 'disable', + 'disable_flow_control', + 'disable_link_detect', + 'duplex', + 'eapol.ca_certificate', + 'eapol.certificate', + 'eapol.passphrase', + 'mirror.egress', + 'mirror.ingress', + 'offload.gro', + 'offload.gso', + 'offload.lro', + 'offload.rfs', + 'offload.rps', + 'offload.sg', + 'offload.tso', + 'redirect', + 'ring_buffer.rx', + 'ring_buffer.tx', + 'speed', + 'hw_id' + ] + return bond_allowed_sections + def __init__(self, ifname, **kargs): super().__init__(ifname, **kargs) self.ethtool = Ethtool(ifname) diff --git a/python/vyos/nat.py b/python/vyos/nat.py index 0887bfdf1..e32b5ae74 100644 --- a/python/vyos/nat.py +++ b/python/vyos/nat.py @@ -170,7 +170,7 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False): operator = '' if addr_prefix[:1] == '!': operator = '!=' - addr_prefix = addr[1:] + addr_prefix = addr_prefix[1:] output.append(f'ip6 {prefix}addr {operator} {addr_prefix}') port = dict_search_args(side_conf, 'port') diff --git a/python/vyos/progressbar.py b/python/vyos/progressbar.py new file mode 100644 index 000000000..1793c445b --- /dev/null +++ b/python/vyos/progressbar.py @@ -0,0 +1,70 @@ +# 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 +# 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/>. + +import math +import os +import signal +import subprocess +import sys + +from vyos.utils.io import print_error + +class Progressbar: + def __init__(self, step=None): + self.total = 0.0 + self.step = step + def __enter__(self): + # Recalculate terminal width with every window resize. + signal.signal(signal.SIGWINCH, lambda signum, frame: self._update_cols()) + # Disable line wrapping to prevent the staircase effect. + subprocess.run(['tput', 'rmam'], check=False) + self._update_cols() + # Print an empty progressbar with entry. + self.progress(0, 1) + return self + def __exit__(self, exc_type, kexc_val, exc_tb): + # Revert to the default SIGWINCH handler (ie nothing). + signal.signal(signal.SIGWINCH, signal.SIG_DFL) + # Reenable line wrapping. + subprocess.run(['tput', 'smam'], check=False) + def _update_cols(self): + # `os.get_terminal_size()' is fast enough for our purposes. + self.col = max(os.get_terminal_size().columns - 15, 20) + def increment(self): + """ + Stateful progressbar taking the step fraction at init and no input at + callback (for FTP) + """ + if self.step: + if self.total < 1.0: + self.total += self.step + if self.total >= 1.0: + self.total = 1.0 + # Ignore superfluous calls caused by fuzzy FTP size calculations. + self.step = None + self.progress(self.total, 1.0) + def progress(self, done, total): + """ + Stateless progressbar taking no input at init and current progress with + final size at callback (for SSH) + """ + if done <= total: + length = math.ceil(self.col * done / total) + percentage = str(math.ceil(100 * done / total)).rjust(3) + # Carriage return at the end will make sure the line will get overwritten. + print_error(f'[{length * "#"}{(self.col - length) * "_"}] {percentage}%', end='\r') + # Print a newline to make sure the full progressbar doesn't get overwritten by the next line. + if done == total: + print_error() diff --git a/python/vyos/qos/trafficshaper.py b/python/vyos/qos/trafficshaper.py index c63c7cf39..0d5f9a8a1 100644 --- a/python/vyos/qos/trafficshaper.py +++ b/python/vyos/qos/trafficshaper.py @@ -1,4 +1,4 @@ -# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2022-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 @@ -89,6 +89,10 @@ class TrafficShaper(QoSBase): if 'priority' in cls_config: priority = cls_config['priority'] tmp += f' prio {priority}' + + if 'ceiling' in cls_config: + f_ceil = self._rate_convert(cls_config['ceiling']) + tmp += f' ceil {f_ceil}' self._cmd(tmp) tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{cls:x} sfq' @@ -102,6 +106,9 @@ class TrafficShaper(QoSBase): if 'priority' in config['default']: priority = config['default']['priority'] tmp += f' prio {priority}' + if 'ceiling' in config['default']: + f_ceil = self._rate_convert(config['default']['ceiling']) + tmp += f' ceil {f_ceil}' self._cmd(tmp) tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{default_minor_id:x} sfq' diff --git a/python/vyos/remote.py b/python/vyos/remote.py index cf731c881..1ca8a9530 100644 --- a/python/vyos/remote.py +++ b/python/vyos/remote.py @@ -32,9 +32,8 @@ from requests import Session from requests.adapters import HTTPAdapter from requests.packages.urllib3 import PoolManager +from vyos.progressbar import Progressbar from vyos.utils.io import ask_yes_no -from vyos.utils.io import make_incremental_progressbar -from vyos.utils.io import make_progressbar from vyos.utils.io import print_error from vyos.utils.misc import begin from vyos.utils.process import cmd @@ -131,16 +130,16 @@ class FtpC: if self.secure: conn.prot_p() # Almost all FTP servers support the `SIZE' command. + size = conn.size(self.path) if self.check_space: - check_storage(path, conn.size(self.path)) + check_storage(path, size) # No progressbar if we can't determine the size or if the file is too small. if self.progressbar and size and size > CHUNK_SIZE: - progress = make_incremental_progressbar(CHUNK_SIZE / size) - next(progress) - callback = lambda block: begin(f.write(block), next(progress)) + with Progressbar(CHUNK_SIZE / size) as p: + callback = lambda block: begin(f.write(block), p.increment()) + conn.retrbinary('RETR ' + self.path, callback, CHUNK_SIZE) else: - callback = f.write - conn.retrbinary('RETR ' + self.path, callback, CHUNK_SIZE) + conn.retrbinary('RETR ' + self.path, f.write, CHUNK_SIZE) def upload(self, location: str): size = os.path.getsize(location) @@ -150,12 +149,10 @@ class FtpC: if self.secure: conn.prot_p() if self.progressbar and size and size > CHUNK_SIZE: - progress = make_incremental_progressbar(CHUNK_SIZE / size) - next(progress) - callback = lambda block: next(progress) + with Progressbar(CHUNK_SIZE / size) as p: + conn.storbinary('STOR ' + self.path, f, CHUNK_SIZE, lambda block: p.increment()) else: - callback = None - conn.storbinary('STOR ' + self.path, f, CHUNK_SIZE, callback) + conn.storbinary('STOR ' + self.path, f, CHUNK_SIZE) class SshC: known_hosts = os.path.expanduser('~/.ssh/known_hosts') @@ -190,14 +187,16 @@ class SshC: return ssh def download(self, location: str): - callback = make_progressbar() if self.progressbar else None with self._establish() as ssh, ssh.open_sftp() as sftp: if self.check_space: check_storage(location, sftp.stat(self.path).st_size) - sftp.get(self.path, location, callback=callback) + if self.progressbar: + with Progressbar() as p: + sftp.get(self.path, location, callback=p.progress) + else: + sftp.get(self.path, location) def upload(self, location: str): - callback = make_progressbar() if self.progressbar else None with self._establish() as ssh, ssh.open_sftp() as sftp: try: # If the remote path is a directory, use the original filename. @@ -210,7 +209,11 @@ class SshC: except IOError: path = self.path finally: - sftp.put(location, path, callback=callback) + if self.progressbar: + with Progressbar() as p: + sftp.put(location, path, callback=p.progress) + else: + sftp.put(location, path) class HttpC: @@ -264,10 +267,9 @@ class HttpC: with s.get(final_urlstring, stream=True, timeout=self.timeout) as r, open(location, 'wb') as f: if self.progressbar and size: - progress = make_incremental_progressbar(CHUNK_SIZE / size) - next(progress) - for chunk in iter(lambda: begin(next(progress), r.raw.read(CHUNK_SIZE)), b''): - f.write(chunk) + with Progressbar(CHUNK_SIZE / size) as p: + for chunk in iter(lambda: begin(p.increment(), r.raw.read(CHUNK_SIZE)), b''): + f.write(chunk) else: # We'll try to stream the download directly with `copyfileobj()` so that large # files (like entire VyOS images) don't occupy much memory. diff --git a/python/vyos/template.py b/python/vyos/template.py index 3be486cc4..c778d0de8 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -582,10 +582,11 @@ def nft_rule(rule_conf, fw_hook, fw_name, rule_id, ip_name='ip'): def nft_default_rule(fw_conf, fw_name, ipv6=False): output = ['counter'] default_action = fw_conf['default_action'] + family = 'ipv6' if ipv6 else 'ipv4' if 'enable_default_log' in fw_conf: action_suffix = default_action[:1].upper() - output.append(f'log prefix "[{fw_name[:19]}-default-{action_suffix}]"') + output.append(f'log prefix "[{family}-{fw_name[:19]}-default-{action_suffix}]"') #output.append(nft_action(default_action)) output.append(f'{default_action}') diff --git a/python/vyos/utils/dict.py b/python/vyos/utils/dict.py index 9484eacdd..d36b6fcfb 100644 --- a/python/vyos/utils/dict.py +++ b/python/vyos/utils/dict.py @@ -199,6 +199,31 @@ def dict_search_recursive(dict_object, key, path=[]): for x in dict_search_recursive(j, key, new_path): yield x + +def dict_set(key_path, value, dict_object): + """ Set value to Python dictionary (dict_object) using path to key delimited by dot (.). + The key will be added if it does not exist. + """ + path_list = key_path.split(".") + dynamic_dict = dict_object + if len(path_list) > 0: + for i in range(0, len(path_list)-1): + dynamic_dict = dynamic_dict[path_list[i]] + dynamic_dict[path_list[len(path_list)-1]] = value + +def dict_delete(key_path, dict_object): + """ Delete key in Python dictionary (dict_object) using path to key delimited by dot (.). + """ + path_dict = dict_object + path_list = key_path.split('.') + inside = path_list[:-1] + if not inside: + del dict_object[path_list] + else: + for key in path_list[:-1]: + path_dict = path_dict[key] + del path_dict[path_list[len(path_list)-1]] + def dict_to_list(d, save_key_to=None): """ Convert a dict to a list of dicts. @@ -228,6 +253,39 @@ def dict_to_list(d, save_key_to=None): return collect +def dict_to_paths_values(conf: dict) -> dict: + """ + Convert nested dictionary to simple dictionary, where key is a path is delimited by dot (.). + """ + list_of_paths = [] + dict_of_options ={} + for path in dict_to_key_paths(conf): + str_path = '.'.join(path) + list_of_paths.append(str_path) + + for path in list_of_paths: + dict_of_options[path] = dict_search(path,conf) + + return dict_of_options +def dict_to_key_paths(d: dict) -> list: + """ Generator to return list of key paths from dict of list[str]|str + """ + def func(d, path): + if isinstance(d, dict): + if not d: + yield path + for k, v in d.items(): + for r in func(v, path + [k]): + yield r + elif isinstance(d, list): + yield path + elif isinstance(d, str): + yield path + else: + raise ValueError('object is not a dict of strings/list of strings') + for r in func(d, []): + yield r + def dict_to_paths(d: dict) -> list: """ Generator to return list of paths from dict of list[str]|str """ @@ -305,3 +363,4 @@ class FixedDict(dict): if k not in self._allowed: raise ConfigError(f'Option "{k}" has no defined default') super().__setitem__(k, v) + diff --git a/python/vyos/utils/io.py b/python/vyos/utils/io.py index 843494855..5fffa62f8 100644 --- a/python/vyos/utils/io.py +++ b/python/vyos/utils/io.py @@ -24,45 +24,6 @@ def print_error(str='', end='\n'): sys.stderr.write(end) sys.stderr.flush() -def make_progressbar(): - """ - Make a procedure that takes two arguments `done` and `total` and prints a - progressbar based on the ratio thereof, whose length is determined by the - width of the terminal. - """ - import shutil, math - col, _ = shutil.get_terminal_size() - col = max(col - 15, 20) - def print_progressbar(done, total): - if done <= total: - increment = total / col - length = math.ceil(done / increment) - percentage = str(math.ceil(100 * done / total)).rjust(3) - print_error(f'[{length * "#"}{(col - length) * "_"}] {percentage}%', '\r') - # Print a newline so that the subsequent prints don't overwrite the full bar. - if done == total: - print_error() - return print_progressbar - -def make_incremental_progressbar(increment: float): - """ - Make a generator that displays a progressbar that grows monotonically with - every iteration. - First call displays it at 0% and every subsequent iteration displays it - at `increment` increments where 0.0 < `increment` < 1.0. - Intended for FTP and HTTP transfers with stateless callbacks. - """ - print_progressbar = make_progressbar() - total = 0.0 - while total < 1.0: - print_progressbar(total, 1.0) - yield - total += increment - print_progressbar(1, 1) - # Ignore further calls. - while True: - yield - def ask_input(question, default='', numeric_only=False, valid_responses=[]): question_out = question if default: |