diff --git a/tempest_lib/api_schema/response/compute/v2_1/aggregates.py b/tempest_lib/api_schema/response/compute/v2_1/aggregates.py new file mode 100644 index 0000000..1a9fe41 --- /dev/null +++ b/tempest_lib/api_schema/response/compute/v2_1/aggregates.py @@ -0,0 +1,92 @@ +# Copyright 2014 NEC 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 + +# create-aggregate api doesn't have 'hosts' and 'metadata' attributes. +aggregate_for_create = { + 'type': 'object', + 'properties': { + 'availability_zone': {'type': ['string', 'null']}, + 'created_at': {'type': 'string'}, + 'deleted': {'type': 'boolean'}, + 'deleted_at': {'type': ['string', 'null']}, + 'id': {'type': 'integer'}, + 'name': {'type': 'string'}, + 'updated_at': {'type': ['string', 'null']} + }, + 'additionalProperties': False, + 'required': ['availability_zone', 'created_at', 'deleted', + 'deleted_at', 'id', 'name', 'updated_at'], +} + +common_aggregate_info = copy.deepcopy(aggregate_for_create) +common_aggregate_info['properties'].update({ + 'hosts': {'type': 'array'}, + 'metadata': {'type': 'object'} +}) +common_aggregate_info['required'].extend(['hosts', 'metadata']) + +list_aggregates = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'aggregates': { + 'type': 'array', + 'items': common_aggregate_info + } + }, + 'additionalProperties': False, + 'required': ['aggregates'], + } +} + +get_aggregate = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'aggregate': common_aggregate_info + }, + 'additionalProperties': False, + 'required': ['aggregate'], + } +} + +aggregate_set_metadata = get_aggregate +# The 'updated_at' attribute of 'update_aggregate' can't be null. +update_aggregate = copy.deepcopy(get_aggregate) +update_aggregate['response_body']['properties']['aggregate']['properties'][ + 'updated_at'] = { + 'type': 'string' + } + +delete_aggregate = { + 'status_code': [200] +} + +create_aggregate = { + 'status_code': [200], + 'response_body': { + 'type': 'object', + 'properties': { + 'aggregate': aggregate_for_create + }, + 'additionalProperties': False, + 'required': ['aggregate'], + } +} + +aggregate_add_remove_host = get_aggregate diff --git a/tempest_lib/services/compute/aggregates_client.py b/tempest_lib/services/compute/aggregates_client.py new file mode 100644 index 0000000..5edcd60 --- /dev/null +++ b/tempest_lib/services/compute/aggregates_client.py @@ -0,0 +1,100 @@ +# Copyright 2013 NEC 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. + +from oslo_serialization import jsonutils as json + +from tempest_lib.api_schema.response.compute.v2_1 import aggregates as schema +from tempest_lib.common import rest_client +from tempest_lib import exceptions as lib_exc + + +class AggregatesClient(rest_client.RestClient): + + def list_aggregates(self): + """Get aggregate list.""" + resp, body = self.get("os-aggregates") + body = json.loads(body) + self.validate_response(schema.list_aggregates, resp, body) + return rest_client.ResponseBody(resp, body) + + def show_aggregate(self, aggregate_id): + """Get details of the given aggregate.""" + resp, body = self.get("os-aggregates/%s" % aggregate_id) + body = json.loads(body) + self.validate_response(schema.get_aggregate, resp, body) + return rest_client.ResponseBody(resp, body) + + def create_aggregate(self, **kwargs): + """Creates a new aggregate.""" + post_body = json.dumps({'aggregate': kwargs}) + resp, body = self.post('os-aggregates', post_body) + + body = json.loads(body) + self.validate_response(schema.create_aggregate, resp, body) + return rest_client.ResponseBody(resp, body) + + def update_aggregate(self, aggregate_id, **kwargs): + """Update a aggregate.""" + put_body = json.dumps({'aggregate': kwargs}) + resp, body = self.put('os-aggregates/%s' % aggregate_id, put_body) + + body = json.loads(body) + self.validate_response(schema.update_aggregate, resp, body) + return rest_client.ResponseBody(resp, body) + + def delete_aggregate(self, aggregate_id): + """Deletes the given aggregate.""" + resp, body = self.delete("os-aggregates/%s" % aggregate_id) + self.validate_response(schema.delete_aggregate, resp, body) + return rest_client.ResponseBody(resp, body) + + def is_resource_deleted(self, id): + try: + self.show_aggregate(id) + except lib_exc.NotFound: + return True + return False + + @property + def resource_type(self): + """Returns the primary type of resource this client works with.""" + return 'aggregate' + + def add_host(self, aggregate_id, **kwargs): + """Adds a host to the given aggregate.""" + post_body = json.dumps({'add_host': kwargs}) + resp, body = self.post('os-aggregates/%s/action' % aggregate_id, + post_body) + body = json.loads(body) + self.validate_response(schema.aggregate_add_remove_host, resp, body) + return rest_client.ResponseBody(resp, body) + + def remove_host(self, aggregate_id, **kwargs): + """Removes a host from the given aggregate.""" + post_body = json.dumps({'remove_host': kwargs}) + resp, body = self.post('os-aggregates/%s/action' % aggregate_id, + post_body) + body = json.loads(body) + self.validate_response(schema.aggregate_add_remove_host, resp, body) + return rest_client.ResponseBody(resp, body) + + def set_metadata(self, aggregate_id, **kwargs): + """Replaces the aggregate's existing metadata with new metadata.""" + post_body = json.dumps({'set_metadata': kwargs}) + resp, body = self.post('os-aggregates/%s/action' % aggregate_id, + post_body) + body = json.loads(body) + self.validate_response(schema.aggregate_set_metadata, resp, body) + return rest_client.ResponseBody(resp, body) diff --git a/tempest_lib/tests/services/compute/test_aggregates_client.py b/tempest_lib/tests/services/compute/test_aggregates_client.py new file mode 100644 index 0000000..06a32be --- /dev/null +++ b/tempest_lib/tests/services/compute/test_aggregates_client.py @@ -0,0 +1,130 @@ +# Copyright 2015 NEC 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. + +from tempest_lib.services.compute import aggregates_client +from tempest_lib.tests import fake_auth_provider +from tempest_lib.tests.services.compute import base + + +class TestAggregatesClient(base.BaseComputeServiceTest): + FAKE_SHOW_AGGREGATE = { + "aggregate": + { + "name": "hoge", + "availability_zone": None, + "deleted": False, + "created_at": + "2015-07-16T03:07:32.000000", + "updated_at": None, + "hosts": [], + "deleted_at": None, + "id": 1, + "metadata": {} + } + } + + FAKE_CREATE_AGGREGATE = { + "aggregate": + { + "name": u'\xf4', + "availability_zone": None, + "deleted": False, + "created_at": "2015-07-21T04:11:18.000000", + "updated_at": None, + "deleted_at": None, + "id": 1 + } + } + + FAKE_UPDATE_AGGREGATE = { + "aggregate": + { + "name": u'\xe9', + "availability_zone": None, + "deleted": False, + "created_at": "2015-07-16T03:07:32.000000", + "updated_at": "2015-07-23T05:16:29.000000", + "hosts": [], + "deleted_at": None, + "id": 1, + "metadata": {} + } + } + + def setUp(self): + super(TestAggregatesClient, self).setUp() + fake_auth = fake_auth_provider.FakeAuthProvider() + self.client = aggregates_client.AggregatesClient( + fake_auth, 'compute', 'regionOne') + + def _test_list_aggregates(self, bytes_body=False): + self.check_service_client_function( + self.client.list_aggregates, + 'tempest_lib.common.rest_client.RestClient.get', + {"aggregates": []}, + bytes_body) + + def test_list_aggregates_with_str_body(self): + self._test_list_aggregates() + + def test_list_aggregates_with_bytes_body(self): + self._test_list_aggregates(bytes_body=True) + + def _test_show_aggregate(self, bytes_body=False): + self.check_service_client_function( + self.client.show_aggregate, + 'tempest_lib.common.rest_client.RestClient.get', + self.FAKE_SHOW_AGGREGATE, + bytes_body, + aggregate_id=1) + + def test_show_aggregate_with_str_body(self): + self._test_show_aggregate() + + def test_show_aggregate_with_bytes_body(self): + self._test_show_aggregate(bytes_body=True) + + def _test_create_aggregate(self, bytes_body=False): + self.check_service_client_function( + self.client.create_aggregate, + 'tempest_lib.common.rest_client.RestClient.post', + self.FAKE_CREATE_AGGREGATE, + bytes_body, + name='hoge') + + def test_create_aggregate_with_str_body(self): + self._test_create_aggregate() + + def test_create_aggregate_with_bytes_body(self): + self._test_create_aggregate(bytes_body=True) + + def test_delete_aggregate(self): + self.check_service_client_function( + self.client.delete_aggregate, + 'tempest_lib.common.rest_client.RestClient.delete', + {}, aggregate_id="1") + + def _test_update_aggregate(self, bytes_body=False): + self.check_service_client_function( + self.client.update_aggregate, + 'tempest_lib.common.rest_client.RestClient.put', + self.FAKE_UPDATE_AGGREGATE, + bytes_body, + aggregate_id=1) + + def test_update_aggregate_with_str_body(self): + self._test_update_aggregate() + + def test_update_aggregate_with_bytes_body(self): + self._test_update_aggregate(bytes_body=True)