Skip to content

Commit 22fcfcd

Browse files
tests: Add functional test for vDPA device
Add a simple test to validate behavior with vDPA devices. Most of this is simply fleshing out the fixtures we use to fake out vDPA devices and generally tweaking things to make them better. Change-Id: I1423d8a9652751b667463f90c69eae1a054dd776 Signed-off-by: Stephen Finucane <[email protected]> Co-authored-by: Sean Mooney <[email protected]>
1 parent 45798ad commit 22fcfcd

File tree

6 files changed

+483
-102
lines changed

6 files changed

+483
-102
lines changed

nova/pci/stats.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ def _filter_pools(self, pools, request, numa_cells):
441441

442442
if after_count < before_count:
443443
LOG.debug(
444-
'Dropped %d devices due to mismatched PCI attribute(s)',
444+
'Dropped %d device(s) due to mismatched PCI attribute(s)',
445445
before_count - after_count
446446
)
447447

@@ -458,7 +458,7 @@ def _filter_pools(self, pools, request, numa_cells):
458458

459459
if after_count < before_count:
460460
LOG.debug(
461-
'Dropped %d devices as they are on the wrong NUMA node(s)',
461+
'Dropped %d device(s) as they are on the wrong NUMA node(s)',
462462
before_count - after_count
463463
)
464464

@@ -474,7 +474,7 @@ def _filter_pools(self, pools, request, numa_cells):
474474

475475
if after_count < before_count:
476476
LOG.debug(
477-
'Dropped %d devices as they are PFs which we have not '
477+
'Dropped %d device(s) as they are PFs which we have not '
478478
'requested',
479479
before_count - after_count
480480
)
@@ -491,7 +491,7 @@ def _filter_pools(self, pools, request, numa_cells):
491491

492492
if after_count < before_count:
493493
LOG.debug(
494-
'Dropped %d devices as they are VDPA devices which we have '
494+
'Dropped %d device(s) as they are VDPA devices which we have '
495495
'not requested',
496496
before_count - after_count
497497
)

nova/tests/functional/integrated_helpers.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,24 @@ def _reboot_server(self, server, hard=False, expected_state='ACTIVE'):
413413
fake_notifier.wait_for_versioned_notifications('instance.reboot.end')
414414
return self._wait_for_state_change(server, expected_state)
415415

