Add tempest case to test container operaton
Change-Id: I6ceb5aeb7babd8119424ad4b792aa4e39a5c23a2
This commit is contained in:
parent
b73d70961f
commit
9df621ae66
@ -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 ``<TEMPEST_DIR>`` 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 <ZUN_SRC_DIR>
|
||||
|
||||
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
|
||||
|
94
zun/tests/tempest/api/clients.py
Normal file
94
zun/tests/tempest/api/clients.py
Normal file
@ -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)
|
0
zun/tests/tempest/api/common/__init__.py
Normal file
0
zun/tests/tempest/api/common/__init__.py
Normal file
70
zun/tests/tempest/api/common/base_model.py
Normal file
70
zun/tests/tempest/api/common/base_model.py
Normal file
@ -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
|
62
zun/tests/tempest/api/common/datagen.py
Normal file
62
zun/tests/tempest/api/common/datagen.py
Normal file
@ -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
|
0
zun/tests/tempest/api/models/__init__.py
Normal file
0
zun/tests/tempest/api/models/__init__.py
Normal file
30
zun/tests/tempest/api/models/container_model.py
Normal file
30
zun/tests/tempest/api/models/container_model.py
Normal file
@ -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
|
@ -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
|
67
zun/tests/tempest/api/test_containers.py
Normal file
67
zun/tests/tempest/api/test_containers.py
Normal file
@ -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)
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user