Skip to content

Commit 3b1bf80

Browse files
authored
Merge pull request #102 from stackhpc/2024.1-to-master
Forward porting from stackhpc/2024.1 to stackhpc/master
2 parents 77032ea + 3192ba9 commit 3b1bf80

15 files changed

+480
-7
lines changed

Diff for: networking_generic_switch/devices/__init__.py

+5
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
{'name': 'ngs_network_name_format', 'default': '{network_id}'},
4747
# If false, ngs will not add and delete VLANs from switches
4848
{'name': 'ngs_manage_vlans', 'default': True},
49+
{'name': 'vlan_translation_supported', 'default': False},
4950
# If False, ngs will skip saving configuration on devices
5051
{'name': 'ngs_save_configuration', 'default': True},
5152
# When true try to batch up in flight switch requests
@@ -222,6 +223,10 @@ def add_network(self, segmentation_id, network_id):
222223
def del_network(self, segmentation_id, network_id):
223224
pass
224225

226+
def plug_port_to_network_trunk(self, port_id, segmentation_id,
227+
trunk_details=None, vtr=False):
228+
pass
229+
225230
@abc.abstractmethod
226231
def plug_port_to_network(self, port_id, segmentation_id):
227232
pass

Diff for: networking_generic_switch/devices/netmiko_devices/__init__.py

+45
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ class NetmikoSwitch(devices.GenericSwitchDevice):
9090

9191
SAVE_CONFIGURATION = None
9292

