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
8 changes: 8 additions & 0 deletions networking_generic_switch/devices/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,11 @@ def plug_port_to_network(self, port_id, segmentation_id):
@abc.abstractmethod
def delete_port(self, port_id, segmentation_id):
pass

def plug_bond_to_network(self, bond_id, segmentation_id):
# Fall back to interface method.
return self.plug_port_to_network(bond_id, segmentation_id)

def unplug_bond_from_network(self, bond_id, segmentation_id):
# Fall back to interface method.
return self.delete_port(bond_id, segmentation_id)
49 changes: 49 additions & 0 deletions networking_generic_switch/devices/netmiko_devices/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ class NetmikoSwitch(devices.GenericSwitchDevice):

DELETE_PORT = None

PLUG_BOND_TO_NETWORK = None

UNPLUG_BOND_FROM_NETWORK = None

ADD_NETWORK_TO_TRUNK = None

REMOVE_NETWORK_FROM_TRUNK = None
Expand All @@ -79,6 +83,10 @@ class NetmikoSwitch(devices.GenericSwitchDevice):

DISABLE_PORT = None

ENABLE_BOND = None

DISABLE_BOND = None

SAVE_CONFIGURATION = None

ERROR_MSG_PATTERNS = ()
Expand Down Expand Up @@ -284,6 +292,47 @@ def delete_port(self, port, segmentation_id):
cmds += self._format_commands(self.DISABLE_PORT, port=port)
return self.send_commands_to_device(cmds)

@check_output('plug bond')
def plug_bond_to_network(self, bond, segmentation_id):
cmds = []
if self._disable_inactive_ports() and self.ENABLE_BOND:
cmds += self._format_commands(self.ENABLE_BOND, bond=bond)
ngs_port_default_vlan = self._get_port_default_vlan()
if ngs_port_default_vlan:
cmds += self._format_commands(
self.UNPLUG_BOND_FROM_NETWORK,
bond=bond,
segmentation_id=ngs_port_default_vlan)
cmds += self._format_commands(
self.PLUG_BOND_TO_NETWORK,
bond=bond,
segmentation_id=segmentation_id)
return self.send_commands_to_device(cmds)

@check_output('unplug bond')
def unplug_bond_from_network(self, bond, segmentation_id):
cmds = self._format_commands(self.UNPLUG_BOND_FROM_NETWORK,
bond=bond,
segmentation_id=segmentation_id)
ngs_port_default_vlan = self._get_port_default_vlan()
if ngs_port_default_vlan:
# NOTE(mgoddard): Pass network_id and segmentation_id for drivers
# not yet using network_name.
network_name = self._get_network_name(ngs_port_default_vlan,
ngs_port_default_vlan)
cmds += self._format_commands(
self.ADD_NETWORK,
segmentation_id=ngs_port_default_vlan,
network_id=ngs_port_default_vlan,
network_name=network_name)
cmds += self._format_commands(
self.PLUG_BOND_TO_NETWORK,
bond=bond,
segmentation_id=ngs_port_default_vlan)
if self._disable_inactive_ports() and self.DISABLE_BOND:
cmds += self._format_commands(self.DISABLE_BOND, bond=bond)
return self.send_commands_to_device(cmds)

def send_config_set(self, net_connect, cmd_set):
"""Send a set of configuration lines to the device.

Expand Down
16 changes: 16 additions & 0 deletions networking_generic_switch/devices/netmiko_devices/cumulus.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ class Cumulus(netmiko_devices.NetmikoSwitch):
'net del interface {port} bridge access {segmentation_id}',
]

PLUG_BOND_TO_NETWORK = [
'net add bond {bond} bridge access {segmentation_id}',
]

UNPLUG_BOND_FROM_NETWORK = [
'net del bond {bond} bridge access {segmentation_id}',
]

ENABLE_PORT = [
'net del interface {port} link down',
]
Expand All @@ -59,6 +67,14 @@ class Cumulus(netmiko_devices.NetmikoSwitch):
'net add interface {port} link down',
]

ENABLE_BOND = [
'net del bond {bond} link down',
]

DISABLE_BOND = [
'net add bond {bond} link down',
]

SAVE_CONFIGURATION = [
'net commit',
]
Expand Down
68 changes: 46 additions & 22 deletions networking_generic_switch/generic_switch_mech.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,28 +442,32 @@ def bind_port(self, context):
# of the links should be processed.
if not self._is_link_valid(port, network):
return
else:
for link in local_link_information:
port_id = link.get('port_id')
switch_info = link.get('switch_info')
switch_id = link.get('switch_id')
switch = device_utils.get_switch_device(
self.switches, switch_info=switch_info,
ngs_mac_address=switch_id)

segments = context.segments_to_bind
# If segmentation ID is None, set vlan 1
segmentation_id = segments[0].get('segmentation_id') or 1
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

is_802_3ad = self._is_802_3ad(port)
for link in local_link_information:
port_id = link.get('port_id')
switch_info = link.get('switch_info')
switch_id = link.get('switch_id')
switch = device_utils.get_switch_device(
self.switches, switch_info=switch_info,
ngs_mac_address=switch_id)

segments = context.segments_to_bind
# If segmentation ID is None, set vlan 1
segmentation_id = segments[0].get('segmentation_id') or 1
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)
LOG.info("Successfully bound port %(port_id)s in segment "
"%(segment_id)s on device %(device)s",
{'port_id': port['id'], 'device': switch_info,
'segment_id': segmentation_id})
LOG.info("Successfully bound port %(port_id)s in segment "
"%(segment_id)s on device %(device)s",
{'port_id': port['id'], 'device': switch_info,
'segment_id': segmentation_id})

context.set_binding(segments[0][api.ID],
portbindings.VIF_TYPE_OTHER, {})
Expand Down Expand Up @@ -541,6 +545,21 @@ def _is_port_bound(port):
vif_type = port[portbindings.VIF_TYPE]
return vif_type == portbindings.VIF_TYPE_OTHER

@staticmethod
def _is_802_3ad(port):
"""Return whether a port is using 802.3ad link aggregation.

