Skip to content

Commit

Permalink
Add vlan aware VMs support
Browse files Browse the repository at this point in the history
With this patch ngs starts supporting attaching of trunk port to
baremetal server. Only VLAN Neutron network is supported.

There are two ways to configure trunk port:

 * C1: When segmentation details for trunk ports are inherited from
   Neutron network. VLAN translation support is not required.
   Added implementation for cisco, arista, OVS on Linux.

 * C2: When user set segmentation details for trunk port explicitly.
   Switch should support VLAN translation for this case.
   Implement only for OVS on Linux, experimental.

NetmikoSwitch.plug_port_to_network() is deprecated. New bind_port()
should be used instead.

New switch config option: vlan_translation_supported was introduced.
This option defines if switch support vlan translation which affect
the way how trunk is configured.

Change-Id: If084382f4c17438e5142f51f88d6e436f8b85082
(cherry picked from commit 1163a1f)
(cherry picked from commit 7dfe3e2)
(cherry picked from commit c54e9ba)
(cherry picked from commit 701e93f)
  • Loading branch information
jumpojoy authored and mnasiadka committed Sep 7, 2023
1 parent 6dcc83c commit 0196f83
Show file tree
Hide file tree
Showing 13 changed files with 349 additions and 7 deletions.
2 changes: 1 addition & 1 deletion networking_generic_switch/devices/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def del_network(self, segmentation_id, network_id):
pass

@abc.abstractmethod
def plug_port_to_network(self, port_id, segmentation_id):
def plug_port_to_network(self, port_id, segmentation_id, trunk_details=None, vtr=False):
pass

@abc.abstractmethod
Expand Down
41 changes: 41 additions & 0 deletions networking_generic_switch/devices/netmiko_devices/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ class NetmikoSwitch(devices.GenericSwitchDevice):

SAVE_CONFIGURATION = None

SET_NATIVE_VLAN = None

ALLOW_NETWORK_ON_TRUNK = None

ERROR_MSG_PATTERNS = ()
"""Sequence of error message patterns.
Expand Down Expand Up @@ -276,6 +280,27 @@ def del_network(self, segmentation_id, network_id):
network_name=network_name)
return self.send_commands_to_device(cmds)

@check_output('plug port trunk')
def plug_port_to_network_trunk(self, port, segmentation_id, trunk_details=None, vtr=False):
cmd_set = []
vts = self.ngs_config.get('vlan_translation_supported', False)
# NOTE(vsaienko) Always use vlan translation if it is supported.
if vts:
cmd_set.extend(self.get_trunk_port_cmds_vlan_translation(
port, segmentation_id, trunk_details))
else:
if vtr:
msg = _("Cannot bind_port VLAN aware port as switch %s "
"doesn't support VLAN translation. "
"But it is required.") % self.config['ip']
raise exc.GenericSwitchNotSupported(error=msg)
else:
cmd_set.extend(
self.get_trunk_port_cmds_no_vlan_translation(
port, segmentation_id, trunk_details))

self.send_commands_to_device(cmd_set)

@check_output('plug port')
def plug_port_to_network(self, port, segmentation_id):
cmds = []
Expand Down Expand Up @@ -409,3 +434,19 @@ def check_output(self, output, operation):
raise exc.GenericSwitchNetmikoConfigError(
config=device_utils.sanitise_config(self.config),
error=msg)

def get_trunk_port_cmds_no_vlan_translation(self, port_id, segmentation_id, trunk_details):
cmd_set = []
cmd_set.extend(
self._format_commands(self.SET_NATIVE_VLAN,
port=port_id,
segmentation_id=segmentation_id))
for sub_port in trunk_details.get('sub_ports'):
cmd_set.extend(
self._format_commands(
self.ALLOW_NETWORK_ON_TRUNK, port=port_id,
segmentation_id=sub_port['segmentation_id']))
return cmd_set

def get_trunk_port_cmds_vlan_translation(self, port_id, segmentation_id, trunk_details):
pass
12 changes: 12 additions & 0 deletions networking_generic_switch/devices/netmiko_devices/arista.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,15 @@ class AristaEos(netmiko_devices.NetmikoSwitch):
'no switchport mode trunk',
'switchport trunk allowed vlan none'
)

SET_NATIVE_VLAN = (
'interface {port}',
'switchport mode trunk',
'switchport trunk native vlan {segmentation_id}',
'switchport trunk allowed vlan add {segmentation_id}'
)

ALLOW_NETWORK_ON_TRUNK = (
'interface {port}',
'switchport trunk allowed vlan add {segmentation_id}'
)
12 changes: 12 additions & 0 deletions networking_generic_switch/devices/netmiko_devices/cisco.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ class CiscoIos(netmiko_devices.NetmikoSwitch):
'switchport trunk allowed vlan none'
)

SET_NATIVE_VLAN = (
'interface {port}',
'switchport mode trunk',
'switchport trunk native vlan {segmentation_id}',
'switchport trunk allowed vlan add {segmentation_id}'
)

ALLOW_NETWORK_ON_TRUNK = (
'interface {port}',
'switchport trunk allowed vlan add {segmentation_id}'
)


class CiscoNxOS(netmiko_devices.NetmikoSwitch):
"""Netmiko device driver for Cisco Nexus switches running NX-OS."""
Expand Down
11 changes: 11 additions & 0 deletions networking_generic_switch/devices/netmiko_devices/dell.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ class DellOS10(netmiko_devices.NetmikoSwitch):
"exit",
)

SET_NATIVE_VLAN = (
'interface {port}',
'switchport mode trunk',
'switchport access vlan {segmentation_id}',
)

ALLOW_NETWORK_ON_TRUNK = (
'interface {port}',
'switchport trunk allowed vlan {segmentation_id}'
)

ERROR_MSG_PATTERNS = ()
"""Sequence of error message patterns.
Expand Down
5 changes: 5 additions & 0 deletions networking_generic_switch/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,8 @@ class GenericSwitchNetmikoConfigError(GenericSwitchException):

