diff --git a/openstack/compute/v2/_proxy.py b/openstack/compute/v2/_proxy.py index 1f03c51ab..6426b8ea8 100644 --- a/openstack/compute/v2/_proxy.py +++ b/openstack/compute/v2/_proxy.py @@ -43,6 +43,7 @@ from openstack.identity.v3 import user as _user from openstack.network.v2 import security_group as _sg from openstack import proxy from openstack import resource +from openstack import types from openstack import utils from openstack import warnings as os_warnings @@ -1196,7 +1197,9 @@ class Proxy(proxy.Proxy): server = self._get_resource(_server.Server, server) server.shelve_offload(self) - def unshelve_server(self, server, *, host=None): + def unshelve_server( + self, server, *, host=None, availability_zone=types.UNSET + ): """Unshelves or restores a shelved server. Policy defaults enable only users with administrative role or the @@ -1210,7 +1213,7 @@ class Proxy(proxy.Proxy): :returns: None """ server = self._get_resource(_server.Server, server) - server.unshelve(self, host=host) + server.unshelve(self, host=host, availability_zone=availability_zone) def trigger_server_crash_dump(self, server): """Trigger a crash dump in a server. diff --git a/openstack/compute/v2/server.py b/openstack/compute/v2/server.py index bdbb1b80d..74f09182e 100644 --- a/openstack/compute/v2/server.py +++ b/openstack/compute/v2/server.py @@ -19,18 +19,10 @@ from openstack.compute.v2 import volume_attachment from openstack import exceptions from openstack.image.v2 import image from openstack import resource +from openstack import types from openstack import utils -# Workaround Python's lack of an undefined sentinel -# https://python-patterns.guide/python/sentinel-object/ -class Unset: - def __bool__(self) -> ty.Literal[False]: - return False - - -UNSET: Unset = Unset() - CONSOLE_TYPE_ACTION_MAPPING = { 'novnc': 'os-getVNCConsole', 'xvpvnc': 'os-getVNCConsole', @@ -53,11 +45,6 @@ class Server(resource.Resource, metadata.MetadataMixin, tag.TagMixin): allow_delete = True allow_list = True - # Sentinel used to differentiate API called without parameter or None - # Ex unshelve API can be called without an availability_zone or with - # availability_zone = None to unpin the az. - _sentinel = object() - _query_mapping = resource.QueryParameters( "auto_disk_config", "availability_zone", @@ -410,17 +397,17 @@ class Server(resource.Resource, metadata.MetadataMixin, tag.TagMixin): self, session, image, - name=UNSET, - admin_password=UNSET, - preserve_ephemeral=UNSET, - access_ipv4=UNSET, - access_ipv6=UNSET, - metadata=UNSET, - user_data=UNSET, - key_name=UNSET, - description=UNSET, - trusted_image_certificates=UNSET, - hostname=UNSET, + name=types.UNSET, + admin_password=types.UNSET, + preserve_ephemeral=types.UNSET, + access_ipv4=types.UNSET, + access_ipv6=types.UNSET, + metadata=types.UNSET, + user_data=types.UNSET, + key_name=types.UNSET, + description=types.UNSET, + trusted_image_certificates=types.UNSET, + hostname=types.UNSET, ): """Rebuild the server with the given arguments. @@ -449,27 +436,27 @@ class Server(resource.Resource, metadata.MetadataMixin, tag.TagMixin): :returns: The updated server. """ action = {'imageRef': resource.Resource._get_id(image)} - if preserve_ephemeral is not UNSET: + if preserve_ephemeral is not types.UNSET: action['preserve_ephemeral'] = preserve_ephemeral - if name is not UNSET: + if name is not types.UNSET: action['name'] = name - if admin_password is not UNSET: + if admin_password is not types.UNSET: action['adminPass'] = admin_password - if access_ipv4 is not UNSET: + if access_ipv4 is not types.UNSET: action['accessIPv4'] = access_ipv4 - if access_ipv6 is not UNSET: + if access_ipv6 is not types.UNSET: action['accessIPv6'] = access_ipv6 - if metadata is not UNSET: + if metadata is not types.UNSET: action['metadata'] = metadata - if user_data is not UNSET: + if user_data is not types.UNSET: action['user_data'] = user_data - if key_name is not UNSET: + if key_name is not types.UNSET: action['key_name'] = key_name - if description is not UNSET: + if description is not types.UNSET: action['description'] = description - if trusted_image_certificates is not UNSET: + if trusted_image_certificates is not types.UNSET: action['trusted_image_certificates'] = trusted_image_certificates - if hostname is not UNSET: + if hostname is not types.UNSET: action['hostname'] = hostname body = {'rebuild': action} @@ -821,7 +808,7 @@ class Server(resource.Resource, metadata.MetadataMixin, tag.TagMixin): body = {"shelveOffload": None} self._action(session, body) - def unshelve(self, session, availability_zone=_sentinel, host=None): + def unshelve(self, session, availability_zone=types.UNSET, host=None): """Unshelve the server. :param session: The session to use for making this request. diff --git a/openstack/tests/unit/compute/v2/test_proxy.py b/openstack/tests/unit/compute/v2/test_proxy.py index c579f1c10..879bbff98 100644 --- a/openstack/tests/unit/compute/v2/test_proxy.py +++ b/openstack/tests/unit/compute/v2/test_proxy.py @@ -44,6 +44,7 @@ from openstack.identity.v3 import project from openstack import proxy as proxy_base from openstack.tests.unit import base from openstack.tests.unit import test_proxy_base +from openstack import types from openstack import warnings as os_warnings @@ -1372,6 +1373,7 @@ class TestCompute(TestComputeProxy): expected_args=[self.proxy], expected_kwargs={ "host": None, + "availability_zone": types.UNSET, }, ) @@ -1380,11 +1382,9 @@ class TestCompute(TestComputeProxy): "openstack.compute.v2.server.Server.unshelve", self.proxy.unshelve_server, method_args=["value"], - method_kwargs={"host": "HOST2"}, + method_kwargs={"host": "HOST2", "availability_zone": "AZ2"}, expected_args=[self.proxy], - expected_kwargs={ - "host": "HOST2", - }, + expected_kwargs={"host": "HOST2", "availability_zone": "AZ2"}, ) def test_server_trigger_dump(self): diff --git a/openstack/types.py b/openstack/types.py new file mode 100644 index 000000000..d84685e88 --- /dev/null +++ b/openstack/types.py @@ -0,0 +1,23 @@ +# 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 typing as ty + + +# Workaround Python's lack of an undefined sentinel +# https://python-patterns.guide/python/sentinel-object/ +class Unset: + def __bool__(self) -> ty.Literal[False]: + return False + + +UNSET: Unset = Unset() diff --git a/releasenotes/notes/fix-bug-9e1a976958d2543b.yaml b/releasenotes/notes/fix-bug-9e1a976958d2543b.yaml new file mode 100644 index 000000000..913d33a10 --- /dev/null +++ b/releasenotes/notes/fix-bug-9e1a976958d2543b.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fixed the issue that unshelving a server to a specific availability zone + was failed due to unhandled ``availability_zone`` option.