From 408cf57f1d2a306a1cd448943d3f0a6fc397601c Mon Sep 17 00:00:00 2001 From: "jeremy.zhang" Date: Mon, 28 May 2018 17:09:10 +0800 Subject: [PATCH] Add extra apis to volume v3 services client Just like compute services client (Nova), volume services client (Cinder) also has some extra apis, such as 'enable_service', 'disable_service', 'disable_log_reason', 'freeze_host' and 'thaw_host'. This patch supplements these five apis to volume v3 services client. As it maybe dangerous for Tempest gate jobs to test these apis, only some negative tests are provided. Including: [1] Add the apis to volume v3 services_client [2] Add unit tests for these apis [3] Add release note [4] Add negative tests Change-Id: Ic7c170122321483a89d399f67ce4441b00dfc781 --- ...e-v3-services-client-bf9b235cf5a611fe.yaml | 6 + .../admin/test_volume_services_negative.py | 65 ++++++ .../lib/services/volume/v3/services_client.py | 71 +++++- .../volume/v3/test_services_client.py | 214 ++++++++++++++++++ 4 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/add-extra-apis-to-volume-v3-services-client-bf9b235cf5a611fe.yaml create mode 100644 tempest/api/volume/admin/test_volume_services_negative.py create mode 100644 tempest/tests/lib/services/volume/v3/test_services_client.py diff --git a/releasenotes/notes/add-extra-apis-to-volume-v3-services-client-bf9b235cf5a611fe.yaml b/releasenotes/notes/add-extra-apis-to-volume-v3-services-client-bf9b235cf5a611fe.yaml new file mode 100644 index 0000000000..03d0ae81d6 --- /dev/null +++ b/releasenotes/notes/add-extra-apis-to-volume-v3-services-client-bf9b235cf5a611fe.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``enable_service``, ``disable_service`` , ``disable_log_reason``, + ``freeze_host`` and ``thaw_host`` API endpoints to volume v3 + ``services_client``. diff --git a/tempest/api/volume/admin/test_volume_services_negative.py b/tempest/api/volume/admin/test_volume_services_negative.py new file mode 100644 index 0000000000..6f3dbc6ecd --- /dev/null +++ b/tempest/api/volume/admin/test_volume_services_negative.py @@ -0,0 +1,65 @@ +# Copyright 2018 FiberHome Telecommunication Technologies CO.,LTD +# All Rights Reserved. +# +# 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. + +from tempest.api.volume import base +from tempest.lib import decorators +from tempest.lib import exceptions as lib_exc + + +class VolumeServicesNegativeTest(base.BaseVolumeAdminTest): + + @classmethod + def resource_setup(cls): + super(VolumeServicesNegativeTest, cls).resource_setup() + cls.services = cls.admin_volume_services_client.list_services()[ + 'services'] + cls.host = cls.services[0]['host'] + cls.binary = cls.services[0]['binary'] + + @decorators.attr(type='negative') + @decorators.idempotent_id('3246ce65-ba70-4159-aa3b-082c28e4b484') + def test_enable_service_with_invalid_host(self): + self.assertRaises(lib_exc.NotFound, + self.admin_volume_services_client.enable_service, + host='invalid_host', binary=self.binary) + + @decorators.attr(type='negative') + @decorators.idempotent_id('c571f179-c6e6-4c50-a0ab-368b628a8ac1') + def test_disable_service_with_invalid_binary(self): + self.assertRaises(lib_exc.NotFound, + self.admin_volume_services_client.disable_service, + host=self.host, binary='invalid_binary') + + @decorators.attr(type='negative') + @decorators.idempotent_id('77767b36-5e8f-4c68-a0b5-2308cc21ec64') + def test_disable_log_reason_with_no_reason(self): + self.assertRaises(lib_exc.BadRequest, + self.admin_volume_services_client.disable_log_reason, + host=self.host, binary=self.binary, + disabled_reason=None) + + @decorators.attr(type='negative') + @decorators.idempotent_id('712bfab8-1f44-4eb5-a632-fa70bf78f05e') + def test_freeze_host_with_invalid_host(self): + self.assertRaises(lib_exc.BadRequest, + self.admin_volume_services_client.freeze_host, + host='invalid_host') + + @decorators.attr(type='negative') + @decorators.idempotent_id('7c6287c9-d655-47e1-9a11-76f6657a6dce') + def test_thaw_host_with_invalid_host(self): + self.assertRaises(lib_exc.BadRequest, + self.admin_volume_services_client.thaw_host, + host='invalid_host') diff --git a/tempest/lib/services/volume/v3/services_client.py b/tempest/lib/services/volume/v3/services_client.py index 09036a4833..22155a96e4 100644 --- a/tempest/lib/services/volume/v3/services_client.py +++ b/tempest/lib/services/volume/v3/services_client.py @@ -20,9 +20,15 @@ from tempest.lib.common import rest_client class ServicesClient(rest_client.RestClient): - """Client class to send CRUD Volume API requests""" + """Client class to send CRUD Volume Services API requests""" def list_services(self, **params): + """List all Cinder services. + + For a full list of available parameters, please refer to the official + API reference: + https://developer.openstack.org/api-ref/block-storage/v3/#list-all-cinder-services + """ url = 'os-services' if params: url += '?%s' % urllib.urlencode(params) @@ -31,3 +37,66 @@ class ServicesClient(rest_client.RestClient): body = json.loads(body) self.expected_success(200, resp.status) return rest_client.ResponseBody(resp, body) + + def enable_service(self, **kwargs): + """Enable service on a host. + + For a full list of available parameters, please refer to the official + API reference: + https://developer.openstack.org/api-ref/block-storage/v3/#enable-a-cinder-service + """ + put_body = json.dumps(kwargs) + resp, body = self.put('os-services/enable', put_body) + body = json.loads(body) + self.expected_success(200, resp.status) + return rest_client.ResponseBody(resp, body) + + def disable_service(self, **kwargs): + """Disable service on a host. + + For a full list of available parameters, please refer to the official + API reference: + https://developer.openstack.org/api-ref/block-storage/v3/#disable-a-cinder-service + """ + put_body = json.dumps(kwargs) + resp, body = self.put('os-services/disable', put_body) + body = json.loads(body) + self.expected_success(200, resp.status) + return rest_client.ResponseBody(resp, body) + + def disable_log_reason(self, **kwargs): + """Disable scheduling for a volume service and log disabled reason. + + For a full list of available parameters, please refer to the official + API reference: + https://developer.openstack.org/api-ref/block-storage/v3/#log-disabled-cinder-service-information + """ + put_body = json.dumps(kwargs) + resp, body = self.put('os-services/disable-log-reason', put_body) + body = json.loads(body) + self.expected_success(200, resp.status) + return rest_client.ResponseBody(resp, body) + + def freeze_host(self, **kwargs): + """Freeze a Cinder backend host. + + For a full list of available parameters, please refer to the official + API reference: + https://developer.openstack.org/api-ref/block-storage/v3/#freeze-a-cinder-backend-host + """ + put_body = json.dumps(kwargs) + resp, _ = self.put('os-services/freeze', put_body) + self.expected_success(200, resp.status) + return rest_client.ResponseBody(resp) + + def thaw_host(self, **kwargs): + """Thaw a Cinder backend host. + + For a full list of available parameters, please refer to the official + API reference: + https://developer.openstack.org/api-ref/block-storage/v3/#thaw-a-cinder-backend-host + """ + put_body = json.dumps(kwargs) + resp, _ = self.put('os-services/thaw', put_body) + self.expected_success(200, resp.status) + return rest_client.ResponseBody(resp) diff --git a/tempest/tests/lib/services/volume/v3/test_services_client.py b/tempest/tests/lib/services/volume/v3/test_services_client.py new file mode 100644 index 0000000000..f65228f231 --- /dev/null +++ b/tempest/tests/lib/services/volume/v3/test_services_client.py @@ -0,0 +1,214 @@ +# Copyright 2018 FiberHome Telecommunication Technologies CO.,LTD +# All Rights Reserved. +# +# 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 copy + +import mock +from oslo_serialization import jsonutils as json + +from tempest.lib.services.volume.v3 import services_client +from tempest.tests.lib import fake_auth_provider +from tempest.tests.lib.services import base + + +class TestServicesClient(base.BaseServiceTest): + + FAKE_SERVICE_LIST = { + "services": [ + { + "status": "enabled", + "binary": "cinder-backup", + "zone": "nova", + "state": "up", + "updated_at": "2017-07-20T07:20:17.000000", + "host": "fake-host", + "disabled_reason": None + }, + { + "status": "enabled", + "binary": "cinder-scheduler", + "zone": "nova", + "state": "up", + "updated_at": "2017-07-20T07:20:24.000000", + "host": "fake-host", + "disabled_reason": None + }, + { + "status": "enabled", + "binary": "cinder-volume", + "zone": "nova", + "frozen": False, + "state": "up", + "updated_at": "2017-07-20T07:20:20.000000", + "host": "fake-host@lvm", + "replication_status": "disabled", + "active_backend_id": None, + "disabled_reason": None + } + ] + } + + FAKE_SERVICE_REQUEST = { + "host": "fake-host", + "binary": "cinder-volume" + } + + FAKE_SERVICE_RESPONSE = { + "disabled": False, + "status": "enabled", + "host": "fake-host@lvm", + "service": "", + "binary": "cinder-volume", + "disabled_reason": None + } + + def setUp(self): + super(TestServicesClient, self).setUp() + fake_auth = fake_auth_provider.FakeAuthProvider() + self.client = services_client.ServicesClient(fake_auth, + 'volume', + 'regionOne') + + def _test_list_services(self, bytes_body=False, + mock_args='os-services', **params): + self.check_service_client_function( + self.client.list_services, + 'tempest.lib.common.rest_client.RestClient.get', + self.FAKE_SERVICE_LIST, + to_utf=bytes_body, + mock_args=[mock_args], + **params) + + def _test_enable_service(self, bytes_body=False): + resp_body = self.FAKE_SERVICE_RESPONSE + kwargs = self.FAKE_SERVICE_REQUEST + payload = json.dumps(kwargs, sort_keys=True) + json_dumps = json.dumps + + # NOTE: Use sort_keys for json.dumps so that the expected and actual + # payloads are guaranteed to be identical for mock_args assert check. + with mock.patch.object(services_client.json, 'dumps') as mock_dumps: + mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True) + + self.check_service_client_function( + self.client.enable_service, + 'tempest.lib.common.rest_client.RestClient.put', + resp_body, + to_utf=bytes_body, + mock_args=['os-services/enable', payload], + **kwargs) + + def _test_disable_service(self, bytes_body=False): + resp_body = copy.deepcopy(self.FAKE_SERVICE_RESPONSE) + resp_body.pop('disabled_reason') + resp_body['disabled'] = True + resp_body['status'] = 'disabled' + kwargs = self.FAKE_SERVICE_REQUEST + payload = json.dumps(kwargs, sort_keys=True) + json_dumps = json.dumps + + # NOTE: Use sort_keys for json.dumps so that the expected and actual + # payloads are guaranteed to be identical for mock_args assert check. + with mock.patch.object(services_client.json, 'dumps') as mock_dumps: + mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True) + + self.check_service_client_function( + self.client.disable_service, + 'tempest.lib.common.rest_client.RestClient.put', + resp_body, + to_utf=bytes_body, + mock_args=['os-services/disable', payload], + **kwargs) + + def _test_disable_log_reason(self, bytes_body=False): + resp_body = copy.deepcopy(self.FAKE_SERVICE_RESPONSE) + resp_body['disabled_reason'] = "disabled for test" + resp_body['disabled'] = True + resp_body['status'] = 'disabled' + kwargs = copy.deepcopy(self.FAKE_SERVICE_REQUEST) + kwargs.update({"disabled_reason": "disabled for test"}) + payload = json.dumps(kwargs, sort_keys=True) + json_dumps = json.dumps + + # NOTE: Use sort_keys for json.dumps so that the expected and actual + # payloads are guaranteed to be identical for mock_args assert check. + with mock.patch.object(services_client.json, 'dumps') as mock_dumps: + mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True) + + self.check_service_client_function( + self.client.disable_log_reason, + 'tempest.lib.common.rest_client.RestClient.put', + resp_body, + to_utf=bytes_body, + mock_args=['os-services/disable-log-reason', payload], + **kwargs) + + def _test_freeze_host(self, bytes_body=False): + kwargs = {'host': 'host1@lvm'} + self.check_service_client_function( + self.client.freeze_host, + 'tempest.lib.common.rest_client.RestClient.put', + {}, + bytes_body, + **kwargs) + + def _test_thaw_host(self, bytes_body=False): + kwargs = {'host': 'host1@lvm'} + self.check_service_client_function( + self.client.thaw_host, + 'tempest.lib.common.rest_client.RestClient.put', + {}, + bytes_body, + **kwargs) + + def test_list_services_with_str_body(self): + self._test_list_services() + + def test_list_services_with_bytes_body(self): + self._test_list_services(bytes_body=True) + + def test_list_services_with_params(self): + mock_args = 'os-services?host=fake-host' + self._test_list_services(mock_args=mock_args, host='fake-host') + + def test_enable_service_with_str_body(self): + self._test_enable_service() + + def test_enable_service_with_bytes_body(self): + self._test_enable_service(bytes_body=True) + + def test_disable_service_with_str_body(self): + self._test_disable_service() + + def test_disable_service_with_bytes_body(self): + self._test_disable_service(bytes_body=True) + + def test_disable_log_reason_with_str_body(self): + self._test_disable_log_reason() + + def test_disable_log_reason_with_bytes_body(self): + self._test_disable_log_reason(bytes_body=True) + + def test_freeze_host_with_str_body(self): + self._test_freeze_host() + + def test_freeze_host_with_bytes_body(self): + self._test_freeze_host(bytes_body=True) + + def test_thaw_host_with_str_body(self): + self._test_thaw_host() + + def test_thaw_host_with_bytes_body(self): + self._test_thaw_host(bytes_body=True)