Skip to content

Commit 53e80fe

Browse files
authored
Merge pull request #27 from stackhpc/backport/wallaby-port-groups
Backport port group support (wallaby)
2 parents 052a8cd + e631a01 commit 53e80fe

File tree

8 files changed

+292
-95
lines changed

8 files changed

+292
-95
lines changed

networking_generic_switch/generic_switch_mech.py

Lines changed: 105 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -343,13 +343,14 @@ def update_port_postcommit(self, context):
343343
'local_link_information')
344344
if not local_link_information:
345345
return
346-
switch_info = local_link_information[0].get('switch_info')
347-
switch_id = local_link_information[0].get('switch_id')
348-
switch = device_utils.get_switch_device(
349-
self.switches, switch_info=switch_info,
350-
ngs_mac_address=switch_id)
351-
if not switch:
352-
return
346+
for link in local_link_information:
347+
switch_info = link.get('switch_info')
348+
switch_id = link.get('switch_id')
349+
switch = device_utils.get_switch_device(
350+
self.switches, switch_info=switch_info,
351+
ngs_mac_address=switch_id)
352+
if not switch:
353+
return
353354
provisioning_blocks.provisioning_complete(
354355
context._plugin_context, port['id'], resources.PORT,
355356
GENERIC_SWITCH_ENTITY)
@@ -432,45 +433,86 @@ def bind_port(self, context):
432433
"""
433434

434435
port = context.current
436+
network = context.network.current
435437
binding_profile = port['binding:profile']
436438
local_link_information = binding_profile.get('local_link_information')
439+
437440
if self._is_port_supported(port) and local_link_information:
438-
switch_info = local_link_information[0].get('switch_info')
439-
switch_id = local_link_information[0].get('switch_id')
441+
# NOTE(jamesdenton): If any link of the port is invalid, none
442+
# of the links should be processed.
443+
if not self._is_link_valid(port, network):
444+
return
445+
else:
446+
for link in local_link_information:
447+
port_id = link.get('port_id')
448+
switch_info = link.get('switch_info')
449+
switch_id = link.get('switch_id')
450+
switch = device_utils.get_switch_device(
451+
self.switches, switch_info=switch_info,
452+
ngs_mac_address=switch_id)
453+
454+
segments = context.segments_to_bind
455+
# If segmentation ID is None, set vlan 1
456+
segmentation_id = segments[0].get('segmentation_id') or 1
457+
LOG.debug("Putting port %(port_id)s on %(switch_info)s "
458+
"to vlan: %(segmentation_id)s",
459+
{'port_id': port_id, 'switch_info': switch_info,
460+
'segmentation_id': segmentation_id})
461+
# Move port to network
462+
switch.plug_port_to_network(port_id, segmentation_id)
463+
LOG.info("Successfully bound port %(port_id)s in segment "
464+
"%(segment_id)s on device %(device)s",
465+
{'port_id': port['id'], 'device': switch_info,
466+
'segment_id': segmentation_id})
467+
468+
context.set_binding(segments[0][api.ID],
469+
portbindings.VIF_TYPE_OTHER, {})
470+
provisioning_blocks.add_provisioning_component(
471+
context._plugin_context, port['id'], resources.PORT,
472+
GENERIC_SWITCH_ENTITY)
473+
474+
def _is_link_valid(self, port, network):
475+
"""Return whether a link references valid switch and physnet.
476+
477+
If the local link information refers to a switch that is not
478+
known to NGS or the switch is not associated with the respective
479+
physnet, the port will not be processed and no exception will
480+
be raised.
481+
482+
:param port: The port to check
483+
:param network: the network mapped to physnet
484+
:returns: Whether the link refers to a configured switch and/or switch
485+
is associated with physnet
486+
"""
487+
488+
binding_profile = port['binding:profile']
489+
local_link_information = binding_profile.get('local_link_information')
490+
491+
for link in local_link_information:
492+
switch_info = link.get('switch_info')
493+
switch_id = link.get('switch_id')
440494
switch = device_utils.get_switch_device(
441495
self.switches, switch_info=switch_info,
442496
ngs_mac_address=switch_id)
443497
if not switch:
444-
return
445-
network = context.network.current
498+
LOG.error("Cannot bind port %(port)s as device %(device)s "
499+
"is not configured. Check baremetal port link "
500+
"configuration.",
501+
{'port': port['id'],
502+
'device': switch_info})
503+
return False
504+
446505
physnet = network['provider:physical_network']
447506
switch_physnets = switch._get_physical_networks()
507+
448508
if switch_physnets and physnet not in switch_physnets:
449-
LOG.error("Cannot bind port %(port)s as device %(device)s is "
450-
"not on physical network %(physnet)",
451-
{'port_id': port['id'], 'device': switch_info,
509+
LOG.error("Cannot bind port %(port)s as device %(device)s "
510+
"is not on physical network %(physnet)s. Check "
511+
"baremetal port link configuration.",
512+
{'port': port['id'], 'device': switch_info,
452513
'physnet': physnet})
453-
return
454-
port_id = local_link_information[0].get('port_id')
455-
segments = context.segments_to_bind
456-
# If segmentation ID is None, set vlan 1
457-
segmentation_id = segments[0].get('segmentation_id') or 1
458-
provisioning_blocks.add_provisioning_component(
459-
context._plugin_context, port['id'], resources.PORT,
460-
GENERIC_SWITCH_ENTITY)
461-
LOG.debug("Putting port {port} on {switch_info} to vlan: "
462-
"{segmentation_id}".format(
463-
port=port_id,
464-
switch_info=switch_info,
465-
segmentation_id=segmentation_id))
466-
# Move port to network
467-
switch.plug_port_to_network(port_id, segmentation_id)
468-
LOG.info("Successfully bound port %(port_id)s in segment "
469-
"%(segment_id)s on device %(device)s",
470-
{'port_id': port['id'], 'device': switch_info,
471-
'segment_id': segmentation_id})
472-
context.set_binding(segments[0][api.ID],
473-
portbindings.VIF_TYPE_OTHER, {})
514+
return False
515+
return True
474516

475517
@staticmethod
476518
def _is_port_supported(port):
@@ -513,34 +555,34 @@ def _unplug_port_from_network(self, port, network):
513555
local_link_information = binding_profile.get('local_link_information')
514556
if not local_link_information:
515557
return
516-
switch_info = local_link_information[0].get('switch_info')
517-
switch_id = local_link_information[0].get('switch_id')
518-
switch = device_utils.get_switch_device(
519-
self.switches, switch_info=switch_info,
520-
ngs_mac_address=switch_id)
521-
if not switch:
522-
return
523-
port_id = local_link_information[0].get('port_id')
524-
# If segmentation ID is None, set vlan 1
525-
segmentation_id = network.get('provider:segmentation_id') or 1
526-
LOG.debug("Unplugging port {port} on {switch_info} from vlan: "
527-
"{segmentation_id}".format(
528-
port=port_id,
529-
switch_info=switch_info,
530-
segmentation_id=segmentation_id))
531-
try:
532-
switch.delete_port(port_id, segmentation_id)
533-
except Exception as e:
534-
LOG.error("Failed to unplug port %(port_id)s "
535-
"on device: %(switch)s from network %(net_id)s "
536-
"reason: %(exc)s",
537-
{'port_id': port['id'], 'net_id': network['id'],
538-
'switch': switch_info, 'exc': e})
539-
raise e
540-
LOG.info('Port %(port_id)s has been unplugged from network '
541-
'%(net_id)s on device %(device)s',
542-
{'port_id': port['id'], 'net_id': network['id'],
543-
'device': switch_info})
558+
for link in local_link_information:
559+
switch_info = link.get('switch_info')
560+
switch_id = link.get('switch_id')
561+
switch = device_utils.get_switch_device(
562+
self.switches, switch_info=switch_info,
563+
ngs_mac_address=switch_id)
564+
if not switch:
565+
continue
566+
port_id = link.get('port_id')
567+
# If segmentation ID is None, set vlan 1
568+
segmentation_id = network.get('provider:segmentation_id') or 1
569+
LOG.debug("Unplugging port %(port)s on %(switch_info)s from vlan: "
570+
"%(segmentation_id)s",
571+
{'port': port_id, 'switch_info': switch_info,
572+
'segmentation_id': segmentation_id})
573+
try:
574+
switch.delete_port(port_id, segmentation_id)
575+
except Exception as e:
576+
LOG.error("Failed to unplug port %(port_id)s "
577+
"on device: %(switch)s from network %(net_id)s "
578+
"reason: %(exc)s",
579+
{'port_id': port['id'], 'net_id': network['id'],
580+
'switch': switch_info, 'exc': e})
581+
raise e
582+
LOG.info('Port %(port_id)s has been unplugged from network '
583+
'%(net_id)s on device %(device)s',
584+
{'port_id': port['id'], 'net_id': network['id'],
585+
'device': switch_info})
544586

545587
def _get_devices_by_physnet(self, physnet):
546588
"""Generator yielding switches on a particular physical network.

