diff --git a/doc/v3/api_samples/os-pci/pci-detail-resp.json b/doc/v3/api_samples/os-pci/pci-detail-resp.json new file mode 100644 index 000000000000..61cb17c6b4ee --- /dev/null +++ b/doc/v3/api_samples/os-pci/pci-detail-resp.json @@ -0,0 +1,36 @@ +{ + "pci_devices": [ + { + "address": "0000:04:10.0", + "compute_node_id": 1, + "dev_id": "pci_0000_04_10_0", + "dev_type": "type-VF", + "extra_info": { + "key1": "value1", + "key2": "value2" + }, + "id": 1, + "server_uuid": "69ba1044-0766-4ec0-b60d-09595de034a1", + "label": "label_8086_1520", + "product_id": "1520", + "status": "available", + "vendor_id": "8086" + }, + { + "address": "0000:04:10.1", + "compute_node_id": 1, + "dev_id": "pci_0000_04_10_1", + "dev_type": "type-VF", + "extra_info": { + "key3": "value3", + "key4": "value4" + }, + "id": 2, + "server_uuid": "d5b446a6-a1b4-4d01-b4f0-eac37b3a62fc", + "label": "label_8086_1520", + "product_id": "1520", + "status": "available", + "vendor_id": "8086" + } + ] +} diff --git a/doc/v3/api_samples/os-pci/pci-index-resp.json b/doc/v3/api_samples/os-pci/pci-index-resp.json new file mode 100644 index 000000000000..6268f316dfe2 --- /dev/null +++ b/doc/v3/api_samples/os-pci/pci-index-resp.json @@ -0,0 +1,20 @@ +{ + "pci_devices": [ + { + "address": "0000:04:10.0", + "compute_node_id": 1, + "id": 1, + "product_id": "1520", + "status": "available", + "vendor_id": "8086" + }, + { + "address": "0000:04:10.1", + "compute_node_id": 1, + "id": 2, + "product_id": "1520", + "status": "available", + "vendor_id": "8086" + } + ] +} \ No newline at end of file diff --git a/doc/v3/api_samples/os-pci/pci-show-resp.json b/doc/v3/api_samples/os-pci/pci-show-resp.json new file mode 100644 index 000000000000..997776988174 --- /dev/null +++ b/doc/v3/api_samples/os-pci/pci-show-resp.json @@ -0,0 +1,18 @@ +{ + "pci_device": { + "address": "0000:04:10.0", + "compute_node_id": 1, + "dev_id": "pci_0000_04_10_0", + "dev_type": "type-VF", + "extra_info": { + "key1": "value1", + "key2": "value2" + }, + "id": 1, + "server_uuid": "69ba1044-0766-4ec0-b60d-09595de034a1", + "label": "label_8086_1520", + "product_id": "1520", + "status": "available", + "vendor_id": "8086" + } +} diff --git a/etc/nova/policy.json b/etc/nova/policy.json index 291297d3f906..56328ed55453 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -189,6 +189,9 @@ "compute_extension:v3:os-pause-server:unpause": "rule:admin_or_owner", "compute_extension:v3:os-pci:pci_servers": "", "compute_extension:v3:os-pci:discoverable": "", + "compute_extension:v3:os-pci:index": "rule:admin_api", + "compute_extension:v3:os-pci:detail": "rule:admin_api", + "compute_extension:v3:os-pci:show": "rule:admin_api", "compute_extension:quotas:show": "", "compute_extension:quotas:update": "rule:admin_api", "compute_extension:quotas:delete": "rule:admin_api", diff --git a/nova/api/openstack/compute/plugins/v3/pci.py b/nova/api/openstack/compute/plugins/v3/pci.py index bdf5cdfb0078..fdce310898a3 100644 --- a/nova/api/openstack/compute/plugins/v3/pci.py +++ b/nova/api/openstack/compute/plugins/v3/pci.py @@ -14,8 +14,13 @@ # under the License. +import webob.exc + from nova.api.openstack import extensions from nova.api.openstack import wsgi +from nova import compute +from nova import exception +from nova.objects import pci_device from nova.openstack.common import jsonutils @@ -23,6 +28,13 @@ ALIAS = 'os-pci' instance_authorize = extensions.soft_extension_authorizer( 'compute', 'v3:' + ALIAS + ':pci_servers') +authorize = extensions.extension_authorizer('compute', 'v3:' + ALIAS) + +PCI_ADMIN_KEYS = ['id', 'address', 'vendor_id', 'product_id', 'status', + 'compute_node_id'] +PCI_DETAIL_KEYS = ['dev_type', 'label', 'instance_uuid', 'dev_id', + 'extra_info'] + class PciServerController(wsgi.Controller): def _extend_server(self, server, instance): @@ -68,6 +80,57 @@ class PciHypervisorController(wsgi.Controller): self._extend_hypervisor(hypervisor, compute_node) +class PciController(object): + + def __init__(self): + self.host_api = compute.HostAPI() + + def _view_pcidevice(self, device, detail=False): + dev_dict = {} + for key in PCI_ADMIN_KEYS: + dev_dict[key] = device[key] + if detail: + for field in PCI_DETAIL_KEYS: + if field == 'instance_uuid': + dev_dict['server_uuid'] = device[field] + else: + dev_dict[field] = device[field] + return dev_dict + + def _get_all_nodes_pci_devices(self, req, detail, action): + context = req.environ['nova.context'] + authorize(context, action=action) + compute_nodes = self.host_api.compute_node_get_all(context) + results = [] + for node in compute_nodes: + pci_devs = pci_device.PciDeviceList.get_by_compute_node( + context, node['id']) + results.extend([self._view_pcidevice(dev, detail) + for dev in pci_devs]) + return results + + @extensions.expected_errors(()) + def detail(self, req): + results = self._get_all_nodes_pci_devices(req, True, 'detail') + return dict(pci_devices=results) + + @extensions.expected_errors(404) + def show(self, req, id): + context = req.environ['nova.context'] + authorize(context, action='show') + try: + pci_dev = pci_device.PciDevice.get_by_dev_id(context, id) + except exception.PciDeviceNotFoundById as e: + raise webob.exc.HTTPNotFound(explanation=e.format_message()) + result = self._view_pcidevice(pci_dev, True) + return dict(pci_device=result) + + @extensions.expected_errors(()) + def index(self, req): + results = self._get_all_nodes_pci_devices(req, False, 'index') + return dict(pci_devices=results) + + class Pci(extensions.V3APIExtensionBase): """Pci access support.""" name = "PCIAccess" @@ -75,7 +138,10 @@ class Pci(extensions.V3APIExtensionBase): version = 1 def get_resources(self): - return [] + resources = [extensions.ResourceExtension(ALIAS, + PciController(), + collection_actions={'detail': 'GET'})] + return resources def get_controller_extensions(self): server_extension = extensions.ControllerExtension( diff --git a/nova/tests/api/openstack/compute/plugins/v3/test_pci.py b/nova/tests/api/openstack/compute/plugins/v3/test_pci.py index db2ba810c71f..33a83046d2e8 100644 --- a/nova/tests/api/openstack/compute/plugins/v3/test_pci.py +++ b/nova/tests/api/openstack/compute/plugins/v3/test_pci.py @@ -13,10 +13,13 @@ # under the License. +from webob import exc + from nova.api.openstack.compute.plugins.v3 import pci from nova.api.openstack import wsgi from nova import context from nova import db +from nova import exception from nova.objects import instance from nova.objects import pci_device from nova.openstack.common import jsonutils @@ -148,3 +151,86 @@ class PciHypervisorControllerTest(test.NoDBTestCase): self.assertIn('os-pci:pci_stats', resp.obj['hypervisors'][0]) self.assertEqual(fake_compute_node['pci_stats'][0], resp.obj['hypervisors'][0]['os-pci:pci_stats'][0]) + + +class PciControlletest(test.NoDBTestCase): + def setUp(self): + super(PciControlletest, self).setUp() + self.controller = pci.PciController() + + def test_show(self): + def fake_pci_device_get_by_id(context, id): + return test_pci_device.fake_db_dev + + self.stubs.Set(db, 'pci_device_get_by_id', fake_pci_device_get_by_id) + req = fakes.HTTPRequestV3.blank('/os-pci/1', use_admin_context=True) + result = self.controller.show(req, '1') + dist = {'pci_device': {'address': 'a', + 'compute_node_id': 1, + 'dev_id': 'i', + 'extra_info': {}, + 'dev_type': 't', + 'id': 1, + 'server_uuid': None, + 'label': 'l', + 'product_id': 'p', + 'status': 'available', + 'vendor_id': 'v'}} + self.assertEqual(dist, result) + + def test_show_error_id(self): + def fake_pci_device_get_by_id(context, id): + raise exception.PciDeviceNotFoundById(id=id) + + self.stubs.Set(db, 'pci_device_get_by_id', fake_pci_device_get_by_id) + req = fakes.HTTPRequestV3.blank('/os-pci/0', use_admin_context=True) + self.assertRaises(exc.HTTPNotFound, self.controller.show, req, '0') + + def _fake_compute_node_get_all(self, context): + return [dict(id=1, + service_id=1, + cpu_info='cpu_info', + disk_available_least=100)] + + def _fake_pci_device_get_all_by_node(self, context, node): + return [test_pci_device.fake_db_dev, test_pci_device.fake_db_dev_1] + + def test_index(self): + self.stubs.Set(db, 'compute_node_get_all', + self._fake_compute_node_get_all) + self.stubs.Set(db, 'pci_device_get_all_by_node', + self._fake_pci_device_get_all_by_node) + + req = fakes.HTTPRequestV3.blank('/os-pci', use_admin_context=True) + result = self.controller.index(req) + dist = {'pci_devices': [test_pci_device.fake_db_dev, + test_pci_device.fake_db_dev_1]} + for i in range(len(result['pci_devices'])): + self.assertEqual(dist['pci_devices'][i]['vendor_id'], + result['pci_devices'][i]['vendor_id']) + self.assertEqual(dist['pci_devices'][i]['id'], + result['pci_devices'][i]['id']) + self.assertEqual(dist['pci_devices'][i]['status'], + result['pci_devices'][i]['status']) + self.assertEqual(dist['pci_devices'][i]['address'], + result['pci_devices'][i]['address']) + + def test_detail(self): + self.stubs.Set(db, 'compute_node_get_all', + self._fake_compute_node_get_all) + self.stubs.Set(db, 'pci_device_get_all_by_node', + self._fake_pci_device_get_all_by_node) + req = fakes.HTTPRequestV3.blank('/os-pci/detail', + use_admin_context=True) + result = self.controller.detail(req) + dist = {'pci_devices': [test_pci_device.fake_db_dev, + test_pci_device.fake_db_dev_1]} + for i in range(len(result['pci_devices'])): + self.assertEqual(dist['pci_devices'][i]['vendor_id'], + result['pci_devices'][i]['vendor_id']) + self.assertEqual(dist['pci_devices'][i]['id'], + result['pci_devices'][i]['id']) + self.assertEqual(dist['pci_devices'][i]['label'], + result['pci_devices'][i]['label']) + self.assertEqual(dist['pci_devices'][i]['dev_id'], + result['pci_devices'][i]['dev_id']) diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py index 16030878d146..3b236e42998f 100644 --- a/nova/tests/fake_policy.py +++ b/nova/tests/fake_policy.py @@ -241,6 +241,9 @@ policy_data = """ "compute_extension:v3:os-pause-server:pause": "", "compute_extension:v3:os-pause-server:unpause": "", "compute_extension:v3:os-pci:pci_servers": "", + "compute_extension:v3:os-pci:index": "", + "compute_extension:v3:os-pci:detail": "", + "compute_extension:v3:os-pci:show": "", "compute_extension:quotas:show": "", "compute_extension:quotas:update": "", "compute_extension:quotas:delete": "", diff --git a/nova/tests/integrated/v3/api_samples/os-pci/pci-detail-resp.json.tpl b/nova/tests/integrated/v3/api_samples/os-pci/pci-detail-resp.json.tpl new file mode 100644 index 000000000000..61cb17c6b4ee --- /dev/null +++ b/nova/tests/integrated/v3/api_samples/os-pci/pci-detail-resp.json.tpl @@ -0,0 +1,36 @@ +{ + "pci_devices": [ + { + "address": "0000:04:10.0", + "compute_node_id": 1, + "dev_id": "pci_0000_04_10_0", + "dev_type": "type-VF", + "extra_info": { + "key1": "value1", + "key2": "value2" + }, + "id": 1, + "server_uuid": "69ba1044-0766-4ec0-b60d-09595de034a1", + "label": "label_8086_1520", + "product_id": "1520", + "status": "available", + "vendor_id": "8086" + }, + { + "address": "0000:04:10.1", + "compute_node_id": 1, + "dev_id": "pci_0000_04_10_1", + "dev_type": "type-VF", + "extra_info": { + "key3": "value3", + "key4": "value4" + }, + "id": 2, + "server_uuid": "d5b446a6-a1b4-4d01-b4f0-eac37b3a62fc", + "label": "label_8086_1520", + "product_id": "1520", + "status": "available", + "vendor_id": "8086" + } + ] +} diff --git a/nova/tests/integrated/v3/api_samples/os-pci/pci-index-resp.json.tpl b/nova/tests/integrated/v3/api_samples/os-pci/pci-index-resp.json.tpl new file mode 100644 index 000000000000..6268f316dfe2 --- /dev/null +++ b/nova/tests/integrated/v3/api_samples/os-pci/pci-index-resp.json.tpl @@ -0,0 +1,20 @@ +{ + "pci_devices": [ + { + "address": "0000:04:10.0", + "compute_node_id": 1, + "id": 1, + "product_id": "1520", + "status": "available", + "vendor_id": "8086" + }, + { + "address": "0000:04:10.1", + "compute_node_id": 1, + "id": 2, + "product_id": "1520", + "status": "available", + "vendor_id": "8086" + } + ] +} \ No newline at end of file diff --git a/nova/tests/integrated/v3/api_samples/os-pci/pci-show-resp.json.tpl b/nova/tests/integrated/v3/api_samples/os-pci/pci-show-resp.json.tpl new file mode 100644 index 000000000000..997776988174 --- /dev/null +++ b/nova/tests/integrated/v3/api_samples/os-pci/pci-show-resp.json.tpl @@ -0,0 +1,18 @@ +{ + "pci_device": { + "address": "0000:04:10.0", + "compute_node_id": 1, + "dev_id": "pci_0000_04_10_0", + "dev_type": "type-VF", + "extra_info": { + "key1": "value1", + "key2": "value2" + }, + "id": 1, + "server_uuid": "69ba1044-0766-4ec0-b60d-09595de034a1", + "label": "label_8086_1520", + "product_id": "1520", + "status": "available", + "vendor_id": "8086" + } +} diff --git a/nova/tests/integrated/v3/test_pci.py b/nova/tests/integrated/v3/test_pci.py index 0faa79283ddd..3e1ba8bfda4e 100644 --- a/nova/tests/integrated/v3/test_pci.py +++ b/nova/tests/integrated/v3/test_pci.py @@ -18,6 +18,43 @@ from nova.tests.integrated.v3 import api_sample_base from nova.tests.integrated.v3 import test_servers +fake_db_dev_1 = { + 'created_at': None, + 'updated_at': None, + 'deleted_at': None, + 'deleted': None, + 'id': 1, + 'compute_node_id': 1, + 'address': '0000:04:10.0', + 'vendor_id': '8086', + 'product_id': '1520', + 'dev_type': 'type-VF', + 'status': 'available', + 'dev_id': 'pci_0000_04_10_0', + 'label': 'label_8086_1520', + 'instance_uuid': '69ba1044-0766-4ec0-b60d-09595de034a1', + 'extra_info': '{"key1": "value1", "key2": "value2"}' + } + +fake_db_dev_2 = { + 'created_at': None, + 'updated_at': None, + 'deleted_at': None, + 'deleted': None, + 'id': 2, + 'compute_node_id': 1, + 'address': '0000:04:10.1', + 'vendor_id': '8086', + 'product_id': '1520', + 'dev_type': 'type-VF', + 'status': 'available', + 'dev_id': 'pci_0000_04_10_1', + 'label': 'label_8086_1520', + 'instance_uuid': 'd5b446a6-a1b4-4d01-b4f0-eac37b3a62fc', + 'extra_info': '{"key3": "value3", "key4": "value4"}' + } + + class ExtendedServerPciSampleJsonTest(test_servers.ServersSampleBase): extension_name = "os-pci" @@ -104,3 +141,34 @@ class ExtendedHyervisorPciSampleJsonTest(api_sample_base.ApiSampleTestBaseV3): subs.update(self._get_regexes()) self._verify_response('hypervisors-pci-detail-resp', subs, response, 200) + + +class PciSampleJsonTest(api_sample_base.ApiSampleTestBaseV3): + extension_name = "os-pci" + + def _fake_pci_device_get_by_id(self, context, id): + return fake_db_dev_1 + + def _fake_pci_device_get_all_by_node(self, context, id): + return [fake_db_dev_1, fake_db_dev_2] + + def test_pci_show(self): + self.stubs.Set(db, 'pci_device_get_by_id', + self._fake_pci_device_get_by_id) + response = self._do_get('os-pci/1') + subs = self._get_regexes() + self._verify_response('pci-show-resp', subs, response, 200) + + def test_pci_index(self): + self.stubs.Set(db, 'pci_device_get_all_by_node', + self._fake_pci_device_get_all_by_node) + response = self._do_get('os-pci') + subs = self._get_regexes() + self._verify_response('pci-index-resp', subs, response, 200) + + def test_pci_detail(self): + self.stubs.Set(db, 'pci_device_get_all_by_node', + self._fake_pci_device_get_all_by_node) + response = self._do_get('os-pci/detail') + subs = self._get_regexes() + self._verify_response('pci-detail-resp', subs, response, 200)