Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions networking_generic_switch/devices/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
{'name': 'ngs_network_name_format', 'default': '{network_id}'},
# If false, ngs will not add and delete VLANs from switches
{'name': 'ngs_manage_vlans', 'default': True},
{'name': 'vlan_translation_supported', 'default': False},
# If False, ngs will skip saving configuration on devices
{'name': 'ngs_save_configuration', 'default': True},
# When true try to batch up in flight switch requests
Expand Down Expand Up @@ -174,6 +175,10 @@ def add_network(self, segmentation_id, network_id):
def del_network(self, segmentation_id, network_id):
pass

def plug_port_to_network_trunk(self, port_id, segmentation_id,
trunk_details=None, vtr=False):
pass

@abc.abstractmethod
def plug_port_to_network(self, port_id, segmentation_id):
pass
Expand Down
45 changes: 45 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,28 @@ 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 +435,22 @@ 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
13 changes: 13 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,19 @@ class DellOS10(netmiko_devices.NetmikoSwitch):
"exit",
)

SET_NATIVE_VLAN = (
'interface {port}',
# Clean all the old trunked vlans by switching to access mode first
'switchport mode access',
'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")
42 changes: 37 additions & 5 deletions networking_generic_switch/generic_switch_mech.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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 +394,14 @@ 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.
"""
# FIXME: removed for simplicity
return False

def bind_port(self, context):
"""Attempt to bind a port.

Expand Down Expand Up @@ -445,7 +454,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 +466,39 @@ 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
43 changes: 41 additions & 2 deletions networking_generic_switch/tests/unit/netmiko/test_dell.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 dell
from networking_generic_switch import exceptions as exc
from networking_generic_switch.tests.unit.netmiko import test_netmiko_base
Expand Down Expand Up @@ -144,7 +146,7 @@ def test_add_network(self, m_exec):
@mock.patch('networking_generic_switch.devices.netmiko_devices.'
'NetmikoSwitch.send_commands_to_device')
def test_add_network_with_trunk_ports(self, mock_exec):
switch = self._make_switch_device({'ngs_trunk_ports': 'port1, port2'})
switch = self._make_switch_device({'ngs_trunk_ports': 'port1,port2'})
switch.add_network(33, '0ae071f5-5be9-43e4-80ea-e41fefe85b21')
mock_exec.assert_called_with(
['interface vlan 33',
Expand All @@ -165,7 +167,7 @@ def test_del_network(self, mock_exec):
@mock.patch('networking_generic_switch.devices.netmiko_devices.'
'NetmikoSwitch.send_commands_to_device')
def test_del_network_with_trunk_ports(self, mock_exec):
switch = self._make_switch_device({'ngs_trunk_ports': 'port1, port2'})
switch = self._make_switch_device({'ngs_trunk_ports': 'port1,port2'})
switch.del_network(33, '0ae071f55be943e480eae41fefe85b21')
mock_exec.assert_called_with(
['interface port1', 'no switchport trunk allowed vlan 33', 'exit',
Expand Down Expand Up @@ -238,6 +240,43 @@ def test__format_commands(self):
'no switchport trunk allowed vlan 33',
'exit'])

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 access',
'switchport mode trunk',
'switchport access vlan 777',
'interface 2222',
'switchport trunk allowed vlan 130'],
res)


class TestNetmikoDellPowerConnect(test_netmiko_base.NetmikoSwitchTestBase):

Expand Down
Loading