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
|
name: zun-tempest-multinode-docker-sql
|
||||||
parent: zun-tempest-base-multinode
|
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:
|
- project:
|
||||||
templates:
|
templates:
|
||||||
- check-requirements
|
- check-requirements
|
||||||
@ -90,6 +115,8 @@
|
|||||||
- zun-tempest-docker-sql
|
- zun-tempest-docker-sql
|
||||||
- zun-tempest-py3-docker-sql
|
- zun-tempest-py3-docker-sql
|
||||||
- zun-tempest-multinode-docker-sql
|
- zun-tempest-multinode-docker-sql
|
||||||
|
- zun-fullstack:
|
||||||
|
voting: false
|
||||||
- kolla-ansible-ubuntu-source-zun:
|
- kolla-ansible-ubuntu-source-zun:
|
||||||
voting: false
|
voting: false
|
||||||
gate:
|
gate:
|
||||||
|
@ -121,6 +121,7 @@ python-keystoneclient==3.15.0
|
|||||||
python-mimeparse==1.6.0
|
python-mimeparse==1.6.0
|
||||||
python-neutronclient==6.7.0
|
python-neutronclient==6.7.0
|
||||||
python-subunit==1.2.0
|
python-subunit==1.2.0
|
||||||
|
python-zunclient==3.3.0
|
||||||
pytz==2018.3
|
pytz==2018.3
|
||||||
PyYAML==3.12
|
PyYAML==3.12
|
||||||
reno==2.5.0
|
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
|
testtools>=2.2.0 # MIT
|
||||||
stestr>=1.0.0 # Apache-2.0
|
stestr>=1.0.0 # Apache-2.0
|
||||||
Pygments>=2.2.0 # BSD license
|
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.
|
# separately, outside of the requirements files.
|
||||||
deps = bindep
|
deps = bindep
|
||||||
commands = bindep test
|
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