Skip to content

Commit 3625d53

Browse files
committed
Test SRIOV port move operations with PCI conflicts
This patch tests cold migration, unshelve and evacuate in a situation where the existing port binding's pci_slot would cause a conflict on the destination compute node. While cold migration and evacuation work correctly, in the unshelve case the pci_slot is not updated, resulting in two instances attempting to consume the same PCI device. This "passed" in the functional tests, but with a real libvirt this would obviously explode. Related: bug 1851545 Change-Id: Ib81532dc1e6dd85822e38eb1785ffb7162d2a84d (cherry picked from commit bea0612)
1 parent 83ca8b3 commit 3625d53

File tree

2 files changed

+162
-0
lines changed

2 files changed

+162
-0
lines changed

nova/tests/functional/libvirt/base.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,36 @@ class LibvirtNeutronFixture(nova_fixtures.NeutronFixture):
305305
'binding:vif_type': 'hw_veb',
306306
'binding:vnic_type': 'direct',
307307
}
308+
network_4_port_2 = {
309+
'id': '4a0e3b05-4704-4adb-bfb1-f31f0e4d1bdc',
310+
'network_id': network_4['id'],
311+
'status': 'ACTIVE',
312+
'mac_address': 'b5:bc:2e:e7:51:ef',
313+
'fixed_ips': [
314+
{
315+
'ip_address': '192.168.4.7',
316+
'subnet_id': subnet_4['id']
317+
}
318+
],
319+
'binding:vif_details': {'vlan': 42},
320+
'binding:vif_type': 'hw_veb',
321+
'binding:vnic_type': 'direct',
322+
}
323+
network_4_port_3 = {
324+
'id': 'fb2de1a1-d096-41be-9dbe-43066da64804',
325+
'network_id': network_4['id'],
326+
'status': 'ACTIVE',
327+
'mac_address': 'b5:bc:2e:e7:51:ff',
328+
'fixed_ips': [
329+
{
330+
'ip_address': '192.168.4.8',
331+
'subnet_id': subnet_4['id']
332+
}
333+
],
334+
'binding:vif_details': {'vlan': 42},
335+
'binding:vif_type': 'hw_veb',
336+
'binding:vnic_type': 'direct',
337+
}
308338

309339
def __init__(self, test):
310340
super(LibvirtNeutronFixture, self).__init__(test)

nova/tests/functional/libvirt/test_pci_sriov_servers.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,138 @@ def test_live_migrate_server_with_VF(self):
358358
self.assertEqual(500, ex.response.status_code)
359359
self.assertIn('NoValidHost', str(ex))
360360

