diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst index 073a0bbbe6..6dd00d3149 100644 --- a/doc/source/microversion_testing.rst +++ b/doc/source/microversion_testing.rst @@ -358,6 +358,10 @@ Microversion tests implemented in Tempest .. _2.49: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id44 + * `2.53`_ + + .. _2.53: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-pike + * `2.54`_ .. _2.54: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id49 diff --git a/releasenotes/notes/tempest-lib-compute-update-service-6019d2dcfe4a1c5d.yaml b/releasenotes/notes/tempest-lib-compute-update-service-6019d2dcfe4a1c5d.yaml new file mode 100644 index 0000000000..d67cdb85ba --- /dev/null +++ b/releasenotes/notes/tempest-lib-compute-update-service-6019d2dcfe4a1c5d.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + The ``update_service`` API is added to the ``services_client`` compute + library. This API is introduced in microversion 2.53 and supersedes + the following APIs: + + * ``PUT /os-services/disable`` (``disable_service``) + * ``PUT /os-services/disable-log-reason`` (``disable_log_reason``) + * ``PUT /os-services/enable`` (``enable_service``) + * ``PUT /os-services/force-down`` (``update_forced_down``) diff --git a/tempest/api/compute/admin/test_services_negative.py b/tempest/api/compute/admin/test_services_negative.py index 201670ab29..993c8ecad4 100644 --- a/tempest/api/compute/admin/test_services_negative.py +++ b/tempest/api/compute/admin/test_services_negative.py @@ -13,12 +13,14 @@ # under the License. from tempest.api.compute import base +from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc class ServicesAdminNegativeTestJSON(base.BaseV2ComputeAdminTest): """Tests Services API. List and Enable/Disable require admin privileges.""" + max_microversion = '2.52' @classmethod def setup_clients(cls): @@ -35,7 +37,8 @@ class ServicesAdminNegativeTestJSON(base.BaseV2ComputeAdminTest): @decorators.attr(type=['negative']) @decorators.idempotent_id('d0884a69-f693-4e79-a9af-232d15643bf7') def test_get_service_by_invalid_params(self): - # return all services if send the request with invalid parameter + # Expect all services to be returned when the request contains invalid + # parameters. services = self.client.list_services()['services'] services_xxx = (self.client.list_services(xxx='nova-compute') ['services']) @@ -58,3 +61,45 @@ class ServicesAdminNegativeTestJSON(base.BaseV2ComputeAdminTest): services = self.client.list_services(host='xxx', binary=binary_name)['services'] self.assertEmpty(services) + + +class ServicesAdminNegativeV253TestJSON(ServicesAdminNegativeTestJSON): + min_microversion = '2.53' + max_microversion = 'latest' + + # NOTE(felipemonteiro): This class tests the services APIs response schema + # for the 2.53 microversion. Schema testing is done for `list_services` + # tests. + + @classmethod + def resource_setup(cls): + super(ServicesAdminNegativeV253TestJSON, cls).resource_setup() + # Nova returns 400 if `binary` is not nova-compute. + cls.binary = 'nova-compute' + cls.fake_service_id = data_utils.rand_uuid() + + @decorators.attr(type=['negative']) + @decorators.idempotent_id('508671aa-c929-4479-bd10-8680d40dd0a6') + def test_enable_service_with_invalid_service_id(self): + self.assertRaises(lib_exc.NotFound, + self.client.update_service, + service_id=self.fake_service_id, + status='enabled') + + @decorators.attr(type=['negative']) + @decorators.idempotent_id('a9eeeade-42b3-419f-87aa-c9342aa068cf') + def test_disable_service_with_invalid_service_id(self): + self.assertRaises(lib_exc.NotFound, + self.client.update_service, + service_id=self.fake_service_id, + status='disabled') + + @decorators.attr(type=['negative']) + @decorators.idempotent_id('f46a9d91-1e85-4b96-8e7a-db7706fa2e9a') + def test_disable_log_reason_with_invalid_service_id(self): + # disabled_reason requires that status='disabled' be provided. + self.assertRaises(lib_exc.NotFound, + self.client.update_service, + service_id=self.fake_service_id, + status='disabled', + disabled_reason='maintenance') diff --git a/tempest/lib/api_schema/response/compute/v2_11/services.py b/tempest/lib/api_schema/response/compute/v2_11/services.py index 18b833bd27..9ece1f9b32 100644 --- a/tempest/lib/api_schema/response/compute/v2_11/services.py +++ b/tempest/lib/api_schema/response/compute/v2_11/services.py @@ -44,3 +44,10 @@ update_forced_down = { 'required': ['service'] } } + +# **** Schemas unchanged in microversion 2.11 since microversion 2.1 **** +# Note(felipemonteiro): Below are the unchanged schema in this microversion. We +# need to keep this schema in this file to have the generic way to select the +# right schema based on self.schema_versions_info mapping in service client. +enable_disable_service = copy.deepcopy(services.enable_disable_service) +disable_log_reason = copy.deepcopy(services.disable_log_reason) diff --git a/tempest/lib/api_schema/response/compute/v2_53/__init__.py b/tempest/lib/api_schema/response/compute/v2_53/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tempest/lib/api_schema/response/compute/v2_53/services.py b/tempest/lib/api_schema/response/compute/v2_53/services.py new file mode 100644 index 0000000000..aa132a915e --- /dev/null +++ b/tempest/lib/api_schema/response/compute/v2_53/services.py @@ -0,0 +1,70 @@ +# Copyright 2018 AT&T Corporation. +# 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 + +from tempest.lib.api_schema.response.compute.v2_1 import parameter_types +from tempest.lib.api_schema.response.compute.v2_11 import services \ + as servicesv211 + +# ***************** Schemas changed in microversion 2.53 ***************** + +# NOTE(felipemonteiro): This is schema for microversion 2.53 which includes: +# +# * changing the service 'id' to 'string' type only +# * adding update_service which supersedes enable_service, disable_service, +# disable_log_reason, update_forced_down. + +list_services = copy.deepcopy(servicesv211.list_services) +# The ID of the service is a uuid, so v2.1 pattern does not apply. +list_services['response_body']['properties']['services']['items'][ + 'properties']['id'] = {'type': 'string', 'format': 'uuid'} + +update_service = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'service': { + 'type': 'object', + 'properties': { + 'id': {'type': 'string', 'format': 'uuid'}, + 'binary': {'type': 'string'}, + 'disabled_reason': {'type': 'string'}, + 'host': {'type': 'string'}, + 'state': {'type': 'string'}, + 'status': {'type': 'string'}, + 'updated_at': parameter_types.date_time, + 'zone': {'type': 'string'}, + 'forced_down': {'type': 'boolean'} + }, + 'additionalProperties': False, + 'required': ['id', 'binary', 'disabled_reason', 'host', + 'state', 'status', 'updated_at', 'zone', + 'forced_down'] + } + }, + 'additionalProperties': False, + 'required': ['service'] + } +} + +# **** Schemas unchanged in microversion 2.53 since microversion 2.11 **** +# Note(felipemonteiro): Below are the unchanged schema in this microversion. We +# need to keep this schema in this file to have the generic way to select the +# right schema based on self.schema_versions_info mapping in service client. +enable_disable_service = copy.deepcopy(servicesv211.enable_disable_service) +update_forced_down = copy.deepcopy(servicesv211.update_forced_down) +disable_log_reason = copy.deepcopy(servicesv211.disable_log_reason) diff --git a/tempest/lib/services/compute/services_client.py b/tempest/lib/services/compute/services_client.py index b046c35a1b..d52de3ae77 100644 --- a/tempest/lib/services/compute/services_client.py +++ b/tempest/lib/services/compute/services_client.py @@ -20,6 +20,8 @@ from six.moves.urllib import parse as urllib from tempest.lib.api_schema.response.compute.v2_1 import services as schema from tempest.lib.api_schema.response.compute.v2_11 import services \ as schemav211 +from tempest.lib.api_schema.response.compute.v2_53 import services \ + as schemav253 from tempest.lib.common import rest_client from tempest.lib.services.compute import base_compute_client @@ -28,7 +30,8 @@ class ServicesClient(base_compute_client.BaseComputeClient): schema_versions_info = [ {'min': None, 'max': '2.10', 'schema': schema}, - {'min': '2.11', 'max': None, 'schema': schemav211}] + {'min': '2.11', 'max': '2.52', 'schema': schemav211}, + {'min': '2.53', 'max': None, 'schema': schemav253}] def list_services(self, **params): """Lists all running Compute services for a tenant. @@ -47,9 +50,30 @@ class ServicesClient(base_compute_client.BaseComputeClient): self.validate_response(_schema.list_services, resp, body) return rest_client.ResponseBody(resp, body) + def update_service(self, service_id, **kwargs): + """Update a compute service. + + Update a compute service to enable or disable scheduling, including + recording a reason why a compute service was disabled from scheduling. + + This API is available starting with microversion 2.53. + + For a full list of available parameters, please refer to the official + API reference: + https://developer.openstack.org/api-ref/compute/#update-compute-service + """ + put_body = json.dumps(kwargs) + resp, body = self.put('os-services/%s' % service_id, put_body) + body = json.loads(body) + _schema = self.get_schema(self.schema_versions_info) + self.validate_response(_schema.update_service, resp, body) + return rest_client.ResponseBody(resp, body) + def enable_service(self, **kwargs): """Enable service on a host. + ``update_service`` supersedes this API starting with microversion 2.53. + For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#enable-scheduling-for-a-compute-service @@ -63,6 +87,8 @@ class ServicesClient(base_compute_client.BaseComputeClient): def disable_service(self, **kwargs): """Disable service on a host. + ``update_service`` supersedes this API starting with microversion 2.53. + For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#disable-scheduling-for-a-compute-service @@ -76,6 +102,8 @@ class ServicesClient(base_compute_client.BaseComputeClient): def disable_log_reason(self, **kwargs): """Disables scheduling for a Compute service and logs reason. + ``update_service`` supersedes this API starting with microversion 2.53. + For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#disable-scheduling-for-a-compute-service-and-log-disabled-reason @@ -89,6 +117,8 @@ class ServicesClient(base_compute_client.BaseComputeClient): def update_forced_down(self, **kwargs): """Set or unset ``forced_down`` flag for the service. + ``update_service`` supersedes this API starting with microversion 2.53. + For a full list of available parameters, please refer to the official API reference: https://developer.openstack.org/api-ref/compute/#update-forced-down diff --git a/tempest/tests/lib/services/compute/test_services_client.py b/tempest/tests/lib/services/compute/test_services_client.py index 2dd981c007..ba432e39e1 100644 --- a/tempest/tests/lib/services/compute/test_services_client.py +++ b/tempest/tests/lib/services/compute/test_services_client.py @@ -56,6 +56,20 @@ class TestServicesClient(base.BaseServiceTest): } } + FAKE_UPDATE_SERVICE = { + "service": { + "id": "e81d66a4-ddd3-4aba-8a84-171d1cb4d339", + "binary": "nova-compute", + "disabled_reason": "test2", + "host": "host1", + "state": "down", + "status": "disabled", + "updated_at": "2012-10-29T13:42:05.000000", + "forced_down": False, + "zone": "nova" + } + } + def setUp(self): super(TestServicesClient, self).setUp() fake_auth = fake_auth_provider.FakeAuthProvider() @@ -119,6 +133,28 @@ class TestServicesClient(base.BaseServiceTest): binary="controller", disabled_reason='test reason') + def _test_update_service(self, bytes_body=False, status=None, + disabled_reason=None, forced_down=None): + resp_body = copy.deepcopy(self.FAKE_UPDATE_SERVICE) + kwargs = {} + + if status is not None: + kwargs['status'] = status + if disabled_reason is not None: + kwargs['disabled_reason'] = disabled_reason + if forced_down is not None: + kwargs['forced_down'] = forced_down + + resp_body['service'].update(kwargs) + + self.check_service_client_function( + self.client.update_service, + 'tempest.lib.common.rest_client.RestClient.put', + resp_body, + bytes_body, + service_id=resp_body['service']['id'], + **kwargs) + def test_log_reason_disabled_service_with_str_body(self): self._test_log_reason_disabled_service() @@ -144,3 +180,36 @@ class TestServicesClient(base.BaseServiceTest): new_callable=mock.PropertyMock(return_value='2.11')) def test_update_forced_down_with_bytes_body(self, _): self._test_update_forced_down(bytes_body=True) + + @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', + new_callable=mock.PropertyMock(return_value='2.53')) + def test_update_service_disable_scheduling_with_str_body(self, _): + self._test_update_service(status='disabled', + disabled_reason='maintenance') + + @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', + new_callable=mock.PropertyMock(return_value='2.53')) + def test_update_service_disable_scheduling_with_bytes_body(self, _): + self._test_update_service(status='disabled', + disabled_reason='maintenance', + bytes_body=True) + + @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', + new_callable=mock.PropertyMock(return_value='2.53')) + def test_update_service_enable_scheduling_with_str_body(self, _): + self._test_update_service(status='enabled') + + @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', + new_callable=mock.PropertyMock(return_value='2.53')) + def test_update_service_enable_scheduling_with_bytes_body(self, _): + self._test_update_service(status='enabled', bytes_body=True) + + @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', + new_callable=mock.PropertyMock(return_value='2.53')) + def test_update_service_forced_down_with_str_body(self, _): + self._test_update_service(forced_down=True) + + @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION', + new_callable=mock.PropertyMock(return_value='2.53')) + def test_update_service_forced_down_with_bytes_body(self, _): + self._test_update_service(forced_down=True, bytes_body=True)