Skip to content

Commit 30295f1

Browse files
committed
Add get_instance_pci_request_from_vif
This change extends nova/pci/request.py with a method that retrieves an instance's PCI request from a given VIF if the given VIF required a PCI allocation during instance creation. The PCI request, if retrieved, belongs to a PCI device in the compute node where the instance is running. The change is required to facilitate SR-IOV live migration allowing to claim VIF related PCI resources on the destination node. Change-Id: I9ba475e91b8283f063db446de74d3e4b2de002c5 Partial-Implements: blueprint libvirt-neutron-sriov-livemigration
1 parent a7fae37 commit 30295f1

File tree

3 files changed

+191
-1
lines changed

3 files changed

+191
-1
lines changed

nova/exception.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1804,6 +1804,11 @@ class PciConfigInvalidWhitelist(Invalid):
18041804
msg_fmt = _("Invalid PCI devices Whitelist config: %(reason)s")
18051805

18061806

1807+
class PciRequestFromVIFNotFound(NotFound):
1808+
msg_fmt = _("Failed to locate PCI request associated with the given VIF "
1809+
"PCI address: %(pci_slot)s on compute node: %(node_id)s")
1810+
1811+
18071812
# Cannot be templated, msg needs to be constructed when raised.
18081813
class InternalError(NovaException):
18091814
"""Generic hypervisor errors.

nova/pci/request.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"""
4040

4141
import jsonschema
42+
from oslo_log import log as logging
4243
from oslo_serialization import jsonutils
4344
import six
4445

@@ -50,6 +51,7 @@
5051
from nova.objects import fields as obj_fields
5152
from nova.pci import utils
5253

54+
LOG = logging.getLogger(__name__)
5355
PCI_NET_TAG = 'physical_network'
5456
PCI_TRUSTED_TAG = 'trusted'
5557
PCI_DEVICE_TYPE_TAG = 'dev_type'
@@ -171,6 +173,60 @@ def _translate_alias_to_requests(alias_spec):
171173
return pci_requests
172174

173175