:param port: The port to check
:returns: Whether the port is a port group using 802.3ad link
aggregation.
"""
binding_profile = port['binding:profile']
local_group_information = binding_profile.get(
'local_group_information')
if not local_group_information:
return False
return local_group_information.get('bond_mode') in ['4', '802.3ad']

def _unplug_port_from_network(self, port, network):
"""Unplug a port from a network.

Expand All @@ -555,6 +574,8 @@ def _unplug_port_from_network(self, port, network):
local_link_information = binding_profile.get('local_link_information')
if not local_link_information:
return

is_802_3ad = self._is_802_3ad(port)
for link in local_link_information:
switch_info = link.get('switch_info')
switch_id = link.get('switch_id')
Expand All @@ -571,7 +592,10 @@ def _unplug_port_from_network(self, port, network):
{'port': port_id, 'switch_info': switch_info,
'segmentation_id': segmentation_id})
try:
switch.delete_port(port_id, segmentation_id)
if is_802_3ad and hasattr(switch, 'unplug_bond_from_network'):
switch.unplug_bond_from_network(port_id, segmentation_id)
else:
switch.delete_port(port_id, segmentation_id)
except Exception as e:
LOG.error("Failed to unplug port %(port_id)s "
"on device: %(switch)s from network %(net_id)s "
Expand Down
45 changes: 45 additions & 0 deletions networking_generic_switch/tests/unit/netmiko/test_cumulus.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,51 @@ def test_delete_port_simple(self, mock_exec):
mock_exec.assert_called_with(
['net del interface 3333 bridge access 33'])

@mock.patch('networking_generic_switch.devices.netmiko_devices.'
'NetmikoSwitch.send_commands_to_device',
return_value="")
def test_plug_bond_to_network(self, mock_exec):
self.switch.plug_bond_to_network(3333, 33)
mock_exec.assert_called_with(
['net del bond 3333 link down',
'net del bond 3333 bridge access 123',
'net add bond 3333 bridge access 33'])

@mock.patch('networking_generic_switch.devices.netmiko_devices.'
'NetmikoSwitch.send_commands_to_device',
return_value="")
def test_plug_bond_simple(self, mock_exec):
switch = self._make_switch_device({
'ngs_disable_inactive_ports': 'false',
'ngs_port_default_vlan': '',
})
switch.plug_bond_to_network(3333, 33)
mock_exec.assert_called_with(
['net add bond 3333 bridge access 33'])

@mock.patch('networking_generic_switch.devices.netmiko_devices.'
'NetmikoSwitch.send_commands_to_device',
return_value="")
def test_unplug_bond_from_network(self, mock_exec):
self.switch.unplug_bond_from_network(3333, 33)
mock_exec.assert_called_with(
['net del bond 3333 bridge access 33',
'net add vlan 123',
'net add bond 3333 bridge access 123',
'net add bond 3333 link down'])

@mock.patch('networking_generic_switch.devices.netmiko_devices.'
'NetmikoSwitch.send_commands_to_device',
return_value="")
def test_unplug_bond_from_network_simple(self, mock_exec):
switch = self._make_switch_device({
'ngs_disable_inactive_ports': 'false',
'ngs_port_default_vlan': '',
})
switch.unplug_bond_from_network(3333, 33)
mock_exec.assert_called_with(
['net del bond 3333 bridge access 33'])

def test_save(self):
mock_connect = mock.MagicMock()
mock_connect.save_config.side_effect = NotImplementedError
Expand Down
12 changes: 12 additions & 0 deletions networking_generic_switch/tests/unit/test_devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ def test_fault_class(self):
self.assertIn(m, ex.exception.args[0])
self.assertIsNone(device)

@mock.patch.object(FakeDevice, 'plug_port_to_network')
def test_plug_bond_to_network_fallback(self, mock_plug):
device = FakeDevice({'spam': 'ham'})
device.plug_bond_to_network(22, 33)
mock_plug.assert_called_once_with(22, 33)

@mock.patch.object(FakeDevice, 'delete_port')
def test_unplug_bond_from_network_fallback(self, mock_delete):
device = FakeDevice({'spam': 'ham'})
device.unplug_bond_from_network(22, 33)
mock_delete.assert_called_once_with(22, 33)


class TestDeviceManager(unittest.TestCase):

Expand Down
Loading