From 5a0f3f1dd5ac0a28648f5db12d95b8e2b8a76e93 Mon Sep 17 00:00:00 2001 From: Sina Sadeghi Date: Thu, 27 Feb 2025 14:29:27 +1100 Subject: [PATCH] Support server unshelve to specific availability zone Closes-Bug: #2100345 Change-Id: I6d16b9251ea342ea921c9a508965eba681ca76e8 --- openstack/compute/v2/_proxy.py | 7 ++- openstack/compute/v2/server.py | 61 ++++++++----------- openstack/tests/unit/compute/v2/test_proxy.py | 8 +-- openstack/types.py | 23 +++++++ .../notes/fix-bug-9e1a976958d2543b.yaml | 5 ++ 5 files changed, 61 insertions(+), 43 deletions(-) create mode 100644 openstack/types.py create mode 100644 releasenotes/notes/fix-bug-9e1a976958d2543b.yaml diff --git a/openstack/compute/v2/_proxy.py b/openstack/compute/v2/_proxy.py index f48e1f684..1cf020e09 100644 --- a/openstack/compute/v2/_proxy.py +++ b/openstack/compute/v2/_proxy.py @@ -42,6 +42,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 @@ -1194,7 +1195,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 @@ -1208,7 +1211,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 5ecf09898..f8aeebac6 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', @@ -52,11 +44,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", @@ -409,17 +396,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. @@ -448,27 +435,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} @@ -820,7 +807,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 0e9cd9843..9c2f26238 100644 --- a/openstack/tests/unit/compute/v2/test_proxy.py +++ b/openstack/tests/unit/compute/v2/test_proxy.py @@ -43,6 +43,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 @@ -1371,6 +1372,7 @@ class TestCompute(TestComputeProxy): expected_args=[self.proxy], expected_kwargs={ "host": None, + "availability_zone": types.UNSET, }, ) @@ -1379,11 +1381,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.