Merge "Introduce fullstack tests"

This commit is contained in:
Zuul 2019-06-29 23:13:54 +00:00 committed by Gerrit Code Review
commit d1a03d2e0a
9 changed files with 365 additions and 0 deletions

View File

@ -75,6 +75,31 @@
name: zun-tempest-multinode-docker-sql
parent: zun-tempest-base-multinode
- job:
name: zun-fullstack
parent: devstack
required-projects:
- openstack/devstack
- openstack/devstack-plugin-container
- openstack/kuryr-libnetwork
- openstack/zun
- openstack/zun-tempest-plugin
- openstack/python-zunclient
run: playbooks/fullstack/run.yaml
irrelevant-files:
- ^.*\.rst$
- ^doc/.*$
- ^api-ref/.*$
vars:
tox_envlist: fullstack
tox_install_siblings: false
devstack_localrc:
USE_PYTHON3: true
devstack_plugins:
zun: https://opendev.org/openstack/zun
kuryr-libnetwork: https://opendev.org/openstack/kuryr-libnetwork
devstack-plugin-container: https://opendev.org/openstack/devstack-plugin-container
- project:
templates:
- check-requirements
@ -90,6 +115,8 @@
- zun-tempest-docker-sql
- zun-tempest-py3-docker-sql
- zun-tempest-multinode-docker-sql
- zun-fullstack:
voting: false
- kolla-ansible-ubuntu-source-zun:
voting: false
gate:

View File

@ -121,6 +121,7 @@ python-keystoneclient==3.15.0
python-mimeparse==1.6.0
python-neutronclient==6.7.0
python-subunit==1.2.0
python-zunclient==3.3.0
pytz==2018.3
PyYAML==3.12
reno==2.5.0

View File

@ -0,0 +1,5 @@
- hosts: all
roles:
- orchestrate-devstack
- ensure-tox
- tox

View File

@ -19,3 +19,4 @@ testscenarios>=0.4 # Apache-2.0/BSD
testtools>=2.2.0 # MIT
stestr>=1.0.0 # Apache-2.0
Pygments>=2.2.0 # BSD license
python-zunclient>=3.3.0 # Apache-2.0

View File

@ -144,3 +144,11 @@ deps =
# separately, outside of the requirements files.
deps = bindep
commands = bindep test
[testenv:fullstack]
basepython = python3
setenv = {[testenv]setenv}
deps = {[testenv]deps}
commands =
stestr --test-path=./zun/tests/fullstack run {posargs}
stestr slowest

View File

View File

@ -0,0 +1,77 @@
# 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 docker
from oslo_log import log as logging
import zun.conf
from zun.tests import base
from zun.tests.fullstack import utils
CONF = zun.conf.CONF
LOG = logging.getLogger(__name__)
class BaseFullStackTestCase(base.TestCase):
def setUp(self):
super(BaseFullStackTestCase, self).setUp()
self.docker = docker.APIClient(base_url='tcp://0.0.0.0:2375')
try:
self.zun = utils.get_zun_client_from_env()
except Exception as e:
# We may missing or didn't source configured openrc file.
message = ("Missing environment variable %s in your local."
"Please add it and also check other missing "
"environment variables. After that please source "
"the openrc file. "
"Trying credentials from DevStack cloud.yaml ...")
LOG.warning(message, e.args[0])
self.zun = utils.get_zun_client_from_creds()
def ensure_container_deleted(self, container_id):
def is_container_deleted():
containers = self.zun.containers.list()
container_ids = [c.uuid for c in containers]
if container_id in container_ids:
return False
else:
return True
utils.wait_for_condition(is_container_deleted)
def ensure_container_in_desired_state(self, container_id, status):
def is_container_in_desired_state():
c = self.zun.containers.get(container_id)
if c.status == status:
return True
else:
return False
utils.wait_for_condition(is_container_in_desired_state, timeout=300)
def _get_container_in_docker(self, container):
return self.docker.inspect_container('zun-' + container.uuid)
def _get_state_in_docker(self, container):
container = self.docker.inspect_container('zun-' + container.uuid)
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'