416+
def _attach_interface(self, server, port_uuid):
417+
"""attach a neutron port to a server."""
418+
body = {
419+
"interfaceAttachment": {
420+
"port_id": port_uuid
421+
}
422+
}
423+
attachment = self.api.attach_interface(server['id'], body)
424+
fake_notifier.wait_for_versioned_notifications(
425+
'instance.interface_attach.end')
426+
return attachment
427+
428+
def _detach_interface(self, server, port_uuid):
429+
"""detach a neutron port form a server."""
430+
self.api.detach_interface(server['id'], port_uuid)
431+
fake_notifier.wait_for_versioned_notifications(
432+
'instance.interface_detach.end')
433+
416434
def _rebuild_server(self, server, image_uuid, expected_state='ACTIVE'):
417435
"""Rebuild a server."""
418436
self.api.post_server_action(

nova/tests/functional/libvirt/base.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def _setup_scheduler_service(self):
8585
return self.start_service('scheduler')
8686

8787
def _get_connection(
88-
self, host_info=None, pci_info=None, mdev_info=None,
88+
self, host_info=None, pci_info=None, mdev_info=None, vdpa_info=None,
8989
libvirt_version=None, qemu_version=None, hostname=None,
9090
):
9191
if not host_info:
@@ -107,12 +107,14 @@ def _get_connection(
107107
host_info=host_info,
108108
pci_info=pci_info,
109109
mdev_info=mdev_info,
110+
vdpa_info=vdpa_info,
110111
hostname=hostname)
111112
return fake_connection
112113

113114
def start_compute(
114115
self, hostname='compute1', host_info=None, pci_info=None,
115-
mdev_info=None, libvirt_version=None, qemu_version=None,
116+
mdev_info=None, vdpa_info=None, libvirt_version=None,
117+
qemu_version=None,
116118
):
117119
"""Start a compute service.
118120
@@ -129,8 +131,8 @@ def start_compute(
129131

130132
def _start_compute(hostname, host_info):
131133
fake_connection = self._get_connection(
132-
host_info, pci_info, mdev_info, libvirt_version, qemu_version,
133-
hostname,
134+
host_info, pci_info, mdev_info, vdpa_info, libvirt_version,
135+
qemu_version, hostname,
134136
)
135137
# This is fun. Firstly we need to do a global'ish mock so we can
136138
# actually start the service.
@@ -299,8 +301,8 @@ class LibvirtNeutronFixture(nova_fixtures.NeutronFixture):
299301
'subnet_id': subnet_4['id']
300302
}
301303
],
302-
'binding:vif_type': 'hw_veb',
303304
'binding:vif_details': {'vlan': 42},
305+
'binding:vif_type': 'hw_veb',
304306
'binding:vnic_type': 'direct',
305307
}
306308

nova/tests/functional/libvirt/test_pci_sriov_servers.py

Lines changed: 217 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from oslo_config import cfg
2424
from oslo_log import log as logging
2525
from oslo_serialization import jsonutils
26+
from oslo_utils.fixture import uuidsentinel as uuids
2627
from oslo_utils import units
2728

2829
import nova
@@ -275,9 +276,7 @@ def fake_create(cls, xml, host):
275276
self.assertNotIn('binding:profile', port)
276277

277278
# create a server using the VF via neutron
278-
flavor_id = self._create_flavor()
279279
self._create_server(
280-
flavor_id=flavor_id,
281280
networks=[
282281
{'port': base.LibvirtNeutronFixture.network_4_port_1['id']},
283282
],
@@ -548,9 +547,7 @@ def test_create_server_after_change_in_nonsriov_pf_to_sriov_pf(self):
548547
self.neutron.create_port({'port': self.neutron.network_4_port_1})
549548

550549
# create a server using the VF via neutron
551-
flavor_id = self._create_flavor()
552550
self._create_server(
553-
flavor_id=flavor_id,
554551
networks=[
555552
{'port': base.LibvirtNeutronFixture.network_4_port_1['id']},
556553
],
@@ -672,6 +669,222 @@ def test_detach_direct_physical(self):
672669
self.neutron.sriov_pf_port2['id'])
673670

674671

672+
class VDPAServersTest(_PCIServersTestBase):
673+
674+
# this is needed for os_compute_api:os-migrate-server:migrate policy
675+
ADMIN_API = True
676+
microversion = 'latest'
677+
678+
# Whitelist both the PF and VF; in reality, you probably wouldn't do this
679+
# but we want to make sure that the PF is correctly taken off the table
680+
# once any VF is used
681+
PCI_PASSTHROUGH_WHITELIST = [jsonutils.dumps(x) for x in (
682+
{
683+
'vendor_id': '15b3',
684+
'product_id': '101d',
685+
'physical_network': 'physnet4',
686+
},
687+
{
688+
'vendor_id': '15b3',
689+
'product_id': '101e',
690+
'physical_network': 'physnet4',
691+
},
692+
)]
693+
# No need for aliases as these test will request SRIOV via neutron
694+
PCI_ALIAS = []
695+
696+
NUM_PFS = 1
697+
NUM_VFS = 4
698+
699+
FAKE_LIBVIRT_VERSION = 6_009_000 # 6.9.0
700+
FAKE_QEMU_VERSION = 5_001_000 # 5.1.0
701+
702+
def setUp(self):
703+
super().setUp()
704+
705+
# The ultimate base class _IntegratedTestBase uses NeutronFixture but
706+
# we need a bit more intelligent neutron for these tests. Applying the
707+
# new fixture here means that we re-stub what the previous neutron
708+
# fixture already stubbed.
709+
self.neutron = self.useFixture(base.LibvirtNeutronFixture(self))
710+
711+
def start_compute(self):
712+
vf_ratio = self.NUM_VFS // self.NUM_PFS
713+
714+
pci_info = fakelibvirt.HostPCIDevicesInfo(
715+
num_pci=0, num_pfs=0, num_vfs=0)
716+
vdpa_info = fakelibvirt.HostVDPADevicesInfo()
717+
718+
pci_info.add_device(
719+
dev_type='PF',
720+
bus=0x6,
721+
slot=0x0,
722+
function=0,
723+
iommu_group=40, # totally arbitrary number
724+
numa_node=0,
725+
vf_ratio=vf_ratio,
726+
vend_id='15b3',
727+
vend_name='Mellanox Technologies',
728+
prod_id='101d',
729+
prod_name='MT2892 Family [ConnectX-6 Dx]',
730+
driver_name='mlx5_core')
731+
732+
for idx in range(self.NUM_VFS):
733+
vf = pci_info.add_device(
734+
dev_type='VF',
735+
bus=0x6,
736+
slot=0x0,
737+
function=idx + 1,
738+
iommu_group=idx + 41, # totally arbitrary number + offset
739+
numa_node=0,
740+
vf_ratio=vf_ratio,
741+
parent=(0x6, 0x0, 0),
742+
vend_id='15b3',
743+
vend_name='Mellanox Technologies',
744+
prod_id='101e',
745+
prod_name='ConnectX Family mlx5Gen Virtual Function',
746+
driver_name='mlx5_core')
747+
vdpa_info.add_device(f'vdpa_vdpa{idx}', idx, vf)
748+
749+
return super().start_compute(
750+
pci_info=pci_info, vdpa_info=vdpa_info,
751+
libvirt_version=self.FAKE_LIBVIRT_VERSION,
752+
qemu_version=self.FAKE_QEMU_VERSION)
753+
754+
def create_vdpa_port(self):
755+
vdpa_port = {
756+
'id': uuids.vdpa_port,
757+
'network_id': self.neutron.network_4['id'],
758+
'status': 'ACTIVE',
759+
'mac_address': 'b5:bc:2e:e7:51:ee',
760+
'fixed_ips': [
761+
{
762+
'ip_address': '192.168.4.6',
763+
'subnet_id': self.neutron.subnet_4['id']
764+
}
765+
],
766+
'binding:vif_details': {},
767+
'binding:vif_type': 'ovs',
768+
'binding:vnic_type': 'vdpa',
769+
}
770+
771+
# create the port
772+
self.neutron.create_port({'port': vdpa_port})
773+
return vdpa_port
774+
775+
def test_create_server(self):
776+
"""Create an instance using a neutron-provisioned vDPA VIF."""
777+
778+
orig_create = nova.virt.libvirt.guest.Guest.create
779+
780+
def fake_create(cls, xml, host):
781+
tree = etree.fromstring(xml)
782+
elem = tree.find('./devices/interface/[@type="vdpa"]')
783+
784+
# compare source device
785+
# the MAC address is derived from the neutron port, while the
786+
# source dev path assumes we attach vDPA devs in order
787+
expected = """
788+
<interface type="vdpa">
789+
<mac address="b5:bc:2e:e7:51:ee"/>
790+
<model type="virtio"/>
791+
<source dev="/dev/vhost-vdpa-3"/>
792+
</interface>"""
793+
actual = etree.tostring(elem, encoding='unicode')
794+
795+
self.assertXmlEqual(expected, actual)
796+
797+
return orig_create(xml, host)
798+
799+
self.stub_out(
800+
'nova.virt.libvirt.guest.Guest.create',
801+
fake_create,
802+
)
803+
804+
hostname = self.start_compute()
805+
num_pci = self.NUM_PFS + self.NUM_VFS
806+
807+
# both the PF and VF with vDPA capabilities (dev_type=vdpa) should have
808+
# been counted
809+
self.assertPCIDeviceCounts(hostname, total=num_pci, free=num_pci)
810+
811+
# create the port
812+
vdpa_port = self.create_vdpa_port()
813+
814+
# ensure the binding details are currently unset
815+
port = self.neutron.show_port(vdpa_port['id'])['port']
816+
self.assertNotIn('binding:profile', port)
817+
818+
# create a server using the vDPA device via neutron
819+
self._create_server(networks=[{'port': vdpa_port['id']}])
820+
821+
# ensure there is one less VF available and that the PF is no longer
822+
# usable
823+
self.assertPCIDeviceCounts(hostname, total=num_pci, free=num_pci - 2)
824+
825+
# ensure the binding details sent to "neutron" were correct
826+
port = self.neutron.show_port(vdpa_port['id'])['port']
827+
self.assertIn('binding:profile', port)
828+
self.assertEqual(
829+
{
830+
'pci_vendor_info': '15b3:101e',
831+
'pci_slot': '0000:06:00.4',
832+
'physical_network': 'physnet4',
833+
},
834+
port['binding:profile'],
835+
)
836+
837+
def _test_common(self, op, *args, **kwargs):
838+
self.start_compute()
839+
840+
# create the port and a server, with the port attached to the server
841+
vdpa_port = self.create_vdpa_port()
842+
server = self._create_server(networks=[{'port': vdpa_port['id']}])
843+
844+
# attempt the unsupported action and ensure it fails
845+
ex = self.assertRaises(
846+
client.OpenStackApiException,
847+
op, server, *args, **kwargs)
848+
self.assertIn(
849+
'not supported for instance with vDPA ports',
850+
ex.response.text)
851+
852+
def test_attach_interface(self):
853+
self.start_compute()
854+
855+
# create the port and a server, but don't attach the port to the server
856+
# yet
857+
vdpa_port = self.create_vdpa_port()
858+
server = self._create_server(networks='none')
859+
860+
# attempt to attach the port to the server
861+
ex = self.assertRaises(
862+
client.OpenStackApiException,
863+
self._attach_interface, server, vdpa_port['id'])
864+
self.assertIn(
865+
'not supported for instance with vDPA ports',
866+
ex.response.text)
867+
868+
def test_detach_interface(self):
869+
self._test_common(self._detach_interface, uuids.vdpa_port)
870+
871+
def test_shelve(self):
872+
self._test_common(self._shelve_server)
873+
874+
def test_suspend(self):
875+
self._test_common(self._suspend_server)
876+
877+
def test_evacute(self):
878+
self._test_common(self._evacuate_server)
879+
880+
def test_resize(self):
881+
flavor_id = self._create_flavor()
882+
self._test_common(self._resize_server, flavor_id)
883+
884+
def test_cold_migrate(self):
885+
self._test_common(self._migrate_server)
886+
887+
675888
class PCIServersTest(_PCIServersTestBase):
676889

677890
ADMIN_API = True

0 commit comments

Comments
 (0)