networking_generic_switch/tests/unit/netmiko/test_dell.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,8 @@ def test_check_output(self):
256256
def _test_check_output_error(self, output):
257257
msg = ("Found invalid configuration in device response. Operation: "
258258
"fake op. Output: %s" % output)
259-
self.assertRaisesRegexp(exc.GenericSwitchNetmikoConfigError, msg,
260-
self.switch.check_output, output, 'fake op')
259+
self.assertRaisesRegex(exc.GenericSwitchNetmikoConfigError, msg,
260+
self.switch.check_output, output, 'fake op')
261261

262262
def test_check_output_incomplete_command(self):
263263
output = """

networking_generic_switch/tests/unit/netmiko/test_juniper.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,10 @@ def test_save_configuration_db_locked_timeout(self, m_stop, m_wait):
166166
mock_connection.commit.side_effect = ValueError(
167167
"Commit failed with the following errors:\n\n{0}".format(output))
168168

169-
self.assertRaisesRegexp(exc.GenericSwitchNetmikoConfigError,
170-
"Reached timeout waiting for",
171-
self.switch.save_configuration,
172-
mock_connection)
169+
self.assertRaisesRegex(exc.GenericSwitchNetmikoConfigError,
170+
"Reached timeout waiting for",
171+
self.switch.save_configuration,
172+
mock_connection)
173173
self.assertGreater(mock_connection.commit.call_count, 1)
174174
m_stop.assert_called_once_with(60)
175175
m_wait.assert_called_once_with(5)
@@ -214,10 +214,10 @@ def test_save_configuration_warn_already_exists_timeout(
214214
mock_connection.commit.side_effect = ValueError(
215215
"Commit failed with the following errors:\n\n{0}".format(output))
216216

217-
self.assertRaisesRegexp(exc.GenericSwitchNetmikoConfigError,
218-
"Reached timeout while attempting",
219-
self.switch.save_configuration,
220-
mock_connection)
217+
self.assertRaisesRegex(exc.GenericSwitchNetmikoConfigError,
218+
"Reached timeout while attempting",
219+
self.switch.save_configuration,
220+
mock_connection)
221221
self.assertGreater(mock_connection.commit.call_count, 1)
222222
m_stop.assert_called_once_with(60)
223223
m_wait.assert_called_once_with(5)
@@ -262,10 +262,10 @@ def test_save_configuration_warn_does_not_exist_timeout(
262262
mock_connection.commit.side_effect = ValueError(
263263
"Commit failed with the following errors:\n\n{0}".format(output))
264264

265-
self.assertRaisesRegexp(exc.GenericSwitchNetmikoConfigError,
266-
"Reached timeout while attempting",
267-
self.switch.save_configuration,
268-
mock_connection)
265+
self.assertRaisesRegex(exc.GenericSwitchNetmikoConfigError,
266+
"Reached timeout while attempting",
267+
self.switch.save_configuration,
268+
mock_connection)
269269
self.assertGreater(mock_connection.commit.call_count, 1)
270270
m_stop.assert_called_once_with(60)
271271
m_wait.assert_called_once_with(5)
@@ -284,10 +284,10 @@ def test_save_configuration_error(self):
284284
mock_connection.commit.side_effect = ValueError(
285285
"Commit failed with the following errors:\n\n{0}".format(output))
286286

287-
self.assertRaisesRegexp(exc.GenericSwitchNetmikoConfigError,
288-
"Failed to commit configuration",
289-
self.switch.save_configuration,
290-
mock_connection)
287+
self.assertRaisesRegex(exc.GenericSwitchNetmikoConfigError,
288+
"Failed to commit configuration",
289+
self.switch.save_configuration,
290+
mock_connection)
291291
mock_connection.commit.assert_called_once_with()
292292

293293
@mock.patch.object(netmiko_devices.tenacity, 'wait_fixed',

networking_generic_switch/tests/unit/netmiko/test_netmiko_base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -382,5 +382,5 @@ def test_check_output_error(self):
382382
"""
383383
msg = ("Found invalid configuration in device response. Operation: "
384384
"fake op. Output: %s" % output)
385-
self.assertRaisesRegexp(exc.GenericSwitchNetmikoConfigError, msg,
386-
self.switch.check_output, output, 'fake op')
385+
self.assertRaisesRegex(exc.GenericSwitchNetmikoConfigError, msg,
386+
self.switch.check_output, output, 'fake op')

networking_generic_switch/tests/unit/test_devices.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ def test__validate_network_name_format(self):
156156

157157
def test__validate_network_name_format_failure(self):
158158
device_cfg = {'ngs_network_name_format': '{invalid}'}
159-
self.assertRaisesRegexp(
159+
self.assertRaisesRegex(
160160
exc.GenericSwitchNetworkNameFormatInvalid,
161161
r"Invalid value for 'ngs_network_name_format'",
162162
FakeDevice, device_cfg)

0 commit comments

Comments
 (0)