summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--op-mode-definitions/ipoe-server.xml.in8
-rw-r--r--op-mode-definitions/nhrp.xml.in2
-rw-r--r--op-mode-definitions/pptp-server.xml.in2
-rw-r--r--op-mode-definitions/show-rpki.xml.in2
-rw-r--r--op-mode-definitions/show-system.xml.in2
-rw-r--r--op-mode-definitions/show-vrf.xml.in2
-rw-r--r--op-mode-definitions/show-zebra.xml.in2
-rw-r--r--python/vyos/configdict.py22
-rw-r--r--python/vyos/ifconfig/bridge.py38
-rwxr-xr-xscripts/build-command-op-templates11
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_bridge.py118
-rwxr-xr-xsrc/conf_mode/interfaces-bridge.py10
-rwxr-xr-xsrc/conf_mode/service_router-advert.py4
-rw-r--r--src/services/api/graphql/recipes/session.py18
-rwxr-xr-xsrc/services/api/graphql/utils/schema_from_op_mode.py2
-rw-r--r--src/services/api/graphql/utils/util.py31
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, '')