class GenericSwitchBatchError(GenericSwitchException):
message = _("Batching error: %(device)s, error: %(error)s")


class GenericSwitchNotSupported(GenericSwitchException):
message = _("Requested feature is not supported by "
"networking-generic-switch. %(error)s")
51 changes: 46 additions & 5 deletions networking_generic_switch/generic_switch_mech.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
from neutron_lib.api.definitions import portbindings
from neutron_lib.callbacks import resources
from neutron_lib.plugins.ml2 import api
from neutron_lib.plugins import directory
from oslo_log import log as logging

from networking_generic_switch import config as gsw_conf
from networking_generic_switch import devices
from networking_generic_switch.devices import utils as device_utils
from networking_generic_switch import exceptions as ngs_exc

LOG = logging.getLogger(__name__)

Expand Down Expand Up @@ -393,6 +395,23 @@ def delete_port_postcommit(self, context):
if self._is_port_bound(port):
self._unplug_port_from_network(port, context.network.current)

def _is_vlan_translation_required(self, trunk_details):
"""Check if vlan translation is required to configure specific trunk.
:returns: True if vlan translation is required, False otherwise.
"""
core_plugin = directory.get_plugin()
for sub_port in trunk_details.get('sub_ports', {}):
sp_id = sub_port['port_id']
sp_sid = sub_port['segmentation_id']
sp_port = core_plugin.get_port(context._plugin_context, sp_id)
sp_net = core_plugin.get_network(context._plugin_context,
sp_port['network_id'])
if sp_sid != sp_net['provider:segmentation_id']:
return True

return False

