From 6fee18fcd1c1fc4715d0520abd9ab18e259c43e0 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 16 Dec 2022 12:07:25 +0000 Subject: [PATCH] Add 'all_projects' support to proxy layers A number of compute and block storage APIs support this parameter. Plumb them through. This turns out to be a bigger change than expected as it highlights a number of gaps in our documentation. Change-Id: Id4049193ab2e6c173208692ed46fc5f9491da3dc Signed-off-by: Stephen Finucane --- openstack/block_storage/v2/_proxy.py | 58 +++++- openstack/block_storage/v3/_proxy.py | 115 +++++++--- openstack/compute/v2/_proxy.py | 196 ++++++++++++++---- openstack/resource.py | 13 +- .../tests/unit/block_storage/v2/test_proxy.py | 61 ++++-- .../tests/unit/block_storage/v3/test_proxy.py | 19 +- openstack/tests/unit/compute/v2/test_proxy.py | 17 +- .../tests/unit/workflow/v2/test_proxy.py | 7 +- openstack/workflow/v2/_proxy.py | 42 +++- ...-all_projects-filter-27f1d471a7848507.yaml | 33 +++ 10 files changed, 448 insertions(+), 113 deletions(-) create mode 100644 releasenotes/notes/list-all_projects-filter-27f1d471a7848507.yaml diff --git a/openstack/block_storage/v2/_proxy.py b/openstack/block_storage/v2/_proxy.py index 50f065a79..c1808e4a7 100644 --- a/openstack/block_storage/v2/_proxy.py +++ b/openstack/block_storage/v2/_proxy.py @@ -37,7 +37,13 @@ class Proxy(_base_proxy.BaseBlockStorageProxy): """ return self._get(_snapshot.Snapshot, snapshot) - def find_snapshot(self, name_or_id, ignore_missing=True): + def find_snapshot( + self, + name_or_id, + ignore_missing=True, + *, + all_projects=False, + ): """Find a single snapshot :param snapshot: The name or ID a snapshot @@ -45,6 +51,9 @@ class Proxy(_base_proxy.BaseBlockStorageProxy): :class:`~openstack.exceptions.ResourceNotFound` will be raised when the snapshot does not exist. When set to ``True``, None will be returned when attempting to find a nonexistent resource. + :param bool all_projects: When set to ``True``, search for snapshot by + name across all projects. Note that this will likely result in + a higher chance of duplicates. Admin-only by default. :returns: One :class:`~openstack.block_storage.v2.snapshot.Snapshot` or None. @@ -53,13 +62,17 @@ class Proxy(_base_proxy.BaseBlockStorageProxy): :raises: :class:`~openstack.exceptions.DuplicateResource` when multiple resources are found. """ + query = {} + if all_projects: + query['all_projects'] = True return self._find( _snapshot.Snapshot, name_or_id, ignore_missing=ignore_missing, + **query, ) - def snapshots(self, details=True, **query): + def snapshots(self, *, details=True, all_projects=False, **query): """Retrieve a generator of snapshots :param bool details: When set to ``False`` @@ -67,17 +80,20 @@ class Proxy(_base_proxy.BaseBlockStorageProxy): objects will be returned. The default, ``True``, will cause :class:`~openstack.block_storage.v2.snapshot.SnapshotDetail` objects to be returned. + :param bool all_projects: When set to ``True``, list snapshots from all + projects. Admin-only by default. :param kwargs query: Optional query parameters to be sent to limit the snapshots being returned. Available parameters include: * name: Name of the snapshot as a string. - * all_projects: Whether return the snapshots in all projects. * volume_id: volume id of a snapshot. * status: Value of the status of the snapshot so that you can filter on "available" for example. :returns: A generator of snapshot objects. """ + if all_projects: + query['all_projects'] = True base_path = '/snapshots/detail' if details else None return self._list(_snapshot.Snapshot, base_path=base_path, **query) @@ -221,37 +237,59 @@ class Proxy(_base_proxy.BaseBlockStorageProxy): """ return self._get(_volume.Volume, volume) - def find_volume(self, name_or_id, ignore_missing=True): + def find_volume( + self, + name_or_id, + ignore_missing=True, + *, + all_projects=False, + ): """Find a single volume - :param snapshot: The name or ID a volume + :param volume: The name or ID a volume :param bool ignore_missing: When set to ``False`` :class:`~openstack.exceptions.ResourceNotFound` will be raised when the volume does not exist. + :param bool all_projects: When set to ``True``, search for volume by + name across all projects. Note that this will likely result in + a higher chance of duplicates. Admin-only by default. - :returns: One :class:`~openstack.block_storage.v2.volume.Volume` + :returns: One :class:`~openstack.block_storage.v2.volume.Volume` or + None. :raises: :class:`~openstack.exceptions.ResourceNotFound` when no resource can be found. + :raises: :class:`~openstack.exceptions.DuplicateResource` when multiple + resources are found. """ - return self._find(_volume.Volume, name_or_id, - ignore_missing=ignore_missing) + query = {} + if all_projects: + query['all_projects'] = True + return self._find( + _volume.Volume, + name_or_id, + ignore_missing=ignore_missing, + **query, + ) - def volumes(self, details=True, **query): + def volumes(self, *, details=True, all_projects=False, **query): """Retrieve a generator of volumes :param bool details: When set to ``False`` no extended attributes will be returned. The default, ``True``, will cause objects with additional attributes to be returned. + :param bool all_projects: When set to ``True``, list volumes from all + projects. Admin-only by default. :param kwargs query: Optional query parameters to be sent to limit the volumes being returned. Available parameters include: * name: Name of the volume as a string. - * all_projects: Whether return the volumes in all projects * status: Value of the status of the volume so that you can filter on "available" for example. :returns: A generator of volume objects. """ + if all_projects: + query['all_projects'] = True base_path = '/volumes/detail' if details else None return self._list(_volume.Volume, base_path=base_path, **query) diff --git a/openstack/block_storage/v3/_proxy.py b/openstack/block_storage/v3/_proxy.py index d3bb65623..7dc7518ea 100644 --- a/openstack/block_storage/v3/_proxy.py +++ b/openstack/block_storage/v3/_proxy.py @@ -62,33 +62,53 @@ class Proxy(_base_proxy.BaseBlockStorageProxy): """ return self._get(_snapshot.Snapshot, snapshot) - def find_snapshot(self, name_or_id, ignore_missing=True): + def find_snapshot( + self, + name_or_id, + ignore_missing=True, + *, + all_projects=False, + ): """Find a single snapshot :param snapshot: The name or ID a snapshot :param bool ignore_missing: When set to ``False`` :class:`~openstack.exceptions.ResourceNotFound` will be raised - when the snapshot does not exist. + when the snapshot does not exist. When set to ``True``, None will + be returned when attempting to find a nonexistent resource. + :param bool all_projects: When set to ``True``, search for snapshot by + name across all projects. Note that this will likely result in + a higher chance of duplicates. Admin-only by default. :returns: One :class:`~openstack.block_storage.v3.snapshot.Snapshot` :raises: :class:`~openstack.exceptions.ResourceNotFound` when no resource can be found. + :raises: :class:`~openstack.exceptions.DuplicateResource` when multiple + resources are found. """ - return self._find(_snapshot.Snapshot, name_or_id, - ignore_missing=ignore_missing) + query = {} + if all_projects: + query['all_projects'] = True + return self._find( + _snapshot.Snapshot, + name_or_id, + ignore_missing=ignore_missing, + **query, + ) - def snapshots(self, details=True, **query): + def snapshots(self, *, details=True, all_projects=False, **query): """Retrieve a generator of snapshots :param bool details: When set to ``False`` :class: `~openstack.block_storage.v3.snapshot.Snapshot` objects will be returned. The default, ``True``, will cause more attributes to be returned. + :param bool all_projects: When set to ``True``, list snapshots from all + projects. Admin-only by default. :param kwargs query: Optional query parameters to be sent to limit the snapshots being returned. Available parameters include: * name: Name of the snapshot as a string. - * all_projects: Whether return the snapshots in all projects. * project_id: Filter the snapshots by project. * volume_id: volume id of a snapshot. * status: Value of the status of the snapshot so that you can @@ -96,6 +116,8 @@ class Proxy(_base_proxy.BaseBlockStorageProxy): :returns: A generator of snapshot objects. """ + if all_projects: + query['all_projects'] = True base_path = '/snapshots/detail' if details else None return self._list(_snapshot.Snapshot, base_path=base_path, **query) @@ -237,14 +259,19 @@ class Proxy(_base_proxy.BaseBlockStorageProxy): :param snapshot: The name or ID a volume type :param bool ignore_missing: When set to ``False`` :class:`~openstack.exceptions.ResourceNotFound` will be raised - when the type does not exist. + when the volume type does not exist. :returns: One :class:`~openstack.block_storage.v3.type.Type` :raises: :class:`~openstack.exceptions.ResourceNotFound` when no resource can be found. + :raises: :class:`~openstack.exceptions.DuplicateResource` when multiple + resources are found. """ - return self._find(_type.Type, name_or_id, - ignore_missing=ignore_missing) + return self._find( + _type.Type, + name_or_id, + ignore_missing=ignore_missing, + ) def types(self, **query): """Retrieve a generator of volume types @@ -469,38 +496,59 @@ class Proxy(_base_proxy.BaseBlockStorageProxy): """ return self._get(_volume.Volume, volume) - def find_volume(self, name_or_id, ignore_missing=True): + def find_volume( + self, + name_or_id, + ignore_missing=True, + *, + all_projects=False, + ): """Find a single volume :param snapshot: The name or ID a volume :param bool ignore_missing: When set to ``False`` :class:`~openstack.exceptions.ResourceNotFound` will be raised when the volume does not exist. + :param bool all_projects: When set to ``True``, search for volume by + name across all projects. Note that this will likely result in + a higher chance of duplicates. Admin-only by default. :returns: One :class:`~openstack.block_storage.v3.volume.Volume` :raises: :class:`~openstack.exceptions.ResourceNotFound` when no resource can be found. + :raises: :class:`~openstack.exceptions.DuplicateResource` when multiple + resources are found. """ - return self._find(_volume.Volume, name_or_id, - ignore_missing=ignore_missing, - list_base_path='/volumes/detail') + query = {} + if all_projects: + query['all_projects'] = True + return self._find( + _volume.Volume, + name_or_id, + ignore_missing=ignore_missing, + list_base_path='/volumes/detail', + **query, + ) - def volumes(self, details=True, **query): + def volumes(self, *, details=True, all_projects=False, **query): """Retrieve a generator of volumes :param bool details: When set to ``False`` no extended attributes will be returned. The default, ``True``, will cause objects with additional attributes to be returned. + :param bool all_projects: When set to ``True``, list volumes from all + projects. Admin-only by default. :param kwargs query: Optional query parameters to be sent to limit the volumes being returned. Available parameters include: * name: Name of the volume as a string. - * all_projects: Whether return the volumes in all projects * status: Value of the status of the volume so that you can filter on "available" for example. :returns: A generator of volume objects. """ + if all_projects: + query['all_projects'] = True base_path = '/volumes/detail' if details else None return self._list(_volume.Volume, base_path=base_path, **query) @@ -872,7 +920,7 @@ class Proxy(_base_proxy.BaseBlockStorageProxy): return self._list(_stats.Pools, **query) # ====== BACKUPS ====== - def backups(self, details=True, **query): + def backups(self, *, details=True, **query): """Retrieve a generator of backups :param bool details: When set to ``False`` @@ -921,9 +969,14 @@ class Proxy(_base_proxy.BaseBlockStorageProxy): :returns: One :class:`~openstack.block_storage.v3.backup.Backup` :raises: :class:`~openstack.exceptions.ResourceNotFound` when no resource can be found. + :raises: :class:`~openstack.exceptions.DuplicateResource` when multiple + resources are found. """ - return self._find(_backup.Backup, name_or_id, - ignore_missing=ignore_missing) + return self._find( + _backup.Backup, + name_or_id, + ignore_missing=ignore_missing, + ) def create_backup(self, **attrs): """Create a new Backup from attributes with native API @@ -1032,11 +1085,16 @@ class Proxy(_base_proxy.BaseBlockStorageProxy): :returns: One :class:`~openstack.block_storage.v3.group.Group` :raises: :class:`~openstack.exceptions.ResourceNotFound` when no resource can be found. + :raises: :class:`~openstack.exceptions.DuplicateResource` when multiple + resources are found. """ return self._find( - _group.Group, name_or_id, ignore_missing=ignore_missing) + _group.Group, + name_or_id, + ignore_missing=ignore_missing, + ) - def groups(self, details=True, **query): + def groups(self, *, details=True, **query): """Retrieve a generator of groups :param bool details: When set to ``False``, no additional details will @@ -1155,12 +1213,16 @@ class Proxy(_base_proxy.BaseBlockStorageProxy): :returns: One :class:`~openstack.block_storage.v3.group_snapshot` :raises: :class:`~openstack.exceptions.ResourceNotFound` when no resource can be found. + :raises: :class:`~openstack.exceptions.DuplicateResource` when multiple + resources are found. """ return self._find( - _group_snapshot.GroupSnapshot, name_or_id, - ignore_missing=ignore_missing) + _group_snapshot.GroupSnapshot, + name_or_id, + ignore_missing=ignore_missing, + ) - def group_snapshots(self, details=True, **query): + def group_snapshots(self, *, details=True, **query): """Retrieve a generator of group snapshots :param bool details: When ``True``, returns @@ -1245,9 +1307,14 @@ class Proxy(_base_proxy.BaseBlockStorageProxy): :class:`~openstack.block_storage.v3.group_type.GroupType` :raises: :class:`~openstack.exceptions.ResourceNotFound` when no resource can be found. + :raises: :class:`~openstack.exceptions.DuplicateResource` when multiple + resources are found. """ return self._find( - _group_type.GroupType, name_or_id, ignore_missing=ignore_missing) + _group_type.GroupType, + name_or_id, + ignore_missing=ignore_missing, + ) def group_types(self, **query): """Retrive a generator of group types diff --git a/openstack/compute/v2/_proxy.py b/openstack/compute/v2/_proxy.py index 4f701ef3a..9dc86f333 100644 --- a/openstack/compute/v2/_proxy.py +++ b/openstack/compute/v2/_proxy.py @@ -78,11 +78,19 @@ class Proxy(proxy.Proxy): raised when the resource does not exist. When set to ``True``, None will be returned when attempting to find a nonexistent resource. + :returns: One :class:`~openstack.compute.v2.extension.Extension` or None + :raises: :class:`~openstack.exceptions.ResourceNotFound` + when no resource can be found. + :raises: :class:`~openstack.exceptions.DuplicateResource` when multiple + resources are found. """ - return self._find(extension.Extension, name_or_id, - ignore_missing=ignore_missing) + return self._find( + extension.Extension, + name_or_id, + ignore_missing=ignore_missing, + ) def extensions(self): """Retrieve a generator of extensions @@ -94,8 +102,15 @@ class Proxy(proxy.Proxy): # ========== Flavors ========== - def find_flavor(self, name_or_id, ignore_missing=True, - get_extra_specs=False, **query): + # TODO(stephenfin): Drop 'query' parameter or apply it consistently + def find_flavor( + self, + name_or_id, + ignore_missing=True, + *, + get_extra_specs=False, + **query, + ): """Find a single flavor :param name_or_id: The name or ID of a flavor. @@ -106,14 +121,21 @@ class Proxy(proxy.Proxy): :param bool get_extra_specs: When set to ``True`` and extra_specs not present in the response will invoke additional API call to fetch extra_specs. - :param kwargs query: Optional query parameters to be sent to limit the flavors being returned. :returns: One :class:`~openstack.compute.v2.flavor.Flavor` or None + :raises: :class:`~openstack.exceptions.ResourceNotFound` + when no resource can be found. + :raises: :class:`~openstack.exceptions.DuplicateResource` when multiple + resources are found. """ flavor = self._find( - _flavor.Flavor, name_or_id, ignore_missing=ignore_missing, **query) + _flavor.Flavor, + name_or_id, + ignore_missing=ignore_missing, + **query, + ) if flavor and get_extra_specs and not flavor.extra_specs: flavor = flavor.fetch_extra_specs(self) return flavor @@ -325,11 +347,19 @@ class Proxy(proxy.Proxy): :class:`~openstack.exceptions.ResourceNotFound` will be raised when the resource does not exist. When set to ``True``, None will be returned when attempting to find a nonexistent resource. + :returns: One :class:`~openstack.compute.v2.aggregate.Aggregate` or None + :raises: :class:`~openstack.exceptions.ResourceNotFound` + when no resource can be found. + :raises: :class:`~openstack.exceptions.DuplicateResource` when multiple + resources are found. """ - return self._find(_aggregate.Aggregate, name_or_id, - ignore_missing=ignore_missing) + return self._find( + _aggregate.Aggregate, + name_or_id, + ignore_missing=ignore_missing, + ) def create_aggregate(self, **attrs): """Create a new host aggregate from attributes @@ -463,15 +493,23 @@ class Proxy(proxy.Proxy): raised when the resource does not exist. When set to ``True``, None will be returned when attempting to find a nonexistent resource. + :returns: One :class:`~openstack.compute.v2.image.Image` or None + :raises: :class:`~openstack.exceptions.ResourceNotFound` + when no resource can be found. + :raises: :class:`~openstack.exceptions.DuplicateResource` when multiple + resources are found. """ warnings.warn( 'This API is a proxy to the image service and has been ' 'deprecated; use the image service proxy API instead', DeprecationWarning, ) - return self._find(_image.Image, name_or_id, - ignore_missing=ignore_missing) + return self._find( + _image.Image, + name_or_id, + ignore_missing=ignore_missing, + ) def get_image(self, image): """Get a single image @@ -615,7 +653,7 @@ class Proxy(proxy.Proxy): attrs = {'user_id': user_id} if user_id else {} return self._get(_keypair.Keypair, keypair, **attrs) - def find_keypair(self, name_or_id, ignore_missing=True, user_id=None): + def find_keypair(self, name_or_id, ignore_missing=True, *, user_id=None): """Find a single keypair :param name_or_id: The name or ID of a keypair. @@ -626,11 +664,18 @@ class Proxy(proxy.Proxy): :param str user_id: Optional user_id owning the keypair :returns: One :class:`~openstack.compute.v2.keypair.Keypair` or None + :raises: :class:`~openstack.exceptions.ResourceNotFound` + when no resource can be found. + :raises: :class:`~openstack.exceptions.DuplicateResource` when multiple + resources are found. """ attrs = {'user_id': user_id} if user_id else {} - return self._find(_keypair.Keypair, name_or_id, - ignore_missing=ignore_missing, - **attrs) + return self._find( + _keypair.Keypair, + name_or_id, + ignore_missing=ignore_missing, + **attrs, + ) def keypairs(self, **query): """Return a generator of keypairs @@ -690,20 +735,40 @@ class Proxy(proxy.Proxy): else: self._delete(_server.Server, server, ignore_missing=ignore_missing) - def find_server(self, name_or_id, ignore_missing=True): + def find_server( + self, + name_or_id, + ignore_missing=True, + *, + all_projects=False, + ): """Find a single server :param name_or_id: The name or ID of a server. :param bool ignore_missing: When set to ``False`` - :class:`~openstack.exceptions.ResourceNotFound` will be - raised when the resource does not exist. - When set to ``True``, None will be returned when - attempting to find a nonexistent resource. + :class:`~openstack.exceptions.ResourceNotFound` will be raised when + the resource does not exist. When set to ``True``, None will be + returned when attempting to find a nonexistent resource. + :param bool all_projects: When set to ``True``, search for server + by name across all projects. Note that this will likely result in a + higher chance of duplicates. Admin-only by default. + :returns: One :class:`~openstack.compute.v2.server.Server` or None + :raises: :class:`~openstack.exceptions.ResourceNotFound` + when no resource can be found. + :raises: :class:`~openstack.exceptions.DuplicateResource` when multiple + resources are found. """ - return self._find(_server.Server, name_or_id, - ignore_missing=ignore_missing, - list_base_path='/servers/detail') + query = {} + if all_projects: + query['all_projects'] = True + return self._find( + _server.Server, + name_or_id, + ignore_missing=ignore_missing, + list_base_path='/servers/detail', + **query, + ) def get_server(self, server): """Get a single server @@ -723,6 +788,8 @@ class Proxy(proxy.Proxy): :param bool details: When set to ``False`` instances with only basic data will be returned. The default, ``True``, will cause instances with full data to be returned. + :param bool all_projects: When set to ``True``, lists servers from all + projects. Admin-only by default. :param kwargs query: Optional query parameters to be sent to limit the servers being returned. Available parameters can be seen under https://docs.openstack.org/api-ref/compute/#list-servers @@ -1374,21 +1441,40 @@ class Proxy(proxy.Proxy): self._delete(_server_group.ServerGroup, server_group, ignore_missing=ignore_missing) - def find_server_group(self, name_or_id, ignore_missing=True): + def find_server_group( + self, + name_or_id, + ignore_missing=True, + *, + all_projects=False, + ): """Find a single server group :param name_or_id: The name or ID of a server group. :param bool ignore_missing: When set to ``False`` - :class:`~openstack.exceptions.ResourceNotFound` will be - raised when the resource does not exist. - When set to ``True``, None will be returned when - attempting to find a nonexistent resource. - :returns: - One :class:`~openstack.compute.v2.server_group.ServerGroup` object + :class:`~openstack.exceptions.ResourceNotFound` will be raised when + the resource does not exist. When set to ``True``, None will be + returned when attempting to find a nonexistent resource. + :param bool all_projects: When set to ``True``, search for server + groups by name across all projects. Note that this will likely + result in a higher chance of duplicates. Admin-only by default. + + :returns: One :class:`~openstack.compute.v2.server_group.ServerGroup` or None + :raises: :class:`~openstack.exceptions.ResourceNotFound` + when no resource can be found. + :raises: :class:`~openstack.exceptions.DuplicateResource` when multiple + resources are found. """ - return self._find(_server_group.ServerGroup, name_or_id, - ignore_missing=ignore_missing) + query = {} + if all_projects: + query['all_projects'] = True + return self._find( + _server_group.ServerGroup, + name_or_id, + ignore_missing=ignore_missing, + **query, + ) def get_server_group(self, server_group): """Get a single server group @@ -1404,21 +1490,25 @@ class Proxy(proxy.Proxy): """ return self._get(_server_group.ServerGroup, server_group) - def server_groups(self, **query): + def server_groups(self, *, all_projects=False, **query): """Return a generator of server groups + :param bool all_projects: When set to ``True``, lists servers groups + from all projects. Admin-only by default. :param kwargs query: Optional query parameters to be sent to limit the resources being returned. :returns: A generator of ServerGroup objects :rtype: :class:`~openstack.compute.v2.server_group.ServerGroup` """ + if all_projects: + query['all_projects'] = True return self._list(_server_group.ServerGroup, **query) # ========== Hypervisors ========== def hypervisors(self, details=False, **query): - """Return a generator of hypervisor + """Return a generator of hypervisors :param bool details: When set to the default, ``False``, :class:`~openstack.compute.v2.hypervisor.Hypervisor` @@ -1438,20 +1528,36 @@ class Proxy(proxy.Proxy): pattern=query.pop('hypervisor_hostname_pattern')) return self._list(_hypervisor.Hypervisor, base_path=base_path, **query) - def find_hypervisor(self, name_or_id, ignore_missing=True, details=True): - """Find a hypervisor from name or id to get the corresponding info + def find_hypervisor( + self, + name_or_id, + ignore_missing=True, + *, + details=True, + ): + """Find a single hypervisor - :param name_or_id: The name or id of a hypervisor + :param name_or_id: The name or ID of a hypervisor + :param bool ignore_missing: When set to ``False`` + :class:`~openstack.exceptions.ResourceNotFound` will be raised when + the resource does not exist. When set to ``True``, None will be + returned when attempting to find a nonexistent resource. - :returns: - One: class:`~openstack.compute.v2.hypervisor.Hypervisor` object + :returns: One: class:`~openstack.compute.v2.hypervisor.Hypervisor` or None + :raises: :class:`~openstack.exceptions.ResourceNotFound` + when no resource can be found. + :raises: :class:`~openstack.exceptions.DuplicateResource` when multiple + resources are found. """ list_base_path = '/os-hypervisors/detail' if details else None - return self._find(_hypervisor.Hypervisor, name_or_id, - list_base_path=list_base_path, - ignore_missing=ignore_missing) + return self._find( + _hypervisor.Hypervisor, + name_or_id, + list_base_path=list_base_path, + ignore_missing=ignore_missing, + ) def get_hypervisor(self, hypervisor): """Get a single hypervisor @@ -1580,9 +1686,11 @@ class Proxy(proxy.Proxy): attempting to find a nonexistent resource. :param dict query: Additional attributes like 'host' - :returns: - One: class:`~openstack.compute.v2.hypervisor.Hypervisor` object - or None + :returns: One: class:`~openstack.compute.v2.service.Service` or None + :raises: :class:`~openstack.exceptions.ResourceNotFound` + when no resource can be found. + :raises: :class:`~openstack.exceptions.DuplicateResource` when multiple + resources are found. """ return self._find( _service.Service, diff --git a/openstack/resource.py b/openstack/resource.py index 6044de88e..7cb275820 100644 --- a/openstack/resource.py +++ b/openstack/resource.py @@ -2192,6 +2192,7 @@ class Resource(dict): list_base_path=None, *, microversion=None, + all_projects=None, **params, ): """Find a resource by its name or id. @@ -2220,10 +2221,13 @@ class Resource(dict): is found and ignore_missing is ``False``. """ session = cls._get_session(session) + # Try to short-circuit by looking directly for a matching ID. try: match = cls.existing( - id=name_or_id, connection=session._get_connection(), **params + id=name_or_id, + connection=session._get_connection(), + **params, ) return match.fetch(session, microversion=microversion, **params) except (exceptions.NotFoundException, exceptions.BadRequestException): @@ -2234,6 +2238,12 @@ class Resource(dict): if list_base_path: params['base_path'] = list_base_path + # all_projects is a special case that is used by multiple services. We + # handle it here since it doesn't make sense to pass it to the .fetch + # call above + if all_projects is not None: + params['all_projects'] = all_projects + if ( 'name' in cls._query_mapping._mapping.keys() and 'name' not in params @@ -2248,6 +2258,7 @@ class Resource(dict): if ignore_missing: return None + raise exceptions.ResourceNotFound( "No %s found for %s" % (cls.__name__, name_or_id) ) diff --git a/openstack/tests/unit/block_storage/v2/test_proxy.py b/openstack/tests/unit/block_storage/v2/test_proxy.py index 87db84477..cc9194b86 100644 --- a/openstack/tests/unit/block_storage/v2/test_proxy.py +++ b/openstack/tests/unit/block_storage/v2/test_proxy.py @@ -23,8 +23,9 @@ from openstack.tests.unit import test_proxy_base class TestVolumeProxy(test_proxy_base.TestProxyBase): + def setUp(self): - super(TestVolumeProxy, self).setUp() + super().setUp() self.proxy = _proxy.Proxy(self.session) @@ -34,18 +35,31 @@ class TestVolume(TestVolumeProxy): self.verify_get(self.proxy.get_volume, volume.Volume) def test_volume_find(self): - self.verify_find(self.proxy.find_volume, volume.Volume) + self.verify_find( + self.proxy.find_volume, + volume.Volume, + method_kwargs={'all_projects': True}, + expected_kwargs={'all_projects': True}, + ) def test_volumes_detailed(self): - self.verify_list(self.proxy.volumes, volume.Volume, - method_kwargs={"details": True, "query": 1}, - expected_kwargs={"query": 1, - "base_path": "/volumes/detail"}) + self.verify_list( + self.proxy.volumes, + volume.Volume, + method_kwargs={"details": True, "all_projects": True}, + expected_kwargs={ + "base_path": "/volumes/detail", + "all_projects": True, + } + ) def test_volumes_not_detailed(self): - self.verify_list(self.proxy.volumes, volume.Volume, - method_kwargs={"details": False, "query": 1}, - expected_kwargs={"query": 1}) + self.verify_list( + self.proxy.volumes, + volume.Volume, + method_kwargs={"details": False, "all_projects": True}, + expected_kwargs={"all_projects": True}, + ) def test_volume_create_attrs(self): self.verify_create(self.proxy.create_volume, volume.Volume) @@ -197,6 +211,7 @@ class TestVolumeActions(TestVolumeProxy): class TestBackup(TestVolumeProxy): + def test_backups_detailed(self): self.verify_list( self.proxy.backups, @@ -257,21 +272,33 @@ class TestBackup(TestVolumeProxy): class TestSnapshot(TestVolumeProxy): + def test_snapshot_get(self): self.verify_get(self.proxy.get_snapshot, snapshot.Snapshot) def test_snapshot_find(self): - self.verify_find(self.proxy.find_snapshot, snapshot.Snapshot) + self.verify_find( + self.proxy.find_snapshot, + snapshot.Snapshot, + method_kwargs={'all_projects': True}, + expected_kwargs={'all_projects': True}, + ) def test_snapshots_detailed(self): - self.verify_list(self.proxy.snapshots, snapshot.SnapshotDetail, - method_kwargs={"details": True, "query": 1}, - expected_kwargs={"query": 1}) + self.verify_list( + self.proxy.snapshots, + snapshot.SnapshotDetail, + method_kwargs={"details": True, "all_projects": True}, + expected_kwargs={"all_projects": True}, + ) def test_snapshots_not_detailed(self): - self.verify_list(self.proxy.snapshots, snapshot.Snapshot, - method_kwargs={"details": False, "query": 1}, - expected_kwargs={"query": 1}) + self.verify_list( + self.proxy.snapshots, + snapshot.Snapshot, + method_kwargs={"details": False, "all_projects": True}, + expected_kwargs={"all_projects": 1}, + ) def test_snapshot_create_attrs(self): self.verify_create(self.proxy.create_snapshot, snapshot.Snapshot) @@ -325,6 +352,7 @@ class TestSnapshot(TestVolumeProxy): class TestType(TestVolumeProxy): + def test_type_get(self): self.verify_get(self.proxy.get_type, type.Type) @@ -363,6 +391,7 @@ class TestType(TestVolumeProxy): class TestQuota(TestVolumeProxy): + def test_get(self): self._verify( 'openstack.resource.Resource.fetch', diff --git a/openstack/tests/unit/block_storage/v3/test_proxy.py b/openstack/tests/unit/block_storage/v3/test_proxy.py index 539b82b45..39677628f 100644 --- a/openstack/tests/unit/block_storage/v3/test_proxy.py +++ b/openstack/tests/unit/block_storage/v3/test_proxy.py @@ -41,9 +41,15 @@ class TestVolume(TestVolumeProxy): self.verify_get(self.proxy.get_volume, volume.Volume) def test_volume_find(self): - self.verify_find(self.proxy.find_volume, volume.Volume, - expected_kwargs=dict( - list_base_path='/volumes/detail')) + self.verify_find( + self.proxy.find_volume, + volume.Volume, + method_kwargs={'all_projects': True}, + expected_kwargs={ + "list_base_path": "/volumes/detail", + "all_projects": True, + }, + ) def test_volumes_detailed(self): self.verify_list( @@ -612,7 +618,12 @@ class TestSnapshot(TestVolumeProxy): self.verify_get(self.proxy.get_snapshot, snapshot.Snapshot) def test_snapshot_find(self): - self.verify_find(self.proxy.find_snapshot, snapshot.Snapshot) + self.verify_find( + self.proxy.find_snapshot, + snapshot.Snapshot, + method_kwargs={'all_projects': True}, + expected_kwargs={'all_projects': True}, + ) def test_snapshots_detailed(self): self.verify_list( diff --git a/openstack/tests/unit/compute/v2/test_proxy.py b/openstack/tests/unit/compute/v2/test_proxy.py index f9010c10b..12a0c41e6 100644 --- a/openstack/tests/unit/compute/v2/test_proxy.py +++ b/openstack/tests/unit/compute/v2/test_proxy.py @@ -80,7 +80,8 @@ class TestFlavor(TestComputeProxy): self._verify( 'openstack.proxy.Proxy._find', self.proxy.find_flavor, - method_args=['res', True, True], + method_args=['res', True], + method_kwargs={'get_extra_specs': True}, expected_result=res, expected_args=[flavor.Flavor, 'res'], expected_kwargs={'ignore_missing': True} @@ -851,7 +852,11 @@ class TestCompute(TestComputeProxy): self.verify_find( self.proxy.find_server, server.Server, - expected_kwargs={'list_base_path': '/servers/detail'}, + method_kwargs={'all_projects': True}, + expected_kwargs={ + 'list_base_path': '/servers/detail', + 'all_projects': True, + }, ) def test_server_get(self): @@ -1209,8 +1214,12 @@ class TestCompute(TestComputeProxy): server_group.ServerGroup, True) def test_server_group_find(self): - self.verify_find(self.proxy.find_server_group, - server_group.ServerGroup) + self.verify_find( + self.proxy.find_server_group, + server_group.ServerGroup, + method_kwargs={'all_projects': True}, + expected_kwargs={'all_projects': True}, + ) def test_server_group_get(self): self.verify_get(self.proxy.get_server_group, diff --git a/openstack/tests/unit/workflow/v2/test_proxy.py b/openstack/tests/unit/workflow/v2/test_proxy.py index 2ba862d09..428b59fd6 100644 --- a/openstack/tests/unit/workflow/v2/test_proxy.py +++ b/openstack/tests/unit/workflow/v2/test_proxy.py @@ -85,5 +85,8 @@ class TestCronTriggerProxy(test_proxy_base.TestProxyBase): cron_trigger.CronTrigger, True) def test_cron_trigger_find(self): - self.verify_find(self.proxy.find_cron_trigger, - cron_trigger.CronTrigger) + self.verify_find( + self.proxy.find_cron_trigger, + cron_trigger.CronTrigger, + expected_kwargs={'all_projects': False}, + ) diff --git a/openstack/workflow/v2/_proxy.py b/openstack/workflow/v2/_proxy.py index c29c1ae17..915a76aa9 100644 --- a/openstack/workflow/v2/_proxy.py +++ b/openstack/workflow/v2/_proxy.py @@ -196,9 +196,11 @@ class Proxy(proxy.Proxy): """ return self._get(_cron_trigger.CronTrigger, cron_trigger) - def cron_triggers(self, **query): + def cron_triggers(self, *, all_projects=False, **query): """Retrieve a generator of cron triggers + :param bool all_projects: When set to ``True``, list cron triggers from + all projects. Admin-only by default. :param kwargs query: Optional query parameters to be sent to restrict the cron triggers to be returned. Available parameters include: @@ -212,6 +214,8 @@ class Proxy(proxy.Proxy): :returns: A generator of CronTrigger instances. """ + if all_projects: + query['all_projects'] = True return self._list(_cron_trigger.CronTrigger, **query) def delete_cron_trigger(self, value, ignore_missing=True): @@ -231,17 +235,39 @@ class Proxy(proxy.Proxy): return self._delete(_cron_trigger.CronTrigger, value, ignore_missing=ignore_missing) - def find_cron_trigger(self, name_or_id, ignore_missing=True): + # TODO(stephenfin): Drop 'query' parameter or apply it consistently + def find_cron_trigger( + self, + name_or_id, + ignore_missing=True, + *, + all_projects=False, + **query, + ): """Find a single cron trigger :param name_or_id: The name or ID of a cron trigger. :param bool ignore_missing: When set to ``False`` - :class:`~openstack.exceptions.ResourceNotFound` will be - raised when the resource does not exist. - When set to ``True``, None will be returned when - attempting to find a nonexistent resource. + :class:`~openstack.exceptions.ResourceNotFound` will be raised when + the resource does not exist. When set to ``True``, None will be + returned when attempting to find a nonexistent resource. + :param bool all_projects: When set to ``True``, search for cron + triggers by name across all projects. Note that this will likely + result in a higher chance of duplicates. + :param kwargs query: Optional query parameters to be sent to limit + the cron triggers being returned. + :returns: One :class:`~openstack.compute.v2.cron_trigger.CronTrigger` or None + :raises: :class:`~openstack.exceptions.ResourceNotFound` when no + resource can be found. + :raises: :class:`~openstack.exceptions.DuplicateResource` when multiple + resources are found. """ - return self._find(_cron_trigger.CronTrigger, name_or_id, - ignore_missing=ignore_missing) + return self._find( + _cron_trigger.CronTrigger, + name_or_id, + ignore_missing=ignore_missing, + all_projects=all_projects, + **query, + ) diff --git a/releasenotes/notes/list-all_projects-filter-27f1d471a7848507.yaml b/releasenotes/notes/list-all_projects-filter-27f1d471a7848507.yaml new file mode 100644 index 000000000..cb90309e1 --- /dev/null +++ b/releasenotes/notes/list-all_projects-filter-27f1d471a7848507.yaml @@ -0,0 +1,33 @@ +--- +features: + - | + A number of APIs support passing an admin-only ``all_projects`` filter when + listing certain resources, allowing you to retrieve resources from all + projects rather than just the current projects. This filter is now + explicitly supported at the proxy layer for services and resources that + support it. These are: + + * Block storage (v2) + + * ``find_snapshot`` + * ``snapshots`` + * ``find_volume`` + * ``volumes`` + + * Block storage (v3) + + * ``find_snapshot`` + * ``snapshots`` + * ``find_volume`` + * ``volumes`` + + * Compute (v2) + + * ``find_server`` + * ``find_server_group`` + * ``server_groups`` + + * Workflow (v2) + + * ``find_cron_triggers`` + * ``cron_triggers``