diff options
| -rw-r--r-- | op-mode-definitions/container.xml.in | 35 | ||||
| -rw-r--r-- | python/vyos/configverify.py | 27 | ||||
| -rw-r--r-- | python/vyos/ethtool.py | 24 | ||||
| -rw-r--r-- | python/vyos/frr.py | 16 | ||||
| -rw-r--r-- | python/vyos/ifconfig/ethernet.py | 31 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_protocols_bgp.py | 7 | ||||
| -rwxr-xr-x | src/helpers/vyos_config_sync.py | 48 | ||||
| -rwxr-xr-x | src/services/vyos-http-api-server | 2 | 
8 files changed, 98 insertions, 92 deletions
| diff --git a/op-mode-definitions/container.xml.in b/op-mode-definitions/container.xml.in index 4aa13e913..bb6f97b02 100644 --- a/op-mode-definitions/container.xml.in +++ b/op-mode-definitions/container.xml.in @@ -103,12 +103,28 @@          </properties>          <command>sudo ${vyos_op_scripts_dir}/container.py show_container</command>          <children> -          <leafNode name="image"> +          <node name="json"> +            <properties> +              <help>Show containers in JSON format</help> +            </properties> +            <!-- no admin check --> +            <command>sudo ${vyos_op_scripts_dir}/container.py show_container --raw</command> +          </node> +          <node name="image">              <properties>                <help>Show container image</help>              </properties>              <command>sudo ${vyos_op_scripts_dir}/container.py show_image</command> -          </leafNode> +            <children> +              <node name="json"> +                <properties> +                  <help>Show container image in JSON format</help> +                </properties> +                <!-- no admin check --> +                <command>sudo ${vyos_op_scripts_dir}/container.py show_image --raw</command> +              </node> +            </children> +          </node>            <tagNode name="log">              <properties>                <help>Show logs from a given container</help> @@ -116,14 +132,25 @@                  <path>container name</path>                </completionHelp>              </properties> +            <!-- no admin check -->              <command>sudo podman logs --names "$4"</command>            </tagNode> -          <leafNode name="network"> +          <node name="network">              <properties>                <help>Show available container networks</help>              </properties> +            <!-- no admin check -->              <command>sudo ${vyos_op_scripts_dir}/container.py show_network</command> -          </leafNode> +            <children> +              <node name="json"> +                <properties> +                  <help>Show available container networks in JSON format</help> +                </properties> +                <!-- no admin check --> +                <command>sudo ${vyos_op_scripts_dir}/container.py show_network --raw</command> +              </node> +            </children> +          </node>          </children>        </node>        <node name="log"> diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 5d3723876..6508ccdd9 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -269,14 +269,33 @@ def verify_bridge_delete(config):          raise ConfigError(f'Interface "{interface}" cannot be deleted as it '                            f'is a member of bridge "{bridge_name}"!') -def verify_interface_exists(ifname): +def verify_interface_exists(ifname, warning_only=False):      """      Common helper function used by interface implementations to perform -    recurring validation if an interface actually exists. +    recurring validation if an interface actually exists. We first probe +    if the interface is defined on the CLI, if it's not found we try if +    it exists at the OS level.      """      import os -    if not os.path.exists(f'/sys/class/net/{ifname}'): -        raise ConfigError(f'Interface "{ifname}" does not exist!') +    from vyos.base import Warning +    from vyos.configquery import ConfigTreeQuery +    from vyos.utils.dict import dict_search_recursive + +    # Check if interface is present in CLI config +    config = ConfigTreeQuery() +    tmp = config.get_config_dict(['interfaces'], get_first_key=True) +    if bool(list(dict_search_recursive(tmp, ifname))): +        return True + +    # Interface not found on CLI, try Linux Kernel +    if os.path.exists(f'/sys/class/net/{ifname}'): +        return True + +    message = f'Interface "{ifname}" does not exist!' +    if warning_only: +        Warning(message) +        return False +    raise ConfigError(message)  def verify_source_interface(config):      """ diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py index 473c98d0c..5e241fc08 100644 --- a/python/vyos/ethtool.py +++ b/python/vyos/ethtool.py @@ -24,7 +24,6 @@ from vyos.utils.process import popen  _drivers_without_speed_duplex_flow = ['vmxnet3', 'virtio_net', 'xen_netfront',                                        'iavf', 'ice', 'i40e', 'hv_netvsc', 'veth', 'ixgbevf',                                        'tun'] -_drivers_without_eee = ['vmxnet3', 'virtio_net', 'xen_netfront', 'hv_netvsc']  class Ethtool:      """ @@ -63,8 +62,6 @@ class Ethtool:      _auto_negotiation = False      _auto_negotiation_supported = None      _flow_control = None -    _eee = False -    _eee_enabled = None      def __init__(self, ifname):          # Get driver used for interface @@ -118,15 +115,6 @@ class Ethtool:          if not bool(err):              self._flow_control = loads(out) -        # Get current Energy Efficient Ethernet (EEE) settings, but this is -        # not supported by all NICs (e.g. vmxnet3 does not support is) -        out, _ = popen(f'ethtool --show-eee {ifname}') -        if len(out.splitlines()) > 1: -            self._eee = True -            # read current EEE setting, this returns: -            # EEE status: disabled || EEE status: enabled - inactive || EEE status: enabled - active -            self._eee_enabled = bool('enabled' in out.splitlines()[1]) -      def check_auto_negotiation_supported(self):          """ Check if the NIC supports changing auto-negotiation """          return self._auto_negotiation_supported @@ -211,15 +199,3 @@ class Ethtool:                               'flow-control settings!')          return 'on' if bool(self._flow_control[0]['autonegotiate']) else 'off' - -    def check_eee(self): -        """ Check if the NIC supports eee """ -        if self.get_driver_name() in _drivers_without_eee: -            return False -        return self._eee - -    def get_eee(self): -        if self._eee_enabled == None: -            raise ValueError('Interface does not support changing '\ -                             'EEE settings!') -        return self._eee_enabled diff --git a/python/vyos/frr.py b/python/vyos/frr.py index a01d967e4..c3703cbb4 100644 --- a/python/vyos/frr.py +++ b/python/vyos/frr.py @@ -68,6 +68,7 @@ Apply the new configuration:  import tempfile  import re +from vyos import ConfigError  from vyos.utils.permission import chown  from vyos.utils.process import cmd  from vyos.utils.process import popen @@ -95,6 +96,7 @@ path_config = '/run/frr'  default_add_before = r'(ip prefix-list .*|route-map .*|line vty|end)' +  class FrrError(Exception):      pass @@ -210,13 +212,12 @@ def reload_configuration(config, daemon=None):      LOG.debug(f'reload_configuration: Executing command against frr-reload: "{cmd}"')      output, code = popen(cmd, stderr=STDOUT)      f.close() +      for i, e in enumerate(output.split('\n')):          LOG.debug(f'frr-reload output: {i:3} {e}') +      if code == 1: -        raise CommitError('FRR configuration failed while running commit. Please ' \ -                          'enable debugging to examine logs.\n\n\n' \ -                          'To enable debugging run: "touch /tmp/vyos.frr.debug" ' \ -                          'and "sudo systemctl stop vyos-configd"') +        raise ConfigError(output)      elif code:          raise OSError(code, output) @@ -469,17 +470,22 @@ class FRRConfig:          # https://github.com/FRRouting/frr/issues/10133          count = 0          count_max = 5 +        emsg = ''          while count < count_max:              count += 1              try:                  reload_configuration('\n'.join(self.config), daemon=daemon)                  break +            except ConfigError as e: +                emsg = str(e)              except:                  # we just need to re-try the commit of the configuration                  # for the listed FRR issues above                  pass          if count >= count_max: -            raise ConfigurationNotValid(f'Config commit retry counter ({count_max}) exceeded for {daemon} dameon!') +            if emsg: +                raise ConfigError(emsg) +            raise ConfigurationNotValid(f'Config commit retry counter ({count_max}) exceeded for {daemon} daemon!')          # Save configuration to /run/frr/config/frr.conf          save_configuration() diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index c3f5bbf47..8d96c863f 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -404,34 +404,6 @@ class EthernetIf(Interface):              print(f'could not set "{rx_tx}" ring-buffer for {ifname}')          return output -    def set_eee(self, enable): -        """ -        Enable/Disable Energy Efficient Ethernet (EEE) settings - -        Example: -        >>> from vyos.ifconfig import EthernetIf -        >>> i = EthernetIf('eth0') -        >>> i.set_eee(enable=False) -        """ -        if not isinstance(enable, bool): -            raise ValueError('Value out of range') - -        if not self.ethtool.check_eee(): -            self._debug_msg(f'NIC driver does not support changing EEE settings!') -            return False - -        current = self.ethtool.get_eee() -        if current != enable: -            # Assemble command executed on system. Unfortunately there is no way -            # to change this setting via sysfs -            cmd = f'ethtool --set-eee {self.ifname} eee ' -            cmd += 'on' if enable else 'off' -            output, code = self._popen(cmd) -            if code: -                Warning(f'could not change "{self.ifname}" EEE setting!') -            return output -        return None -      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 @@ -442,9 +414,6 @@ class EthernetIf(Interface):          value = 'off' if 'disable_flow_control' in config else 'on'          self.set_flow_control(value) -        # Always disable Energy Efficient Ethernet -        self.set_eee(False) -          # GRO (generic receive offload)          self.set_gro(dict_search('offload.gro', config) != None) diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 1d68ae08b..1dc865977 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -1236,6 +1236,13 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):          self.assertIn(f'  neighbor {pg_ipv6} activate', afiv6_config)          self.assertIn(f'  neighbor {pg_ipv6} maximum-prefix {ipv6_max_prefix}', afiv6_config) +    def test_bgp_26_commit_error(self): +        self.cli_set(base_path + ['peer-group', 'peer1', 'address-family', 'l2vpn-evpn', 'route-reflector-client']) +        with self.assertRaises(ConfigSessionError) as e: +            self.cli_commit() + +        self.assertTrue("% Invalid command. Not an internal neighbor" in str(e.exception)) +      def test_bgp_99_bmp(self):          target_name = 'instance-bmp'          target_address = '127.0.0.1' diff --git a/src/helpers/vyos_config_sync.py b/src/helpers/vyos_config_sync.py index 572fea61f..77f7cd810 100755 --- a/src/helpers/vyos_config_sync.py +++ b/src/helpers/vyos_config_sync.py @@ -61,14 +61,16 @@ def post_request(url: str, -def retrieve_config(section: str = None) -> Optional[Dict[str, Any]]: +def retrieve_config(section: Optional[List[str]] = None) -> Optional[Dict[str, Any]]:      """Retrieves the configuration from the local server.      Args: -        section: str: The section of the configuration to retrieve. Default is None. +        section: List[str]: The section of the configuration to retrieve. +        Default is None.      Returns: -        Optional[Dict[str, Any]]: The retrieved configuration as a dictionary, or None if an error occurred. +        Optional[Dict[str, Any]]: The retrieved configuration as a +        dictionary, or None if an error occurred.      """      if section is None:          section = [] @@ -83,23 +85,21 @@ def retrieve_config(section: str = None) -> Optional[Dict[str, Any]]:  def set_remote_config(          address: str,          key: str, -        op: str, -        path: str = None, -        section: Optional[str] = None) -> Optional[Dict[str, Any]]: +        commands: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:      """Loads the VyOS configuration in JSON format to a remote host.      Args:          address (str): The address of the remote host.          key (str): The key to use for loading the configuration. -        path (Optional[str]): The path of the configuration. Default is None. -        section (Optional[str]): The section of the configuration to load. Default is None. +        commands (list): List of set/load commands for request, given as: +                         [{'op': str, 'path': list[str], 'section': dict}, +                         ...]      Returns: -        Optional[Dict[str, Any]]: The response from the remote host as a dictionary, or None if an error occurred. +        Optional[Dict[str, Any]]: The response from the remote host as a +        dictionary, or None if a RequestException occurred.      """ -    if path is None: -        path = []      headers = {'Content-Type': 'application/json'}      # Disable the InsecureRequestWarning @@ -107,9 +107,7 @@ def set_remote_config(      url = f'https://{address}/configure-section'      data = json.dumps({ -        'op': mode, -        'path': path, -        'section': section, +        'commands': commands,          'key': key      }) @@ -122,14 +120,14 @@ def set_remote_config(          return None -def is_section_revised(section: str) -> bool: +def is_section_revised(section: List[str]) -> bool:      from vyos.config_mgmt import is_node_revised      return is_node_revised(section)  def config_sync(secondary_address: str,                  secondary_key: str, -                sections: List[list], +                sections: List[list[str]],                  mode: str):      """Retrieve a config section from primary router in JSON format and send it to         secondary router @@ -142,21 +140,25 @@ def config_sync(secondary_address: str,      )      # Sync sections ("nat", "firewall", etc) +    commands = []      for section in sections:          config_json = retrieve_config(section=section)          # Check if config path deesn't exist, for example "set nat"          # we set empty value for config_json data          # As we cannot send to the remote host section "nat None" config          if not config_json: -            config_json = "" +            config_json = {}          logger.debug(              f"Retrieved config for section '{section}': {config_json}") -        set_config = set_remote_config(address=secondary_address, -                                       key=secondary_key, -                                       op=mode, -                                       path=section, -                                       section=config_json) -        logger.debug(f"Set config for section '{section}': {set_config}") + +        d = {'op': mode, 'path': section, 'section': config_json} +        commands.append(d) + +    set_config = set_remote_config(address=secondary_address, +                                   key=secondary_key, +                                   commands=commands) + +    logger.debug(f"Set config for sections '{sections}': {set_config}")  if __name__ == '__main__': diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index a7b14a1a3..77870a84c 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -463,7 +463,7 @@ def _configure_op(data: Union[ConfigureModel, ConfigureListModel,      endpoint = request.url.path      # Allow users to pass just one command -    if not isinstance(data, ConfigureListModel): +    if not isinstance(data, (ConfigureListModel, ConfigSectionListModel)):          data = [data]      else:          data = data.commands | 