def bind_port(self, context):
"""Attempt to bind a port.
Expand Down Expand Up @@ -445,7 +464,6 @@ def bind_port(self, context):
# of the links should be processed.
if not self._is_link_valid(port, network):
return

is_802_3ad = self._is_802_3ad(port)
for link in local_link_information:
port_id = link.get('port_id')
Expand All @@ -458,15 +476,38 @@ def bind_port(self, context):
segments = context.segments_to_bind
# If segmentation ID is None, set vlan 1
segmentation_id = segments[0].get('segmentation_id') or 1
trunk_details = port.get('trunk_details', {})
LOG.debug("Putting port %(port_id)s on %(switch_info)s "
"to vlan: %(segmentation_id)s",
{'port_id': port_id, 'switch_info': switch_info,
'segmentation_id': segmentation_id})
# Move port to network
if is_802_3ad and hasattr(switch, 'plug_bond_to_network'):
switch.plug_bond_to_network(port_id, segmentation_id)
else:
switch.plug_port_to_network(port_id, segmentation_id)
# START
try:
if trunk_details:
vtr = self._is_vlan_translation_required(trunk_details)
switch.plug_port_to_network_trunk(
port_id, segmentation_id, trunk_details, vtr)
elif is_802_3ad and hasattr(switch, 'plug_bond_to_network'):
switch.plug_bond_to_network(port_id, segmentation_id)
else:
switch.plug_port_to_network(
port_id, segmentation_id)
except ngs_exc.GenericSwitchNotSupported as e:
LOG.warning("Operation is not supported by "
"networking-generic-switch. %(err)s)",
{'err': e})
raise e
except Exception as e:
LOG.error("Failed to bind port %(port_id)s in "
"segment %(segment_id)s on device "
"%(device)s due to error %(err)s",
{'port_id': port['id'],
'device': switch_info,
'segment_id': segmentation_id,
'err': e})
raise e
# END
LOG.info("Successfully bound port %(port_id)s in segment "
"%(segment_id)s on device %(device)s",
{'port_id': port['id'], 'device': switch_info,
Expand Down
39 changes: 39 additions & 0 deletions networking_generic_switch/tests/unit/netmiko/test_arista_eos.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

from unittest import mock

from neutron.plugins.ml2 import driver_context

from networking_generic_switch.devices.netmiko_devices import arista
from networking_generic_switch.tests.unit.netmiko import test_netmiko_base

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

def test_get_trunk_port_cmds_no_vlan_translation(self):
mock_context = mock.create_autospec(driver_context.PortContext)
self.switch.ngs_config['vlan_translation_supported'] = False
trunk_details = {'trunk_id': 'aaa-bbb-ccc-ddd',
'sub_ports': [{'segmentation_id': 130,
'port_id': 'aaa-bbb-ccc-ddd',
'segmentation_type': 'vlan',
'mac_address': u'fa:16:3e:1c:c2:7e'}]}
mock_context.current = {'binding:profile':
{'local_link_information':
[
{
'switch_info': 'foo',
'port_id': '2222'
}
]
},
'binding:vnic_type': 'baremetal',
'id': 'aaaa-bbbb-cccc',
'trunk_details': trunk_details}
mock_context.network = mock.Mock()
mock_context.network.current = {'provider:segmentation_id': 123}
mock_context.segments_to_bind = [
{
'segmentation_id': 777,
'id': 123
}
]
res = self.switch.get_trunk_port_cmds_no_vlan_translation(
'2222', 777, trunk_details)
self.assertEqual(['interface 2222', 'switchport mode trunk',
'switchport trunk native vlan 777',
'switchport trunk allowed vlan add 777',
'interface 2222',
'switchport trunk allowed vlan add 130'],
res)

def test__format_commands(self):
cmd_set = self.switch._format_commands(
arista.AristaEos.ADD_NETWORK,
Expand Down
39 changes: 39 additions & 0 deletions networking_generic_switch/tests/unit/netmiko/test_cisco_ios.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

from unittest import mock

from neutron.plugins.ml2 import driver_context

from networking_generic_switch.devices.netmiko_devices import cisco
from networking_generic_switch.tests.unit.netmiko import test_netmiko_base

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

def test_get_trunk_port_cmds_no_vlan_translation(self):
mock_context = mock.create_autospec(driver_context.PortContext)
self.switch.ngs_config['vlan_translation_supported'] = True
trunk_details = {'trunk_id': 'aaa-bbb-ccc-ddd',
'sub_ports': [{'segmentation_id': 130,
'port_id': 'aaa-bbb-ccc-ddd',
'segmentation_type': 'vlan',
'mac_address': u'fa:16:3e:1c:c2:7e'}]}
mock_context.current = {'binding:profile':
{'local_link_information':
[
{
'switch_info': 'foo',
'port_id': '2222'
}
]
},
'binding:vnic_type': 'baremetal',
'id': 'aaaa-bbbb-cccc',
'trunk_details': trunk_details}
mock_context.network = mock.Mock()
mock_context.network.current = {'provider:segmentation_id': 123}
mock_context.segments_to_bind = [
{
'segmentation_id': 777,
'id': 123
}
]
res = self.switch.get_trunk_port_cmds_no_vlan_translation(
'2222', 777, trunk_details)
self.assertEqual(['interface 2222', 'switchport mode trunk',
'switchport trunk native vlan 777',
'switchport trunk allowed vlan add 777',
'interface 2222',
'switchport trunk allowed vlan add 130'],
res)

def test__format_commands(self):
cmd_set = self.switch._format_commands(
cisco.CiscoIos.ADD_NETWORK,
Expand Down
47 changes: 47 additions & 0 deletions networking_generic_switch/tests/unit/netmiko/test_dell.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,58 @@

from unittest import mock

from neutron.plugins.ml2 import driver_context

from networking_generic_switch.devices.netmiko_devices import dell
from networking_generic_switch import exceptions as exc
from networking_generic_switch.tests.unit.netmiko import test_netmiko_base


class TestNetmikoDellOS10(test_netmiko_base.NetmikoSwitchTestBase):

def _make_switch_device(self, extra_cfg={}):
device_cfg = {'device_type': 'netmiko_dell_os10'}
device_cfg.update(extra_cfg)
return dell.DellOS10(device_cfg)


def test_get_trunk_port_cmds_no_vlan_translation(self):
mock_context = mock.create_autospec(driver_context.PortContext)
self.switch.ngs_config['vlan_translation_supported'] = True
trunk_details = {'trunk_id': 'aaa-bbb-ccc-ddd',
'sub_ports': [{'segmentation_id': 130,
'port_id': 'aaa-bbb-ccc-ddd',
'segmentation_type': 'vlan',
'mac_address': u'fa:16:3e:1c:c2:7e'}]}
mock_context.current = {'binding:profile':
{'local_link_information':
[
{
'switch_info': 'foo',
'port_id': '2222'
}
]
},
'binding:vnic_type': 'baremetal',
'id': 'aaaa-bbbb-cccc',
'trunk_details': trunk_details}
mock_context.network = mock.Mock()
mock_context.network.current = {'provider:segmentation_id': 123}
mock_context.segments_to_bind = [
{
'segmentation_id': 777,
'id': 123
}
]
res = self.switch.get_trunk_port_cmds_no_vlan_translation(
'2222', 777, trunk_details)
self.assertEqual(['interface 2222', 'switchport mode trunk',
'switchport access vlan 777',
'interface 2222',
'switchport trunk allowed vlan 130'],
res)


class TestNetmikoDellNos(test_netmiko_base.NetmikoSwitchTestBase):

def _make_switch_device(self, extra_cfg={}):
Expand Down
Loading

0 comments on commit 0196f83

Please sign in to comment.