From bb369db11ef5f39ec2730161ada9b504cd624a83 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 31 Aug 2016 03:06:57 +0000 Subject: [PATCH] Add tempest case to test container operaton Change-Id: I6ceb5aeb7babd8119424ad4b792aa4e39a5c23a2 --- zun_tempest_plugin/tests/tempest/README.rst | 54 ++++++++++- .../tests/tempest/api/clients.py | 94 +++++++++++++++++++ .../tests/tempest/api/common/__init__.py | 0 .../tests/tempest/api/common/base_model.py | 70 ++++++++++++++ .../tests/tempest/api/common/datagen.py | 62 ++++++++++++ .../tests/tempest/api/models/__init__.py | 0 .../tempest/api/models/container_model.py | 30 ++++++ .../tests/tempest/api/test_basic.py | 24 ----- .../tests/tempest/api/test_containers.py | 67 +++++++++++++ zun_tempest_plugin/tests/tempest/config.py | 3 +- 10 files changed, 377 insertions(+), 27 deletions(-) create mode 100644 zun_tempest_plugin/tests/tempest/api/clients.py create mode 100644 zun_tempest_plugin/tests/tempest/api/common/__init__.py create mode 100644 zun_tempest_plugin/tests/tempest/api/common/base_model.py create mode 100644 zun_tempest_plugin/tests/tempest/api/common/datagen.py create mode 100644 zun_tempest_plugin/tests/tempest/api/models/__init__.py create mode 100644 zun_tempest_plugin/tests/tempest/api/models/container_model.py delete mode 100644 zun_tempest_plugin/tests/tempest/api/test_basic.py create mode 100644 zun_tempest_plugin/tests/tempest/api/test_containers.py diff --git a/zun_tempest_plugin/tests/tempest/README.rst b/zun_tempest_plugin/tests/tempest/README.rst index 4dc0e5c..8612281 100644 --- a/zun_tempest_plugin/tests/tempest/README.rst +++ b/zun_tempest_plugin/tests/tempest/README.rst @@ -4,14 +4,66 @@ Tempest Plugin This directory contains Tempest tests to cover Zun project. + +Tempest installation +-------------------- + +To install Tempest you can issue the following commands:: + + $ git clone https://github.com/openstack/tempest/ + $ cd tempest/ + $ pip install . + +The folder you are into now will be called ```` from now onwards. + +Please note that although it is fully working outside a virtual environment, it +is recommended to install within a `venv`. + +Zun Tempest testing sutup +------------------------- + +Before using zun tempest plugin, you need to install zun first:: + + $ pip install -e + To list all Zun tempest cases, go to tempest directory, then run:: $ testr list-tests zun +Need to adopt tempest.conf, an example as follows:: + + $ cat /etc/tempest/tempest.conf + + [auth] + use_dynamic_credentials=True + admin_username=admin + admin_password=123 + admin_project_name=admin + + [identity] + disable_ssl_certificate_validation=True + uri=http://127.0.0.1:5000/v2.0/ + auth_version=v2 + region=RegionOne + + [identity-feature-enabled] + api_v2 = true + api_v3 = false + trust = false + + [oslo_concurrency] + lock_path = /tmp/ + + [container_management] + catalog_type = container + + [debug] + trace_requests=true + To run only these tests in tempest, go to tempest directory, then run:: $ ./run_tempest.sh -N -- zun To run a single test case, go to tempest directory, then run with test case name, e.g.:: - $ ./run_tempest.sh -- -N zun.tests.tempest.api.test_basic.TestBasic.test_basic + $ ./run_tempest.sh -- -N zun.tests.tempest.api.test_containers.TestContainer.test_create_list_delete diff --git a/zun_tempest_plugin/tests/tempest/api/clients.py b/zun_tempest_plugin/tests/tempest/api/clients.py new file mode 100644 index 0000000..f7965a0 --- /dev/null +++ b/zun_tempest_plugin/tests/tempest/api/clients.py @@ -0,0 +1,94 @@ +# 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 six.moves.urllib import parse +from tempest import config +from tempest.lib.common import rest_client +from tempest.lib.services.compute import keypairs_client +from tempest import manager + +from zun.tests.tempest.api.models import container_model + + +CONF = config.CONF + + +class Manager(manager.Manager): + + def __init__(self, credentials=None, service=None): + super(Manager, self).__init__(credentials=credentials) + + params = {'service': CONF.container_management.catalog_type, + 'region': CONF.identity.region} + self.keypairs_client = keypairs_client.KeyPairsClient( + self.auth_provider, **params) + self.container_client = ZunClient(self.auth_provider) + + +class ZunClient(rest_client.RestClient): + + def __init__(self, auth_provider): + super(ZunClient, self).__init__( + auth_provider=auth_provider, + service=CONF.container_management.catalog_type, + region=CONF.identity.region, + disable_ssl_certificate_validation=True + ) + + @classmethod + def deserialize(cls, resp, body, model_type): + return resp, model_type.from_json(body) + + @classmethod + def add_filters(cls, url, filters): + """add_filters adds dict values (filters) to url as query parameters + + :param url: base URL for the request + :param filters: dict with var:val pairs to add as parameters to URL + :returns: url string + """ + return url + "?" + parse(filters) + + @classmethod + def containers_uri(cls, filters=None): + url = "/containers/" + if filters: + url = cls.add_filters(url, filters) + return url + + @classmethod + def container_uri(cls, container_id): + """Construct container uri + + """ + return "{0}/{1}".format(cls.containers_uri(), container_id) + + def post_container(self, model, **kwargs): + """Makes POST /container request + + """ + resp, body = self.post( + self.containers_uri(), + body=model.to_json(), **kwargs) + return self.deserialize(resp, body, container_model.ContainerEntity) + + def get_container(self, container_id): + resp, body = self.get(self.container_uri(container_id)) + return self.deserialize(resp, body, container_model.ContainerEntity) + + def list_containers(self, filters=None, **kwargs): + resp, body = self.get(self.containers_uri(filters), **kwargs) + return self.deserialize(resp, body, + container_model.ContainerCollection) + + def delete_container(self, container_id, **kwargs): + self.delete(self.container_uri(container_id), **kwargs) diff --git a/zun_tempest_plugin/tests/tempest/api/common/__init__.py b/zun_tempest_plugin/tests/tempest/api/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zun_tempest_plugin/tests/tempest/api/common/base_model.py b/zun_tempest_plugin/tests/tempest/api/common/base_model.py new file mode 100644 index 0000000..23536af --- /dev/null +++ b/zun_tempest_plugin/tests/tempest/api/common/base_model.py @@ -0,0 +1,70 @@ +# 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 json + + +class BaseModel(object): + """Superclass responsible for converting json data to/from model""" + + @classmethod + def from_json(cls, json_str): + return cls.from_dict(json.loads(json_str)) + + def to_json(self): + return json.dumps(self.to_dict()) + + @classmethod + def from_dict(cls, data): + model = cls() + for key in data: + setattr(model, key, data.get(key)) + return model + + def to_dict(self): + result = {} + for key in self.__dict__: + result[key] = getattr(self, key) + if isinstance(result[key], BaseModel): + result[key] = result[key].to_dict() + return result + + def __str__(self): + return "%s" % self.to_dict() + + +class EntityModel(BaseModel): + """Superclass resposible from converting dict to instance of model""" + + @classmethod + def from_dict(cls, data): + model = super(EntityModel, cls).from_dict(data) + if hasattr(model, cls.ENTITY_NAME): + val = getattr(model, cls.ENTITY_NAME) + setattr(model, cls.ENTITY_NAME, cls.MODEL_TYPE.from_dict(val)) + return model + + +class CollectionModel(BaseModel): + """Superclass resposible from converting dict to list of models""" + + @classmethod + def from_dict(cls, data): + model = super(CollectionModel, cls).from_dict(data) + + collection = [] + if hasattr(model, cls.COLLECTION_NAME): + for d in getattr(model, cls.COLLECTION_NAME): + collection.append(cls.MODEL_TYPE.from_dict(d)) + setattr(model, cls.COLLECTION_NAME, collection) + + return model diff --git a/zun_tempest_plugin/tests/tempest/api/common/datagen.py b/zun_tempest_plugin/tests/tempest/api/common/datagen.py new file mode 100644 index 0000000..efa9701 --- /dev/null +++ b/zun_tempest_plugin/tests/tempest/api/common/datagen.py @@ -0,0 +1,62 @@ +# 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 random +import socket +import string +import struct + +from tempest.lib.common.utils import data_utils +from zun.tests.tempest.api.models import container_model + + +def random_int(min_int=1, max_int=100): + return random.randrange(min_int, max_int) + + +def gen_random_port(): + return random_int(49152, 65535) + + +def gen_docker_volume_size(min_int=3, max_int=5): + return random_int(min_int, max_int) + + +def gen_fake_ssh_pubkey(): + chars = "".join( + random.choice(string.ascii_uppercase + + string.ascii_letters + string.digits + '/+=') + for _ in range(372)) + return "ssh-rsa " + chars + + +def gen_random_ip(): + return socket.inet_ntoa(struct.pack('>I', random.randint(1, 0xffffffff))) + + +def gen_url(scheme="http", domain="example.com", port=80): + return "%s://%s:%s" % (scheme, domain, port) + + +def contaienr_data(**kwargs): + data = { + 'name': data_utils.rand_name('container'), + 'image': 'cirros:latest', + 'command': 'sleep 10000', + 'memory': '100m', + 'environment': {} + } + + data.update(kwargs) + model = container_model.ContainerEntity.from_dict(data) + + return model diff --git a/zun_tempest_plugin/tests/tempest/api/models/__init__.py b/zun_tempest_plugin/tests/tempest/api/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zun_tempest_plugin/tests/tempest/api/models/container_model.py b/zun_tempest_plugin/tests/tempest/api/models/container_model.py new file mode 100644 index 0000000..e938aa9 --- /dev/null +++ b/zun_tempest_plugin/tests/tempest/api/models/container_model.py @@ -0,0 +1,30 @@ +# 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 zun.tests.tempest.api.common import base_model + + +class ContainerData(base_model.BaseModel): + """Data that encapsulates container attributes""" + pass + + +class ContainerEntity(base_model.EntityModel): + """Entity Model that represents a single instance of ContainerData""" + ENTITY_NAME = 'container' + MODEL_TYPE = ContainerData + + +class ContainerCollection(base_model.CollectionModel): + """Collection Model that represents a list of ContainerData objects""" + COLLECTION_NAME = 'containerlists' + MODEL_TYPE = ContainerData diff --git a/zun_tempest_plugin/tests/tempest/api/test_basic.py b/zun_tempest_plugin/tests/tempest/api/test_basic.py deleted file mode 100644 index ab0c188..0000000 --- a/zun_tempest_plugin/tests/tempest/api/test_basic.py +++ /dev/null @@ -1,24 +0,0 @@ -# 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 import decorators - -from zun.tests.tempest import base - - -class TestBasic(base.BaseZunTest): - - @decorators.idempotent_id('a04f61f2-15ae-4200-83b7-1f311b101f65') - def test_basic(self): - # This is a basic test used to verify zun tempest plugin - # works. Remove it after real test cases being added. - pass diff --git a/zun_tempest_plugin/tests/tempest/api/test_containers.py b/zun_tempest_plugin/tests/tempest/api/test_containers.py new file mode 100644 index 0000000..af2a2d3 --- /dev/null +++ b/zun_tempest_plugin/tests/tempest/api/test_containers.py @@ -0,0 +1,67 @@ +# 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 import decorators + +from zun.tests.tempest.api import clients +from zun.tests.tempest.api.common import datagen +from zun.tests.tempest import base + + +class TestContainer(base.BaseZunTest): + + @classmethod + def get_client_manager(cls, credential_type=None, roles=None, + force_new=None): + + manager = super(TestContainer, cls).get_client_manager( + credential_type=credential_type, + roles=roles, + force_new=force_new + ) + return clients.Manager(manager.credentials) + + @classmethod + def setup_clients(cls): + + super(TestContainer, cls).setup_clients() + cls.container_client = cls.os.container_client + + @classmethod + def resource_setup(cls): + + super(TestContainer, cls).resource_setup() + + def _create_container(self, **kwargs): + + model = datagen.contaienr_data(**kwargs) + return self.container_client.post_container(model) + + def _delete_container(self, container_id): + + self.container_client.delete_container(container_id) + + @decorators.idempotent_id('a04f61f2-15ae-4200-83b7-1f311b101f35') + def test_container_create_list_delete(self): + + resp, container = self._create_container() + self.assertEqual(202, resp.status) + + resp, model = self.container_client.list_containers() + self.assertEqual(200, resp.status) + self.assertGreater(len(model.containers), 0) + + self._delete_container(container.uuid) + + resp, model = self.container_client.list_containers() + self.assertEqual(200, resp.status) + self.assertEqual(len(model.containers), 0) diff --git a/zun_tempest_plugin/tests/tempest/config.py b/zun_tempest_plugin/tests/tempest/config.py index 21b53f2..4b65603 100644 --- a/zun_tempest_plugin/tests/tempest/config.py +++ b/zun_tempest_plugin/tests/tempest/config.py @@ -13,7 +13,6 @@ from oslo_config import cfg - service_available_group = cfg.OptGroup(name="service_available", title="Available OpenStack Services") @@ -28,7 +27,7 @@ container_management_group = cfg.OptGroup( ContainerManagementGroup = [ cfg.StrOpt("catalog_type", - default="container_management", + default="container", help="Catalog type of the container management service."), cfg.IntOpt("wait_timeout", default=60,