View File

@ -0,0 +1,168 @@
# 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 docker
from tempest.lib import decorators
from zun.tests.fullstack import base
from zun.tests.fullstack import utils
class TestContainer(base.BaseFullStackTestCase):
def setUp(self):
super(TestContainer, self).setUp()
self.containers = []
def tearDown(self):
containers = self.zun.containers.list()
for c in containers:
if c.uuid in self.containers:
self.zun.containers.delete(c.uuid, stop=True)
self.ensure_container_deleted(c.uuid)
super(TestContainer, self).tearDown()
@decorators.idempotent_id('039fc590-7711-4b87-86bf-fe9048c3feb9')
def test_run_container(self):
self._run_container()
def _run_container(self, **kwargs):
if not kwargs.get('image'):
kwargs['image'] = 'cirros:latest'
kwargs['command'] = ['sleep', '100000']
kwargs.setdefault('cpu', 0.1)
kwargs.setdefault('memory', 128)
container = self.zun.containers.run(**kwargs)
self.containers.append(container.uuid)
# Wait for container to started
self.ensure_container_in_desired_state(container.uuid, 'Running')
# Assert the container is started
container = self.zun.containers.get(container.uuid)
self.assertEqual('Running', container.status)
self.assertEqual('Running', self._get_state_in_docker(container))
return container
def _create_container(self, **kwargs):
if not kwargs.get('image'):
kwargs['image'] = 'cirros:latest'
kwargs['command'] = ['sleep', '100000']
kwargs.setdefault('cpu', 0.1)
kwargs.setdefault('memory', 128)
container = self.zun.containers.create(**kwargs)
self.containers.append(container.uuid)
# Wait for container to finish creation
self.ensure_container_in_desired_state(container.uuid, 'Created')
# Assert the container is created
container = self.zun.containers.get(container.uuid)
self.assertEqual('Created', container.status)
self.assertEqual('Created', self._get_state_in_docker(container))
return container
@decorators.idempotent_id('8c6f0844-1a5c-4bf4-81d5-38dccb2c2b25')
def test_delete_container(self):
container = self._create_container()
self.zun.containers.delete(container.uuid)
self.ensure_container_deleted(container.uuid)
self.assertRaises(docker.errors.NotFound,
self._get_container_in_docker, container)
@decorators.idempotent_id('6f7a4d0f-273a-4321-ba14-246c6ea387a1')
def test_run_container_with_environment(self):
container = self._run_container(
environment={'key1': 'env1', 'key2': 'env2'})
docker_container = self._get_container_in_docker(container)
env = docker_container['Config']['Env']
self.assertTrue('key1=env1' in env)
self.assertTrue('key2=env2' in env)
@decorators.idempotent_id('25e19899-d450-4d6b-9dbd-160f9c557877')
def test_run_container_with_labels(self):
container = self._run_container(
labels={'key1': 'label1', 'key2': 'label2'})
docker_container = self._get_container_in_docker(container)
labels = docker_container['Config']['Labels']
self.assertEqual({'key1': 'label1', 'key2': 'label2'}, labels)
@decorators.idempotent_id('8a920b08-32df-448e-ab53-b640611ac769')
def test_run_container_with_restart_policy(self):
container = self._run_container(restart_policy={
'Name': 'on-failure', 'MaximumRetryCount': 2})
docker_container = self._get_container_in_docker(container)
policy = docker_container['HostConfig']['RestartPolicy']
self.assertEqual('on-failure', policy['Name'])
self.assertEqual(2, policy['MaximumRetryCount'])
@decorators.idempotent_id('6b3229af-c8c8-4a11-8b22-981e2ff63b51')
def test_run_container_with_interactive(self):
container = self._run_container(interactive=True)
docker_container = self._get_container_in_docker(container)
tty = docker_container['Config']['Tty']
stdin_open = docker_container['Config']['OpenStdin']
self.assertIs(True, tty)
self.assertIs(True, stdin_open)
@decorators.idempotent_id('f189624c-c9b8-4181-9485-2b5cacb633bc')
def test_reboot_container(self):
container = self._run_container()
docker_container = self._get_container_in_docker(container)
pid = docker_container['State']['Pid']
self.zun.containers.restart(container.uuid, timeout=10)
self._ensure_container_pid_changed(container, pid)
self.assertEqual('Running', self._get_state_in_docker(container))
# assert pid is changed
docker_container = self._get_container_in_docker(container)
self.assertNotEqual(pid, docker_container['State']['Pid'])
def _ensure_container_pid_changed(self, container, pid):
def is_pid_changed():
docker_container = self._get_container_in_docker(container)
new_pid = docker_container['State']['Pid']
if pid != new_pid:
return True
else:
return False
utils.wait_for_condition(is_pid_changed)
@decorators.idempotent_id('44e28cdc-6b33-4394-b7cb-3b2f36a2839a')
def test_update_container(self):
container = self._run_container(cpu=0.1, memory=100)
self.assertEqual('100', container.memory)
self.assertEqual(0.1, container.cpu)
docker_container = self._get_container_in_docker(container)
self._assert_resource_constraints(docker_container, cpu=0.1,
memory=100)
container = self.zun.containers.update(container.uuid, cpu=0.2,
memory=200)
self.assertEqual('200', container.memory)
self.assertEqual(0.2, container.cpu)
docker_container = self._get_container_in_docker(container)
self._assert_resource_constraints(docker_container, cpu=0.2,
memory=200)
def _assert_resource_constraints(self, docker_container, cpu, memory):
cpu_shares = docker_container['HostConfig']['CpuShares']
self.assertEqual(int(cpu * 1024), cpu_shares)
docker_memory = docker_container['HostConfig']['Memory']
self.assertEqual(memory * 1024 * 1024, docker_memory)

