From ba637a177aa5bdefa4b6db9a062e8c0ece0b597b Mon Sep 17 00:00:00 2001 From: Yunhong Jiang Date: Mon, 27 Jan 2014 14:50:33 -0800 Subject: [PATCH] Separate the PCI device object handling code Currently the PCI device object includes several functions like alloc/free/claim etc. However, the NovaObject should not be used this way, and it makes the PCI device object really different with other NovaObject implementations. This patch duplicates the PCI device handling code as separated functions. No logic changes but simply code copy. A later patch will delete the code at PciDevice object. Closes-Bug: #1273852 Change-Id: I2389d29218d8619998f8df973004af004e8b1b76 --- nova/pci/pci_device.py | 121 ++++++++++++++++++++++++++++++ nova/tests/pci/test_pci_device.py | 119 +++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) create mode 100644 nova/pci/pci_device.py create mode 100644 nova/tests/pci/test_pci_device.py diff --git a/nova/pci/pci_device.py b/nova/pci/pci_device.py new file mode 100644 index 000000000000..4ee2ef6660ff --- /dev/null +++ b/nova/pci/pci_device.py @@ -0,0 +1,121 @@ +# Copyright 2014 Intel Corporation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import copy +import functools + +from nova import exception + + +def check_device_status(dev_status=None): + """Decorator to check device status before changing it.""" + + if dev_status is not None and not isinstance(dev_status, set): + dev_status = set(dev_status) + + def outer(f): + @functools.wraps(f) + def inner(devobj, instance=None): + if devobj['status'] not in dev_status: + raise exception.PciDeviceInvalidStatus( + compute_node_id=devobj.compute_node_id, + address=devobj.address, status=devobj.status, + hopestatus=dev_status) + if instance: + return f(devobj, instance) + else: + return f(devobj) + return inner + return outer + + +@check_device_status(dev_status=['available']) +def claim(devobj, instance): + devobj.status = 'claimed' + devobj.instance_uuid = instance['uuid'] + + +@check_device_status(dev_status=['available', 'claimed']) +def allocate(devobj, instance): + if devobj.status == 'claimed' and devobj.instance_uuid != instance['uuid']: + raise exception.PciDeviceInvalidOwner( + compute_node_id=devobj.compute_node_id, + address=devobj.address, owner=devobj.instance_uuid, + hopeowner=instance['uuid']) + + devobj.status = 'allocated' + devobj.instance_uuid = instance['uuid'] + + # Notes(yjiang5): remove this check when instance object for + # compute manager is finished + if isinstance(instance, dict): + if 'pci_devices' not in instance: + instance['pci_devices'] = [] + instance['pci_devices'].append(copy.copy(devobj)) + else: + instance.pci_devices.objects.append(copy.copy(devobj)) + + +@check_device_status(dev_status=['available']) +def remove(devobj): + devobj.status = 'removed' + devobj.instance_uuid = None + + +@check_device_status(dev_status=['claimed', 'allocated']) +def free(devobj, instance=None): + if instance and devobj.instance_uuid != instance['uuid']: + raise exception.PciDeviceInvalidOwner( + compute_node_id=devobj.compute_node_id, + address=devobj.address, owner=devobj.instance_uuid, + hopeowner=instance['uuid']) + old_status = devobj.status + devobj.status = 'available' + devobj.instance_uuid = None + if old_status == 'allocated' and instance: + # Notes(yjiang5): remove this check when instance object for + # compute manager is finished + existed = next((dev for dev in instance['pci_devices'] + if dev.id == devobj.id)) + if isinstance(instance, dict): + instance['pci_devices'].remove(existed) + else: + instance.pci_devices.objects.remove(existed) + + +def update_device(devobj, dev_dict): + """Sync the content from device dictionary to device object. + + The resource tracker updates the available devices periodically. + To avoid meaningless syncs with the database, we update the device + object only if a value changed. + """ + + # Note(yjiang5): status/instance_uuid should only be updated by + # functions like claim/allocate etc. The id is allocated by + # database. The extra_info is created by the object. + no_changes = ('status', 'instance_uuid', 'id', 'extra_info') + map(lambda x: dev_dict.pop(x, None), + [key for key in no_changes]) + + for k, v in dev_dict.items(): + if k in devobj.fields.keys(): + devobj[k] = v + else: + # Note (yjiang5) extra_info.update does not update + # obj_what_changed, set it explicitely + extra_info = devobj.extra_info + extra_info.update({k: v}) + devobj.extra_info = extra_info diff --git a/nova/tests/pci/test_pci_device.py b/nova/tests/pci/test_pci_device.py new file mode 100644 index 000000000000..4b5cc1e37d96 --- /dev/null +++ b/nova/tests/pci/test_pci_device.py @@ -0,0 +1,119 @@ +# Copyright 2014 Intel Corporation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from nova import context +from nova import exception +from nova.objects import instance +from nova.objects import pci_device as pci_device_obj +from nova.pci import pci_device +from nova import test + + +dev_dict = { + 'created_at': None, + 'updated_at': None, + 'deleted_at': None, + 'deleted': None, + 'id': 1, + 'compute_node_id': 1, + 'address': 'a', + 'vendor_id': 'v', + 'product_id': 'p', + 'dev_type': 't', + 'status': 'available', + 'dev_id': 'i', + 'label': 'l', + 'instance_uuid': None, + 'extra_info': '{}', + } + + +class PciDeviceTestCase(test.TestCase): + def setUp(self): + super(PciDeviceTestCase, self).setUp() + self.ctxt = context.get_admin_context() + self.inst = instance.Instance() + self.inst.uuid = 'fake-inst-uuid' + self.inst.pci_devices = pci_device_obj.PciDeviceList() + self.devobj = pci_device_obj.PciDevice._from_db_object( + self.ctxt, + pci_device_obj.PciDevice(), + dev_dict) + + def test_claim_device(self): + pci_device.claim(self.devobj, self.inst) + self.assertEqual(self.devobj.status, 'claimed') + self.assertEqual(self.devobj.instance_uuid, + self.inst.uuid) + self.assertEqual(len(self.inst.pci_devices), 0) + + def test_claim_device_fail(self): + self.devobj.status = 'allocated' + self.assertRaises(exception.PciDeviceInvalidStatus, + pci_device.claim, self.devobj, self.inst) + + def test_allocate_device(self): + pci_device.claim(self.devobj, self.inst) + pci_device.allocate(self.devobj, self.inst) + self.assertEqual(self.devobj.status, 'allocated') + self.assertEqual(self.devobj.instance_uuid, 'fake-inst-uuid') + self.assertEqual(len(self.inst.pci_devices), 1) + self.assertEqual(self.inst.pci_devices[0]['vendor_id'], 'v') + self.assertEqual(self.inst.pci_devices[0]['status'], 'allocated') + + def test_allocacte_device_fail_status(self): + self.devobj.status = 'removed' + self.assertRaises(exception.PciDeviceInvalidStatus, + pci_device.allocate, + self.devobj, + self.inst) + + def test_allocacte_device_fail_owner(self): + inst_2 = instance.Instance() + inst_2.uuid = 'fake-inst-uuid-2' + pci_device.claim(self.devobj, self.inst) + self.assertRaises(exception.PciDeviceInvalidOwner, + pci_device.allocate, + self.devobj, inst_2) + + def test_free_claimed_device(self): + pci_device.claim(self.devobj, self.inst) + pci_device.free(self.devobj, self.inst) + self.assertEqual(self.devobj.status, 'available') + self.assertIsNone(self.devobj.instance_uuid) + + def test_free_allocated_device(self): + pci_device.claim(self.devobj, self.inst) + pci_device.allocate(self.devobj, self.inst) + self.assertEqual(len(self.inst.pci_devices), 1) + pci_device.free(self.devobj, self.inst) + self.assertEqual(len(self.inst.pci_devices), 0) + self.assertEqual(self.devobj.status, 'available') + self.assertIsNone(self.devobj.instance_uuid) + + def test_free_device_fail(self): + self.devobj.status = 'removed' + self.assertRaises(exception.PciDeviceInvalidStatus, + pci_device.free, self.devobj) + + def test_remove_device(self): + pci_device.remove(self.devobj) + self.assertEqual(self.devobj.status, 'removed') + self.assertIsNone(self.devobj.instance_uuid) + + def test_remove_device_fail(self): + pci_device.claim(self.devobj, self.inst) + self.assertRaises(exception.PciDeviceInvalidStatus, + pci_device.remove, self.devobj)