From ee9b277c5f442f299b853118c900c3ee2996c67a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Ribaud?= Date: Thu, 3 Mar 2022 11:18:59 +0100 Subject: Microversion 2.91: Support specifying destination host to unshelve This patch adds ``host`` to novaclient api. This can help administrators to specify a ``host`` to unshelve a shelve offloaded server from 2.91 microversion. Depends-On: https://review.opendev.org/c/openstack/nova/+/831507 Implements: blueprint unshelve-to-host Change-Id: I7efc8f0b0ef159e16cefee761bff5d7e90d0c427 --- novaclient/__init__.py | 2 +- novaclient/tests/unit/fixture_data/servers.py | 11 +++ novaclient/tests/unit/v2/fakes.py | 2 +- novaclient/tests/unit/v2/test_servers.py | 98 +++++++++++++++++++++- novaclient/v2/servers.py | 65 +++++++++++++- .../bp-unshelve-to-host-b220131a00dff8a2.yaml | 9 ++ 6 files changed, 182 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/bp-unshelve-to-host-b220131a00dff8a2.yaml diff --git a/novaclient/__init__.py b/novaclient/__init__.py index d49e8841..bbfd9522 100644 --- a/novaclient/__init__.py +++ b/novaclient/__init__.py @@ -25,4 +25,4 @@ API_MIN_VERSION = api_versions.APIVersion("2.1") # when client supported the max version, and bumped sequentially, otherwise # the client may break due to server side new version may include some # backward incompatible change. -API_MAX_VERSION = api_versions.APIVersion("2.90") +API_MAX_VERSION = api_versions.APIVersion("2.91") diff --git a/novaclient/tests/unit/fixture_data/servers.py b/novaclient/tests/unit/fixture_data/servers.py index a8301548..8e53ecdb 100644 --- a/novaclient/tests/unit/fixture_data/servers.py +++ b/novaclient/tests/unit/fixture_data/servers.py @@ -439,6 +439,17 @@ class V1(Base): elif action == 'lock': return None elif action == 'unshelve': + if api_version >= api_versions.APIVersion("2.91"): + # In 2.91 and above, we allow body to be one of these: + # {'unshelve': None} + # {'unshelve': {'availability_zone': }} + # {'unshelve': {'availability_zone': None}} (Unpin az) + # {'unshelve': {'host': }} + # {'unshelve': {'availability_zone': , 'host': }} + # {'unshelve': {'availability_zone': None, 'host': }} + if body[action] is not None: + for key in body[action].keys(): + key in ['availability_zone', 'host'] return None elif action == 'rebuild': body = body[action] diff --git a/novaclient/tests/unit/v2/fakes.py b/novaclient/tests/unit/v2/fakes.py index 07216adc..059a7147 100644 --- a/novaclient/tests/unit/v2/fakes.py +++ b/novaclient/tests/unit/v2/fakes.py @@ -835,7 +835,7 @@ class FakeSessionClient(base_client.SessionClient): if self.api_version < api_versions.APIVersion("2.77"): assert body[action] is None else: - # In 2.77 and above, we allow body to be one of these: + # In 2.77 to 2.91, we allow body to be one of these: # {'unshelve': None} # {'unshelve': {'availability_zone': 'foo-az'}} if body[action] is not None: diff --git a/novaclient/tests/unit/v2/test_servers.py b/novaclient/tests/unit/v2/test_servers.py index 071ae5c1..93fefabf 100644 --- a/novaclient/tests/unit/v2/test_servers.py +++ b/novaclient/tests/unit/v2/test_servers.py @@ -1851,6 +1851,23 @@ class ServersV277Test(ServersV274Test): api_version = "2.77" + def test_unshelve(self): + s = self.cs.servers.get(1234) + # Test going through the Server object. + ret = s.unshelve() + self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) + self.assert_called( + 'POST', + '/servers/1234/action', + {'unshelve': None}) + # Test going through the ServerManager directly. + ret = self.cs.servers.unshelve(s) + self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) + self.assert_called( + 'POST', + '/servers/1234/action', + {'unshelve': None}) + def test_unshelve_with_az(self): s = self.cs.servers.get(1234) # Test going through the Server object. @@ -1883,7 +1900,7 @@ class ServersV277Test(ServersV274Test): str(ex)) -class ServersV278Test(ServersV273Test): +class ServersV278Test(ServersV277Test): api_version = "2.78" @@ -1992,3 +2009,82 @@ class ServersV290Test(ServersV278Test): s.update, hostname='new-hostname') self.assertIn('hostname', str(ex)) + + +class ServersV291Test(ServersV290Test): + + api_version = "2.91" + + def test_unshelve_with_host(self): + s = self.cs.servers.get(1234) + # Test going through the Server object. + ret = s.unshelve(host='server1') + self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) + self.assert_called( + 'POST', + '/servers/1234/action', + {'unshelve': {'host': 'server1'}}) + # Test going through the ServerManager directly. + ret = self.cs.servers.unshelve(s, host='server1') + self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) + self.assert_called( + 'POST', + '/servers/1234/action', + {'unshelve': {'host': 'server1'}}) + + def test_unshelve_server_with_az_and_host(self): + s = self.cs.servers.get(1234) + # Test going through the Server object. + ret = s.unshelve(host='server1', availability_zone='foo-az') + self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) + self.assert_called( + 'POST', + '/servers/1234/action', + {'unshelve': {'host': 'server1', + 'availability_zone': 'foo-az'}}) + # Test going through the ServerManager directly. + ret = self.cs.servers.unshelve( + s, host='server1', availability_zone='foo-az') + self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) + self.assert_called( + 'POST', + '/servers/1234/action', + {'unshelve': {'host': 'server1', + 'availability_zone': 'foo-az'}}) + + def test_unshelve_unpin_az(self): + s = self.cs.servers.get(1234) + # Test going through the Server object. + ret = s.unshelve(availability_zone=None) + self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) + self.assert_called( + 'POST', + '/servers/1234/action', + {'unshelve': {'availability_zone': None}}) + # Test going through the ServerManager directly. + ret = self.cs.servers.unshelve(s, availability_zone=None) + self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) + self.assert_called( + 'POST', + '/servers/1234/action', + {'unshelve': {'availability_zone': None}}) + + def test_unshelve_server_with_host_and_unpin(self): + s = self.cs.servers.get(1234) + # Test going through the Server object. + ret = s.unshelve(availability_zone=None, host='server1') + self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) + self.assert_called( + 'POST', + '/servers/1234/action', + {'unshelve': {'host': 'server1', + 'availability_zone': None}}) + # Test going through the ServerManager directly. + ret = self.cs.servers.unshelve( + s, availability_zone=None, host='server1') + self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) + self.assert_called( + 'POST', + '/servers/1234/action', + {'unshelve': {'host': 'server1', + 'availability_zone': None}}) diff --git a/novaclient/v2/servers.py b/novaclient/v2/servers.py index 76709ec5..8a9301f6 100644 --- a/novaclient/v2/servers.py +++ b/novaclient/v2/servers.py @@ -314,7 +314,7 @@ class Server(base.Resource): """ return self.manager.unshelve(self) - @api_versions.wraps("2.77") + @api_versions.wraps("2.77", "2.90") def unshelve(self, availability_zone=None): """ Unshelve -- Unshelve the server. @@ -326,6 +326,37 @@ class Server(base.Resource): return self.manager.unshelve(self, availability_zone=availability_zone) + @api_versions.wraps("2.91") + def unshelve(self, availability_zone=object(), host=None): + """ + Unshelve -- Unshelve the server. + + :param availability_zone: If specified the instance will be unshelved + to the availability_zone. + If None is passed the instance defined + availability_zone is unpin and the instance + will be scheduled to any availability_zone + (free scheduling). + If not specified the instance will be + unshelved to either its defined + availability_zone or any + availability_zone (free scheduling). + :param host: The specified host + (Optional) + :returns: An instance of novaclient.base.TupleWithMeta + """ + if ( + availability_zone is None or isinstance(availability_zone, str) + ) and host: + return self.manager.unshelve( + self, availability_zone=availability_zone, host=host) + if availability_zone is None or isinstance(availability_zone, str): + return self.manager.unshelve( + self, availability_zone=availability_zone) + if host: + return self.manager.unshelve(self, host=host) + return self.manager.unshelve(self) + def diagnostics(self): """Diagnostics -- Retrieve server diagnostics.""" return self.manager.diagnostics(self) @@ -1266,7 +1297,7 @@ class ServerManager(base.BootingManagerWithFind): """ return self._action('unshelve', server, None) - @api_versions.wraps("2.77") + @api_versions.wraps("2.77", "2.90") def unshelve(self, server, availability_zone=None): """ Unshelve the server. @@ -1281,6 +1312,36 @@ class ServerManager(base.BootingManagerWithFind): info = {'availability_zone': availability_zone} return self._action('unshelve', server, info) + @api_versions.wraps("2.91") + def unshelve(self, server, availability_zone=object(), host=None): + """ + Unshelve the server. + + :param availability_zone: If specified the instance will be unshelved + to the availability_zone. + If None is passed the instance defined + availability_zone is unpin and the instance + will be scheduled to any availability_zone + (free scheduling). + If not specified the instance will be + unshelved to either its defined + availability_zone or any + availability_zone (free scheduling). + :param host: The specified host + (Optional) + :returns: An instance of novaclient.base.TupleWithMeta + """ + info = None + + if availability_zone is None or isinstance(availability_zone, str): + info = {'availability_zone': availability_zone} + if host: + if info: + info['host'] = host + else: + info = {'host': host} + return self._action('unshelve', server, info) + def ips(self, server): """ Return IP Addresses associated with the server. diff --git a/releasenotes/notes/bp-unshelve-to-host-b220131a00dff8a2.yaml b/releasenotes/notes/bp-unshelve-to-host-b220131a00dff8a2.yaml new file mode 100644 index 00000000..98fe2c9f --- /dev/null +++ b/releasenotes/notes/bp-unshelve-to-host-b220131a00dff8a2.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Support has been added for `microversion 2.91`_. This microversion + allows specifying a destination host to unshelve a shelve + offloaded server. And availability zone can be set to None to unpin + the availability zone of a server. + + .. _microversion 2.91: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#microversion-2-91 -- cgit v1.2.1