361+
def _test_move_operation_with_neutron(self, move_operation,
362+
expect_fail=False):
363+
# The purpose here is to force an observable PCI slot update when
364+
# moving from source to dest. This is accomplished by having a single
365+
# PCI device on the source, 2 PCI devices on the test, and relying on
366+
# the fact that our fake HostPCIDevicesInfo creates predictable PCI
367+
# addresses. The PCI device on source and the first PCI device on dest
368+
# will have identical PCI addresses. By sticking a "placeholder"
369+
# instance on that first PCI device on the dest, the incoming instance
370+
# from source will be forced to consume the second dest PCI device,
371+
# with a different PCI address.
372+
self.start_compute(
373+
hostname='source',
374+
pci_info=fakelibvirt.HostPCIDevicesInfo(
375+
num_pfs=1, num_vfs=1))
376+
self.start_compute(
377+
hostname='dest',
378+
pci_info=fakelibvirt.HostPCIDevicesInfo(
379+
num_pfs=1, num_vfs=2))
380+
381+
source_port = self.neutron.create_port(
382+
{'port': self.neutron.network_4_port_1})
383+
dest_port1 = self.neutron.create_port(
384+
{'port': self.neutron.network_4_port_2})
385+
dest_port2 = self.neutron.create_port(
386+
{'port': self.neutron.network_4_port_3})
387+
388+
source_server = self._create_server(
389+
networks=[{'port': source_port['port']['id']}], host='source')
390+
dest_server1 = self._create_server(
391+
networks=[{'port': dest_port1['port']['id']}], host='dest')
392+
dest_server2 = self._create_server(
393+
networks=[{'port': dest_port2['port']['id']}], host='dest')
394+
395+
# Refresh the ports.
396+
source_port = self.neutron.show_port(source_port['port']['id'])
397+
dest_port1 = self.neutron.show_port(dest_port1['port']['id'])
398+
dest_port2 = self.neutron.show_port(dest_port2['port']['id'])
399+
400+
# Find the server on the dest compute that's using the same pci_slot as
401+
# the server on the source compute, and delete the other one to make
402+
# room for the incoming server from the source.
403+
source_pci_slot = source_port['port']['binding:profile']['pci_slot']
404+
dest_pci_slot1 = dest_port1['port']['binding:profile']['pci_slot']
405+
if dest_pci_slot1 == source_pci_slot:
406+
same_slot_port = dest_port1
407+
self._delete_server(dest_server2)
408+
else:
409+
same_slot_port = dest_port2
410+
self._delete_server(dest_server1)
411+
412+
# Before moving, explictly assert that the servers on source and dest
413+
# have the same pci_slot in their port's binding profile
414+
self.assertEqual(source_port['port']['binding:profile']['pci_slot'],
415+
same_slot_port['port']['binding:profile']['pci_slot'])
416+
417+
# Before moving, assert that the servers on source and dest have the
418+
# same PCI source address in their XML for their SRIOV nic.
419+
source_conn = self.computes['source'].driver._host.get_connection()
420+
dest_conn = self.computes['source'].driver._host.get_connection()
421+
source_vms = [vm._def for vm in source_conn._vms.values()]
422+
dest_vms = [vm._def for vm in dest_conn._vms.values()]
423+
self.assertEqual(1, len(source_vms))
424+
self.assertEqual(1, len(dest_vms))
425+
self.assertEqual(1, len(source_vms[0]['devices']['nics']))
426+
self.assertEqual(1, len(dest_vms[0]['devices']['nics']))
427+
self.assertEqual(source_vms[0]['devices']['nics'][0]['source'],
428+
dest_vms[0]['devices']['nics'][0]['source'])
429+
430+
move_operation(source_server)
431+
432+
# Refresh the ports again, keeping in mind the source_port is now bound
433+
# on the dest after unshelving.
434+
source_port = self.neutron.show_port(source_port['port']['id'])
435+
same_slot_port = self.neutron.show_port(same_slot_port['port']['id'])
436+
437+
# FIXME(artom) Until bug 1851545 is fixed, unshelve will not update the
438+
# pci_slot.
439+
if expect_fail:
440+
self.assertEqual(
441+
source_port['port']['binding:profile']['pci_slot'],
442+
same_slot_port['port']['binding:profile']['pci_slot'])
443+
else:
444+
self.assertNotEqual(
445+
source_port['port']['binding:profile']['pci_slot'],
446+
same_slot_port['port']['binding:profile']['pci_slot'])
447+
448+
conn = self.computes['dest'].driver._host.get_connection()
449+
vms = [vm._def for vm in conn._vms.values()]
450+
self.assertEqual(2, len(vms))
451+
for vm in vms:
452+
self.assertEqual(1, len(vm['devices']['nics']))
453+
# FIXME(artom) Until bug 1851545 is fixed, unshelve will not update the
454+
# XML.
455+
if expect_fail:
456+
self.assertEqual(vms[0]['devices']['nics'][0]['source'],
457+
vms[1]['devices']['nics'][0]['source'])
458+
else:
459+
self.assertNotEqual(vms[0]['devices']['nics'][0]['source'],
460+
vms[1]['devices']['nics'][0]['source'])
461+
462+
def test_unshelve_server_with_neutron(self):
463+
def move_operation(source_server):
464+
self._shelve_server(source_server)
465+
# Disable the source compute, to force unshelving on the dest.
466+
self.api.put_service(self.computes['source'].service_ref.uuid,
467+
{'status': 'disabled'})
468+
self._unshelve_server(source_server)
469+
# FIXME(artom) Bug 1851545 means we explain failure here: the pci_slot
470+
# and XML will not get updated.
471+
self._test_move_operation_with_neutron(move_operation,
472+
expect_fail=True)
473+
474+
def test_cold_migrate_server_with_neutron(self):
475+
def move_operation(source_server):
476+
# TODO(stephenfin): The mock of 'migrate_disk_and_power_off' should
477+
# probably be less...dumb
478+
with mock.patch('nova.virt.libvirt.driver.LibvirtDriver'
479+
'.migrate_disk_and_power_off', return_value='{}'):
480+
self._migrate_server(source_server)
481+
self._confirm_resize(source_server)
482+
self._test_move_operation_with_neutron(move_operation)
483+
484+
def test_evacuate_server_with_neutron(self):
485+
def move_operation(source_server):
486+
# Down the source compute to enable the evacuation
487+
self.api.put_service(self.computes['source'].service_ref.uuid,
488+
{'forced_down': True})
489+
self.computes['source'].stop()
490+
self._evacuate_server(source_server)
491+
self._test_move_operation_with_neutron(move_operation)
492+
361493
def test_live_migrate_server_with_neutron(self):
362494
"""Live migrate an instance using a neutron-provisioned SR-IOV VIF.
363495

0 commit comments

Comments
 (0)