Skip to content

Commit a44ba42

Browse files
authored
Merge pull request #52 from stackhpc/yoga-trunking
Add VLAN aware VMs support (yoga)
2 parents 4b82022 + 9ad464f commit a44ba42

File tree

13 files changed

+460
-6
lines changed

13 files changed

+460
-6
lines changed

networking_generic_switch/devices/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
{'name': 'ngs_network_name_format', 'default': '{network_id}'},
4343
# If false, ngs will not add and delete VLANs from switches
4444
{'name': 'ngs_manage_vlans', 'default': True},
45+
{'name': 'vlan_translation_supported', 'default': False}
4546
]
4647

4748

@@ -144,6 +145,10 @@ def add_network(self, segmentation_id, network_id):
144145
def del_network(self, segmentation_id, network_id):
145146
pass
146147

148+
def plug_port_to_network_trunk(self, port_id, segmentation_id,
149+
trunk_details=None, vtr=False):
150+
pass
151+
147152
@abc.abstractmethod
148153
def plug_port_to_network(self, port_id, segmentation_id):
149154
pass

networking_generic_switch/devices/netmiko_devices/__init__.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ class NetmikoSwitch(devices.GenericSwitchDevice):
8989

9090
SAVE_CONFIGURATION = None
9191

92+
SET_NATIVE_VLAN = None
93+
94+
ALLOW_NETWORK_ON_TRUNK = None
95+
9296
ERROR_MSG_PATTERNS = ()
9397
"""Sequence of error message patterns.
9498
@@ -251,6 +255,28 @@ def del_network(self, segmentation_id, network_id):
251255
network_name=network_name)
252256
return self.send_commands_to_device(cmds)
253257

258+
@check_output('plug port trunk')
259+
def plug_port_to_network_trunk(self, port, segmentation_id,
260+
trunk_details=None, vtr=False):
261+
cmd_set = []
262+
vts = self.ngs_config.get('vlan_translation_supported', False)
263+
# NOTE(vsaienko) Always use vlan translation if it is supported.
264+
if vts:
265+
cmd_set.extend(self.get_trunk_port_cmds_vlan_translation(
266+
port, segmentation_id, trunk_details))
267+
else:
268+
if vtr:
269+
msg = ("Cannot bind_port VLAN aware port as switch %s "
270+
"doesn't support VLAN translation. "
271+
"But it is required.") % self.config['ip']
272+
raise exc.GenericSwitchNotSupported(error=msg)
273+
else:
274+
cmd_set.extend(
275+
self.get_trunk_port_cmds_no_vlan_translation(
276+
port, segmentation_id, trunk_details))
277+
278+
self.send_commands_to_device(cmd_set)
279+
254280
@check_output('plug port')
255281
def plug_port_to_network(self, port, segmentation_id):
256282
cmds = []
@@ -384,3 +410,22 @@ def check_output(self, output, operation):
384410
raise exc.GenericSwitchNetmikoConfigError(
385411
config=device_utils.sanitise_config(self.config),
386412
error=msg)
413+
414+
def get_trunk_port_cmds_no_vlan_translation(self, port_id,
415+
segmentation_id,
416+
trunk_details):
417+
cmd_set = []
418+
cmd_set.extend(
419+
self._format_commands(self.SET_NATIVE_VLAN,
420+
port=port_id,
421+
segmentation_id=segmentation_id))
422+
for sub_port in trunk_details.get('sub_ports'):
423+
cmd_set.extend(
424+
self._format_commands(
425+
self.ALLOW_NETWORK_ON_TRUNK, port=port_id,
426+
segmentation_id=sub_port['segmentation_id']))
427+
return cmd_set
428+
429+
def get_trunk_port_cmds_vlan_translation(self, port_id, segmentation_id,
430+
trunk_details):
431+
pass

networking_generic_switch/devices/netmiko_devices/arista.py

Lines changed: 12 additions & 0 deletions
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+
)

networking_generic_switch/devices/netmiko_devices/cisco.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,15 @@ class CiscoIos(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+
)

networking_generic_switch/devices/netmiko_devices/dell.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,78 @@
1818
from networking_generic_switch import exceptions as exc
1919

2020

21+
class DellOS10(netmiko_devices.NetmikoSwitch):
22+
"""Netmiko device driver for Dell PowerSwitch switches."""
23+
24+
ADD_NETWORK = (
25+
"interface vlan {segmentation_id}",
26+
"exit",
27+
)
28+
29+
DELETE_NETWORK = (
30+
"no interface vlan {segmentation_id}",
31+
"exit",
32+
)
33+
34+
PLUG_PORT_TO_NETWORK = (
35+
"interface {port}",
36+
"switchport mode access",
37+
"switchport access vlan {segmentation_id}",
38+
"exit",
39+
)
40+
41+
DELETE_PORT = (
42+
"interface {port}",
43+
"no switchport access vlan",
44+
"exit",
45+
)
46+
47+
ADD_NETWORK_TO_TRUNK = (
48+
"interface {port}",
49+
"switchport mode trunk",
50+
"switchport trunk allowed vlan {segmentation_id}",
51+
"exit",
52+
)
53+
54+
REMOVE_NETWORK_FROM_TRUNK = (
55+
"interface {port}",
56+
"no switchport trunk allowed vlan {segmentation_id}",
57+
"exit",
58+
)
59+
60+
ENABLE_PORT = (
61+
"interface {port}",
62+
"no shutdown",
63+
"exit",
64+
)
65+
66+
DISABLE_PORT = (
67+
"interface {port}",
68+
"shutdown",
69+
"exit",
70+
)
71+
72+
SET_NATIVE_VLAN = (
73+
'interface {port}',
74+
# Clean all the old trunked vlans by switching to access mode first
75+
'switchport mode access',
76+
'switchport mode trunk',
77+
'switchport access vlan {segmentation_id}',
78+
)
79+
80+
ALLOW_NETWORK_ON_TRUNK = (
81+
'interface {port}',
82+
'switchport trunk allowed vlan {segmentation_id}'
83+
)
84+
85+
ERROR_MSG_PATTERNS = ()
86+
"""Sequence of error message patterns.
87+
88+
Sequence of re.RegexObject objects representing patterns to check for in
89+
device output that indicate a failure to apply configuration.
90+
"""
91+
92+
2193
class DellNos(netmiko_devices.NetmikoSwitch):
2294
"""Netmiko device driver for Dell Force10 switches."""
2395

networking_generic_switch/exceptions.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,8 @@ class GenericSwitchNetmikoConnectError(GenericSwitchException):
4949

5050
class GenericSwitchNetmikoConfigError(GenericSwitchException):
5151
message = _("Netmiko configuration error: %(config)s, error: %(error)s")
52+
53+
54+
class GenericSwitchNotSupported(GenericSwitchException):
55+
message = _("Requested feature is not supported by "
56+
"networking-generic-switch. %(error)s")

networking_generic_switch/generic_switch_mech.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from networking_generic_switch import config as gsw_conf
2424
from networking_generic_switch import devices
2525
from networking_generic_switch.devices import utils as device_utils
26+
from networking_generic_switch import exceptions as ngs_exc
2627

2728
LOG = logging.getLogger(__name__)
2829

@@ -390,6 +391,14 @@ def delete_port_postcommit(self, context):
390391
if self._is_port_bound(port):
391392
self._unplug_port_from_network(port, context.network.current)
392393

394+
def _is_vlan_translation_required(self, trunk_details):
395+
"""Check if vlan translation is required to configure specific trunk.
396+
397+
:returns: True if vlan translation is required, False otherwise.
398+
"""
399+
# FIXME: removed for simplicity
400+
return False
401+
393402
def bind_port(self, context):
394403
"""Attempt to bind a port.
395404
@@ -442,7 +451,6 @@ def bind_port(self, context):
442451
# of the links should be processed.
443452
if not self._is_link_valid(port, network):
444453
return
445-
446454
is_802_3ad = self._is_802_3ad(port)
447455
for link in local_link_information:
448456
port_id = link.get('port_id')
@@ -455,15 +463,39 @@ def bind_port(self, context):
455463
segments = context.segments_to_bind
456464
# If segmentation ID is None, set vlan 1
457465
segmentation_id = segments[0].get('segmentation_id') or 1
466+
trunk_details = port.get('trunk_details', {})
458467
LOG.debug("Putting port %(port_id)s on %(switch_info)s "
459468
"to vlan: %(segmentation_id)s",
460469
{'port_id': port_id, 'switch_info': switch_info,
461470
'segmentation_id': segmentation_id})
462471
# Move port to network
463-
if is_802_3ad and hasattr(switch, 'plug_bond_to_network'):
464-
switch.plug_bond_to_network(port_id, segmentation_id)
465-
else:
466-
switch.plug_port_to_network(port_id, segmentation_id)
472+
# START
473+
try:
474+
if trunk_details:
475+
vtr = self._is_vlan_translation_required(trunk_details)
476+
switch.plug_port_to_network_trunk(
477+
port_id, segmentation_id, trunk_details, vtr)
478+
elif (is_802_3ad
479+
and hasattr(switch, 'plug_bond_to_network')):
480+
switch.plug_bond_to_network(port_id, segmentation_id)
481+
else:
482+
switch.plug_port_to_network(
483+
port_id, segmentation_id)
484+
except ngs_exc.GenericSwitchNotSupported as e:
485+
LOG.warning("Operation is not supported by "
486+
"networking-generic-switch. %(err)s)",
487+
{'err': e})
488+
raise e
489+
except Exception as e:
490+
LOG.error("Failed to bind port %(port_id)s in "
491+
"segment %(segment_id)s on device "
492+
"%(device)s due to error %(err)s",
493+
{'port_id': port['id'],
494+
'device': switch_info,
495+
'segment_id': segmentation_id,
496+
'err': e})
497+
raise e
498+
# END
467499
LOG.info("Successfully bound port %(port_id)s in segment "
468500
"%(segment_id)s on device %(device)s",
469501
{'port_id': port['id'], 'device': switch_info,

networking_generic_switch/tests/unit/netmiko/test_arista_eos.py

Lines changed: 39 additions & 0 deletions
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

@@ -57,6 +59,43 @@ def test_delete_port(self, mock_exec):
5759
'no switchport mode trunk',
5860
'switchport trunk allowed vlan none'])
5961

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

networking_generic_switch/tests/unit/netmiko/test_cisco_ios.py

Lines changed: 39 additions & 0 deletions
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

@@ -56,6 +58,43 @@ def test_delete_port(self, mock_exec):
5658
['interface 3333', 'no switchport access vlan 33',
5759
'no switchport mode trunk', 'switchport trunk allowed vlan none'])
5860

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

0 commit comments

Comments
 (0)