View File

@ -0,0 +1,78 @@
# 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
import time
from keystoneauth1 import identity
from keystoneauth1 import session as ks
import os_client_config
from zunclient import client
def get_zun_client_from_env():
# We should catch KeyError exception with the purpose of
# source or configure openrc file.
auth_url = os.environ['OS_AUTH_URL']
username = os.environ['OS_USERNAME']
password = os.environ['OS_PASSWORD']
project_name = os.environ['OS_PROJECT_NAME']
# Either project(user)_domain_name or project(user)_domain_id
# would be acceptable.
project_domain_name = os.environ.get("OS_PROJECT_DOMAIN_NAME")
project_domain_id = os.environ.get("OS_PROJECT_DOMAIN_ID")
user_domain_name = os.environ.get("OS_USER_DOMAIN_NAME")
user_domain_id = os.environ.get("OS_USER_DOMAIN_ID")
auth = identity.Password(auth_url=auth_url,
username=username,
password=password,
project_name=project_name,
project_domain_id=project_domain_id,
project_domain_name=project_domain_name,
user_domain_id=user_domain_id,
user_domain_name=user_domain_name)
session = ks.Session(auth=auth)
return client.Client('1.latest', session=session)
def _get_cloud_config_auth_data(cloud='devstack-admin'):
"""Retrieves Keystone auth data to run tests
Credentials are either read via os-client-config from the environment
or from a config file ('clouds.yaml'). Environment variables override
those from the config file.
devstack produces a clouds.yaml with two named clouds - one named
'devstack' which has user privs and one named 'devstack-admin' which
has admin privs. This function will default to getting the devstack-admin
cloud as that is the current expected behavior.
"""
cloud_config = os_client_config.OpenStackConfig().get_one_cloud(cloud)
return cloud_config.get_auth(), cloud_config.get_session()
def get_zun_client_from_creds():
auth_plugin, session = _get_cloud_config_auth_data()
return client.Client('1.latest', session=session, auth=auth_plugin)
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))