Add tempest case to test container operaton
Change-Id: I6ceb5aeb7babd8119424ad4b792aa4e39a5c23a2
This commit is contained in:
parent
902fa22f67
commit
bb369db11e
@ -4,14 +4,66 @@ Tempest Plugin
|
|||||||
|
|
||||||
This directory contains Tempest tests to cover Zun project.
|
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::
|
To list all Zun tempest cases, go to tempest directory, then run::
|
||||||
|
|
||||||
$ testr list-tests zun
|
$ 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::
|
To run only these tests in tempest, go to tempest directory, then run::
|
||||||
|
|
||||||
$ ./run_tempest.sh -N -- zun
|
$ ./run_tempest.sh -N -- zun
|
||||||
|
|
||||||
To run a single test case, go to tempest directory, then run with test case name, e.g.::
|
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_tempest_plugin/tests/tempest/api/clients.py
Normal file
94
zun_tempest_plugin/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)
|
70
zun_tempest_plugin/tests/tempest/api/common/base_model.py
Normal file
70
zun_tempest_plugin/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_tempest_plugin/tests/tempest/api/common/datagen.py
Normal file
62
zun_tempest_plugin/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,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_tempest_plugin/tests/tempest/api/test_containers.py
Normal file
67
zun_tempest_plugin/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
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
|
||||||
service_available_group = cfg.OptGroup(name="service_available",
|
service_available_group = cfg.OptGroup(name="service_available",
|
||||||
title="Available OpenStack Services")
|
title="Available OpenStack Services")
|
||||||
|
|
||||||
@ -28,7 +27,7 @@ container_management_group = cfg.OptGroup(
|
|||||||
|
|
||||||
ContainerManagementGroup = [
|
ContainerManagementGroup = [
|
||||||
cfg.StrOpt("catalog_type",
|
cfg.StrOpt("catalog_type",
|
||||||
default="container_management",
|
default="container",
|
||||||
help="Catalog type of the container management service."),
|
help="Catalog type of the container management service."),
|
||||||
cfg.IntOpt("wait_timeout",
|
cfg.IntOpt("wait_timeout",
|
||||||
default=60,
|
default=60,
|
||||||
|
Loading…
Reference in New Issue
Block a user