From 67cd66688d1a657f1daf6dd26ee49c959b91beed Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 9 May 2024 12:51:00 +0100 Subject: [PATCH] compute: Add Server.clear_password action Change-Id: I5960605944fef2a300d6a3a9ff723a701e32cb64 Signed-off-by: Stephen Finucane --- openstack/compute/v2/_proxy.py | 11 +++++++ openstack/compute/v2/server.py | 26 ++++++++++++++--- openstack/tests/unit/compute/v2/test_proxy.py | 24 +++++++++++++++ .../tests/unit/compute/v2/test_server.py | 29 +++++++++++++++++++ openstack/tests/unit/fakes.py | 19 ++++++++++++ ...erver-clear-password-256e269223453bd7.yaml | 5 ++++ 6 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/add-server-clear-password-256e269223453bd7.yaml diff --git a/openstack/compute/v2/_proxy.py b/openstack/compute/v2/_proxy.py index 4ec0535f4..f7c7ab7bd 100644 --- a/openstack/compute/v2/_proxy.py +++ b/openstack/compute/v2/_proxy.py @@ -851,6 +851,17 @@ class Proxy(proxy.Proxy): server = self._get_resource(_server.Server, server) return server.get_password(self) + def clear_server_password(self, server): + """Clear the administrator password + + :param server: Either the ID of a server or a + :class:`~openstack.compute.v2.server.Server` instance. + + :returns: None + """ + server = self._get_resource(_server.Server, server) + server.clear_password(self) + def reset_server_state(self, server, state): """Reset the state of server diff --git a/openstack/compute/v2/server.py b/openstack/compute/v2/server.py index 078d75d8b..6b6a1508c 100644 --- a/openstack/compute/v2/server.py +++ b/openstack/compute/v2/server.py @@ -323,7 +323,7 @@ class Server(resource.Resource, metadata.MetadataMixin, tag.TagMixin): exceptions.raise_from_response(response) return response - def change_password(self, session, password): + def change_password(self, session, password, *, microversion=None): """Change the administrator password to the given password. :param session: The session to use for making this request. @@ -331,22 +331,40 @@ class Server(resource.Resource, metadata.MetadataMixin, tag.TagMixin): :returns: None """ body = {'changePassword': {'adminPass': password}} - self._action(session, body) + self._action(session, body, microversion=microversion) - def get_password(self, session): + def get_password(self, session, *, microversion=None): """Get the encrypted administrator password. :param session: The session to use for making this request. :returns: The encrypted administrator password. """ url = utils.urljoin(Server.base_path, self.id, 'os-server-password') + if microversion is None: + microversion = self._get_microversion(session, action='commit') - response = session.get(url) + response = session.get(url, microversion=microversion) exceptions.raise_from_response(response) data = response.json() return data.get('password') + def clear_password(self, session, *, microversion=None): + """Clear the administrator password. + + This removes the password from the database. It does not actually + change the server password. + + :param session: The session to use for making this request. + :returns: None + """ + url = utils.urljoin(Server.base_path, self.id, 'os-server-password') + if microversion is None: + microversion = self._get_microversion(session, action='commit') + + response = session.delete(url, microversion=microversion) + exceptions.raise_from_response(response) + def reboot(self, session, reboot_type): """Reboot server where reboot_type might be 'SOFT' or 'HARD'. diff --git a/openstack/tests/unit/compute/v2/test_proxy.py b/openstack/tests/unit/compute/v2/test_proxy.py index 378935f84..a5aa9805a 100644 --- a/openstack/tests/unit/compute/v2/test_proxy.py +++ b/openstack/tests/unit/compute/v2/test_proxy.py @@ -961,6 +961,30 @@ class TestCompute(TestComputeProxy): def test_server_update(self): self.verify_update(self.proxy.update_server, server.Server) + def test_server_change_password(self): + self._verify( + "openstack.compute.v2.server.Server.change_password", + self.proxy.change_server_password, + method_args=["value", "password"], + expected_args=[self.proxy, "password"], + ) + + def test_server_get_password(self): + self._verify( + "openstack.compute.v2.server.Server.get_password", + self.proxy.get_server_password, + method_args=["value"], + expected_args=[self.proxy], + ) + + def test_server_clear_password(self): + self._verify( + "openstack.compute.v2.server.Server.clear_password", + self.proxy.clear_server_password, + method_args=["value"], + expected_args=[self.proxy], + ) + def test_server_wait_for(self): value = server.Server(id='1234') self.verify_wait_for_status( diff --git a/openstack/tests/unit/compute/v2/test_server.py b/openstack/tests/unit/compute/v2/test_server.py index d996638ef..e04008a77 100644 --- a/openstack/tests/unit/compute/v2/test_server.py +++ b/openstack/tests/unit/compute/v2/test_server.py @@ -10,12 +10,14 @@ # License for the specific language governing permissions and limitations # under the License. +import http from unittest import mock from openstack.compute.v2 import flavor from openstack.compute.v2 import server from openstack.image.v2 import image from openstack.tests.unit import base +from openstack.tests.unit import fakes IDENTIFIER = 'IDENTIFIER' EXAMPLE = { @@ -317,6 +319,33 @@ class TestServer(base.TestCase): microversion=self.sess.default_microversion, ) + def test_get_password(self): + sot = server.Server(**EXAMPLE) + self.sess.get.return_value = fakes.FakeResponse( + data={'password': 'foo'} + ) + + result = sot.get_password(self.sess) + self.assertEqual('foo', result) + + url = 'servers/IDENTIFIER/os-server-password' + self.sess.get.assert_called_with( + url, microversion=self.sess.default_microversion + ) + + def test_clear_password(self): + sot = server.Server(**EXAMPLE) + self.sess.delete.return_value = fakes.FakeResponse( + status_code=http.HTTPStatus.NO_CONTENT, + ) + + self.assertIsNone(sot.clear_password(self.sess)) + + url = 'servers/IDENTIFIER/os-server-password' + self.sess.delete.assert_called_with( + url, microversion=self.sess.default_microversion + ) + def test_reboot(self): sot = server.Server(**EXAMPLE) diff --git a/openstack/tests/unit/fakes.py b/openstack/tests/unit/fakes.py index 535ef5505..50c8d4831 100644 --- a/openstack/tests/unit/fakes.py +++ b/openstack/tests/unit/fakes.py @@ -13,8 +13,11 @@ # License for the specific language governing permissions and limitations # under the License. +import json from unittest import mock +import requests + class FakeTransport(mock.Mock): RESPONSE = mock.Mock('200 OK') @@ -35,3 +38,19 @@ class FakeAuthenticator(mock.Mock): self.get_token.return_value = self.TOKEN self.get_endpoint = mock.Mock() self.get_endpoint.return_value = self.ENDPOINT + + +class FakeResponse(requests.Response): + def __init__( + self, headers=None, status_code=200, data=None, encoding=None + ): + super().__init__() + + headers = headers or {} + + self.status_code = status_code + + self.headers.update(headers) + self._content = json.dumps(data) + if not isinstance(self._content, bytes): + self._content = self._content.encode() diff --git a/releasenotes/notes/add-server-clear-password-256e269223453bd7.yaml b/releasenotes/notes/add-server-clear-password-256e269223453bd7.yaml new file mode 100644 index 000000000..70f07cabe --- /dev/null +++ b/releasenotes/notes/add-server-clear-password-256e269223453bd7.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + The ``Server.clear_password`` and equivalent ``clear_server_password`` + proxy method have been added.