93+
SET_NATIVE_VLAN = None
94+
95+
ALLOW_NETWORK_ON_TRUNK = None
96+
9397
ERROR_MSG_PATTERNS = ()
9498
"""Sequence of error message patterns.
9599
@@ -276,6 +280,28 @@ def del_network(self, segmentation_id, network_id):
276280
network_name=network_name)
277281
return self.send_commands_to_device(cmds)
278282

283+
@check_output('plug port trunk')
284+
def plug_port_to_network_trunk(self, port, segmentation_id,
285+
trunk_details=None, vtr=False):
286+
cmd_set = []
287+
vts = self.ngs_config.get('vlan_translation_supported', False)
288+
# NOTE(vsaienko) Always use vlan translation if it is supported.
289+
if vts:
290+
cmd_set.extend(self.get_trunk_port_cmds_vlan_translation(
291+
port, segmentation_id, trunk_details))
292+
else:
293+
if vtr:
294+
msg = ("Cannot bind_port VLAN aware port as switch %s "
295+
"doesn't support VLAN translation. "
296+
"But it is required.") % self.config['ip']
297+
raise exc.GenericSwitchNotSupported(error=msg)
298+
else:
299+
cmd_set.extend(
300+
self.get_trunk_port_cmds_no_vlan_translation(
301+
port, segmentation_id, trunk_details))
302+
303+
self.send_commands_to_device(cmd_set)
304+
279305
@check_output('plug port')
280306
def plug_port_to_network(self, port, segmentation_id):
281307
cmds = []
@@ -417,3 +443,22 @@ def check_output(self, output, operation):
417443
raise exc.GenericSwitchNetmikoConfigError(
418444
config=device_utils.sanitise_config(self.config),
419445
error=msg)
446+
447+
def get_trunk_port_cmds_no_vlan_translation(self, port_id,
448+
segmentation_id,
449+
trunk_details):
450+
cmd_set = []
451+
cmd_set.extend(
452+
self._format_commands(self.SET_NATIVE_VLAN,
453+
port=port_id,
454+
segmentation_id=segmentation_id))
455+
for sub_port in trunk_details.get('sub_ports'):
456+
cmd_set.extend(
457+
self._format_commands(
458+
self.ALLOW_NETWORK_ON_TRUNK, port=port_id,
459+
segmentation_id=sub_port['segmentation_id']))
460+
return cmd_set
461+
462+
def get_trunk_port_cmds_vlan_translation(self, port_id, segmentation_id,
463+
trunk_details):
464+
pass

Diff for: networking_generic_switch/devices/netmiko_devices/arista.py

+12
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,15 @@ class AristaEos(netmiko_devices.NetmikoSwitch):
3737
'no switchport mode trunk',
3838
'switchport trunk allowed vlan none'
3939
)
40+
41+
SET_NATIVE_VLAN = (
42+
'interface {port}',
43+
'switchport mode trunk',
44+
'switchport trunk native vlan {segmentation_id}',
45+
'switchport trunk allowed vlan add {segmentation_id}'
46+
)
47+
48+
ALLOW_NETWORK_ON_TRUNK = (
49+
'interface {port}',
50+
'switchport trunk allowed vlan add {segmentation_id}'
51+
)

Diff for: networking_generic_switch/devices/netmiko_devices/cisco.py

+12
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,18 @@ class CiscoIos(netmiko_devices.NetmikoSwitch):
3939
'switchport trunk allowed vlan none'
4040
)
4141

42+
SET_NATIVE_VLAN = (
43+
'interface {port}',
44+
'switchport mode trunk',
45+
'switchport trunk native vlan {segmentation_id}',
46+
'switchport trunk allowed vlan add {segmentation_id}'
47+
)
48+
49+
ALLOW_NETWORK_ON_TRUNK = (
50+
'interface {port}',
51+
'switchport trunk allowed vlan add {segmentation_id}'
52+
)
53+
4254

4355
class CiscoNxOS(netmiko_devices.NetmikoSwitch):
4456
"""Netmiko device driver for Cisco Nexus switches running NX-OS."""

Diff for: networking_generic_switch/devices/netmiko_devices/cumulus.py

+14
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ class CumulusNVUE(netmiko_devices.NetmikoSwitch):
117117
]
118118

119119
PLUG_PORT_TO_NETWORK = [
120+
'nv unset interface {port} bridge domain br_default vlan',
121+
'nv unset interface {port} bridge domain br_default untagged',
120122
'nv set interface {port} bridge domain br_default access '
121123
'{segmentation_id}',
122124
]
@@ -137,6 +139,18 @@ class CumulusNVUE(netmiko_devices.NetmikoSwitch):
137139
'nv config save',
138140
]
139141

142+
SET_NATIVE_VLAN = [
143+
'nv unset interface {port} bridge domain br_default access',
144+
'nv set interface {port} bridge domain br_default untagged '
145+
'{segmentation_id}',
146+
'nv set interface {port} bridge domain br_default vlan '
147+
'{segmentation_id}'
148+
]
149+
ALLOW_NETWORK_ON_TRUNK = [
150+
'nv set interface {port} bridge domain br_default vlan '
151+
'{segmentation_id}'
152+
]
153+
140154
ERROR_MSG_PATTERNS = [
141155
# Its tempting to add this error message, but as only one
142156
# bridge-access is allowed, we ignore that error for now:

Diff for: networking_generic_switch/devices/netmiko_devices/dell.py

+13
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,19 @@ class DellOS10(netmiko_devices.NetmikoSwitch):
7070
"exit",
7171
)
7272

73+
SET_NATIVE_VLAN = (
74+
'interface {port}',
75+
# Clean all the old trunked vlans by switching to access mode first
76+
'switchport mode access',
77+
'switchport mode trunk',
78+
'switchport access vlan {segmentation_id}',
79+
)
80+
81+
ALLOW_NETWORK_ON_TRUNK = (
82+
'interface {port}',
83+
'switchport trunk allowed vlan {segmentation_id}'
84+
)
85+
7386
ERROR_MSG_PATTERNS = ()
7487
"""Sequence of error message patterns.
7588

Diff for: networking_generic_switch/exceptions.py

+5
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,8 @@ class GenericSwitchNetmikoConfigError(GenericSwitchException):
5353

