blob: d340cbc6efa5a9a18a75bbfb7149122a4f40a8a9 [file] [log] [blame]
# Copyright 2018 Mirantis, Inc.
#
# 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
import os
from devops.helpers import helpers
from tcp_tests import logger
from tcp_tests import settings
LOG = logger.logger
TEMPEST_CFG_DIR = '/tmp/test'
class RuntestManager(object):
"""Helper manager for execution tempest via runtest-formula"""
image_name = settings.TEMPEST_IMAGE
image_version = settings.TEMPEST_IMAGE_VERSION
container_name = 'run-tempest-ci'
master_host = "cfg01"
class_name = "runtest"
run_cmd = '/bin/bash -c "run-tempest"'
def __init__(self, config, underlay, salt_api, cluster_name,
domain_name, tempest_threads,
tempest_pattern=settings.TEMPEST_PATTERN,
tempest_extra_args=settings.TEMPEST_EXTRA_ARGS,
run_cmd=None):
self.__config = config
self.underlay = underlay
self.__salt_api = salt_api
self.cluster_name = cluster_name
self.domain_name = domain_name
self.tempest_threads = tempest_threads
self.tempest_pattern = tempest_pattern
self.tempest_extra_args = tempest_extra_args
self.run_cmd = run_cmd or self.run_cmd
self.master_name = self.underlay.get_target_node_names(
self.master_host)[0]
self.master_minion = self.underlay.get_target_minion_ids(
self.master_host)[0]
self.__target_name = None
self.__target_minion = None
@property
def salt_api(self):
return self.__salt_api
@property
def target_name(self):
if not self.__target_name:
target_host = self.__salt_api.get_single_pillar(
tgt=self.master_minion,
pillar="runtest:tempest:test_target")
if target_host[-1] == "*":
target_host = target_host[:-1]
self.__target_name = self.underlay.get_target_node_names(
target_host)[0]
return self.__target_name
@property
def target_minion(self):
if not self.__target_minion:
target_host = self.__salt_api.get_single_pillar(
tgt=self.master_minion,
pillar="runtest:tempest:test_target")
if target_host[-1] == "*":
target_host = target_host[:-1]
self.__target_minion = self.underlay.get_target_minion_ids(
target_host)[0]
return self.__target_minion
def fetch_arficats(
self,
username=None,
file_format='xml',
report_pattern="report_*",
report_dir=TEMPEST_CFG_DIR):
with self.underlay.remote(node_name=self.target_name,
username=None) as tgt:
result = tgt.execute('find {} -name "{}.{}"'.format(
report_dir, report_pattern, file_format))
LOG.debug("Find result {0}".format(result))
assert len(result['stdout']) > 0, ('No report found, please check'
' if test run was successful.')
report = result['stdout'][0].rstrip()
LOG.debug("Found files {0}".format(report))
tgt.download(
destination=report, # noqa
target=os.getcwd())
def save_runtime_logs(self, logs=None, inspect=None):
if logs:
with open("{path}/{target}_tempest_run.log".format(
path=settings.LOGS_DIR,
target=self.target_name), 'w') as f:
LOG.info("Save tempest console log")
container_log = logs
f.write(container_log.encode('ascii', 'ignore'))
if inspect:
with open("{path}/{target}_tempest_container_info.json.log".format(
path=settings.LOGS_DIR,
target=self.target_name), 'w') as f:
LOG.info("Save tempest container inspect data")
container_inspect = json.dumps(inspect,
indent=4, sort_keys=True)
f.write(container_inspect)
def prepare(self, pipeline=False):
salt_call_cmd = "salt-call -l info --hard-crash --state-output=mixed "
barbican_integration = self.__salt_api.get_single_pillar(
tgt="ctl01*",
pillar="_param:barbican_integration_enabled")
if self.__salt_api.local('I@opencontrail:compute:enabled:true',
'match.pillar',
'opencontrail:compute:enabled:true'
).get('return', [{}]) != [{}]:
contrail_integration = True
else:
contrail_integration = False
LOG.info("Barbican integration {0}".format(barbican_integration))
LOG.info("Opencontrail integration {0}".format(contrail_integration))
commands = [
{
'description': ("Enable packets forwarding"),
'node_name': self.target_name,
'cmd': ("set -ex;" +
"iptables --policy FORWARD ACCEPT")
}
]
if not pipeline:
commands.append(
{
'description': "Generate config for Tempest",
'node_name': self.master_name,
'cmd': ("set -ex;" +
"salt-run state.orchestrate " +
"runtest.orchestrate.tempest")
}
)
if contrail_integration:
vsrx_router = self.__salt_api.get_single_pillar(
tgt="I@opencontrail:control:role:primary",
pillar="_param:opencontrail_router02_address")
target_asn = '["target:64512:10000"]'
public_network = "192.168.200.0"
contrail_commands = [
{
'description': "Iproute to vsrx router",
'node_name': self.target_name,
'cmd': ("set -ex; ip route replace " +
public_network + "/24 via " + vsrx_router)},
{
'description': "Iptables for public",
'node_name': self.target_name,
'cmd': ("set -ex; iptables -t nat -A POSTROUTING -s " +
public_network + "/24 ! -d " + public_network +
"/24 -j MASQUERADE")},
{
'description': "Run skipped in pipelines neutron.client",
'node_name': self.target_name,
'cmd': ("set -ex;" +
"salt -C 'I@neutron:client and cfg*' " +
"state.sls neutron.client|true")},
{
'description': "Create public network with target",
'node_name': self.target_name,
'cmd': ("set -ex;" +
"salt -C 'I@opencontrail:control:role:primary' " +
"contrail.virtual_network_create public " +
"route_target_list='" + target_asn + "'")}
]
commands = contrail_commands + commands
if barbican_integration:
commands.append({
'description': "Configure barbican",
'node_name': self.master_name,
'cmd': ("set -ex;" +
salt_call_cmd +
" state.sls barbican.client && " +
salt_call_cmd +
" state.sls runtest.test_accounts" +
salt_call_cmd +
" state.sls runtest.barbican_sign_image")},
)
self.__salt_api.execute_commands(commands=commands,
label="Prepare for Tempest")
def run_tempest(self, timeout=600):
tgt = self.target_minion
image_nameversion = "{}:{}".format(self.image_name, self.image_version)
docker_args = (
" -t"
" --net host"
" --name {container_name}"
" -e ARGS=\"-r {tempest_pattern}"
" -w {tempest_threads} {tempest_extra_args}\""
" -v {cfg_dir}/tempest.conf:/etc/tempest/tempest.conf"
" -v /tmp/:/tmp/"
" -v {cfg_dir}:/root/tempest"
" -v /etc/ssl/certs/:/etc/ssl/certs/"
" -d"
" {image_nameversion} {run_cmd}"
.format(
container_name=self.container_name,
image_nameversion=image_nameversion,
tempest_pattern=self.tempest_pattern,
tempest_threads=self.tempest_threads,
tempest_extra_args=self.tempest_extra_args,
cfg_dir=TEMPEST_CFG_DIR,
run_cmd=self.run_cmd,
))
commands = [
{
'description': "Run Tempest tests {0}".format(
image_nameversion),
'node_name': self.target_name,
'cmd': ("set -ex;" +
" docker rm --force {container_name} || true;"
" docker run {docker_args}"
.format(container_name=self.container_name,
docker_args=docker_args)),
'timeout': timeout},
]
self.__salt_api.execute_commands(commands=commands,
label="Run Tempest tests")
def simplify_salt_api_return(api_return, only_first_match=True):
"""
Salt API always returns a dict with one key as 'return'
and value as a list with dict. For example:
For single node:
api.local('cfg01*', 'test.ping', expr_form='compound')
{u'return':[{u'cfg01.cookied-cicd-queens-dvr-sl.local':True}]}
For multinode:
api.local('ctl*', 'test.ping', expr_form='compound')
{u'return': [{u'ctl01.cookied-cicd-queens-dvr-sl.local': True,
u'ctl02.cookied-cicd-queens-dvr-sl.local': True,
u'ctl03.cookied-cicd-queens-dvr-sl.local': True}]}
When wrong function is given:
api.local('ctl01*', 'wrong_func', expr_form='compound')
{u'return': [{u'ctl01.cookied-cicd-queens-dvr-sl.local':
u"'wrong_func' is not available."}]}
Empty return:
api.local('wrong_target', 'test.ping', expr_form='compound')
{u'return': [{}]}
"""
if api_return.get('return', [{}]) != [{}]:
api_return = api_return['return'][0]
if only_first_match:
api_return = next(api_return.iteritems())[1]
return api_return
else:
LOG.info('''Salt api returns empty result: [{}]''')
return False
def wait_status(s):
inspect_res = self.salt_api.local(tgt, 'dockerng.inspect',
self.container_name)
inspect = simplify_salt_api_return(inspect_res)
if inspect:
status = inspect['State']['Status']
return status.lower() == s.lower()
return False
if wait_status('running'):
helpers.wait(lambda: wait_status('exited'),
timeout=timeout,
timeout_msg=('Tempest run didnt finished '
'in {}'.format(timeout)))
inspect_res = self.salt_api.local(tgt, 'dockerng.inspect',
self.container_name)
inspect = simplify_salt_api_return(inspect_res)
logs_res = self.salt_api.local(tgt, 'dockerng.logs',
self.container_name)
logs = simplify_salt_api_return(logs_res)
rm_res = self.salt_api.local(tgt, 'dockerng.rm',
self.container_name)
LOG.info("Tempest container was removed: {}".format(
json.dumps(rm_res, indent=4)))
else:
inspect_res = self.salt_api.local(tgt, 'dockerng.inspect',
self.container_name)
inspect = simplify_salt_api_return(inspect_res)
if inspect:
status = inspect['State']['Status']
LOG.info("Container is not in RUNNING state. "
"Current container status is {}".format(status))
logs_res = self.salt_api.local(tgt,
'dockerng.logs',
self.container_name)
logs = simplify_salt_api_return(logs_res)
else:
LOG.info("dockerng returns unexpected"
" result: {}".format(inspect_res))
logs = None
inspect = None
return {'inspect': inspect,
'logs': logs}
def prepare_and_run_tempest(self, username='root'):
"""
Run tempest tests
"""
tempest_timeout = settings.TEMPEST_TIMEOUT
self.prepare()
test_res = self.run_tempest(tempest_timeout)
self.fetch_arficats(username=username)
self.save_runtime_logs(**test_res)