diff --git a/neutron_lib/clients/placement.py b/neutron_lib/clients/placement.py index 047bda874..4dee2cdd6 100644 --- a/neutron_lib/clients/placement.py +++ b/neutron_lib/clients/placement.py @@ -14,10 +14,12 @@ # under the License. import functools +import re from keystoneauth1 import exceptions as ks_exc from keystoneauth1 import loading as keystone from oslo_log import log as logging +from oslo_utils import versionutils from neutron_lib._i18n import _ from neutron_lib.exceptions import placement as n_exc @@ -26,7 +28,8 @@ from neutron_lib.exceptions import placement as n_exc LOG = logging.getLogger(__name__) API_VERSION_REQUEST_HEADER = 'OpenStack-API-Version' -PLACEMENT_API_WITH_AGGREGATES = 'placement 1.1' +PLACEMENT_API_WITH_MEMBER_OF = 'placement 1.3' +PLACEMENT_API_WITH_NESTED_RESOURCES = 'placement 1.14' def _check_placement_api_available(f): @@ -58,12 +61,19 @@ def _check_placement_api_available(f): return wrapper +def _get_version(openstack_api_version): + match = re.search(r"placement (?P\d+\.\d+)", + openstack_api_version) + return versionutils.convert_version_to_tuple(match.group('api_version')) + + class PlacementAPIClient(object): """Client class for placement ReST API.""" def __init__(self, conf, - openstack_api_version=PLACEMENT_API_WITH_AGGREGATES): + openstack_api_version=PLACEMENT_API_WITH_NESTED_RESOURCES): self._openstack_api_version = openstack_api_version + self._target_version = _get_version(openstack_api_version) self._conf = conf self._ks_filter = {'service_type': 'placement', 'region_name': self._conf.placement.region_name} @@ -116,6 +126,72 @@ class PlacementAPIClient(object): url = '/resource_providers/%s' % resource_provider_uuid self._delete(url) + @_check_placement_api_available + def get_resource_provider(self, resource_provider_uuid): + """Get resource provider by UUID. + + :param resource_provider_uuid: UUID of the resource provider. + :raises PlacementResourceProviderNotFound: If the resource provider is + not present. + :returns: The Resource Provider matching the UUID. + :raises PlacementResourceProviderNotFound: For failure to find resource + """ + url = '/resource_providers/%s' % resource_provider_uuid + try: + return self._get(url, headers={API_VERSION_REQUEST_HEADER: + self._openstack_api_version}).json() + except ks_exc.NotFound: + raise n_exc.PlacementResourceProviderNotFound( + resource_provider=resource_provider_uuid) + + @_check_placement_api_available + def list_resource_providers(self, name=None, member_of=None, + resources=None, in_tree=None, uuid=None): + """Get a list of resource providers. + + :param name: Name of the resource providers. + :param member_of: List of aggregate UUID to get those resource + providers that are associated with. + NOTE: placement 1.3 needed. + :param resources: Dictionary of resource classes and requested values. + :param in_tree: UUID of a resource provider that the caller wants to + limit the returned providers to those within its + 'provider tree'. The returned list will contain only + resource providers with the root_provider_id of the + resource provider with UUID == tree_uuid. + NOTE: placement 1.14 needed. + :param uuid: UUID of the resource provider. + :returns: A list of Resource Provider matching the filters. + :raises PlacementAPIVersionIncorrect: If placement API target version + is too low + """ + url = '/resource_providers' + filters = {} + if name: + filters['name'] = name + if member_of: + needed_version = _get_version(PLACEMENT_API_WITH_MEMBER_OF) + if self._target_version < needed_version: + raise n_exc.PlacementAPIVersionIncorrect( + current_version=self._target_version, + needed_version=needed_version) + filters['member_of'] = member_of + if resources: + filters['resources'] = resources + if in_tree: + needed_version = _get_version( + PLACEMENT_API_WITH_NESTED_RESOURCES) + if self._target_version < needed_version: + raise n_exc.PlacementAPIVersionIncorrect( + current_version=self._target_version, + needed_version=needed_version) + filters['in_tree'] = in_tree + if uuid: + filters['uuid'] = uuid + return self._get(url, headers={API_VERSION_REQUEST_HEADER: + self._openstack_api_version}, + **filters).json() + @_check_placement_api_available def create_inventory(self, resource_provider_uuid, inventory): """Create an inventory. diff --git a/neutron_lib/exceptions/placement.py b/neutron_lib/exceptions/placement.py index 6bc51bf6d..05acc3b6e 100644 --- a/neutron_lib/exceptions/placement.py +++ b/neutron_lib/exceptions/placement.py @@ -37,3 +37,8 @@ class PlacementInventoryUpdateConflict(exceptions.Conflict): class PlacementAggregateNotFound(exceptions.NotFound): message = _("Aggregate not found for resource provider " "%(resource_provider)s.") + + +class PlacementAPIVersionIncorrect(exceptions.NotFound): + message = _("Placement API version %(current_version)s, do not meet the" + "needed version %(needed_version)s.") diff --git a/neutron_lib/tests/unit/clients/test_placement.py b/neutron_lib/tests/unit/clients/test_placement.py index 98a129724..b31f306ae 100644 --- a/neutron_lib/tests/unit/clients/test_placement.py +++ b/neutron_lib/tests/unit/clients/test_placement.py @@ -33,7 +33,8 @@ class TestPlacementAPIClient(base.BaseTestCase): super(TestPlacementAPIClient, self).setUp() config = mock.Mock() config.region_name = 'region_name' - self.openstack_api_version = 'version 1.1' + self.openstack_api_version = ( + placement.PLACEMENT_API_WITH_NESTED_RESOURCES) self.placement_api_client = placement.PlacementAPIClient( config, self.openstack_api_version) self.placement_fixture = self.useFixture( @@ -51,6 +52,50 @@ class TestPlacementAPIClient(base.BaseTestCase): self.placement_fixture.mock_delete.assert_called_once_with( '/resource_providers/%s' % RESOURCE_PROVIDER_UUID) + def test_get_resource_provider(self): + headers = {'OpenStack-API-Version': self.openstack_api_version} + self.placement_api_client.get_resource_provider(RESOURCE_PROVIDER_UUID) + self.placement_fixture.mock_get.assert_called_once_with( + '/resource_providers/%s' % RESOURCE_PROVIDER_UUID, + headers=headers) + + def test_get_resource_provider_no_resource_provider(self): + self.placement_fixture.mock_get.side_effect = ks_exc.NotFound() + self.assertRaises(n_exc.PlacementResourceProviderNotFound, + self.placement_api_client.get_resource_provider, + RESOURCE_PROVIDER_UUID) + + def test_list_resource_providers(self): + headers = {'OpenStack-API-Version': self.openstack_api_version} + filter_1 = {'name': 'name1', 'in_tree': 'tree1_uuid'} + self.placement_api_client.list_resource_providers(**filter_1) + self.placement_fixture.mock_get.assert_called_once_with( + '/resource_providers', headers=headers, **filter_1) + + filter_2 = {'member_of': ['aggregate_uuid'], 'uuid': 'uuid_1', + 'resources': {'r_class1': 'value1'}} + self.placement_fixture.mock_get.reset_mock() + self.placement_api_client.list_resource_providers(**filter_2) + self.placement_fixture.mock_get.assert_called_once_with( + '/resource_providers', headers=headers, **filter_2) + + filter_1.update(filter_2) + self.placement_fixture.mock_get.reset_mock() + self.placement_api_client.list_resource_providers(**filter_1) + self.placement_fixture.mock_get.assert_called_once_with( + '/resource_providers', headers=headers, **filter_1) + + def test_list_resource_providers_placement_api_version_too_low(self): + self.placement_api_client._target_version = (1, 1) + self.assertRaises( + n_exc.PlacementAPIVersionIncorrect, + self.placement_api_client.list_resource_providers, + member_of=['aggregate_uuid']) + self.assertRaises( + n_exc.PlacementAPIVersionIncorrect, + self.placement_api_client.list_resource_providers, + in_tree='tree1_uuid') + def test_create_inventory(self): inventory = mock.ANY self.placement_api_client.create_inventory(RESOURCE_PROVIDER_UUID, diff --git a/releasenotes/notes/placement-resource-provider-functions-17ec45f714ea2b23.yaml b/releasenotes/notes/placement-resource-provider-functions-17ec45f714ea2b23.yaml new file mode 100644 index 000000000..c8885ce8f --- /dev/null +++ b/releasenotes/notes/placement-resource-provider-functions-17ec45f714ea2b23.yaml @@ -0,0 +1,13 @@ +features: + - Added ``list_resource_providers`` function to the Placement API client, which + allows to retrieve a list of Resource Providers filtering by UUID or parent + UUID. + It requires at least version ``1.3`` of placement API for listing resource + providers that are members of any of the list of aggregates provided. + It requires at least version ``1.14`` of placement API for listing nested + resource providers. + - Added ``get_resource_provider`` function to the Placement API client, which + allows to retrieve an specific Resource Provider by its UUID. + - Added ``PlacementAPIVersionIncorrect`` exception class which can be raised + when requested placement API version is incorect and doesn't support + requested API feature.