176+
def get_instance_pci_request_from_vif(context, instance, vif):
177+
"""Given an Instance, return the PCI request associated
178+
to the PCI device related to the given VIF (if any) on the
179+
compute node the instance is currently running.
180+
181+
In this method we assume a VIF is associated with a PCI device
182+
if 'pci_slot' attribute exists in the vif 'profile' dict.
183+
184+
:param context: security context
185+
:param instance: instance object
186+
:param vif: network VIF model object
187+
:raises: raises PciRequestFromVIFNotFound if a pci device is requested
188+
but not found on current host
189+
:return: instance's PCIRequest object associated with the given VIF
190+
or None if no PCI device is requested
191+
"""
192+
193+
# Get PCI device address for VIF if exists
194+
vif_pci_dev_addr = vif['profile'].get('pci_slot') \
195+
if vif['profile'] else None
196+
197+
if not vif_pci_dev_addr:
198+
return None
199+
200+
try:
201+
cn_id = objects.ComputeNode.get_by_host_and_nodename(
202+
context,
203+
instance.host,
204+
instance.node).id
205+
except exception.NotFound:
206+
LOG.warning("expected to find compute node with host %s "
207+
"and node %s when getting instance PCI request "
208+
"from VIF", instance.host, instance.node)
209+
return None
210+
# Find PCIDevice associated with vif_pci_dev_addr on the compute node
211+
# the instance is running on.
212+
found_pci_dev = None
213+
for pci_dev in instance.pci_devices:
214+
if (pci_dev.compute_node_id == cn_id and
215+
pci_dev.address == vif_pci_dev_addr):
216+
found_pci_dev = pci_dev
217+
break
218+
if not found_pci_dev:
219+
return None
220+
# Find PCIRequest associated with the given PCIDevice in instance
221+
for pci_req in instance.pci_requests.requests:
222+
if pci_req.request_id == found_pci_dev.request_id:
223+
return pci_req
224+
225+
raise exception.PciRequestFromVIFNotFound(
226+
pci_slot=vif_pci_dev_addr,
227+
node_id=cn_id)
228+
229+
174230
def get_pci_requests_from_flavor(flavor):
175231
"""Validate and return PCI requests.
176232

nova/tests/unit/pci/test_request.py

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,17 @@
1515

1616
"""Tests for PCI request."""
1717

18+
import mock
19+
20+
from oslo_utils.fixture import uuidsentinel
21+
22+
from nova import context
1823
from nova import exception
24+
from nova.network import model
25+
from nova import objects
1926
from nova.pci import request
2027
from nova import test
28+
from nova.tests.unit.api.openstack import fakes
2129

2230

2331
_fake_alias1 = """{
@@ -62,7 +70,35 @@
6270
}"""
6371

6472

65-
class AliasTestCase(test.NoDBTestCase):
73+
class PciRequestTestCase(test.NoDBTestCase):
74+
75+
@staticmethod
76+
def _create_fake_inst_with_pci_devs(pci_req_list, pci_dev_list):
77+
"""Create a fake Instance object with the provided InstancePciRequests
78+
and PciDevices.
79+
80+
:param pci_req_list: a list of InstancePCIRequest objects.
81+
:param pci_dev_list: a list of PciDevice objects, each element
82+
associated (via request_id attribute)with a corresponding
83+
element from pci_req_list.
84+
:return: A fake Instance object associated with the provided
85+
PciRequests and PciDevices.
86+
"""
87+
88+
inst = objects.Instance()
89+
inst.uuid = uuidsentinel.instance1
90+
inst.pci_requests = objects.InstancePCIRequests(
91+
requests=pci_req_list)
92+
inst.pci_devices = objects.PciDeviceList(objects=pci_dev_list)
93+
inst.host = 'fake-host'
94+
inst.node = 'fake-node'
95+
return inst
96+
97+
def setUp(self):
98+
super(PciRequestTestCase, self).setUp()
99+
self.context = context.RequestContext(fakes.FAKE_USER_ID,
100+
fakes.FAKE_PROJECT_ID)
101+
self.mock_inst_cn = mock.Mock()
66102

67103
def test_valid_alias(self):
68104
self.flags(alias=[_fake_alias1], group='pci')
@@ -232,6 +268,99 @@ def test_alias_2_request_invalid(self):
232268
request._translate_alias_to_requests,
233269
"QuicAssistX : 3")
234270

271+
@mock.patch.object(objects.compute_node.ComputeNode,
272+
'get_by_host_and_nodename')
273+
def test_get_instance_pci_request_from_vif_invalid(
274+
self,
275+
cn_get_by_host_and_node):
276+
# Basically make sure we raise an exception if an instance
277+
# has an allocated PCI device without having the its corresponding
278+
# PCIRequest object in instance.pci_requests
279+
self.mock_inst_cn.id = 1
280+
cn_get_by_host_and_node.return_value = self.mock_inst_cn
281+
282+
# Create a fake instance with PCI request and allocated PCI devices
283+
pci_dev1 = objects.PciDevice(request_id=uuidsentinel.pci_req_id1,
284+
address='0000:04:00.0',
285+
compute_node_id=1)
286+
287+
pci_req2 = objects.InstancePCIRequest(
288+
request_id=uuidsentinel.pci_req_id2)
289+
pci_dev2 = objects.PciDevice(request_id=uuidsentinel.pci_req_id2,
290+
address='0000:05:00.0',
291+
compute_node_id=1)
292+
pci_request_list = [pci_req2]
293+
pci_device_list = [pci_dev1, pci_dev2]
294+
inst = PciRequestTestCase._create_fake_inst_with_pci_devs(
295+
pci_request_list,
296+
pci_device_list)
297+
# Create a VIF with pci_dev1 that has no corresponding PCI request
298+
pci_vif = model.VIF(vnic_type=model.VNIC_TYPE_DIRECT,
299+
profile={'pci_slot': '0000:04:00.0'})
300+
301+
self.assertRaises(exception.PciRequestFromVIFNotFound,
302+
request.get_instance_pci_request_from_vif,
303+
self.context,
304+
inst,
305+
pci_vif)
306+
307+
@mock.patch.object(objects.compute_node.ComputeNode,
308+
'get_by_host_and_nodename')
309+
def test_get_instance_pci_request_from_vif(self, cn_get_by_host_and_node):
310+
self.mock_inst_cn.id = 1
311+
cn_get_by_host_and_node.return_value = self.mock_inst_cn
312+
313+
# Create a fake instance with PCI request and allocated PCI devices
314+
pci_req1 = objects.InstancePCIRequest(
315+
request_id=uuidsentinel.pci_req_id1)
316+
pci_dev1 = objects.PciDevice(request_id=uuidsentinel.pci_req_id1,
317+
address='0000:04:00.0',
318+
compute_node_id = 1)
319+
pci_req2 = objects.InstancePCIRequest(
320+
request_id=uuidsentinel.pci_req_id2)
321+
pci_dev2 = objects.PciDevice(request_id=uuidsentinel.pci_req_id2,
322+
address='0000:05:00.0',
323+
compute_node_id=1)
324+
pci_request_list = [pci_req1, pci_req2]
325+
pci_device_list = [pci_dev1, pci_dev2]
326+
inst = PciRequestTestCase._create_fake_inst_with_pci_devs(
327+
pci_request_list,
328+
pci_device_list)
329+
330+
# Create a vif with normal port and make sure no PCI request returned
331+
normal_vif = model.VIF(vnic_type=model.VNIC_TYPE_NORMAL)
332+
self.assertIsNone(request.get_instance_pci_request_from_vif(
333+
self.context,
334+
inst,
335+
normal_vif))
336+
337+
# Create a vif with PCI address under profile, make sure the correct
338+
# PCI request is returned
339+
pci_vif = model.VIF(vnic_type=model.VNIC_TYPE_DIRECT,
340+
profile={'pci_slot': '0000:05:00.0'})
341+
self.assertEqual(uuidsentinel.pci_req_id2,
342+
request.get_instance_pci_request_from_vif(
343+
self.context,
344+
inst,
345+
pci_vif).request_id)
346+
347+
# Create a vif with PCI under profile which is not claimed
348+
# for the instance, i.e no matching pci device in instance.pci_devices
349+
nonclaimed_pci_vif = model.VIF(vnic_type=model.VNIC_TYPE_DIRECT,
350+
profile={'pci_slot': '0000:08:00.0'})
351+
self.assertIsNone(request.get_instance_pci_request_from_vif(
352+
self.context,
353+
inst,
354+
nonclaimed_pci_vif))
355+
356+
# "Move" the instance to another compute node, make sure that no
357+
# matching PCI request against the new compute.
358+
self.mock_inst_cn.id = 2
359+
self.assertIsNone(request.get_instance_pci_request_from_vif(
360+
self.context,
361+
inst,
362+
pci_vif))
363+
235364
def test_get_pci_requests_from_flavor(self):
236365
self.flags(alias=[_fake_alias1, _fake_alias3], group='pci')
237366
expect_request = [

0 commit comments

Comments
 (0)