5454
class GenericSwitchBatchError(GenericSwitchException):
5555
message = _("Batching error: %(device)s, error: %(error)s")
56+
57+
58+
class GenericSwitchNotSupported(GenericSwitchException):
59+
message = _("Requested feature is not supported by "
60+
"networking-generic-switch. %(error)s")

Diff for: networking_generic_switch/generic_switch_mech.py

+33-4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from networking_generic_switch import config as gsw_conf
2525
from networking_generic_switch import devices
2626
from networking_generic_switch.devices import utils as device_utils
27+
from networking_generic_switch import exceptions as ngs_exc
2728

2829
LOG = logging.getLogger(__name__)
2930

@@ -371,15 +372,35 @@ def update_port_postcommit(self, context):
371372

372373
# If segmentation ID is None, set vlan 1
373374
segmentation_id = network.get('provider:segmentation_id') or 1
375+
trunk_details = port.get('trunk_details', {})
374376
LOG.debug("Putting switch port %(switch_port)s on "
375377
"%(switch_info)s in vlan %(segmentation_id)s",
376378
{'switch_port': port_id, 'switch_info': switch_info,
377379
'segmentation_id': segmentation_id})
378380
# Move port to network
379-
if is_802_3ad:
380-
switch.plug_bond_to_network(port_id, segmentation_id)
381-
else:
382-
switch.plug_port_to_network(port_id, segmentation_id)
381+
try:
382+
if trunk_details:
383+
vtr = self._is_vlan_translation_required(trunk_details)
384+
switch.plug_port_to_network_trunk(
385+
port_id, segmentation_id, trunk_details, vtr)
386+
elif is_802_3ad:
387+
switch.plug_bond_to_network(port_id, segmentation_id)
388+
else:
389+
switch.plug_port_to_network(port_id, segmentation_id)
390+
except ngs_exc.GenericSwitchNotSupported as e:
391+
LOG.warning("Operation is not supported by "
392+
"networking-generic-switch. %(err)s)",
393+
{'err': e})
394+
raise e
395+
except Exception as e:
396+
LOG.error("Failed to plug port %(port_id)s in "
397+
"segment %(segment_id)s on device "
398+
"%(device)s due to error %(err)s",
399+
{'port_id': port['id'],
400+
'device': switch_info,
401+
'segment_id': segmentation_id,
402+
'err': e})
403+
raise e
383404
LOG.info("Successfully plugged port %(port_id)s in segment "
384405
"%(segment_id)s on device %(device)s",
385406
{'port_id': port['id'], 'device': switch_info,
@@ -424,6 +445,14 @@ def delete_port_postcommit(self, context):
424445
if self._is_port_bound(port):
425446
self._unplug_port_from_network(port, context.network.current)
426447

448+
def _is_vlan_translation_required(self, trunk_details):
449+
"""Check if vlan translation is required to configure specific trunk.
450+
451+
:returns: True if vlan translation is required, False otherwise.
452+
"""
453+
# FIXME: removed for simplicity
454+
return False
455+
427456
def bind_port(self, context):
428457
"""Attempt to bind a port.
429458

Diff for: networking_generic_switch/tests/unit/netmiko/test_arista_eos.py

+39
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
from unittest import mock
1616

17+
from neutron.plugins.ml2 import driver_context
18+
1719
from networking_generic_switch.devices.netmiko_devices import arista
1820
from networking_generic_switch.tests.unit.netmiko import test_netmiko_base
1921

@@ -60,6 +62,43 @@ def test_delete_port(self, mock_exec):
6062
'no switchport mode trunk',
6163
'switchport trunk allowed vlan none'])
6264

65+
def test_get_trunk_port_cmds_no_vlan_translation(self):
66+
mock_context = mock.create_autospec(driver_context.PortContext)
67+
self.switch.ngs_config['vlan_translation_supported'] = False
68+
trunk_details = {'trunk_id': 'aaa-bbb-ccc-ddd',
69+
'sub_ports': [{'segmentation_id': 130,
70+
'port_id': 'aaa-bbb-ccc-ddd',
71+
'segmentation_type': 'vlan',
72+
'mac_address': u'fa:16:3e:1c:c2:7e'}]}
73+
mock_context.current = {'binding:profile':
74+
{'local_link_information':
75+
[
76+
{
77+
'switch_info': 'foo',
78+
'port_id': '2222'
79+
}
80+
]
81+
},
82+
'binding:vnic_type': 'baremetal',
83+
'id': 'aaaa-bbbb-cccc',
84+
'trunk_details': trunk_details}
85+
mock_context.network = mock.Mock()
86+
mock_context.network.current = {'provider:segmentation_id': 123}
87+
mock_context.segments_to_bind = [
88+
{
89+
'segmentation_id': 777,
90+
'id': 123
91+
}
92+
]
93+
res = self.switch.get_trunk_port_cmds_no_vlan_translation(
94+
'2222', 777, trunk_details)
95+
self.assertEqual(['interface 2222', 'switchport mode trunk',
96+
'switchport trunk native vlan 777',
97+
'switchport trunk allowed vlan add 777',
98+
'interface 2222',
99+
'switchport trunk allowed vlan add 130'],
100+
res)
101+
63102
def test__format_commands(self):
64103
cmd_set = self.switch._format_commands(
65104
arista.AristaEos.ADD_NETWORK,

Diff for: networking_generic_switch/tests/unit/netmiko/test_cisco_ios.py

+39
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
from unittest import mock
1616

17+
from neutron.plugins.ml2 import driver_context
18+
1719
from networking_generic_switch.devices.netmiko_devices import cisco
1820
from networking_generic_switch.tests.unit.netmiko import test_netmiko_base
1921

@@ -59,6 +61,43 @@ def test_delete_port(self, mock_exec):
5961
['interface 3333', 'no switchport access vlan 33',
6062
'no switchport mode trunk', 'switchport trunk allowed vlan none'])
6163

64+
def test_get_trunk_port_cmds_no_vlan_translation(self):
65+
mock_context = mock.create_autospec(driver_context.PortContext)
66+
self.switch.ngs_config['vlan_translation_supported'] = True
67+
trunk_details = {'trunk_id': 'aaa-bbb-ccc-ddd',
68+
'sub_ports': [{'segmentation_id': 130,
69+
'port_id': 'aaa-bbb-ccc-ddd',
70+
'segmentation_type': 'vlan',
71+
'mac_address': u'fa:16:3e:1c:c2:7e'}]}
72+
mock_context.current = {'binding:profile':
73+
{'local_link_information':
74+
[
75+
{
76+
'switch_info': 'foo',
77+
'port_id': '2222'
78+
}
79+
]
80+
},
81+
'binding:vnic_type': 'baremetal',
82+
'id': 'aaaa-bbbb-cccc',
83+
'trunk_details': trunk_details}
84+
mock_context.network = mock.Mock()
85+
mock_context.network.current = {'provider:segmentation_id': 123}
86+
mock_context.segments_to_bind = [
87+
{
88+
'segmentation_id': 777,
89+
'id': 123
90+
}
91+
]
92+
res = self.switch.get_trunk_port_cmds_no_vlan_translation(
93+
'2222', 777, trunk_details)
94+
self.assertEqual(['interface 2222', 'switchport mode trunk',
95+
'switchport trunk native vlan 777',
96+
'switchport trunk allowed vlan add 777',
97+
'interface 2222',
98+
'switchport trunk allowed vlan add 130'],
99+
res)
100+
62101
def test__format_commands(self):
63102
cmd_set = self.switch._format_commands(
64103
cisco.CiscoIos.ADD_NETWORK,

0 commit comments

Comments
 (0)