diff options
-rw-r--r-- | op-mode-definitions/ipoe-server.xml.in | 8 | ||||
-rw-r--r-- | op-mode-definitions/nhrp.xml.in | 2 | ||||
-rw-r--r-- | op-mode-definitions/pptp-server.xml.in | 2 | ||||
-rw-r--r-- | op-mode-definitions/show-rpki.xml.in | 2 | ||||
-rw-r--r-- | op-mode-definitions/show-system.xml.in | 2 | ||||
-rw-r--r-- | op-mode-definitions/show-vrf.xml.in | 2 | ||||
-rw-r--r-- | op-mode-definitions/show-zebra.xml.in | 2 | ||||
-rw-r--r-- | python/vyos/configdict.py | 22 | ||||
-rw-r--r-- | python/vyos/ifconfig/bridge.py | 38 | ||||
-rwxr-xr-x | scripts/build-command-op-templates | 11 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_interfaces_bridge.py | 118 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-bridge.py | 10 | ||||
-rwxr-xr-x | src/conf_mode/service_router-advert.py | 4 | ||||
-rw-r--r-- | src/services/api/graphql/recipes/session.py | 18 | ||||
-rwxr-xr-x | src/services/api/graphql/utils/schema_from_op_mode.py | 2 | ||||
-rw-r--r-- | src/services/api/graphql/utils/util.py | 31 |
16 files changed, 153 insertions, 121 deletions
diff --git a/op-mode-definitions/ipoe-server.xml.in b/op-mode-definitions/ipoe-server.xml.in index 89cefa08d..3aee303dc 100644 --- a/op-mode-definitions/ipoe-server.xml.in +++ b/op-mode-definitions/ipoe-server.xml.in @@ -4,12 +4,12 @@ <children> <node name="ipoe-server"> <properties> - <help>Clear IPoE server sessions or process</help> + <help>IPoE (Internet Protocol over Ethernet) server</help> </properties> <children> <node name="session"> <properties> - <help>Clear IPoE server session</help> + <help>Clear IPoE (Internet Protocol over Ethernet) server session</help> </properties> <children> <tagNode name="username"> @@ -49,7 +49,7 @@ <children> <node name="ipoe-server"> <properties> - <help>Show IPoE server status</help> + <help>Show IPoE (Internet Protocol over Ethernet) server status</help> </properties> <children> <leafNode name="sessions"> @@ -72,7 +72,7 @@ <children> <leafNode name="ipoe-server"> <properties> - <help>Restart IPoE server process</help> + <help>Restart IPoE (Internet Protocol over Ethernet) server process</help> </properties> <command>${vyos_op_scripts_dir}/ipoe-control.py --action="restart"</command> </leafNode> diff --git a/op-mode-definitions/nhrp.xml.in b/op-mode-definitions/nhrp.xml.in index 89508e2be..c10b111a7 100644 --- a/op-mode-definitions/nhrp.xml.in +++ b/op-mode-definitions/nhrp.xml.in @@ -43,7 +43,7 @@ <children> <node name="nhrp"> <properties> - <help>Show NHRP info</help> + <help>Show NHRP (Next Hop Resolution Protocol) information</help> </properties> <children> <leafNode name="interface"> diff --git a/op-mode-definitions/pptp-server.xml.in b/op-mode-definitions/pptp-server.xml.in index 59be68611..f6f8104d8 100644 --- a/op-mode-definitions/pptp-server.xml.in +++ b/op-mode-definitions/pptp-server.xml.in @@ -4,7 +4,7 @@ <children> <node name="pptp-server"> <properties> - <help>Show PPTP server information</help> + <help>Show PPTP (Point-to-Point Tunneling Protocol) server information</help> </properties> <children> <leafNode name="sessions"> diff --git a/op-mode-definitions/show-rpki.xml.in b/op-mode-definitions/show-rpki.xml.in index f593e4803..c1902ccec 100644 --- a/op-mode-definitions/show-rpki.xml.in +++ b/op-mode-definitions/show-rpki.xml.in @@ -4,7 +4,7 @@ <children> <node name="rpki"> <properties> - <help>Show RPKI information</help> + <help>Show RPKI (Resource Public Key Infrastructure) information</help> </properties> <children> <leafNode name="cache-connection"> diff --git a/op-mode-definitions/show-system.xml.in b/op-mode-definitions/show-system.xml.in index 6f05d0c12..60ed28b6f 100644 --- a/op-mode-definitions/show-system.xml.in +++ b/op-mode-definitions/show-system.xml.in @@ -53,7 +53,7 @@ <properties> <help>Show CPU information</help> </properties> - <command>${vyos_op_scripts_dir}/show_cpu.py</command> + <command>${vyos_op_scripts_dir}/cpu.py show</command> </leafNode> <leafNode name="kernel-messages"> <properties> diff --git a/op-mode-definitions/show-vrf.xml.in b/op-mode-definitions/show-vrf.xml.in index 0e0370445..9728eb1fa 100644 --- a/op-mode-definitions/show-vrf.xml.in +++ b/op-mode-definitions/show-vrf.xml.in @@ -4,7 +4,7 @@ <children> <node name="vrf"> <properties> - <help>Show VRF information</help> + <help>Show VRF (Virtual Routing and Forwarding) information</help> </properties> <command>${vyos_op_scripts_dir}/vrf.py show</command> </node> diff --git a/op-mode-definitions/show-zebra.xml.in b/op-mode-definitions/show-zebra.xml.in index b0ad37f49..69991a1d5 100644 --- a/op-mode-definitions/show-zebra.xml.in +++ b/op-mode-definitions/show-zebra.xml.in @@ -4,7 +4,7 @@ <children> <node name="zebra"> <properties> - <help>Zebra routing information</help> + <help>Show Zebra routing information</help> </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> <children> diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index a61666afc..20cc7de2a 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -222,24 +222,10 @@ def is_member(conf, interface, intftype=None): for intf in conf.list_nodes(base): member = base + [intf, 'member', 'interface', interface] if conf.exists(member): - member_type = Section.section(interface) - # Check if it's a VLAN (QinQ) interface - interface = interface.split('.') - if len(interface) == 3: - if conf.exists(['interfaces', member_type, interface[0], 'vif-s', interface[1], 'vif-c', interface[2]]): - tmp = conf.get_config_dict(['interfaces', member_type, interface[0]], - key_mangling=('-', '_'), get_first_key=True) - ret_val.update({intf : tmp}) - elif len(interface) == 2: - if conf.exists(['interfaces', member_type, interface[0], 'vif', interface[1]]): - tmp = conf.get_config_dict(['interfaces', member_type, interface[0]], - key_mangling=('-', '_'), get_first_key=True) - ret_val.update({intf : tmp}) - else: - if conf.exists(['interfaces', member_type, interface[0]]): - tmp = conf.get_config_dict(['interfaces', member_type, interface[0]], - key_mangling=('-', '_'), get_first_key=True) - ret_val.update({intf : tmp}) + tmp = conf.get_config_dict(member, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + ret_val.update({intf : tmp}) return ret_val diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index e4db69c1f..758967fbc 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -187,6 +187,11 @@ class BridgeIf(Interface): """ self.set_interface('vlan_filter', state) + # VLAN of bridge parent interface is always 1 + # VLAN 1 is the default VLAN for all unlabeled packets + cmd = f'bridge vlan add dev {self.ifname} vid 1 pvid untagged self' + self._cmd(cmd) + def set_multicast_querier(self, enable): """ Sets whether the bridge actively runs a multicast querier or not. When a @@ -293,30 +298,6 @@ class BridgeIf(Interface): vlan_filter = '1' if 'enable_vlan' in config else '0' self.set_vlan_filter(vlan_filter) - ifname = config['ifname'] - if int(vlan_filter): - add_vlan = [] - cur_vlan_ids = get_vlan_ids(ifname) - - tmp = dict_search('vif', config) - if tmp: - for vif, vif_config in tmp.items(): - add_vlan.append(vif) - - # Remove redundant VLANs from the system - for vlan in list_diff(cur_vlan_ids, add_vlan): - cmd = f'bridge vlan del dev {ifname} vid {vlan} self' - self._cmd(cmd) - - for vlan in add_vlan: - cmd = f'bridge vlan add dev {ifname} vid {vlan} self' - self._cmd(cmd) - - # VLAN of bridge parent interface is always 1 - # VLAN 1 is the default VLAN for all unlabeled packets - cmd = f'bridge vlan add dev {ifname} vid 1 pvid untagged self' - self._cmd(cmd) - tmp = dict_search('member.interface', config) if tmp: for interface, interface_config in tmp.items(): @@ -346,15 +327,13 @@ class BridgeIf(Interface): # set bridge port path cost if 'cost' in interface_config: - value = interface_config.get('cost') - lower.set_path_cost(value) + lower.set_path_cost(interface_config['cost']) # set bridge port path priority if 'priority' in interface_config: - value = interface_config.get('priority') - lower.set_path_priority(value) + lower.set_path_priority(interface_config['priority']) - if int(vlan_filter): + if 'enable_vlan' in config: add_vlan = [] native_vlan_id = None allowed_vlan_ids= [] @@ -384,6 +363,7 @@ class BridgeIf(Interface): for vlan in allowed_vlan_ids: cmd = f'bridge vlan add dev {interface} vid {vlan} master' self._cmd(cmd) + # Setting native VLAN to system if native_vlan_id: cmd = f'bridge vlan add dev {interface} vid {native_vlan_id} pvid untagged master' diff --git a/scripts/build-command-op-templates b/scripts/build-command-op-templates index d4515b8db..b008596dc 100755 --- a/scripts/build-command-op-templates +++ b/scripts/build-command-op-templates @@ -27,6 +27,7 @@ import copy import functools from lxml import etree as ET +from textwrap import fill # Defaults validator_dir = "/opt/vyatta/libexec/validators" @@ -123,13 +124,15 @@ def make_node_def(props, command): node_def = "" if "help" in props: - node_def += "help: {0}\n".format(props["help"]) + help = props["help"] + help = fill(help, width=64, subsequent_indent='\t\t\t') + node_def += f'help: {help}\n' if "comp_help" in props: - node_def += "allowed: {0}\n".format(props["comp_help"]) + node_def += f'allowed: {props["comp_help"]}\n' if command is not None: - node_def += "run: {0}\n".format(command.text) + node_def += f'run: {command.text}\n' if debug: - print("The contents of the node.def file:\n", node_def) + print('Contents of the node.def file:\n', node_def) return node_def diff --git a/smoketest/scripts/cli/test_interfaces_bridge.py b/smoketest/scripts/cli/test_interfaces_bridge.py index 664dc48bc..8f711af20 100755 --- a/smoketest/scripts/cli/test_interfaces_bridge.py +++ b/smoketest/scripts/cli/test_interfaces_bridge.py @@ -224,6 +224,51 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase): super().test_vif_8021q_mtu_limits() def test_bridge_vlan_filter(self): + def _verify_members() -> None: + # check member interfaces are added on the bridge + for interface in self._interfaces: + bridge_members = [] + for tmp in glob(f'/sys/class/net/{interface}/lower_*'): + bridge_members.append(os.path.basename(tmp).replace('lower_', '')) + + # We can not use assertListEqual() b/c the position of the interface + # names within the list is not fixed + self.assertEqual(len(self._members), len(bridge_members)) + for member in self._members: + self.assertIn(member, bridge_members) + + def _check_vlan_filter() -> None: + for interface in self._interfaces: + tmp = cmd(f'bridge -j vlan show dev {interface}') + tmp = json.loads(tmp) + self.assertIsNotNone(tmp) + + for interface_status in tmp: + ifname = interface_status['ifname'] + for interface in self._members: + vlan_success = 0; + if interface == ifname: + vlans_status = interface_status['vlans'] + for vlan_status in vlans_status: + vlan_id = vlan_status['vlan'] + flag_num = 0 + if 'flags' in vlan_status: + flags = vlan_status['flags'] + for flag in flags: + flag_num = flag_num +1 + if vlan_id == 2: + if flag_num == 0: + vlan_success = vlan_success + 1 + else: + for id in range(4,10): + if vlan_id == id: + if flag_num == 0: + vlan_success = vlan_success + 1 + if vlan_id >= 101: + if flag_num == 2: + vlan_success = vlan_success + 1 + self.assertGreaterEqual(vlan_success, 7) + vif_vlan = 2 # Add member interface to bridge and set VLAN filter for interface in self._interfaces: @@ -247,61 +292,50 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase): # commit config self.cli_commit() - # Detect the vlan filter function + # Verify correct setting of VLAN filter function for interface in self._interfaces: tmp = read_file(f'/sys/class/net/{interface}/bridge/vlan_filtering') self.assertEqual(tmp, '1') - # Execute the program to obtain status information - json_data = cmd('bridge -j vlan show', shell=True) - vlan_filter_status = None - vlan_filter_status = json.loads(json_data) - - if vlan_filter_status is not None: - for interface_status in vlan_filter_status: - ifname = interface_status['ifname'] - for interface in self._members: - vlan_success = 0; - if interface == ifname: - vlans_status = interface_status['vlans'] - for vlan_status in vlans_status: - vlan_id = vlan_status['vlan'] - flag_num = 0 - if 'flags' in vlan_status: - flags = vlan_status['flags'] - for flag in flags: - flag_num = flag_num +1 - if vlan_id == 2: - if flag_num == 0: - vlan_success = vlan_success + 1 - else: - for id in range(4,10): - if vlan_id == id: - if flag_num == 0: - vlan_success = vlan_success + 1 - if vlan_id >= 101: - if flag_num == 2: - vlan_success = vlan_success + 1 - if vlan_success >= 7: - self.assertTrue(True) - else: - self.assertTrue(False) + # Execute the program to obtain status information and verify proper + # VLAN filter setup + _check_vlan_filter() - else: - self.assertTrue(False) + # check member interfaces are added on the bridge + _verify_members() + + # change member interface description to trigger config update, + # VLANs must still exist (T4565) + for interface in self._interfaces: + for member in self._members: + self.cli_set(['interfaces', Section.section(member), member, 'description', f'foo {member}']) + + # commit config + self.cli_commit() # check member interfaces are added on the bridge + _verify_members() + + # Execute the program to obtain status information and verify proper + # VLAN filter setup + _check_vlan_filter() + + # delete all members + for interface in self._interfaces: + self.cli_delete(self._base_path + [interface, 'member']) + + # commit config + self.cli_commit() + + # verify member interfaces are no longer assigned on the bridge for interface in self._interfaces: bridge_members = [] for tmp in glob(f'/sys/class/net/{interface}/lower_*'): bridge_members.append(os.path.basename(tmp).replace('lower_', '')) + self.assertNotEqual(len(self._members), len(bridge_members)) for member in self._members: - self.assertIn(member, bridge_members) - - # delete all members - for interface in self._interfaces: - self.cli_delete(self._base_path + [interface, 'member']) + self.assertNotIn(member, bridge_members) def test_bridge_vif_members(self): diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py index cd0d9003b..b961408db 100755 --- a/src/conf_mode/interfaces-bridge.py +++ b/src/conf_mode/interfaces-bridge.py @@ -61,7 +61,7 @@ def get_config(config=None): else: bridge.update({'member' : {'interface_remove' : tmp }}) - if dict_search('member.interface', bridge): + if dict_search('member.interface', bridge) != None: # XXX: T2665: we need a copy of the dict keys for iteration, else we will get: # RuntimeError: dictionary changed size during iteration for interface in list(bridge['member']['interface']): @@ -103,6 +103,14 @@ def get_config(config=None): if 'enable_vlan' in bridge and tmp: bridge['member']['interface'][interface].update({'has_vlan' : ''}) + # delete empty dictionary keys - no need to run code paths if nothing is there to do + if 'member' in bridge: + if 'interface' in bridge['member'] and len(bridge['member']['interface']) == 0: + del bridge['member']['interface'] + + if len(bridge['member']) == 0: + del bridge['member'] + return bridge def verify(bridge): diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py index ff7caaa84..1b8377a4a 100755 --- a/src/conf_mode/service_router-advert.py +++ b/src/conf_mode/service_router-advert.py @@ -90,8 +90,8 @@ def verify(rtradv): if preferred_lifetime == 'infinity': preferred_lifetime = 4294967295 - if not (int(valid_lifetime) > int(preferred_lifetime)): - raise ConfigError('Prefix valid-lifetime must be greater then preferred-lifetime') + if not (int(valid_lifetime) >= int(preferred_lifetime)): + raise ConfigError('Prefix valid-lifetime must be greater then or equal to preferred-lifetime') if 'name_server_lifetime' in interface_config: # man page states: diff --git a/src/services/api/graphql/recipes/session.py b/src/services/api/graphql/recipes/session.py index 6b580af01..ac185beb7 100644 --- a/src/services/api/graphql/recipes/session.py +++ b/src/services/api/graphql/recipes/session.py @@ -40,7 +40,7 @@ class Session: try: with open(op_mode_include_file) as f: - self._op_mode_list = f.read() + self._op_mode_list = json.loads(f.read()) except Exception: self._op_mode_list = None @@ -171,11 +171,11 @@ class Session: # handle the case that the op-mode file contains underscores: if op_mode_list is None: raise FileNotFoundError(f"No op-mode file list at '{op_mode_include_file}'") - (func_name, basename) = split_compound_op_mode_name(name, op_mode_list) - if basename == '': - raise FileNotFoundError(f"No op-mode file basename in string '{name}'") + (func_name, scriptname) = split_compound_op_mode_name(name, op_mode_list) + if scriptname == '': + raise FileNotFoundError(f"No op-mode file named in string '{name}'") - mod = load_op_mode_as_module(f'{basename}.py') + mod = load_op_mode_as_module(f'{scriptname}') func = getattr(mod, func_name) if len(list(data)) > 0: res = func(True, **data) @@ -193,11 +193,11 @@ class Session: # handle the case that the op-mode file name contains underscores: if op_mode_list is None: raise FileNotFoundError(f"No op-mode file list at '{op_mode_include_file}'") - (func_name, basename) = split_compound_op_mode_name(name, op_mode_list) - if basename == '': - raise FileNotFoundError(f"No op-mode file basename in string '{name}'") + (func_name, scriptname) = split_compound_op_mode_name(name, op_mode_list) + if scriptname == '': + raise FileNotFoundError(f"No op-mode file named in string '{name}'") - mod = load_op_mode_as_module(f'{basename}.py') + mod = load_op_mode_as_module(f'{scriptname}') func = getattr(mod, func_name) if len(list(data)) > 0: res = func(**data) diff --git a/src/services/api/graphql/utils/schema_from_op_mode.py b/src/services/api/graphql/utils/schema_from_op_mode.py index cdde5f187..d27586747 100755 --- a/src/services/api/graphql/utils/schema_from_op_mode.py +++ b/src/services/api/graphql/utils/schema_from_op_mode.py @@ -138,7 +138,7 @@ def generate_op_mode_definitions(): op_mode_files = json.load(f) for file in op_mode_files: - basename = os.path.splitext(file)[0] + basename = os.path.splitext(file)[0].replace('-', '_') module = load_as_module(basename, os.path.join(OP_MODE_PATH, file)) funcs = getmembers(module, isfunction) diff --git a/src/services/api/graphql/utils/util.py b/src/services/api/graphql/utils/util.py index e3dea31bf..073126853 100644 --- a/src/services/api/graphql/utils/util.py +++ b/src/services/api/graphql/utils/util.py @@ -27,7 +27,7 @@ def load_as_module(name: str, path: str): def load_op_mode_as_module(name: str): path = os.path.join(directories['op_mode'], name) - name = os.path.splitext(name)[0] + name = os.path.splitext(name)[0].replace('-', '_') return load_as_module(name, path) def is_op_mode_function_name(name): @@ -40,16 +40,37 @@ def is_show_function_name(name): return True return False +def _nth_split(delim: str, n: int, s: str): + groups = s.split(delim) + l = len(groups) + if n > l-1 or n < 1: + return (s, '') + return (delim.join(groups[:n]), delim.join(groups[n:])) + def _nth_rsplit(delim: str, n: int, s: str): groups = s.split(delim) l = len(groups) - if n > l-1: - return ('', s) + if n > l-1 or n < 1: + return (s, '') return (delim.join(groups[:l-n]), delim.join(groups[l-n:])) +# Since we have mangled possible hyphens in the file name while constructing +# the snake case of the query/mutation name, we will need to recover the +# file name by searching with mangling: +def _filter_on_mangled(test): + def func(elem): + mangle = os.path.splitext(elem)[0].replace('-', '_') + return test == mangle + return func + +# Find longest name in concatenated string that matches the basename of an +# op-mode script. Should one prefer to concatenate in the reverse order +# (script_name + '_' + function_name), use _nth_rsplit. def split_compound_op_mode_name(name: str, files: list): for i in range(1, name.count('_') + 1): - pair = _nth_rsplit('_', i, name) - if pair[1] in files: + pair = _nth_split('_', i, name) + f = list(filter(_filter_on_mangled(pair[1]), files)) + if f: + pair = (pair[0], f[0]) return pair return (name, '') |