Remove the local tempest plugin

The plugin has been split into its own repository [1] in accordance
with Queens Goal "Split Tempest Plugins into Separate Repos/Projects"
[2]. This patch removes the local copy as well as the setuptools
entry point.

[1] https://github.com/openstack/zun-tempest-plugin
[2] https://governance.openstack.org/tc/goals/queens/
    split-tempest-plugins.html

Implements: blueprint zun-split-tempest-plugins
Change-Id: Ibfeb38b52c05a9f886dc7524d892842971cbd264
This commit is contained in:
Hongbin Lu 2017-09-19 22:21:00 -04:00 committed by Kien Nguyen
parent 4d91ab84bb
commit 83b7fb110e
20 changed files with 8 additions and 1300 deletions

View File

@ -32,6 +32,9 @@ set +o xtrace
ZUN_REPO=${ZUN_REPO:-${GIT_BASE}/openstack/zun.git}
ZUN_BRANCH=${ZUN_BRANCH:-master}
ZUN_DIR=$DEST/zun
ZUN_TEMPEST_PLUGIN_REPO=${ZUN_REPO:-${GIT_BASE}/openstack/zun-tempest-plugin.git}
ZUN_TEMPEST_PLUGIN_BRANCH=${ZUN_BRANCH:-master}
ZUN_TEMPEST_PLUGIN_DIR=$DEST/zun-tempest-plugin
GITREPO["python-zunclient"]=${ZUNCLIENT_REPO:-${GIT_BASE}/openstack/python-zunclient.git}
GITBRANCH["python-zunclient"]=${ZUNCLIENT_BRANCH:-master}
@ -296,6 +299,9 @@ function install_zunclient {
function install_zun {
git_clone $ZUN_REPO $ZUN_DIR $ZUN_BRANCH
setup_develop $ZUN_DIR
git_clone $ZUN_TEMPEST_PLUGIN_REPO $ZUN_TEMPEST_PLUGIN_DIR $ZUN_TEMPEST_PLUGIN_BRANCH
setup_develop $ZUN_TEMPEST_PLUGIN_DIR
}
function install_etcd_server {

View File

@ -61,4 +61,4 @@ Navigate to tempest directory::
Run this command::
tox -eall-plugin -- zun.tests.tempest.api
tox -eall-plugin -- zun_tempest_plugin.tests.tempest.api

View File

@ -85,9 +85,6 @@ zun.network.driver =
zun.volume.driver =
cinder = zun.volume.driver:Cinder
tempest.test_plugins =
zun_tests = zun.tests.tempest.plugin:ZunTempestPlugin
[extras]
osprofiler =
osprofiler>=1.4.0 # Apache-2.0

View File

@ -27,7 +27,7 @@ export DEVSTACK_LOCAL_CONFIG+=$'\n'"ZUN_USE_UWSGI=True"
export DEVSTACK_LOCAL_CONFIG+=$'\n'"KURYR_CONFIG_DIR=/etc/kuryr-libnetwork"
export DEVSTACK_GATE_TEMPEST=1
export DEVSTACK_GATE_TEMPEST_ALL_PLUGINS=1
export DEVSTACK_GATE_TEMPEST_REGEX="zun.tests.tempest.api"
export DEVSTACK_GATE_TEMPEST_REGEX="zun_tempest_plugin.tests.tempest.api"
if [ "$driver" = "docker" ]; then
export DEVSTACK_LOCAL_CONFIG+=$'\n'"ZUN_DRIVER=docker"

View File

@ -1,69 +0,0 @@
==============
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://git.openstack.org/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 setup
-------------------------
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::
$ tempest run zun
To run a single test case, go to tempest directory, then run with test case name, e.g.::
$ tempest run --regex zun.tests.tempest.api.test_containers.TestContainer.test_create_list_delete

View File

@ -1,287 +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.
import contextlib
import sys
from docker import errors as docker_errors
import six
from six.moves.urllib import parse
from tempest import config
from tempest.lib.common import rest_client
from tempest.lib.services.image.v2 import images_client
from tempest.lib.services.network import ports_client
from tempest.lib.services.network import security_groups_client
from tempest import manager
from zun.common import exception
import zun.conf
from zun.container.docker import utils as docker_utils
from zun.tests.tempest.api.models import container_model
from zun.tests.tempest.api.models import service_model
from zun.tests.tempest import utils
ZUN_CONF = zun.conf.CONF
CONF = config.CONF
class Manager(manager.Manager):
def __init__(self, credentials=None, service=None):
super(Manager, self).__init__(credentials=credentials)
self.images_client = images_client.ImagesClient(
self.auth_provider, 'image', CONF.identity.region)
self.ports_client = ports_client.PortsClient(
self.auth_provider, 'network', CONF.identity.region)
self.sgs_client = security_groups_client.SecurityGroupsClient(
self.auth_provider, 'network', CONF.identity.region)
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 containers_uri(cls, params=None):
url = "/containers/"
if params:
url = cls.add_params(url, params)
return url
@classmethod
def container_uri(cls, container_id, action=None, params=None):
"""Construct container uri
"""
url = None
if action is None:
url = "{0}/{1}".format(cls.containers_uri(), container_id)
else:
url = "{0}/{1}/{2}".format(cls.containers_uri(), container_id,
action)
if params:
url = cls.add_params(url, params)
return url
@classmethod
def add_params(cls, url, params):
"""add_params adds dict values (params) to url as query parameters
:param url: base URL for the request
:param params: dict with var:val pairs to add as parameters to URL
:returns: url string
"""
url_parts = list(parse.urlparse(url))
query = dict(parse.parse_qsl(url_parts[4]))
query.update(params)
url_parts[4] = parse.urlencode(query)
return parse.urlunparse(url_parts)
@classmethod
def services_uri(cls):
url = "/services/"
return url
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 run_container(self, model, **kwargs):
resp, body = self.post(
self.containers_uri(params={'run': True}),
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, **kwargs):
resp, body = self.get(self.containers_uri(), **kwargs)
return self.deserialize(resp, body,
container_model.ContainerCollection)
def delete_container(self, container_id, params=None, **kwargs):
return self.delete(
self.container_uri(container_id, params=params), **kwargs)
def commit_container(self, container_id, params=None, **kwargs):
return self.post(
self.container_uri(container_id, action='commit', params=params),
None, **kwargs)
def start_container(self, container_id, **kwargs):
return self.post(
self.container_uri(container_id, action='start'), None, **kwargs)
def stop_container(self, container_id, **kwargs):
return self.post(
self.container_uri(container_id, action='stop'), None, *kwargs)
def pause_container(self, container_id, **kwargs):
return self.post(
self.container_uri(container_id, action='pause'), None, **kwargs)
def unpause_container(self, container_id, **kwargs):
return self.post(
self.container_uri(container_id, action='unpause'), None, **kwargs)
def kill_container(self, container_id, **kwargs):
return self.post(
self.container_uri(container_id, action='kill'), None, **kwargs)
def reboot_container(self, container_id, **kwargs):
return self.post(
self.container_uri(container_id, action='reboot'), None, **kwargs)
def exec_container(self, container_id, command, **kwargs):
return self.post(
self.container_uri(container_id, action='execute'),
'{"command": "%s"}' % command, **kwargs)
def logs_container(self, container_id, **kwargs):
return self.get(
self.container_uri(container_id, action='logs'), None, **kwargs)
def update_container(self, container_id, model, **kwargs):
resp, body = self.patch(
self.container_uri(container_id), body=model.to_json(), **kwargs)
return self.deserialize(resp, body, container_model.ContainerEntity)
def rename_container(self, container_id, model, **kwargs):
resp, body = self.post(
self.container_uri(container_id, action='rename'),
body=model.to_json(), **kwargs)
return self.deserialize(resp, body, container_model.ContainerEntity)
def top_container(self, container_id, **kwargs):
return self.get(
self.container_uri(container_id, action='top'), None, **kwargs)
def stats_container(self, container_id, **kwargs):
return self.get(
self.container_uri(container_id, action='stats'), None, **kwargs)
def add_security_group(self, container_id, model, **kwargs):
return self.post(
self.container_uri(container_id, action='add_security_group'),
body=model.to_json(), **kwargs)
def list_services(self, **kwargs):
resp, body = self.get(self.services_uri(), **kwargs)
return self.deserialize(resp, body,
service_model.ServiceCollection)
def ensure_container_in_desired_state(self, container_id, status):
def is_container_in_desired_state():
_, container = self.get_container(container_id)
if container.status == status:
return True
else:
return False
utils.wait_for_condition(is_container_in_desired_state)
def ensure_container_deleted(self, container_id):
def is_container_deleted():
_, model = self.list_containers()
container_ids = [c['uuid'] for c in model.containers]
if container_id in container_ids:
return False
else:
return True
utils.wait_for_condition(is_container_deleted)
@contextlib.contextmanager
def docker_client(docker_auth_url):
client_kwargs = dict()
if not ZUN_CONF.docker.api_insecure:
client_kwargs['ca_cert'] = CONF.docker.ca_file
client_kwargs['client_key'] = CONF.docker.key_file
client_kwargs['client_cert'] = CONF.docker.key_file
try:
yield docker_utils.DockerHTTPClient(
docker_auth_url,
ZUN_CONF.docker.docker_remote_api_version,
ZUN_CONF.docker.default_timeout,
**client_kwargs
)
except docker_errors.APIError as e:
desired_exc = exception.DockerError(error_msg=six.text_type(e))
six.reraise(type(desired_exc), desired_exc, sys.exc_info()[2])
class DockerClient(object):
def get_container(self, container_id,
docker_auth_url=ZUN_CONF.docker.api_url):
with docker_client(docker_auth_url) as docker:
for info in docker.list_instances(inspect=True):
if container_id in info['Name']:
return info
return None
def ensure_container_pid_changed(
self, container_id, pid,
docker_auth_url=ZUN_CONF.docker.api_url):
def is_pid_changed():
container = self.get_container(container_id,
docker_auth_url=docker_auth_url)
new_pid = container.get('State').get('Pid')
if pid != new_pid:
return True
else:
return False
utils.wait_for_condition(is_pid_changed)
def pull_image(
self, repo, tag=None,
docker_auth_url=ZUN_CONF.docker.api_url):
with docker_client(docker_auth_url) as docker:
docker.pull(repo, tag=tag)
def get_image(self, name, docker_auth_url=ZUN_CONF.docker.api_url):
with docker_client(docker_auth_url) as docker:
return docker.get_image(name)
def delete_image(self, name, docker_auth_url=ZUN_CONF.docker.api_url):
with docker_client(docker_auth_url) as docker:
return docker.remove_image(name)
def list_networks(self, name,
docker_auth_url=ZUN_CONF.docker.api_url):
with docker_client(docker_auth_url) as docker:
return docker.networks(names=[name])
def remove_network(self, name,
docker_auth_url=ZUN_CONF.docker.api_url):
with docker_client(docker_auth_url) as docker:
return docker.remove_network(name)

View File

@ -1,70 +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 oslo_serialization import jsonutils as 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.dump_as_bytes(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

View File

@ -1,105 +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.
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 container_data(default_data=None, **kwargs):
if default_data is None:
default_data = {
'name': data_utils.rand_name('container'),
'image': 'cirros:latest',
'command': 'sleep 10000',
'cpu': 0.1,
'memory': '100',
'environment': {},
'labels': {},
'image_driver': 'docker',
'image_pull_policy': 'always',
'restart_policy': {'Name': 'no'},
'workdir': '/',
'interactive': False,
'security_groups': ['default'],
}
default_data.update(kwargs)
model = container_model.ContainerEntity.from_dict(default_data)
return model
def container_patch_data(**kwargs):
data = {
'cpu': 0.2,
'memory': '512',
}
data.update(kwargs)
model = container_model.ContainerPatchEntity.from_dict(data)
return model
def container_rename_data(**kwargs):
data = {
'name': 'new_name',
}
data.update(kwargs)
model = container_model.ContainerPatchEntity.from_dict(data)
return model
def container_add_sg_data(**kwargs):
data = {
'name': 'sg_name',
}
data.update(kwargs)
model = container_model.ContainerPatchEntity.from_dict(data)
return model

View File

@ -1,41 +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 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
class ContainerPatchData(base_model.BaseModel):
"""Data that encapsulates container update attributes"""
pass
class ContainerPatchEntity(base_model.EntityModel):
"""Entity Model that represents a single instance of ContainerPatchData"""
ENTITY_NAME = 'containerpatch'
MODEL_TYPE = ContainerPatchData

View File

@ -1,30 +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 zun.tests.tempest.api.common import base_model
class ServiceData(base_model.BaseModel):
"""Data that encapsulates service attributes"""
pass
class ServiceEntity(base_model.EntityModel):
"""Entity Model that represents a single instance of ServiceData"""
ENTITY_NAME = 'service'
MODEL_TYPE = ServiceData
class ServiceCollection(base_model.CollectionModel):
"""Collection Model that represents a list of ServiceData objects"""
COLLECTION_NAME = 'servicelists'
MODEL_TYPE = ServiceData

View File

@ -1,511 +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 oslo_utils import encodeutils
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
from zun.tests.tempest import utils
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_primary.container_client
cls.docker_client = clients.DockerClient()
cls.images_client = cls.os_primary.images_client
cls.ports_client = cls.os_primary.ports_client
cls.sgs_client = cls.os_primary.sgs_client
@classmethod
def resource_setup(cls):
super(TestContainer, cls).resource_setup()
def setUp(self):
super(TestContainer, self).setUp()
self.containers = []
def tearDown(self):
hosts = []
_, model = self.container_client.list_containers()
for c in model.containers:
if c['uuid'] in self.containers:
if c['host'] and c['host'] not in hosts:
hosts.append(c['host'])
self.container_client.delete_container(c['uuid'],
params={'force': True})
self.container_client.ensure_container_deleted(c['uuid'])
# cleanup the network resources
project_id = self.container_client.tenant_id
for host in hosts:
# NOTE(kiennt): Default docker remote url
# Remove networks in all hosts
docker_base_url = self._get_docker_url(host)
networks = self.docker_client.list_networks(project_id,
docker_base_url)
for network in networks:
self.docker_client.remove_network(network['Id'],
docker_base_url)
super(TestContainer, self).tearDown()
@decorators.idempotent_id('b8946b8c-57d5-4fdc-a09a-001d6b552725')
def test_create_container(self):
self._create_container()
@decorators.idempotent_id('b3e307d4-844b-4a57-8c60-8fb3f57aea7c')
def test_list_containers(self):
_, container = self._create_container()
resp, model = self.container_client.list_containers()
self.assertEqual(200, resp.status)
self.assertGreater(len(model.containers), 0)
self.assertIn(
container.uuid,
list([c['uuid'] for c in model.containers]))
@decorators.idempotent_id('0dd13c28-c5ff-4b9e-b73b-61185b410de4')
def test_get_container(self):
_, container = self._create_container()
resp, model = self.container_client.get_container(container.uuid)
self.assertEqual(200, resp.status)
self.assertEqual(container.uuid, model.uuid)
@decorators.idempotent_id('cef53a56-22b7-4808-b01c-06b2b7126115')
def test_delete_container(self):
_, container = self._create_container()
self._delete_container(container.uuid, container.host, True)
@decorators.idempotent_id('ef69c9e7-0ce0-4e14-b7ec-c1dc581a3927')
def test_run_container(self):
self._run_container()
@decorators.idempotent_id('a2152d78-b6a6-4f47-8767-d83d29c6fb19')
def test_run_container_with_minimal_params(self):
gen_model = datagen.container_data({'image': 'nginx'})
self._run_container(gen_model=gen_model)
@decorators.idempotent_id('c32f93e3-da88-4c13-be38-25d2e662a28e')
def test_run_container_with_image_driver_glance(self):
image = None
try:
docker_base_url = self._get_docker_url()
self.docker_client.pull_image(
'cirros', docker_auth_url=docker_base_url)
image_data = self.docker_client.get_image(
'cirros', docker_base_url)
image = self.images_client.create_image(
name='cirros', disk_format='raw', container_format='docker')
self.images_client.store_image_file(image['id'], image_data)
# delete the local image that was previously pulled down
self.docker_client.delete_image('cirros', docker_base_url)
_, model = self._run_container(
image='cirros', image_driver='glance')
finally:
if image:
try:
self.images_client.delete_image(image['id'])
except Exception:
pass
@decorators.idempotent_id('b70bedbc-5ba2-400c-8f5f-0cf05ca17151')
def test_run_container_with_environment(self):
_, model = self._run_container(environment={
'key1': 'env1', 'key2': 'env2'})
container = self.docker_client.get_container(
model.uuid,
self._get_docker_url(model.host))
env = container.get('Config').get('Env')
self.assertTrue('key1=env1', env)
self.assertTrue('key2=env2', env)
@decorators.idempotent_id('0e59d549-58ff-440f-8704-10e223c31cbc')
def test_run_container_with_labels(self):
_, model = self._run_container(labels={
'key1': 'label1', 'key2': 'label2'})
container = self.docker_client.get_container(
model.uuid,
self._get_docker_url(model.host))
labels = container.get('Config').get('Labels')
self.assertTrue('key1=label1', labels)
self.assertTrue('key2=label2', labels)
@decorators.idempotent_id('9fc7fec0-e1a9-4f65-a5a6-dba425c1607c')
def test_run_container_with_restart_policy(self):
_, model = self._run_container(restart_policy={
'Name': 'on-failure', 'MaximumRetryCount': 2})
container = self.docker_client.get_container(
model.uuid,
self._get_docker_url(model.host))
policy = container.get('HostConfig').get('RestartPolicy')
self.assertEqual('on-failure', policy['Name'])
self.assertTrue(2, policy['MaximumRetryCount'])
@decorators.idempotent_id('58585a4f-cdce-4dbd-9741-4416d1098f94')
def test_run_container_with_interactive(self):
_, model = self._run_container(interactive=True)
container = self.docker_client.get_container(
model.uuid,
self._get_docker_url(model.host))
tty = container.get('Config').get('Tty')
stdin_open = container.get('Config').get('OpenStdin')
self.assertIs(True, tty)
self.assertIs(True, stdin_open)
@decorators.idempotent_id('f181eeda-a9d1-4b2e-9746-d6634ca81e2f')
def test_run_container_without_security_groups(self):
gen_model = datagen.container_data()
delattr(gen_model, 'security_groups')
_, model = self._run_container(gen_model=gen_model)
sgs = self._get_all_security_groups(model)
self.assertEqual(1, len(sgs))
self.assertEqual('default', sgs[0])
@decorators.idempotent_id('f181eeda-a9d1-4b2e-9746-d6634ca81e2f')
def test_run_container_with_security_groups(self):
sg_name = 'test_sg'
self.sgs_client.create_security_group(name=sg_name)
_, model = self._run_container(security_groups=[sg_name])
sgs = self._get_all_security_groups(model)
self.assertEqual(1, len(sgs))
self.assertEqual(sg_name, sgs[0])
@decorators.idempotent_id('c3f02fa0-fdfb-49fc-95e2-6e4dc982f9be')
def test_commit_container(self):
"""Test container snapshot
This test does the following:
1. Create a container
2. Create and write to a file inside the container
3. Commit the container and upload the snapshot to Glance
4. Create another container from the snapshot image
5. Verify the pre-created file is there
"""
# This command creates a file inside the container
command = "/bin/sh -c 'echo hello > testfile;sleep 1000000'"
_, model = self._run_container(command=command)
try:
resp, _ = self.container_client.commit_container(
model.uuid, params={'repository': 'myrepo'})
self.assertEqual(202, resp.status)
self._ensure_image_active('myrepo')
# This command outputs the content of pre-created file
command = "/bin/sh -c 'cat testfile;sleep 1000000'"
_, model = self._run_container(
image="myrepo", image_driver="glance", command=command)
resp, body = self.container_client.logs_container(model.uuid)
self.assertEqual(200, resp.status)
self.assertTrue('hello' in encodeutils.safe_decode(body))
finally:
try:
response = self.images_client.list_images()
for image in response['images']:
if (image['name'] == 'myrepo' and
image['container_format'] == 'docker'):
self.images_client.delete_image(image['id'])
except Exception:
pass
def _ensure_image_active(self, image_name):
def is_image_in_desired_state():
response = self.images_client.list_images()
for image in response['images']:
if (image['name'] == image_name and
image['container_format'] == 'docker' and
image['status'] == 'active'):
return True
return False
utils.wait_for_condition(is_image_in_desired_state)
@decorators.idempotent_id('3fa024ef-aba1-48fe-9682-0d6b7854faa3')
def test_start_stop_container(self):
_, model = self._run_container()
resp, _ = self.container_client.stop_container(model.uuid)
self.assertEqual(202, resp.status)
self.container_client.ensure_container_in_desired_state(
model.uuid, 'Stopped')
self.assertEqual('Stopped',
self._get_container_state(model.uuid, model.host))
resp, _ = self.container_client.start_container(model.uuid)
self.assertEqual(202, resp.status)
self.container_client.ensure_container_in_desired_state(
model.uuid, 'Running')
self.assertEqual('Running',
self._get_container_state(model.uuid, model.host))
@decorators.idempotent_id('b5f39756-8898-4e0e-a48b-dda0a06b66b6')
def test_pause_unpause_container(self):
_, model = self._run_container()
resp, _ = self.container_client.pause_container(model.uuid)
self.assertEqual(202, resp.status)
self.container_client.ensure_container_in_desired_state(
model.uuid, 'Paused')
self.assertEqual('Paused',
self._get_container_state(model.uuid, model.host))
resp, _ = self.container_client.unpause_container(model.uuid)
self.assertEqual(202, resp.status)
self.container_client.ensure_container_in_desired_state(
model.uuid, 'Running')
self.assertEqual('Running',
self._get_container_state(model.uuid, model.host))
@decorators.idempotent_id('6179a588-3d48-4372-9599-f228411d1449')
def test_kill_container(self):
_, model = self._run_container()
resp, _ = self.container_client.kill_container(model.uuid)
self.assertEqual(202, resp.status)
self.container_client.ensure_container_in_desired_state(
model.uuid, 'Stopped')
self.assertEqual('Stopped',
self._get_container_state(model.uuid, model.host))
@decorators.idempotent_id('c2e54321-0a70-4331-ba62-9dcaa75ac250')
def test_reboot_container(self):
_, model = self._run_container()
docker_base_url = self._get_docker_url(model.host)
container = self.docker_client.get_container(model.uuid,
docker_base_url)
pid = container.get('State').get('Pid')
resp, _ = self.container_client.reboot_container(model.uuid)
self.assertEqual(202, resp.status)
self.docker_client.ensure_container_pid_changed(model.uuid, pid,
docker_base_url)
self.assertEqual('Running',
self._get_container_state(model.uuid, model.host))
# assert pid is changed
container = self.docker_client.get_container(model.uuid,
docker_base_url)
self.assertNotEqual(pid, container.get('State').get('Pid'))
@decorators.idempotent_id('8a591ff8-6793-427f-82a6-e3921d8b4f81')
def test_exec_container(self):
_, model = self._run_container()
resp, body = self.container_client.exec_container(model.uuid,
command='echo hello')
self.assertEqual(200, resp.status)
self.assertTrue('hello' in encodeutils.safe_decode(body))
@decorators.idempotent_id('a912ca23-14e7-442f-ab15-e05aaa315204')
def test_logs_container(self):
_, model = self._run_container(
command="/bin/sh -c 'echo hello;sleep 1000000'")
resp, body = self.container_client.logs_container(model.uuid)
self.assertEqual(200, resp.status)
self.assertTrue('hello' in encodeutils.safe_decode(body))
@decorators.idempotent_id('d383f359-3ebd-40ef-9dc5-d36922790230')
def test_update_container(self):
_, model = self._run_container(cpu=0.1, memory=100)
self.assertEqual('100M', model.memory)
self.assertEqual(0.1, model.cpu)
docker_base_url = self._get_docker_url(model.host)
container = self.docker_client.get_container(model.uuid,
docker_base_url)
self._assert_resource_constraints(container, cpu=0.1, memory=100)
gen_model = datagen.container_patch_data(cpu=0.2, memory=200)
resp, model = self.container_client.update_container(model.uuid,
gen_model)
self.assertEqual(200, resp.status)
self.assertEqual('200M', model.memory)
self.assertEqual(0.2, model.cpu)
container = self.docker_client.get_container(model.uuid,
docker_base_url)
self._assert_resource_constraints(container, cpu=0.2, memory=200)
@decorators.idempotent_id('b218bea7-f19b-499f-9819-c7021ffc59f4')
def test_rename_container(self):
_, model = self._run_container(name='container1')
self.assertEqual('container1', model.name)
gen_model = datagen.container_rename_data(name='container2')
resp, model = self.container_client.rename_container(model.uuid,
gen_model)
self.assertEqual(200, resp.status)
self.assertEqual('container2', model.name)
@decorators.idempotent_id('142b7716-0b21-41ed-b47d-a42fba75636b')
def test_top_container(self):
_, model = self._run_container(
command="/bin/sh -c 'sleep 1000000'")
resp, body = self.container_client.top_container(model.uuid)
self.assertEqual(200, resp.status)
self.assertTrue('sleep 1000000' in encodeutils.safe_decode(body))
@decorators.idempotent_id('09638306-b501-4803-aafa-7e8025632cef')
def test_stats_container(self):
_, model = self._run_container()
resp, body = self.container_client.stats_container(model.uuid)
self.assertEqual(200, resp.status)
self.assertTrue('NET I/O(B)' in encodeutils.safe_decode(body))
self.assertTrue('CONTAINER' in encodeutils.safe_decode(body))
self.assertTrue('MEM LIMIT(MiB)' in encodeutils.safe_decode(body))
self.assertTrue('CPU %' in encodeutils.safe_decode(body))
self.assertTrue('MEM USAGE(MiB)' in encodeutils.safe_decode(body))
self.assertTrue('MEM %' in encodeutils.safe_decode(body))
self.assertTrue('BLOCK I/O(B)' in encodeutils.safe_decode(body))
@decorators.idempotent_id('b3b9cf17-82ad-4c1b-a4af-8210a778a33e')
def test_add_sg_to_container(self):
_, model = self._run_container()
sgs = self._get_all_security_groups(model)
self.assertEqual(1, len(sgs))
self.assertEqual('default', sgs[0])
sg_name = 'test_add_sg'
self.sgs_client.create_security_group(name=sg_name)
gen_model = datagen.container_add_sg_data(name=sg_name)
resp, body = self.container_client.add_security_group(
model.uuid, gen_model)
self.assertEqual(202, resp.status)
def assert_security_group_is_added():
sgs = self._get_all_security_groups(model)
if len(sgs) == 2:
self.assertTrue('default' in sgs)
self.assertTrue(sg_name in sgs)
return True
else:
return False
utils.wait_for_condition(assert_security_group_is_added)
def _assert_resource_constraints(self, container, cpu=None, memory=None):
if cpu is not None:
cpu_quota = container.get('HostConfig').get('CpuQuota')
self.assertEqual(int(cpu * 100000), cpu_quota)
cpu_period = container.get('HostConfig').get('CpuPeriod')
self.assertEqual(100000, cpu_period)
if memory is not None:
docker_memory = container.get('HostConfig').get('Memory')
self.assertEqual(memory * 1024 * 1024, docker_memory)
def _create_container(self, **kwargs):
gen_model = datagen.container_data(**kwargs)
resp, model = self.container_client.post_container(gen_model)
self.containers.append(model.uuid)
self.assertEqual(202, resp.status)
# Wait for container to finish creation
self.container_client.ensure_container_in_desired_state(
model.uuid, 'Created')
# Assert the container is created
resp, model = self.container_client.get_container(model.uuid)
self.assertEqual(200, resp.status)
self.assertEqual('Created', model.status)
self.assertEqual('Created', self._get_container_state(model.uuid,
model.host))
return resp, model
def _run_container(self, gen_model=None, **kwargs):
if gen_model is None:
gen_model = datagen.container_data(**kwargs)
resp, model = self.container_client.run_container(gen_model)
self.containers.append(model.uuid)
self.assertEqual(202, resp.status)
# Wait for container to started
self.container_client.ensure_container_in_desired_state(
model.uuid, 'Running')
# Assert the container is started
resp, model = self.container_client.get_container(model.uuid)
self.assertEqual('Running', model.status)
self.assertEqual('Running', self._get_container_state(model.uuid,
model.host))
self.assertIsNotNone(model.host)
return resp, model
def _delete_container(self, container_id, container_host, force=False):
resp, _ = self.container_client.delete_container(
container_id, params={'force': force})
self.assertEqual(204, resp.status)
self.container_client.ensure_container_deleted(container_id)
container = self.docker_client.get_container(
container_id, self._get_docker_url(container_host))
self.assertIsNone(container)
def _get_container_state(self, container_id, docker_host=None):
if docker_host is not None:
container = self.docker_client.get_container(
container_id, self._get_docker_url(docker_host))
else:
container = self.docker_client.get_container(container_id)
status = container.get('State')
if status.get('Error') is True:
return 'Error'
elif status.get('Paused'):
return 'Paused'
elif status.get('Running'):
return 'Running'
elif status.get('Status') == 'created':
return 'Created'
else:
return 'Stopped'
def _get_all_security_groups(self, container):
# find all neutron ports of this container
port_ids = set()
for addrs_list in container.addresses.values():
for addr in addrs_list:
port_id = addr['port']
port_ids.add(port_id)
# find all security groups of this container
sg_ids = set()
for port_id in port_ids:
port = self.ports_client.show_port(port_id)
for sg in port['port']['security_groups']:
sg_ids.add(sg)
sg_names = []
for sg_id in sg_ids:
sg = self.sgs_client.show_security_group(sg_id)
sg_names.append(sg['security_group']['name'])
return sg_names
def _get_docker_url(self, host='localhost', protocol='tcp', port='2375'):
# NOTE(kiennt): By default, devstack-plugin-container will
# set docker_api_url = {
# "unix://$DOCKER_ENGINE_SOCKET_FILE",
# "tcp://0.0.0.0:$DOCKER_ENGINE_PORT"
# }
base_url = '{}://{}:{}' . format(protocol, host, port)
return base_url

View File

@ -1,50 +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 tempest.lib import exceptions
from zun.tests.tempest.api import clients
from zun.tests.tempest import base
class TestService(base.BaseZunTest):
@classmethod
def get_client_manager(cls, credential_type=None, roles=None,
force_new=None):
manager = super(TestService, 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(TestService, cls).setup_clients()
cls.container_client = cls.os_primary.container_client
@classmethod
def resource_setup(cls):
super(TestService, cls).resource_setup()
# TODO(pksingh): currently functional test doesn't support
# policy, will write another test after
# implementing policy in functional tests
@decorators.idempotent_id('a04f61f2-15ae-4200-83b7-1f311b101f36')
def test_service_list(self):
self.assertRaises(exceptions.Forbidden,
self.container_client.list_services)

View File

@ -1,35 +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 import config
from tempest import test
CONF = config.CONF
class BaseZunTest(test.BaseTestCase):
credentials = ['primary']
@classmethod
def skip_checks(cls):
super(BaseZunTest, cls).skip_checks()
if not CONF.service_available.zun:
skip_msg = 'Zun is disabled'
raise cls.skipException(skip_msg)
@classmethod
def setup_clients(cls):
super(BaseZunTest, cls).setup_clients()
pass

View File

@ -1,31 +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 oslo_config import cfg
service_option = cfg.BoolOpt("zun",
default=True,
help="Whether or not zun is expected to be "
"available")
container_management_group = cfg.OptGroup(
name="container_management", title="Container Management Service Options")
ContainerManagementGroup = [
cfg.StrOpt("catalog_type",
default="container",
help="Catalog type of the container management service."),
cfg.IntOpt("wait_timeout",
default=60,
help="Waiting time for a specific status, in seconds.")
]

View File

@ -1,41 +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.
import os
from tempest.test_discover import plugins
from zun.tests.tempest import config as config_zun
class ZunTempestPlugin(plugins.TempestPlugin):
def load_tests(self):
base_path = os.path.split(os.path.dirname(
os.path.abspath(__file__)))[0]
base_path += '/../..'
test_dir = "zun/tests/tempest"
full_test_dir = os.path.join(base_path, test_dir)
return full_test_dir, base_path
def register_opts(self, conf):
conf.register_opt(config_zun.service_option,
group='service_available')
conf.register_group(config_zun.container_management_group)
conf.register_opts(config_zun.ContainerManagementGroup,
group='container_management')
def get_opt_lists(self):
return [(config_zun.container_management_group.name,
config_zun.ContainerManagementGroup),
('service_available', [config_zun.service_option])]

View File

@ -1,25 +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.
import time
def wait_for_condition(condition, interval=2, timeout=60):
start_time = time.time()
end_time = time.time() + timeout
while time.time() < end_time:
result = condition()
if result:
return result
time.sleep(interval)
raise Exception(("Timed out after %s seconds. Started on %s and ended "
"on %s") % (timeout, start_time, end_time))