Introduce fullstack tests
Change-Id: I4bd1851f608ee657a4f391961e4cff0fac9a1fe4
This commit is contained in:
parent
6e307cc274
commit
9dcdbecbdd
27
.zuul.yaml
27
.zuul.yaml
@ -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:
|
||||
|
@ -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
|
||||
|
5
playbooks/fullstack/run.yaml
Normal file
5
playbooks/fullstack/run.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
- hosts: all
|
||||
roles:
|
||||
- orchestrate-devstack
|
||||
- ensure-tox
|
||||
- tox
|
@ -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
|
||||
|
8
tox.ini
8
tox.ini
@ -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
|
||||
|
0
zun/tests/fullstack/__init__.py
Normal file
0
zun/tests/fullstack/__init__.py
Normal file
77
zun/tests/fullstack/base.py
Normal file
77
zun/tests/fullstack/base.py
Normal 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'
|
168
zun/tests/fullstack/test_containers.py
Normal file
168
zun/tests/fullstack/test_containers.py
Normal 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)
|
78
zun/tests/fullstack/utils.py
Normal file
78
zun/tests/fullstack/utils.py
Normal 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))
|
Loading…
Reference in New Issue
Block a user