Remove intree mistral tempest plugin

* https://review.openstack.org/524869 moves the intree mistral
  tempest plugin to a new home openstack/mistral-tempest-plugin
  let's use it.

* It also removed intree mistral tempest plugin as well as fix the
  jobs to use the same.

Change-Id: I28ba8408452637aa093b343441f3715dc4754a3f
This commit is contained in:
Chandan Kumar 2017-12-10 15:25:48 +05:30 committed by Renat Akhmerov
parent c6e086d9b2
commit 4f57c90d69
48 changed files with 46 additions and 3735 deletions

View File

@ -13,6 +13,7 @@
- openstack/python-mistralclient
- openstack/zaqar
- openstack/python-zaqarclient
- openstack/mistral-tempest-plugin
- job:
name: mistral-devstack-dsvm-kombu
@ -29,6 +30,7 @@
- openstack/python-mistralclient
- openstack/zaqar
- openstack/python-zaqarclient
- openstack/mistral-tempest-plugin
- job:
name: mistral-devstack-dsvm-non-apache
@ -45,6 +47,7 @@
- openstack/python-mistralclient
- openstack/zaqar
- openstack/python-zaqarclient
- openstack/mistral-tempest-plugin
- job:
name: mistral-docker-buildimage

View File

@ -30,5 +30,4 @@ sudo cp $BASE/new/tempest/etc/logging.conf.sample $BASE/new/tempest/etc/logging.
(cd $BASE/new/mistral/; sudo python setup.py install)
export TOX_TESTENV_PASSENV=ZUUL_PROJECT
(cd $BASE/new/tempest/; sudo -E testr init)
(cd $BASE/new/tempest/; sudo -E tox -eall-plugin mistral)
(cd $BASE/new/tempest/; sudo -E tox -evenv-tempest -- tempest run -r mistral)

View File

@ -1,25 +0,0 @@
==============================
Tempest Integration of Mistral
==============================
This directory contains Tempest tests to cover the mistral project.
To list all Mistral tempest cases, go to tempest directory, then run::
$ testr list-tests mistral
To run only these tests in tempest, go to tempest directory, then run::
$ ./run_tempest.sh -N -- mistral
To run a single test case, go to tempest directory, then run with test case name, e.g.::
$ ./run_tempest.sh -N -- mistral_tempest_tests.tests.api.v2.test_mistral_basic_v2.WorkbookTestsV2.test_get_workbook
Alternatively, to run mistral tempest plugin tests using tox, go to tempest directory, then run::
$ tox -eall-plugin mistral
And, to run a specific test::
$ tox -eall-plugin mistral_tempest_tests.tests.api.v2.test_mistral_basic_v2.WorkbookTestsV2.test_get_workbook

View File

@ -1,21 +0,0 @@
# Copyright 2016 Catalyst IT Limited
# All Rights Reserved.
#
# 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('mistral',
default=True,
help="Whether or not Mistral is expected to be"
"available")

View File

@ -1,37 +0,0 @@
# Copyright 2015
# All Rights Reserved.
#
# 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 mistral_tempest_tests import config as mistral_config
class MistralTempestPlugin(plugins.TempestPlugin):
def load_tests(self):
base_path = os.path.split(os.path.dirname(
os.path.abspath(__file__)))[0]
test_dir = "mistral_tempest_tests/tests/"
full_test_dir = os.path.join(base_path, test_dir)
return full_test_dir, base_path
def register_opts(self, conf):
conf.register_opt(mistral_config.service_option,
group='service_available')
def get_opt_lists(self):
return [('service_available', [mistral_config.service_option])]

View File

@ -1,132 +0,0 @@
# Copyright 2013 Mirantis, Inc. All Rights Reserved.
# Copyright 2016 NEC Corporation. All rights reserved.
#
# 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
import time
from tempest import config
from tempest.lib import auth
from tempest.lib.common import rest_client
from tempest.lib import exceptions
CONF = config.CONF
def get_resource(path):
main_package = 'mistral_tempest_tests'
dir_path = __file__[0:__file__.find(main_package)]
return open(dir_path +
'mistral_tempest_tests/tests/resources/' +
path).read()
def find_items(items, **props):
def _matches(item, **props):
for prop_name, prop_val in props.items():
if item[prop_name] != prop_val:
return False
return True
filtered = list([item for item in items if _matches(item, **props)])
if len(filtered) == 1:
return filtered[0]
return filtered
class MistralClientBase(rest_client.RestClient):
def __init__(self, auth_provider, service_type):
super(MistralClientBase, self).__init__(
auth_provider=auth_provider,
service=service_type,
region=CONF.identity.region,
disable_ssl_certificate_validation=True
)
if service_type not in ('workflow', 'workflowv2'):
msg = "Invalid parameter 'service_type'. "
raise exceptions.UnprocessableEntity(msg)
self.endpoint_url = 'publicURL'
self.workbooks = []
self.executions = []
self.workflows = []
self.triggers = []
self.actions = []
self.action_executions = []
self.event_triggers = []
def get_list_obj(self, url_path):
resp, body = self.get(url_path)
return resp, json.loads(body)
def delete_obj(self, obj, name):
return self.delete('{obj}/{name}'.format(obj=obj, name=name))
def get_object(self, obj, id):
resp, body = self.get('{obj}/{id}'.format(obj=obj, id=id))
return resp, json.loads(body)
def wait_execution_success(self, ex_body, timeout=180, url='executions'):
return self.wait_execution(ex_body, timeout=timeout, url=url)
def wait_execution(self, ex_body, timeout=180, url='executions',
target_state='SUCCESS'):
start_time = time.time()
expected_states = [target_state, 'RUNNING']
while ex_body['state'] != target_state:
if time.time() - start_time > timeout:
msg = ("Execution exceeds timeout {0} "
"to change state to {1}. "
"Execution: {2}".format(timeout, target_state, ex_body))
raise exceptions.TimeoutException(msg)
_, ex_body = self.get_object(url, ex_body['id'])
if ex_body['state'] not in expected_states:
msg = ("Execution state %s is not in expected "
"states: %s" % (ex_body['state'], expected_states))
raise exceptions.TempestException(msg)
time.sleep(1)
return ex_body
class AuthProv(auth.KeystoneV2AuthProvider):
def __init__(self):
self.alt_part = None
def auth_request(self, method, url, *args, **kwargs):
req_url, headers, body = super(AuthProv, self).auth_request(
method, url, *args, **kwargs)
return 'http://localhost:8989/{0}/{1}'.format(
os.environ['VERSION'], url), headers, body
def get_auth(self):
return 'mock_str', 'mock_str'
def base_url(self, *args, **kwargs):
return ''

View File

@ -1,227 +0,0 @@
# Copyright 2016 NEC Corporation. All rights reserved.
#
# 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
from oslo_utils import uuidutils
from tempest import config
from mistral_tempest_tests.services import base
CONF = config.CONF
class MistralClientV2(base.MistralClientBase):
def post_request(self, url_path, file_name):
headers = {"headers": "Content-Type:text/plain"}
return self.post(
url_path,
base.get_resource(file_name),
headers=headers
)
def get_request(self, url_path):
headers = {"headers": "Content-Type:application/json"}
return self.get(url_path, headers=headers)
def post_json(self, url_path, obj, extra_headers={}):
headers = {"Content-Type": "application/json"}
headers = dict(headers, **extra_headers)
return self.post(url_path, json.dumps(obj), headers=headers)
def update_request(self, url_path, file_name):
headers = {"headers": "Content-Type:text/plain"}
resp, body = self.put(
url_path,
base.get_resource(file_name),
headers=headers
)
return resp, json.loads(body)
def get_definition(self, item, name):
resp, body = self.get("%s/%s" % (item, name))
return resp, json.loads(body)['definition']
def create_workbook(self, yaml_file):
resp, body = self.post_request('workbooks', yaml_file)
wb_name = json.loads(body)['name']
self.workbooks.append(wb_name)
_, wfs = self.get_list_obj('workflows')
for wf in wfs['workflows']:
if wf['name'].startswith(wb_name):
self.workflows.append(wf['name'])
return resp, json.loads(body)
def create_workflow(self, yaml_file, scope=None, namespace=None):
url_path = 'workflows?'
if scope:
url_path += 'scope=public&'
if namespace:
url_path += 'namespace=' + namespace
resp, body = self.post_request(url_path, yaml_file)
for wf in json.loads(body)['workflows']:
identifier = wf['id'] if namespace else wf['name']
self.workflows.append(identifier)
return resp, json.loads(body)
def get_workflow(self, wf_identifier, namespace=None):
url_path = 'workflows/' + wf_identifier
if namespace:
url_path += 'namespace=' + namespace
resp, body = self.get_request(url_path)
return resp, json.loads(body)
def update_workflow(self, file_name, namespace=None):
url_path = "workflows?"
if namespace:
url_path += 'namespace=' + namespace
return self.update_request(url_path, file_name=file_name)
def get_action_execution(self, action_execution_id):
return self.get('action_executions/%s' % action_execution_id)
def get_action_executions(self, task_id=None):
url_path = 'action_executions'
if task_id:
url_path += '?task_execution_id=%s' % task_id
return self.get_list_obj(url_path)
def create_execution(self, identifier, wf_namespace=None, wf_input=None,
params=None):
if uuidutils.is_uuid_like(identifier):
body = {"workflow_id": "%s" % identifier}
else:
body = {"workflow_name": "%s" % identifier}
if wf_namespace:
body.update({'workflow_namespace': wf_namespace})
if wf_input:
body.update({'input': json.dumps(wf_input)})
if params:
body.update({'params': json.dumps(params)})
resp, body = self.post('executions', json.dumps(body))
self.executions.append(json.loads(body)['id'])
return resp, json.loads(body)
def update_execution(self, execution_id, put_body):
resp, body = self.put('executions/%s' % execution_id, put_body)
return resp, json.loads(body)
def get_execution(self, execution_id):
return self.get('executions/%s' % execution_id)
def get_executions(self, task_id):
url_path = 'executions'
if task_id:
url_path += '?task_execution_id=%s' % task_id
return self.get_list_obj(url_path)
def get_tasks(self, execution_id=None):
url_path = 'tasks'
if execution_id:
url_path += '?workflow_execution_id=%s' % execution_id
return self.get_list_obj(url_path)
def create_cron_trigger(self, name, wf_name, wf_input=None, pattern=None,
first_time=None, count=None):
post_body = {
'name': name,
'workflow_name': wf_name,
'pattern': pattern,
'remaining_executions': count,
'first_execution_time': first_time
}
if wf_input:
post_body.update({'workflow_input': json.dumps(wf_input)})
rest, body = self.post('cron_triggers', json.dumps(post_body))
self.triggers.append(name)
return rest, json.loads(body)
def create_action(self, yaml_file):
resp, body = self.post_request('actions', yaml_file)
self.actions.extend(
[action['name'] for action in json.loads(body)['actions']])
return resp, json.loads(body)
def get_wf_tasks(self, wf_name):
all_tasks = self.get_list_obj('tasks')[1]['tasks']
return [t for t in all_tasks if t['workflow_name'] == wf_name]
def create_action_execution(self, request_body, extra_headers={}):
resp, body = self.post_json('action_executions', request_body,
extra_headers)
params = json.loads(request_body.get('params', '{}'))
if params.get('save_result', False):
self.action_executions.append(json.loads(body)['id'])
return resp, json.loads(body)
def create_event_trigger(self, wf_id, exchange, topic, event, name='',
wf_input=None, wf_params=None):
post_body = {
'workflow_id': wf_id,
'exchange': exchange,
'topic': topic,
'event': event,
'name': name
}
if wf_input:
post_body.update({'workflow_input': json.dumps(wf_input)})
if wf_params:
post_body.update({'workflow_params': json.dumps(wf_params)})
rest, body = self.post('event_triggers', json.dumps(post_body))
event_trigger = json.loads(body)
self.event_triggers.append(event_trigger['id'])
return rest, event_trigger

View File

@ -1,254 +0,0 @@
# Copyright 2016 NEC Corporation. All rights reserved.
#
# 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 six
from oslo_log import log as logging
from tempest.lib import decorators
from tempest.lib import exceptions
from mistral_tempest_tests.tests import base
LOG = logging.getLogger(__name__)
class ActionExecutionTestsV2(base.TestCase):
_service = 'workflowv2'
@classmethod
def resource_setup(cls):
super(ActionExecutionTestsV2, cls).resource_setup()
cls.client.create_action_execution(
{
'name': 'std.echo',
'input': '{"output": "Hello, Mistral!"}'
}
)
@classmethod
def resource_cleanup(cls):
for action_ex in cls.client.action_executions:
try:
cls.client.delete_obj('action_executions', action_ex)
except Exception as e:
LOG.exception(
'Exception raised when deleting '
'action_executions %s, error message: %s.',
action_ex, six.text_type(e)
)
cls.client.action_executions = []
super(ActionExecutionTestsV2, cls).resource_cleanup()
@decorators.attr(type='sanity')
@decorators.idempotent_id('a72603bd-5d49-4d92-9747-8da6322e867d')
def test_run_action_execution(self):
resp, body = self.client.create_action_execution(
{
'name': 'std.echo',
'input': '{"output": "Hello, Mistral!"}'
}
)
self.assertEqual(201, resp.status)
output = json.loads(body['output'])
self.assertDictEqual(
{'result': 'Hello, Mistral!'},
output
)
@decorators.attr(type='sanity')
@decorators.idempotent_id('0623cb62-b20a-45c8-afd9-8da46e1bb3cb')
def test_list_action_executions(self):
resp, body = self.client.get_list_obj('action_executions')
self.assertEqual(200, resp.status)
@decorators.attr(type='sanity')
@decorators.idempotent_id('cd36ea00-7e22-4c3d-90c3-fb441b93cf12')
def test_output_appear_in_response_only_when_needed(self):
resp, body = self.client.get_list_obj('action_executions')
self.assertEqual(200, resp.status)
action_execution = body['action_executions'][0]
self.assertNotIn("output", action_execution)
resp, body = self.client.get_list_obj(
'action_executions?include_output=True'
)
self.assertEqual(200, resp.status)
action_execution = body['action_executions'][0]
self.assertIn("output", action_execution)
resp, body = self.client.get_action_execution(action_execution['id'])
self.assertIn("output", body)
# Test when passing task execution ID
resp, body = self.client.create_workflow('wf_v2.yaml')
wf_name = body['workflows'][0]['name']
self.assertEqual(201, resp.status)
resp, body = self.client.create_execution(wf_name)
self.assertEqual(201, resp.status)
resp, body = self.client.get_list_obj('tasks')
self.assertEqual(200, resp.status)
task_id = body['tasks'][0]['id']
resp, body = self.client.get_list_obj(
'action_executions?include_output=true&task_execution_id=%s' %
task_id
)
self.assertEqual(200, resp.status)
action_execution = body['action_executions'][0]
self.assertIn("output", action_execution)
resp, body = self.client.get_list_obj(
'action_executions?&task_execution_id=%s' %
task_id
)
self.assertEqual(200, resp.status)
action_execution = body['action_executions'][0]
self.assertNotIn("output", action_execution)
@decorators.attr(type='sanity')
@decorators.idempotent_id('dc76aeda-9243-45cf-bfd2-141d3af8b28b')
def test_run_action_std_http(self):
resp, body = self.client.create_action_execution(
{
'name': 'std.http',
'input': '{"url": "http://wiki.openstack.org"}'
}
)
self.assertEqual(201, resp.status)
output = json.loads(body['output'])
self.assertTrue(output['result']['status'] in range(200, 307))
@decorators.attr(type='sanity')
@decorators.idempotent_id('befa9b1c-01a4-41bc-b060-88cb1b147dfb')
def test_run_action_std_http_error(self):
resp, body = self.client.create_action_execution(
{
'name': 'std.http',
'input': '{"url": "http://www.google.ru/not-found-test"}'
}
)
self.assertEqual(201, resp.status)
output = json.loads(body['output'])
self.assertEqual(404, output['result']['status'])
@decorators.attr(type='sanity')
@decorators.related_bug('1667415')
@decorators.idempotent_id('3c73de7a-4af0-4657-90d6-d7ebd3c7da18')
def test_run_action_std_http_non_utf8_response(self):
resp, body = self.client.create_action_execution(
{
'name': 'std.http',
'input':
'{"url": "https://httpbin.org/encoding/utf8"}'
}
)
self.assertEqual(201, resp.status)
output = json.loads(body['output'])
self.assertEqual(200, output['result']['status'])
@decorators.attr(type='sanity')
@decorators.idempotent_id('d98586bf-fdc4-44f6-9837-700d35b5f889')
def test_create_action_execution(self):
resp, body = self.client.create_action_execution(
{
'name': 'std.echo',
'input': '{"output": "Hello, Mistral!"}',
'params': '{"save_result": true}'
}
)
self.assertEqual(201, resp.status)
self.assertEqual('RUNNING', body['state'])
# We must reread action execution in order to get actual
# state and output.
body = self.client.wait_execution_success(
body,
url='action_executions'
)
output = json.loads(body['output'])
self.assertEqual('SUCCESS', body['state'])
self.assertDictEqual(
{'result': 'Hello, Mistral!'},
output
)
@decorators.attr(type='negative')
@decorators.idempotent_id('99f22c17-6fb4-4480-96d3-4a82672916b7')
def test_delete_nonexistent_action_execution(self):
self.assertRaises(
exceptions.NotFound,
self.client.delete_obj,
'action_executions',
'nonexist'
)
@decorators.attr(type='sanity')
@decorators.idempotent_id('2dbd74ba-4950-4c52-8bd3-070d634dcd05')
def test_create_action_execution_sync(self):
resp, body = self.client.create_action_execution(
{
'name': 'std.echo',
'input': '{"output": "Hello Tempest"}'
}
)
self.assertEqual(201, resp.status)
output = json.loads(body['output'])
self.assertEqual("Hello Tempest", output['result'])
@decorators.idempotent_id('9438e195-031c-4502-b216-6d72941ec281')
@decorators.attr(type='sanity')
def test_action_execution_of_workflow_within_namespace(self):
resp, body = self.client.create_workflow('wf_v2.yaml', namespace='abc')
wf_name = "wf"
wf_namespace = body['workflows'][0]['namespace']
self.assertEqual(201, resp.status)
resp, execution = self.client.create_execution(
wf_name,
wf_namespace=wf_namespace
)
self.client.wait_execution_success(execution)
self.assertEqual(201, resp.status)
resp, body = self.client.get_list_obj('tasks')
self.assertEqual(200, resp.status)
task_id = body['tasks'][0]['id']
resp, body = self.client.get_list_obj(
'action_executions?include_output=true&task_execution_id=%s' %
task_id)
self.assertEqual(200, resp.status)
action_execution = body['action_executions'][0]
self.assertEqual(200, resp.status)
action_execution = body['action_executions'][0]
self.assertEqual(wf_namespace, action_execution['workflow_namespace'])

View File

@ -1,414 +0,0 @@
# Copyright 2016 NEC Corporation. All rights reserved.
#
# 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 datetime
from tempest.lib import decorators
from tempest.lib import exceptions
from mistral_tempest_tests.tests import base
from mistral_tempest_tests.tests import utils
class ActionTestsV2(base.TestCase):
_service = 'workflowv2'
def get_field_value(self, body, act_name, field):
return [body['actions'][i][field]
for i in range(len(body['actions']))
if body['actions'][i]['name'] == act_name][0]
def tearDown(self):
for act in self.client.actions:
self.client.delete_obj('actions', act)
self.client.actions = []
super(ActionTestsV2, self).tearDown()
@decorators.attr(type='smoke')
@decorators.idempotent_id('2e1a578a-1c27-409a-96be-84b5c41498cd')
def test_get_list_actions(self):
resp, body = self.client.get_list_obj('actions')
self.assertEqual(200, resp.status)
self.assertNotEmpty(body['actions'])
self.assertNotIn('next', body)
@decorators.attr(type='smoke')
@decorators.idempotent_id('786ee85c-c32d-4ac9-8f45-79ab6bc47ef1')
def test_get_list_actions_with_pagination(self):
resp, body = self.client.get_list_obj(
'actions?limit=1&sort_keys=name&sort_dirs=desc'
)
self.assertEqual(200, resp.status)
self.assertEqual(1, len(body['actions']))
self.assertIn('next', body)
name_1 = body['actions'][0].get('name')
next = body.get('next')
param_dict = utils.get_dict_from_string(
next.split('?')[1],
delimiter='&'
)
# NOTE: 'id' gets included into sort keys automatically with 'desc'
# sorting to avoid pagination looping.
expected_sub_dict = {
'limit': 1,
'sort_keys': 'name,id',
'sort_dirs': 'desc,asc'
}
self.assertDictContainsSubset(expected_sub_dict, param_dict)
# Query again using 'next' hint
url_param = next.split('/')[-1]
resp, body = self.client.get_list_obj(url_param)
self.assertEqual(200, resp.status)
self.assertEqual(1, len(body['actions']))
name_2 = body['actions'][0].get('name')
self.assertGreater(name_1, name_2)
@decorators.attr(type='negative')
@decorators.idempotent_id('5148358e-200f-49c7-8e88-1ddeec61c6a9')
def test_get_list_actions_nonexist_sort_dirs(self):
context = self.assertRaises(
exceptions.BadRequest,
self.client.get_list_obj,
'actions?limit=1&sort_keys=id&sort_dirs=nonexist'
)
self.assertIn(
'Unknown sort direction',
context.resp_body.get('faultstring')
)
@decorators.attr(type='negative')
@decorators.idempotent_id('85482ce8-70f4-47a6-9e80-de1ac22b6412')
def test_get_list_actions_invalid_limit(self):
context = self.assertRaises(
exceptions.BadRequest,
self.client.get_list_obj,
'actions?limit=-1&sort_keys=id&sort_dirs=asc'
)
self.assertIn(
'Limit must be positive',
context.resp_body.get('faultstring')
)
@decorators.attr(type='negative')
@decorators.idempotent_id('a203e75b-2013-422f-b9eb-da4375041058')
def test_get_list_actions_duplicate_sort_keys(self):
context = self.assertRaises(
exceptions.BadRequest,
self.client.get_list_obj,
'actions?limit=1&sort_keys=id,id&sort_dirs=asc,asc'
)
self.assertIn(
'Length of sort_keys must be equal or greater than sort_dirs',
context.resp_body.get('faultstring')
)
@decorators.attr(type='smoke')
@decorators.idempotent_id('9a53af71-8f1e-4ad5-b572-2c4c621715c0')
def test_get_list_actions_equal_to_filter(self):
resp, body = self.client.create_action('action_v2.yaml')
self.assertEqual(201, resp.status)
resp, body = self.client.get_list_obj('actions?is_system=False')
self.assertEqual(200, resp.status)
self.assertNotEmpty(body['actions'])
for act in body['actions']:
self.assertFalse(act['is_system'])
@decorators.attr(type='smoke')
@decorators.idempotent_id('3c3d28ce-9490-41ae-a918-c28f843841e1')
def test_get_list_actions_not_equal_to_filter(self):
resp, body = self.client.create_action('action_v2.yaml')
self.assertEqual(201, resp.status)
resp, body = self.client.get_list_obj('actions?is_system=neq:False')
self.assertEqual(200, resp.status)
self.assertNotEmpty(body['actions'])
for act in body['actions']:
self.assertTrue(act['is_system'])
@decorators.attr(type='smoke')
@decorators.idempotent_id('84823a84-5caa-427d-8a2c-622a1d1893b1')
def test_get_list_actions_in_list_filter(self):
resp, body = self.client.create_action('action_v2.yaml')
self.assertEqual(201, resp.status)
created_acts = [action['name'] for action in body['actions']]
_, body = self.client.get_object('actions', created_acts[0])
time = body['created_at']
resp, body = self.client.get_list_obj(
'actions?created_at=in:' + time.replace(' ', '%20'))
self.assertEqual(200, resp.status)
action_names = [action['name'] for action in body['actions']]
self.assertListEqual(created_acts, action_names)
@decorators.attr(type='smoke')
@decorators.idempotent_id('4b05dfcf-ef39-4032-9528-c8422c7329dd')
def test_get_list_actions_not_in_list_filter(self):
resp, body = self.client.create_action('action_v2.yaml')
self.assertEqual(201, resp.status)
created_acts = [action['name'] for action in body['actions']]
_, body = self.client.get_object('actions', created_acts[0])
time = body['created_at']
resp, body = self.client.get_list_obj(
'actions?created_at=nin:' + time.replace(' ', '%20')
)
self.assertEqual(200, resp.status)
action_names = [action['name'] for action in body['actions']]
for act in created_acts:
self.assertNotIn(act, action_names)
@decorators.attr(type='smoke')
@decorators.idempotent_id('20b3d527-447d-492b-8cb7-ac5e3757d7d5')
def test_get_list_actions_greater_than_filter(self):
time = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
resp, body = self.client.get_list_obj(
'actions?created_at=gt:' + time.replace(' ', '%20')
)
self.assertEqual(200, resp.status)
self.assertEmpty(body['actions'])
@decorators.attr(type='smoke')
@decorators.idempotent_id('7f598dba-f169-47ec-a487-f0ed31484aff')
def test_get_list_actions_greater_than_equal_to_filter(self):
resp, body = self.client.create_action('action_v2.yaml')
self.assertEqual(201, resp.status)
created_acts = [action['name'] for action in body['actions']]
_, body = self.client.get_object('actions', created_acts[0])
time = body['created_at']
resp, body = self.client.get_list_obj(
'actions?created_at=gte:' + time.replace(' ', '%20')
)
actions = [action['name'] for action in body['actions']]
self.assertEqual(200, resp.status)
self.assertIn(created_acts[0], actions)
@decorators.attr(type='smoke')
@decorators.idempotent_id('874fb57d-a762-4dc3-841d-396657510d23')
def test_get_list_actions_less_than_filter(self):
resp, body = self.client.create_action('action_v2.yaml')
self.assertEqual(201, resp.status)
created_acts = [action['name'] for action in body['actions']]
_, body = self.client.get_object('actions', created_acts[0])
time = body['created_at']
resp, body = self.client.get_list_obj(
'actions?created_at=lt:' + time.replace(' ', '%20')
)
actions = [action['name'] for action in body['actions']]
self.assertEqual(200, resp.status)
self.assertNotIn(created_acts[0], actions)
@decorators.attr(type='smoke')
@decorators.idempotent_id('1fda6c31-b0c3-4b78-9f67-b920e1f6c973')
def test_get_list_actions_less_than_equal_to_filter(self):
resp, body = self.client.create_action('action_v2.yaml')
self.assertEqual(201, resp.status)
created_acts = [action['name'] for action in body['actions']]
_, body = self.client.get_object('actions', created_acts[0])
time = body['created_at']
resp, body = self.client.get_list_obj(
'actions?created_at=lte:' + time.replace(' ', '%20')
)
actions = [action['name'] for action in body['actions']]
self.assertEqual(200, resp.status)
self.assertIn(created_acts[0], actions)
@decorators.attr(type='smoke')
@decorators.idempotent_id('cbb716f1-7fc7-4884-8fa9-6ff2bc35ee29')
def test_get_list_actions_multiple_filter(self):
resp, body = self.client.create_action('action_v2.yaml')
self.assertEqual(201, resp.status)
created_acts = [action['name'] for action in body['actions']]
_, body = self.client.get_object('actions', created_acts[0])
time = body['created_at']
resp, body = self.client.get_list_obj(
'actions?created_at=lte:' + time.replace(' ', '%20') +
'&is_system=False'
)
actions = [action['name'] for action in body['actions']]
self.assertEqual(200, resp.status)
self.assertIn(created_acts[0], actions)
@decorators.attr(type='negative')
@decorators.idempotent_id('45fdc1f3-4d89-4035-9b76-08ef94c92628')
def test_get_list_actions_invalid_filter(self):
self.assertRaises(
exceptions.BadRequest,
self.client.get_list_obj,
'actions?is_system<False'
)
self.assertRaises(
exceptions.BadRequest,
self.client.get_list_obj,
'actions?is_system!=False'
)
self.assertRaises(
exceptions.BadRequest,
self.client.get_list_obj,
'actions?created_at>2016-02-23%2008:51:26'
)
@decorators.attr(type='sanity')
@decorators.idempotent_id('5dbceaf3-6a32-4a4f-9427-1bbdb6f3c574')
def test_create_and_delete_few_actions(self):
resp, body = self.client.create_action('action_v2.yaml')
self.assertEqual(201, resp.status)
created_acts = [action['name'] for action in body['actions']]
resp, body = self.client.get_list_obj('actions')
self.assertEqual(200, resp.status)
actions = [action['name'] for action in body['actions']]
for act in created_acts:
self.assertIn(act, actions)
self.client.delete_obj('actions', act)
_, body = self.client.get_list_obj('actions')
actions = [action['name'] for action in body['actions']]
for act in created_acts:
self.assertNotIn(act, actions)
self.client.actions.remove(act)
@decorators.attr(type='sanity')
@decorators.idempotent_id('d7dad5de-6b1f-4813-b789-78f075252639')
def test_get_action(self):
_, body = self.client.create_action('action_v2.yaml')
action_name = body['actions'][0]['name']
resp, body = self.client.get_object('actions', action_name)
self.assertEqual(200, resp.status)
self.assertEqual(action_name, body['name'])
@decorators.attr(type='sanity')
@decorators.idempotent_id('21a031c8-8e2d-421f-8dfe-71a3b5e44381')
def test_update_action(self):
_, body = self.client.create_action('action_v2.yaml')
action = body['actions'][0]['name']
act_created_at = self.get_field_value(
body=body, act_name=action, field='created_at')
self.assertNotIn('updated at', body['actions'])
resp, body = self.client.update_request('actions', 'action_v2.yaml')
self.assertEqual(200, resp.status)
actions = [act['name'] for act in body['actions']]
self.assertIn(action, actions)
updated_act_created_at = self.get_field_value(
body=body, act_name=action, field='created_at')
self.assertEqual(act_created_at.split(".")[0], updated_act_created_at)
self.assertTrue(all(['updated_at' in item
for item in body['actions']]))
@decorators.attr(type='sanity')
@decorators.idempotent_id('329b1030-c55c-45f0-8129-cc892bc23dcc')
def test_get_action_definition(self):
_, body = self.client.create_action('action_v2.yaml')
act_name = body['actions'][0]['name']
resp, body = self.client.get_definition('actions', act_name)
self.assertEqual(200, resp.status)
self.assertIsNotNone(body)
self.assertIn(act_name, body)
@decorators.attr(type='negative')
@decorators.idempotent_id('c2b5be88-571a-4855-922f-9a338dba6adb')
def test_get_nonexistent_action(self):
self.assertRaises(
exceptions.NotFound,
self.client.get_object,
'actions', 'nonexist'
)
@decorators.attr(type='negative')
@decorators.idempotent_id('fc2fafcb-9bb4-4a18-a507-3f9964f4a08a')
def test_double_creation(self):
self.client.create_action('action_v2.yaml')
self.assertRaises(
exceptions.Conflict,
self.client.create_action,
'action_v2.yaml'
)
@decorators.attr(type='negative')
@decorators.idempotent_id('0c456a73-9c39-4aeb-b3ca-3ea4338bc9ab')
def test_create_action_invalid_def(self):
self.assertRaises(
exceptions.BadRequest,
self.client.create_action,
'wb_v2.yaml'
)
@decorators.attr(type='negative')
@decorators.idempotent_id('469677b5-22ab-4e2a-aee6-5bcc9dac93de')
def test_update_action_invalid_def(self):
self.assertRaises(
exceptions.BadRequest,
self.client.update_request,
'actions', 'wb_v2.yaml'
)
@decorators.attr(type='negative')
@decorators.idempotent_id('ab444607-40fc-47cb-982f-83762d5b64c9')
def test_delete_nonexistent_action(self):
self.assertRaises(
exceptions.NotFound,
self.client.delete_obj,
'actions', 'nonexist'
)
@decorators.attr(type='negative')
@decorators.idempotent_id('74d0d480-793a-46ca-b88a-8336c1897f3a')
def test_delete_standard_action(self):
self.assertRaises(
exceptions.BadRequest,
self.client.delete_obj,
'actions', 'nova.servers_create'
)

View File

@ -1,237 +0,0 @@
# Copyright 2016 NEC Corporation. All rights reserved.
#
# 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_concurrency.fixture import lockutils
from tempest.lib import decorators
from tempest.lib import exceptions
from mistral_tempest_tests.tests import base
class CronTriggerTestsV2(base.TestCase):
_service = 'workflowv2'
def setUp(self):
super(CronTriggerTestsV2, self).setUp()
self.useFixture(lockutils.LockFixture('mistral-workflow'))
_, body = self.client.create_workflow('wf_v2.yaml')
self.wf_name = body['workflows'][0]['name']
def tearDown(self):
for tr in self.client.triggers:
self.client.delete_obj('cron_triggers', tr)
self.client.triggers = []
for wf in self.client.workflows:
self.client.delete_obj('workflows', wf)
self.client.workflows = []
super(CronTriggerTestsV2, self).tearDown()
@decorators.attr(type='smoke')
@decorators.idempotent_id('c53b44dd-59b3-4a4b-b22a-21abb4cecea0')
def test_get_list_cron_triggers(self):
resp, body = self.client.get_list_obj('cron_triggers')
self.assertEqual(200, resp.status)
self.assertEmpty(body['cron_triggers'])
@decorators.attr(type='sanity')
@decorators.idempotent_id('fbc641fa-8704-45b3-b259-136eb956394c')
def test_create_and_delete_cron_triggers(self):
tr_name = 'trigger'
resp, body = self.client.create_cron_trigger(
tr_name, self.wf_name, None, '5 * * * *')
self.assertEqual(201, resp.status)
self.assertEqual(tr_name, body['name'])
resp, body = self.client.get_list_obj('cron_triggers')
self.assertEqual(200, resp.status)
trs_names = [tr['name'] for tr in body['cron_triggers']]
self.assertIn(tr_name, trs_names)
self.client.delete_obj('cron_triggers', tr_name)
self.client.triggers.remove(tr_name)
_, body = self.client.get_list_obj('cron_triggers')
trs_names = [tr['name'] for tr in body['cron_triggers']]
self.assertNotIn(tr_name, trs_names)
@decorators.attr(type='sanity')
@decorators.idempotent_id('b8b9102e-b323-492f-af41-4f5368971e36')
def test_create_and_delete_oneshot_cron_triggers(self):
tr_name = 'trigger'
resp, body = self.client.create_cron_trigger(
tr_name, self.wf_name, None, None, "4242-12-25 13:37")
self.assertEqual(201, resp.status)
self.assertEqual(tr_name, body['name'])
self.assertEqual("4242-12-25 13:37:00", body['next_execution_time'])
resp, body = self.client.get_list_obj('cron_triggers')
self.assertEqual(200, resp.status)
trs_names = [tr['name'] for tr in body['cron_triggers']]
self.assertIn(tr_name, trs_names)
self.client.delete_obj('cron_triggers', tr_name)
self.client.triggers.remove(tr_name)
_, body = self.client.get_list_obj('cron_triggers')
trs_names = [tr['name'] for tr in body['cron_triggers']]
self.assertNotIn(tr_name, trs_names)
@decorators.attr(type='sanity')
@decorators.idempotent_id('5224359b-3c31-4fe7-a4eb-dc9da843137e')
def test_create_two_cron_triggers_for_one_wf(self):
tr_name_1 = 'trigger1'
tr_name_2 = 'trigger2'
resp, body = self.client.create_cron_trigger(
tr_name_1, self.wf_name, None, '5 * * * *')
self.assertEqual(201, resp.status)
self.assertEqual(tr_name_1, body['name'])
resp, body = self.client.create_cron_trigger(
tr_name_2, self.wf_name, None, '15 * * * *')
self.assertEqual(201, resp.status)
self.assertEqual(tr_name_2, body['name'])
resp, body = self.client.get_list_obj('cron_triggers')
self.assertEqual(200, resp.status)
trs_names = [tr['name'] for tr in body['cron_triggers']]
self.assertIn(tr_name_1, trs_names)
self.assertIn(tr_name_2, trs_names)
@decorators.attr(type='sanity')
@decorators.idempotent_id('967da6e3-f9a2-430a-9390-0d73f2143aba')
def test_get_cron_trigger(self):
tr_name = 'trigger'
self.client.create_cron_trigger(
tr_name, self.wf_name, None, '5 * * * *')
resp, body = self.client.get_object('cron_triggers', tr_name)
self.assertEqual(200, resp.status)
self.assertEqual(tr_name, body['name'])
@decorators.attr(type='negative')
@decorators.idempotent_id('d0e4d894-9a50-4919-a008-a9f255b6b6a3')
def test_create_cron_trigger_nonexistent_wf(self):
self.assertRaises(exceptions.NotFound,
self.client.create_cron_trigger,
'trigger', 'nonexist', None, '5 * * * *')
@decorators.attr(type='negative')
@decorators.idempotent_id('83f0d420-fd3c-4e75-87b1-854cefb28bda')
def test_create_cron_trigger_invalid_count(self):
self.assertRaises(exceptions.BadRequest,
self.client.create_cron_trigger,
'trigger', 'nonexist', None, '5 * * * *', None, "q")
@decorators.attr(type='negative')
@decorators.idempotent_id('4190e0af-3c64-4f57-a0b8-d9d3d41fd323')
def test_create_cron_trigger_negative_count(self):
self.assertRaises(exceptions.BadRequest,
self.client.create_cron_trigger,
'trigger', 'nonexist', None, '5 * * * *', None, -1)
@decorators.attr(type='negative')
@decorators.idempotent_id('210c37e8-990e-4260-b3b3-93f254e6a4d7')
def test_create_cron_trigger_invalid_first_date(self):
self.assertRaises(exceptions.BadRequest,
self.client.create_cron_trigger,
'trigger', 'nonexist', None, '5 * * * *', "q")
@decorators.attr(type='negative')
@decorators.idempotent_id('17990a39-8f66-4748-8ba5-ca87befbb198')
def test_create_cron_trigger_count_only(self):
self.assertRaises(exceptions.BadRequest,
self.client.create_cron_trigger,
'trigger', 'nonexist', None, None, None, "42")
@decorators.attr(type='negative')
@decorators.idempotent_id('029e0a1e-2252-4a37-b9bd-cfbe407c6ade')
def test_create_cron_trigger_date_and_count_without_pattern(self):
self.assertRaises(exceptions.BadRequest,
self.client.create_cron_trigger,
'trigger', 'nonexist', None, None,
"4242-12-25 13:37", "42")
@decorators.attr(type='negative')
@decorators.idempotent_id('54650d60-ec17-44b7-8732-2183852789ae')
def test_get_nonexistent_cron_trigger(self):
self.assertRaises(exceptions.NotFound,
self.client.get_object,
'cron_triggers', 'trigger')
@decorators.attr(type='negative')
@decorators.idempotent_id('c663599e-5cd7-49ff-9c0f-f82a5bcc5fdb')
def test_delete_nonexistent_trigger(self):
self.assertRaises(exceptions.NotFound,
self.client.delete_obj,
'cron_triggers', 'trigger')
@decorators.attr(type='negative')
@decorators.idempotent_id('d1328d2b-5dc2-4521-93ec-d734d5fb4df7')
def test_create_two_cron_triggers_with_same_name(self):
tr_name = 'trigger'
self.client.create_cron_trigger(
tr_name, self.wf_name, None, '5 * * * *')
self.assertRaises(exceptions.Conflict,
self.client.create_cron_trigger,
tr_name, self.wf_name, None, '5 * * * *')
@decorators.attr(type='negative')
@decorators.idempotent_id('3e51fc44-ce38-4653-9e4e-08b077a1dbc5')
def test_create_two_cron_triggers_with_same_pattern(self):
self.client.create_cron_trigger(
'trigger1',
self.wf_name,
None,
'5 * * * *',
"4242-12-25 13:37",
"42"
)
self.assertRaises(
exceptions.Conflict,
self.client.create_cron_trigger,
'trigger2',
self.wf_name,
None,
'5 * * * *',
"4242-12-25 13:37",
"42"
)
@decorators.attr(type='negative')
@decorators.idempotent_id('6f3c08f3-9498-410e-a44b-4f9c6c971405')
def test_invalid_cron_pattern_not_enough_params(self):
self.assertRaises(exceptions.BadRequest,
self.client.create_cron_trigger,
'trigger', self.wf_name, None, '5 *')
@decorators.attr(type='negative')
@decorators.idempotent_id('26cb52e7-1ef3-45a2-a870-1baec2382c55')
def test_invalid_cron_pattern_out_of_range(self):
self.assertRaises(exceptions.BadRequest,
self.client.create_cron_trigger,
'trigger', self.wf_name, None, '88 * * * *')

View File

@ -1,114 +0,0 @@
# Copyright 2016 Catalyst IT Limited
#
# 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 mistral_tempest_tests.tests import base
EXCHANGE = 'openstack'
EVENT_ENGINE_TOPIC = 'mistral_event_engine'
EVENT = 'fake.event'
class EventTriggerTestsV2(base.TestCase):
"""Test class for event engine function.
NOTE: This test class doesn't fully test event engine functions, because
we can not send real notifications to the internal message queue to
trigger the specified workflows.
So, before notification is supported in Mistral, we can only test the API
functions.
"""
_service = 'workflowv2'
def setUp(self):
super(EventTriggerTestsV2, self).setUp()
_, body = self.client.create_workflow('wf_v2.yaml')
self.wf_id = body['workflows'][0]['id']
def tearDown(self):
for tr in self.client.event_triggers:
self.client.delete_obj('event_triggers', tr)
self.client.event_triggers = []
for wf in self.client.workflows:
self.client.delete_obj('workflows', wf)
self.client.workflows = []
super(EventTriggerTestsV2, self).tearDown()
@decorators.attr(type='sanity')
@decorators.idempotent_id('cfdf9aee-09ce-49bf-af05-97c5542bc131')
def test_create_get_delete_event_trigger(self):
name = 'my_event_trigger'
resp, body = self.client.create_event_trigger(
self.wf_id, EXCHANGE, EVENT_ENGINE_TOPIC, EVENT, name)
trigger_id = body['id']
self.assertEqual(201, resp.status)
self.assertEqual(name, body['name'])
resp, body = self.client.get_list_obj('event_triggers')
self.assertEqual(200, resp.status)
trs_names = [tr['name'] for tr in body['event_triggers']]
self.assertIn(name, trs_names)
self.client.delete_obj('event_triggers', trigger_id)
self.client.event_triggers.remove(trigger_id)
_, body = self.client.get_list_obj('event_triggers')
trs_names = [tr['name'] for tr in body['event_triggers']]
self.assertNotIn(name, trs_names)
@decorators.attr(type='negative')
@decorators.idempotent_id('20e547d6-9a16-4cac-9b1a-f3520c58cdd7')
def test_create_event_trigger_without_necessary_param(self):
self.assertRaises(exceptions.BadRequest,
self.client.create_event_trigger,
self.wf_id, EXCHANGE, EVENT_ENGINE_TOPIC, '')
@decorators.attr(type='negative')
@decorators.idempotent_id('ed02f500-9436-4a7b-a135-f210e1c32b22')
def test_create_event_trigger_with_nonexist_wf(self):
self.assertRaises(exceptions.BadRequest,
self.client.create_event_trigger,
'nonexist', EXCHANGE, EVENT_ENGINE_TOPIC, EVENT)
@decorators.attr(type='negative')
@decorators.idempotent_id('0ab556b6-ab76-492e-8eef-c79955003a93')
def test_create_event_trigger_duplicate(self):
name = 'my_event_trigger'
self.client.create_event_trigger(
self.wf_id, EXCHANGE, EVENT_ENGINE_TOPIC, EVENT, name)
self.assertRaises(exceptions.Conflict,
self.client.create_event_trigger,
self.wf_id, EXCHANGE, EVENT_ENGINE_TOPIC, EVENT)
@decorators.attr(type='negative')
@decorators.idempotent_id('56b90a90-9ff3-42f8-a9eb-04a77198710e')
def test_get_nonexistent_event_trigger(self):
fake_id = '3771c152-d1a7-4a82-8a50-c79d122012dc'
self.assertRaises(exceptions.NotFound,
self.client.get_object,
'event_triggers', fake_id)

View File

@ -1,440 +0,0 @@
# Copyright 2016 NEC Corporation. All rights reserved.
#
# 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_concurrency.fixture import lockutils
from tempest.lib import decorators
from tempest.lib import exceptions
from mistral_tempest_tests.tests import base
from mistral_tempest_tests.tests import utils
import json
class ExecutionTestsV2(base.TestCase):
_service = 'workflowv2'
def setUp(self):
super(ExecutionTestsV2, self).setUp()
self.useFixture(lockutils.LockFixture('mistral-workflow'))
_, body = self.client.create_workflow('wf_v2.yaml')
self.direct_wf_name = 'wf'
self.direct_wf2_name = 'wf2'
self.sub_wf_name = 'subwf1'
self.direct_wf_id = body['workflows'][0]['id']
reverse_wfs = [wf for wf in body['workflows'] if wf['name'] == 'wf1']
self.reverse_wf = reverse_wfs[0]
def tearDown(self):
for wf in self.client.workflows:
self.client.delete_obj('workflows', wf)
self.client.workflows = []
for ex in self.client.executions:
self.client.delete_obj('executions', ex)
self.client.executions = []
super(ExecutionTestsV2, self).tearDown()
@decorators.attr(type='smoke')
@decorators.idempotent_id('c0b4b658-6f01-4680-b402-2f683b3d78b6')
def test_get_list_executions(self):
resp, body = self.client.get_list_obj('executions')
self.assertEqual(200, resp.status)
self.assertNotIn('next', body)
@decorators.attr(type='smoke')
@decorators.idempotent_id('0bfcb4b0-b1e4-4499-b81b-0e86c8a2a841')
def test_get_list_executions_with_pagination(self):
resp, body = self.client.create_execution(self.direct_wf_name)
exec_id_1 = body['id']
self.assertEqual(201, resp.status)
resp, body = self.client.create_execution(self.direct_wf2_name)
exec_id_2 = body['id']
self.assertEqual(201, resp.status)
resp, body = self.client.get_list_obj('executions')
self.assertIn(exec_id_1, [ex['id'] for ex in body['executions']])
self.assertIn(exec_id_2, [ex['id'] for ex in body['executions']])
resp, body = self.client.get_list_obj(
'executions?limit=1&sort_keys=workflow_name&sort_dirs=asc')
self.assertEqual(200, resp.status)
self.assertEqual(1, len(body['executions']))
self.assertIn('next', body)
workflow_name_1 = body['executions'][0].get('workflow_name')
next = body.get('next')
param_dict = utils.get_dict_from_string(
next.split('?')[1],
delimiter='&'
)
# NOTE: 'id' gets included into sort keys automatically with 'desc'
# sorting to avoid pagination looping.
expected_dict = {
'limit': 1,
'sort_keys': 'workflow_name,id',
'sort_dirs': 'asc,asc',
}
self.assertTrue(
set(expected_dict.items()).issubset(set(param_dict.items()))
)
# Query again using 'next' link
url_param = next.split('/')[-1]
resp, body = self.client.get_list_obj(url_param)
self.assertEqual(200, resp.status)
self.assertEqual(1, len(body['executions']))
workflow_name_2 = body['executions'][0].get('workflow_name')
self.assertGreater(workflow_name_2, workflow_name_1)
@decorators.attr(type='sanity')
@decorators.idempotent_id('5d8ebe04-8de6-414d-908f-213af59e4c6a')
def test_create_execution_for_direct_wf(self):
resp, body = self.client.create_execution(self.direct_wf_name)
exec_id = body['id']
self.assertEqual(201, resp.status)
self.assertEqual(self.direct_wf_name, body['workflow_name'])
self.assertEqual('RUNNING', body['state'])
resp, body = self.client.get_list_obj('executions')
self.assertIn(exec_id,
[ex_id['id'] for ex_id in body['executions']])
@decorators.attr(type='sanity')
@decorators.idempotent_id('101bfdff-8309-4add-9504-544b15f13d95')
def test_create_execution_for_reverse_wf(self):
resp, body = self.client.create_execution(
self.reverse_wf['name'],
wf_input={self.reverse_wf['input']: "Bye"},
params={"task_name": "goodbye"})
exec_id = body['id']
self.assertEqual(201, resp.status)
self.assertEqual(self.reverse_wf['name'], body['workflow_name'])
self.assertEqual('RUNNING', body['state'])
resp, body = self.client.get_list_obj('executions')
self.assertIn(exec_id,
[ex_id['id'] for ex_id in body['executions']])
resp, body = self.client.get_object('executions', exec_id)
# TODO(nmakhotkin): Fix this loop. It is infinite now.
while body['state'] != 'SUCCESS':
resp, body = self.client.get_object('executions', exec_id)
self.assertEqual(200, resp.status)
self.assertEqual('SUCCESS', body['state'])
@decorators.attr(type='sanity')
@decorators.idempotent_id('2df30966-9c45-4a2e-942d-e74bd92cb5aa')
def test_create_execution_by_wf_id(self):
resp, body = self.client.create_execution(self.direct_wf_id)
exec_id = body['id']
self.assertEqual(201, resp.status)
self.assertEqual(self.direct_wf_id, body['workflow_id'])
self.assertEqual('RUNNING', body['state'])
resp, body = self.client.get_list_obj('executions')
self.assertIn(
exec_id,
[ex_id['id'] for ex_id in body['executions']]
)
@decorators.attr(type='sanity')
@decorators.idempotent_id('f7f50198-2dbd-4ca1-af51-d0eadc1108ac')
def test_get_execution(self):
_, execution = self.client.create_execution(self.direct_wf_name)
resp, body = self.client.get_object('executions', execution['id'])
del execution['state']
del body['state']
self.assertEqual(200, resp.status)
self.assertEqual(execution['id'], body['id'])
@decorators.attr(type='sanity')
@decorators.idempotent_id('2f142ba0-6b88-4d63-8544-05c3dbfe13cc')
def test_update_execution_pause(self):
_, execution = self.client.create_execution(self.direct_wf_name)
resp, body = self.client.update_execution(
execution['id'], '{"state": "PAUSED"}')
self.assertEqual(200, resp.status)
self.assertEqual('PAUSED', body['state'])
@decorators.attr(type='sanity')
@decorators.idempotent_id('f0557236-55ab-457d-9197-05bc2ae53e21')
def test_update_execution_description(self):
_, execution = self.client.create_execution(self.direct_wf_name)
resp, body = self.client.update_execution(
execution['id'], '{"description": "description"}')
self.assertEqual(200, resp.status)
self.assertEqual('description', body['description'])
@decorators.attr(type='sanity')
@decorators.idempotent_id('c54b4d68-b179-4339-bdab-a91cd6e819b7')
def test_update_execution_fail(self):
_, execution = self.client.create_execution(self.direct_wf_name)
resp, body = self.client.update_execution(
execution['id'], '{"state": "ERROR", "state_info": "Forced"}')
self.assertEqual(200, resp.status)
self.assertEqual('ERROR', body['state'])
self.assertEqual('Forced', body['state_info'])
@decorators.attr(type='sanity')
@decorators.idempotent_id('b5ce0d18-7d78-45bb-813e-ed94cea65fd0')
def test_update_execution_by_admin(self):
_, execution = self.client.create_execution(self.direct_wf_name)
resp, body = self.admin_client.update_execution(
execution['id'], '{"description": "description set by admin"}')
self.assertEqual(200, resp.status)
self.assertEqual('description set by admin', body['description'])
resp, body = self.client.get_object('executions', execution['id'])
self.assertEqual(200, resp.status)
self.assertEqual("description set by admin", body['description'])
@decorators.attr(type='sanity')
@decorators.idempotent_id('c6247362-a082-49ad-a2c3-aaf12419a477')
def test_update_execution_by_other_fail(self):
_, execution = self.client.create_execution(self.direct_wf_name)
self.assertRaises(
exceptions.NotFound,
self.alt_client.update_execution,
execution['id'],
'{"description": "description set by admin"}'
)
@decorators.attr(type='negative')
@decorators.idempotent_id('d8bde271-6785-4ace-9173-a8a3a01d5eaa')
def test_get_nonexistent_execution(self):
self.assertRaises(exceptions.NotFound, self.client.get_object,
'executions', '1a2b3c')
@decorators.attr(type='negative')
@decorators.idempotent_id('e26e31ba-88cf-4b90-8b3a-fd4ecc612252')
def test_update_nonexistent_execution(self):
put_body = '{"state": "STOPPED"}'
self.assertRaises(exceptions.NotFound,
self.client.update_execution,
'1a2b3c', put_body)
@decorators.attr(type='negative')
@decorators.idempotent_id('b337e270-b3b6-41e2-8de2-05030b06fc37')
def test_delete_nonexistent_execution(self):
self.assertRaises(exceptions.NotFound,
self.client.delete_obj,
'executions', 'nonexist')
@decorators.attr(type='negative')
@decorators.idempotent_id('46f7b4b0-7d4a-4bdc-b2b6-46343cdd6f3a')
def test_create_ex_for_nonexistent_wf(self):
self.assertRaises(exceptions.NotFound,
self.client.create_execution,
'nonexist')
@decorators.attr(type='negative')
@decorators.idempotent_id('9d27247e-b4d4-40ab-9181-9986655a6be4')
def test_create_execution_for_reverse_wf_invalid_start_task(self):
self.assertRaises(
exceptions.BadRequest,
self.client.create_execution,
self.reverse_wf['name'],
{self.reverse_wf['input']: "Bye"},
{"task_name": "nonexist"}
)
@decorators.attr(type='negative')
@decorators.idempotent_id('0d6ac42b-4059-40ef-99d0-a65b3cd1837c')
def test_create_execution_forgot_input_params(self):
self.assertRaises(
exceptions.BadRequest,
self.client.create_execution,
self.reverse_wf['name'],
params={"task_name": "nonexist"}
)
@decorators.attr(type='sanity')
@decorators.idempotent_id('52779c73-7563-47b2-8231-a24d6bf531a7')
def test_action_ex_concurrency(self):
resp, wf = self.client.create_workflow("wf_action_ex_concurrency.yaml")
self.assertEqual(201, resp.status)
wf_name = wf['workflows'][0]['name']
resp, execution = self.client.create_execution(wf_name)
self.assertEqual(201, resp.status)
self.assertEqual('RUNNING', execution['state'])
self.client.wait_execution_success(execution)
@decorators.attr(type='sanity')
@decorators.idempotent_id('eb061c4d-2892-47f0-81e6-37ba15c376bb')
def test_task_ex_concurrency(self):
resp, wf = self.client.create_workflow("wf_task_ex_concurrency.yaml")
self.assertEqual(201, resp.status)
wf_name = wf['workflows'][0]['name']
resp, execution = self.client.create_execution(wf_name)
self.assertEqual(201, resp.status)
self.assertEqual('RUNNING', execution['state'])
self.client.wait_execution(execution, target_state='ERROR')
@decorators.attr(type='sanity')
@decorators.idempotent_id('acc8e401-2b26-4c41-9e79-8da791da85c0')
def test_delete_execution_by_admin(self):
_, body = self.client.create_execution(self.direct_wf_id)
exec_id = body['id']
resp, _ = self.admin_client.delete_obj('executions', exec_id)
self.assertEqual(204, resp.status)
self.client.executions.remove(exec_id)
self.assertRaises(
exceptions.NotFound,
self.client.get_object,
'executions',
exec_id
)
@decorators.idempotent_id('a882876b-7565-4f7f-9714-d99032ffaabb')
@decorators.attr(type='sanity')
def test_workflow_execution_of_nested_workflows_within_namespace(self):
low_wf = 'for_wf_namespace/lowest_level_wf.yaml'
middle_wf = 'for_wf_namespace/middle_wf.yaml'
top_wf = 'for_wf_namespace/top_level_wf.yaml'
resp, wf = self.client.create_workflow(low_wf)
self.assertEqual(201, resp.status)
namespace = 'abc'
resp, wf = self.client.create_workflow(low_wf, namespace=namespace)
self.assertEqual(201, resp.status)
resp, wf = self.client.create_workflow(middle_wf)
self.assertEqual(201, resp.status)
resp, wf = self.client.create_workflow(top_wf)
self.assertEqual(201, resp.status)
resp, wf = self.client.create_workflow(top_wf, namespace=namespace)
self.assertEqual(201, resp.status)
wf_name = wf['workflows'][0]['name']
resp, top_execution = self.client.create_execution(wf_name, namespace)
self.assertEqual(201, resp.status)
self.assertEqual('RUNNING', top_execution['state'])
self.assertEqual(wf_name, top_execution['workflow_name'])
self.assertEqual(wf_name, top_execution['workflow_name'])
self.assertEqual(namespace, top_execution['workflow_namespace'])
self.client.wait_execution(top_execution, target_state='SUCCESS')
self.assertEqual(
namespace,
json.loads(top_execution['params'])['namespace']
)
resp, tasks = self.client.get_tasks(top_execution['id'])
top_task = tasks['tasks'][0]
self.assertEqual(wf_name, top_task['workflow_name'])
self.assertEqual(namespace, top_task['workflow_namespace'])
resp, executions = self.client.get_executions(top_task['id'])
middle_execution = executions['executions'][0]
self.assertEqual('middle_wf', middle_execution['workflow_name'])
self.assertEqual('', middle_execution['workflow_namespace'])
self.assertEqual(
namespace,
json.loads(middle_execution['params'])['namespace']
)
resp, tasks = self.client.get_tasks(middle_execution['id'])
middle_task = tasks['tasks'][0]
self.assertEqual('middle_wf', middle_task['workflow_name'])
self.assertEqual('', middle_task['workflow_namespace'])
resp, executions = self.client.get_executions(middle_task['id'])
lowest_execution = executions['executions'][0]
self.assertEqual('lowest_level_wf', lowest_execution['workflow_name'])
self.assertEqual(namespace, lowest_execution['workflow_namespace'])
self.assertEqual(
namespace,
json.loads(lowest_execution['params'])['namespace']
)
resp, tasks = self.client.get_tasks(lowest_execution['id'])
lowest_task = tasks['tasks'][0]
self.assertEqual('lowest_level_wf', lowest_task['workflow_name'])
self.assertEqual(namespace, lowest_task['workflow_namespace'])
resp, action_executions = self.client.get_action_executions(
lowest_task['id']
)
action_execution = action_executions['action_executions'][0]
self.assertEqual('lowest_level_wf', action_execution['workflow_name'])
self.assertEqual(namespace, action_execution['workflow_namespace'])
@decorators.attr(type='sanity')
@decorators.idempotent_id('2baa25c5-0b65-4fbe-8d90-2c6599831b6b')
def test_root_execution_id(self):
resp, execution = self.client.create_execution(self.sub_wf_name)
self.assertEqual(201, resp.status)
self.assertEqual('RUNNING', execution['state'])
self.client.wait_execution_success(execution)
resp, body = self.client.get_list_obj(
'executions?root_execution_id={}'.format(execution['id']))
self.assertEqual(200, resp.status)
self.assertNotEmpty(body['executions'])
self.assertEqual(2, len(body['executions']))
for exc in body['executions']:
self.assertEqual(exc['root_execution_id'], execution['id'])

View File

@ -1,108 +0,0 @@
# Copyright 2016 NEC Corporation. All rights reserved.
#
# 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_concurrency.fixture import lockutils
from tempest.lib import decorators
from mistral_tempest_tests.tests import base
class TasksTestsV2(base.TestCase):
_service = 'workflowv2'
def setUp(self):
super(TasksTestsV2, self).setUp()
self.useFixture(lockutils.LockFixture('mistral-workflow'))
_, body = self.client.create_workflow('wf_v2.yaml')
self.direct_wf_name = body['workflows'][0]['name']
_, execution = self.client.create_execution(self.direct_wf_name)
self.execution_id = execution['id']
def tearDown(self):
for wf in self.client.workflows:
self.client.delete_obj('workflows', wf)
self.client.workflows = []
for wf in self.client.executions:
self.client.delete_obj('executions', wf)
self.client.executions = []
super(TasksTestsV2, self).tearDown()
@decorators.attr(type='smoke')
@decorators.idempotent_id('81159dce-3802-44ee-a8d4-5ddca106fd91')
def test_get_tasks_list(self):
resp, body = self.client.get_list_obj('tasks')
self.assertEqual(200, resp.status)
self.assertNotEmpty(body['tasks'])
@decorators.attr(type='sanity')
@decorators.idempotent_id('f62664de-bd2b-4153-8d0f-5a76d78abbad')
def test_get_task(self):
resp, body = self.client.get_list_obj('tasks')
self.assertEqual(200, resp.status)
self.assertEqual(
self.direct_wf_name, body['tasks'][-1]['workflow_name']
)
@decorators.attr(type='sanity')
@decorators.idempotent_id('3230d694-40fd-4094-ad12-024f40a21b94')
def test_get_tasks_of_execution(self):
resp, body = self.client.get_list_obj(
'tasks?workflow_execution_id=%s' % self.execution_id)
self.assertEqual(200, resp.status)
self.assertEqual(
self.direct_wf_name, body['tasks'][-1]['workflow_name']
)
class TaskTypesTestsV2(base.TestCase):
_service = 'workflowv2'
def setUp(self):
super(TaskTypesTestsV2, self).setUp()
self.useFixture(lockutils.LockFixture('mistral-workflow'))
_, wb_body = self.client.create_workbook('wb_with_nested_wf.yaml')
self.nested_wf_name = 'wb_with_nested_wf.wrapping_wf'
_, execution = self.client.create_execution(self.nested_wf_name)
@decorators.attr(type='sanity')
@decorators.idempotent_id('1ac726eb-b945-4b82-8755-a2fb2dc009bc')
def test_task_type(self):
resp, body = self.client.get_list_obj('tasks')
self.assertEqual(200, resp.status)
bt = body['tasks']
ll = [[v for k, v in d.items() if 'type' in k] for d in bt]
types_list = [item for sublist in ll for item in sublist]
self.assertIn(
'WORKFLOW', types_list
)
self.assertIn(
'ACTION', types_list
)
# there are 2 tasks in the workflow one of each type
self.assertEqual(
2, len(types_list)
)

View File

@ -1,138 +0,0 @@
# Copyright 2016 NEC Corporation. All rights reserved.
#
# 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_concurrency.fixture import lockutils
from tempest.lib import decorators
from tempest.lib import exceptions
from mistral_tempest_tests.tests import base
class WorkbookTestsV2(base.TestCase):
_service = 'workflowv2'
def tearDown(self):
for wf in self.client.workflows:
self.client.delete_obj('workflows', wf)
self.client.workflows = []
super(WorkbookTestsV2, self).tearDown()
@decorators.attr(type='smoke')
@decorators.idempotent_id('4d8752b9-8d69-4d81-8710-5dd8ef699b95')
def test_get_list_workbooks(self):
resp, body = self.client.get_list_obj('workbooks')
self.assertEqual(200, resp.status)
self.assertEmpty(body['workbooks'])
@decorators.attr(type='sanity')
@decorators.idempotent_id('1a078ca2-bcf9-4eb9-8ed5-e3545038aa76')
def test_create_and_delete_workbook(self):
self.useFixture(lockutils.LockFixture('mistral-workflow'))
resp, body = self.client.create_workbook('wb_v2.yaml')
name = body['name']
self.assertEqual(201, resp.status)
resp, body = self.client.get_list_obj('workbooks')
self.assertEqual(200, resp.status)
self.assertEqual(name, body['workbooks'][0]['name'])
self.client.delete_obj('workbooks', name)
self.client.workbooks.remove(name)
_, body = self.client.get_list_obj('workbooks')
self.assertEmpty(body['workbooks'])
@decorators.attr(type='sanity')
@decorators.idempotent_id('80f7d7a6-2821-4ab0-b090-ca45c98258ba')
def test_get_workbook(self):
self.useFixture(lockutils.LockFixture('mistral-workflow'))
_, body = self.client.create_workbook('wb_v2.yaml')
name = body['name']
resp, body = self.client.get_object('workbooks', name)
self.assertEqual(200, resp.status)
self.assertEqual(name, body['name'])
@decorators.attr(type='sanity')
@decorators.idempotent_id('4d3b1e43-a493-41be-9c8a-389511675403')
def test_update_workbook(self):
self.useFixture(lockutils.LockFixture('mistral-workflow'))
_, body = self.client.create_workbook('wb_v2.yaml')
name = body['name']
resp, body = self.client.update_request('workbooks', 'wb_v2.yaml')
self.assertEqual(200, resp.status)
self.assertEqual(name, body['name'])
@decorators.attr(type='sanity')
@decorators.idempotent_id('506cdcc2-082f-4e1f-9ab2-717acd7f0eb5')
def test_get_workbook_definition(self):
self.useFixture(lockutils.LockFixture('mistral-workflow'))
_, body = self.client.create_workbook('wb_v2.yaml')
name = body['name']
resp, body = self.client.get_definition('workbooks', name)
self.assertEqual(200, resp.status)
self.assertIsNotNone(body)
@decorators.attr(type='negative')
@decorators.idempotent_id('d99f11c1-05a3-4d90-89c6-8d85558d3708')
def test_get_nonexistent_workbook_definition(self):
self.assertRaises(exceptions.NotFound,
self.client.get_definition,
'workbooks', 'nonexist')
@decorators.attr(type='negative')
@decorators.idempotent_id('61ed021e-ec56-42cb-ad05-eb6979aa00fd')
def test_get_nonexistent_workbook(self):
self.assertRaises(exceptions.NotFound, self.client.get_object,
'workbooks', 'nonexist')
@decorators.attr(type='negative')
@decorators.idempotent_id('e3d76f8b-220d-4250-8238-0ba27fda6de9')
def test_double_create_workbook(self):
self.useFixture(lockutils.LockFixture('mistral-workflow'))
_, body = self.client.create_workbook('wb_v2.yaml')
name = body['name']
self.assertRaises(exceptions.Conflict,
self.client.create_workbook,
'wb_v2.yaml')
self.client.delete_obj('workbooks', name)
self.client.workbooks.remove(name)
_, body = self.client.get_list_obj('workbooks')
self.assertEmpty(body['workbooks'])
@decorators.attr(type='negative')
@decorators.idempotent_id('1cd6f6f7-b166-454e-96d2-bf1f95c23015')
def test_create_wb_with_invalid_def(self):
self.assertRaises(
exceptions.BadRequest,
self.client.create_workbook,
'wb_v1.yaml'
)
@decorators.attr(type='negative')
@decorators.idempotent_id('ac9a05d3-e285-4d88-91eb-fb9ad694a89a')
def test_update_wb_with_invalid_def(self):
self.assertRaises(
exceptions.BadRequest,
self.client.update_request,
'workbooks',
'wb_v1.yaml'
)

View File

@ -1,442 +0,0 @@
# Copyright 2016 NEC Corporation. All rights reserved.
#
# 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
from oslo_concurrency.fixture import lockutils
from tempest.lib import decorators
from tempest.lib import exceptions
from mistral_tempest_tests.tests import base
from mistral_tempest_tests.tests import utils
class WorkflowTestsV2(base.TestCase):
_service = 'workflowv2'
def tearDown(self):
for wf in self.client.workflows:
self.client.delete_obj('workflows', wf)
self.client.workflows = []
super(WorkflowTestsV2, self).tearDown()
@decorators.attr(type='smoke')
@decorators.idempotent_id('e9cd6817-e8d1-4604-ba76-b0e17219f4c5')
def test_get_list_workflows(self):
resp, body = self.client.get_list_obj('workflows')
self.assertEqual(200, resp.status)
names = [wf['name'] for wf in body['workflows']]
self.assertIn('std.create_instance', names)
self.assertNotIn('next', body)
@decorators.attr(type='smoke')
@decorators.idempotent_id('be8a4a44-eeb3-48e3-b11d-b83ba14dbf2c')
def test_get_list_workflows_by_admin(self):
self.useFixture(lockutils.LockFixture('mistral-workflow'))
_, body = self.client.create_workflow('wf_v2.yaml')
name = body['workflows'][0]['name']
resp, raw_body = self.admin_client.get('workflows?all_projects=true')
body = json.loads(raw_body)
self.assertEqual(200, resp.status)
names = [wf['name'] for wf in body['workflows']]
self.assertIn(name, names)
@decorators.attr(type='smoke')
@decorators.idempotent_id('c9e2ebbc-02aa-4c33-b244-e471c8266aa7')
def test_get_list_workflows_with_project_by_admin(self):
self.useFixture(lockutils.LockFixture('mistral-workflow'))
_, body = self.client.create_workflow('wf_v2.yaml')
name = body['workflows'][0]['name']
resp, raw_body = self.admin_client.get(
'workflows?project_id=%s' %
self.client.auth_provider.credentials.tenant_id
)
body = json.loads(raw_body)
self.assertEqual(200, resp.status)
names = [wf['name'] for wf in body['workflows']]
self.assertIn(name, names)
@decorators.attr(type='smoke')
@decorators.idempotent_id('b8dc1b02-8509-45e2-9df7-4630cdcfa1ab')
def test_get_list_other_project_private_workflows(self):
self.useFixture(lockutils.LockFixture('mistral-workflow'))
_, body = self.client.create_workflow('wf_v2.yaml')
name = body['workflows'][0]['name']
resp, raw_body = self.alt_client.get(
'workflows?project_id=%s' %
self.client.auth_provider.credentials.tenant_id
)
body = json.loads(raw_body)
self.assertEqual(200, resp.status)
names = [wf['name'] for wf in body['workflows']]
self.assertNotIn(name, names)
@decorators.attr(type='smoke')
@decorators.idempotent_id('2063143b-ced8-4037-9383-e2504be581e6')
def test_get_list_workflows_with_fields(self):
resp, body = self.client.get_list_obj('workflows?fields=name')
self.assertEqual(200, resp.status)
for wf in body['workflows']:
self.assertListEqual(sorted(['id', 'name']), sorted(list(wf)))
@decorators.attr(type='smoke')
@decorators.idempotent_id('81f28735-e74e-4dc1-8b94-b548f8a80556')
def test_get_list_workflows_with_pagination(self):
resp, body = self.client.get_list_obj(
'workflows?limit=1&sort_keys=name&sort_dirs=desc'
)
self.assertEqual(200, resp.status)
self.assertEqual(1, len(body['workflows']))
self.assertIn('next', body)
name_1 = body['workflows'][0].get('name')
next = body.get('next')
param_dict = utils.get_dict_from_string(
next.split('?')[1],
delimiter='&'
)
# NOTE: 'id' gets included into sort keys automatically with 'desc'
# sorting to avoid pagination looping.
expected_sub_dict = {
'limit': 1,
'sort_keys': 'name,id',
'sort_dirs': 'desc,asc'
}
self.assertDictContainsSubset(expected_sub_dict, param_dict)
# Query again using 'next' hint
url_param = next.split('/')[-1]
resp, body = self.client.get_list_obj(url_param)
self.assertEqual(200, resp.status)
self.assertEqual(1, len(body['workflows']))
name_2 = body['workflows'][0].get('name')
self.assertGreater(name_1, name_2)
@decorators.attr(type='negative')
@decorators.idempotent_id('cdb5586f-a72f-4371-88d1-1472675915c3')
def test_get_list_workflows_nonexist_sort_dirs(self):
context = self.assertRaises(
exceptions.BadRequest,
self.client.get_list_obj,
'workflows?limit=1&sort_keys=id&sort_dirs=nonexist'
)
self.assertIn(
'Unknown sort direction',
context.resp_body.get('faultstring')
)
@decorators.attr(type='negative')
@decorators.idempotent_id('ac41a0ea-2be6-4307-9003-6b8dd52b0bf9')
def test_get_list_workflows_invalid_limit(self):
context = self.assertRaises(
exceptions.BadRequest,
self.client.get_list_obj,
'workflows?limit=-1&sort_keys=id&sort_dirs=asc'
)
self.assertIn(
'Limit must be positive',
context.resp_body.get('faultstring')
)
@decorators.attr(type='negative')
@decorators.idempotent_id('55759713-a8d7-44c2-aff1-2383f51136bd')
def test_get_list_workflows_duplicate_sort_keys(self):
context = self.assertRaises(
exceptions.BadRequest,
self.client.get_list_obj,
'workflows?limit=1&sort_keys=id,id&sort_dirs=asc,asc'
)
self.assertIn(
'Length of sort_keys must be equal or greater than sort_dirs',
context.resp_body.get('faultstring')
)
@decorators.attr(type='sanity')
@decorators.idempotent_id('e26b30b9-6699-4020-93a0-e25c2daca59a')
def test_create_and_delete_workflow(self):
self.useFixture(lockutils.LockFixture('mistral-workflow'))
resp, body = self.client.create_workflow('wf_v2.yaml')
name = body['workflows'][0]['name']
self.assertEqual(201, resp.status)
resp, body = self.client.get_list_obj('workflows')
self.assertEqual(200, resp.status)
names = [wf['name'] for wf in body['workflows']]
self.assertIn(name, names)
self.client.delete_obj('workflows', name)
self.client.workflows.remove(name)
_, body = self.client.get_list_obj('workflows')
names = [wf['name'] for wf in body['workflows']]
self.assertNotIn(name, names)
@decorators.attr(type='sanity')
@decorators.idempotent_id('f5a4a771-79b2-4f28-bfac-940aa83990a4')
def test_get_workflow(self):
self.useFixture(lockutils.LockFixture('mistral-workflow'))
_, body = self.client.create_workflow('wf_v2.yaml')
name = body['workflows'][0]['name']
resp, body = self.client.get_object('workflows', name)
self.assertEqual(200, resp.status)
self.assertEqual(name, body['name'])
@decorators.attr(type='sanity')
@decorators.idempotent_id('f516aad0-9a50-4ace-a217-fa1931fd9335')
def test_update_workflow(self):
self.useFixture(lockutils.LockFixture('mistral-workflow'))
_, body = self.client.create_workflow('single_wf.yaml')
name = body['workflows'][0]['name']
resp, body = self.client.update_request('workflows', 'single_wf.yaml')
self.assertEqual(200, resp.status)
self.assertEqual(name, body['workflows'][0]['name'])
@decorators.attr(type='sanity')
@decorators.idempotent_id('42f5d135-a2b8-4a31-8135-c5ce8c5f1ed5')
def test_workflow_within_namespace(self):
self.useFixture(lockutils.LockFixture('mistral-workflow'))
namespace = 'abc'
resp, body = self.client.create_workflow(
'single_wf.yaml',
namespace=namespace
)
name = body['workflows'][0]['name']
id = body['workflows'][0]['id']
self.assertEqual(201, resp.status)
self.assertEqual(name, body['workflows'][0]['name'])
resp, body = self.client.get_workflow(
id
)
self.assertEqual(namespace, body['namespace'])
resp, body = self.client.update_workflow('single_wf.yaml', namespace)
self.assertEqual(200, resp.status)
self.assertEqual(name, body['workflows'][0]['name'])
self.assertEqual(namespace, body['workflows'][0]['namespace'])
namespace = 'abc2'
resp, body = self.client.create_workflow(
'single_wf.yaml',
namespace=namespace
)
name = body['workflows'][0]['name']
id = body['workflows'][0]['id']
self.assertEqual(201, resp.status)
self.assertEqual(name, body['workflows'][0]['name'])
resp, body = self.client.get_workflow(id)
self.assertEqual(namespace, body['namespace'])
self.assertRaises(exceptions.NotFound, self.client.get_workflow, name)
self.client.create_workflow(
'single_wf.yaml'
)
resp, body = self.client.get_workflow(id)
self.assertEqual(200, resp.status)
@decorators.attr(type='sanity')
@decorators.idempotent_id('02bc1fc3-c31a-4e37-bb3d-eda46818505c')
def test_get_workflow_definition(self):
self.useFixture(lockutils.LockFixture('mistral-workflow'))
_, body = self.client.create_workflow('wf_v2.yaml')
name = body['workflows'][0]['name']
resp, body = self.client.get_definition('workflows', name)
self.assertEqual(200, resp.status)
self.assertIsNotNone(body)
@decorators.attr(type='sanity')
@decorators.idempotent_id('04fbd003-0e52-4034-858e-6634d4f84b29')
def test_get_workflow_uploaded_in_wb(self):
self.useFixture(lockutils.LockFixture('mistral-workflow'))
_, body = self.client.create_workbook('wb_v2.yaml')
wb_name = body['name']
_, body = self.client.get_list_obj('workflows')
wf_names = [wf['name'] for wf in body['workflows']
if wf['name'].startswith(wb_name)]
self.assertNotEmpty(wf_names)
@decorators.attr(type='negative')
@decorators.idempotent_id('5e5f0403-fb2c-41ae-bf6f-25c181515358')
def test_get_nonexistent_workflow_definition(self):
self.assertRaises(exceptions.NotFound,
self.client.get_definition,
'workflows', 'nonexist')
@decorators.attr(type='negative')
@decorators.idempotent_id('23c72d01-c3bb-43d6-ba15-9b49c15f800c')
def test_get_nonexistent_workflow(self):
self.assertRaises(exceptions.NotFound, self.client.get_object,
'workflows', 'nonexist')
exception = self.assertRaises(
exceptions.NotFound,
self.client.get_workflow,
'nonexist_wf',
'nonexist_namespace'
)
self.assertIn('nonexist_wf', str(exception))
self.assertIn('nonexist_namespace', str(exception))
@decorators.attr(type='negative')
@decorators.idempotent_id('6b917213-7f11-423a-8fe0-55795dcf0fb2')
def test_double_create_workflows(self):
self.useFixture(lockutils.LockFixture('mistral-workflow'))
_, body = self.client.create_workflow('wf_v2.yaml')
self.assertRaises(exceptions.Conflict,
self.client.create_workflow,
'wf_v2.yaml')
@decorators.attr(type='negative')
@decorators.idempotent_id('ffcd63d2-1104-4320-a67b-fadc4e2a0631')
def test_create_wf_with_invalid_def(self):
self.assertRaises(exceptions.BadRequest,
self.client.create_workflow,
'wb_v1.yaml')
@decorators.attr(type='negative')
@decorators.idempotent_id('eed46931-5485-436c-810f-1f63362223b9')
def test_update_wf_with_invalid_def(self):
self.assertRaises(exceptions.BadRequest,
self.client.update_request,
'workflows', 'wb_v1.yaml')
@decorators.attr(type='negative')
@decorators.idempotent_id('9b7f5b5a-cacd-4f98-a35a-decf065b8234')
def test_delete_wf_with_trigger_associate(self):
tr_name = 'trigger'
resp, body = self.client.create_workflow('wf_v2.yaml')
name = body['workflows'][0]['name']
resp, body = self.client.create_cron_trigger(
tr_name, name, None, '5 * * * *')
try:
self.assertRaises(
exceptions.BadRequest,
self.client.delete_obj,
'workflows',
name
)
finally:
self.client.delete_obj('cron_triggers', tr_name)
self.client.triggers.remove(tr_name)
@decorators.attr(type='negative')
@decorators.idempotent_id('46325022-cbd2-48f3-95f3-e587aab3b655')
def test_delete_wf_with_event_trigger_associate(self):
_, body = self.client.create_workflow('wf_v2.yaml')
wf_id = body['workflows'][0]['id']
resp, body = self.client.create_event_trigger(
wf_id, 'openstack', 'notification', 'fake.event')
self.assertEqual(201, resp.status)
try:
self.assertRaises(
exceptions.BadRequest,
self.client.delete_obj,
'workflows',
wf_id
)
finally:
self.client.delete_obj('event_triggers', body['id'])
self.client.event_triggers.remove(body['id'])
@decorators.attr(type='negative')
@decorators.idempotent_id('1cb929e6-d375-4dcb-ab7c-73aa205af896')
def test_delete_wf_with_trigger_associate_in_other_tenant(self):
self.useFixture(lockutils.LockFixture('mistral-workflow'))
tr_name = 'trigger'
_, body = self.client.create_workflow('wf_v2.yaml', scope='public')
name = body['workflows'][0]['name']
resp, body = self.alt_client.create_cron_trigger(
tr_name,
name,
None,
'5 * * * *'
)
try:
exception = self.assertRaises(
exceptions.BadRequest,
self.client.delete_obj,
'workflows',
name
)
self.assertIn(
"Can't delete workflow that has cron triggers associated",
exception.resp_body['faultstring']
)
finally:
self.alt_client.delete_obj('cron_triggers', tr_name)
self.alt_client.triggers.remove(tr_name)
@decorators.attr(type='negative')
@decorators.idempotent_id('f575713b-27fd-4ec8-b84f-468a7adf5ed2')
def test_delete_nonexistent_wf(self):
self.assertRaises(exceptions.NotFound,
self.client.delete_obj,
'workflows', 'nonexist')

View File

@ -1,91 +0,0 @@
# Copyright 2016 NEC Corporation. All rights reserved.
#
# 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 mock
from tempest import config
from tempest import test as test
from mistral_tempest_tests.services import base as service_base
from mistral_tempest_tests.services.v2 import mistral_client
CONF = config.CONF
class TestCase(test.BaseTestCase):
credentials = ['admin', 'primary', 'alt']
@classmethod
def skip_checks(cls):
super(TestCase, cls).skip_checks()
if not CONF.service_available.mistral:
raise cls.skipException("Mistral support is required.")
@classmethod
def resource_setup(cls):
"""Client authentication.
This method allows to initialize authentication before
each test case and define parameters of Mistral API Service.
"""
super(TestCase, cls).resource_setup()
if 'WITHOUT_AUTH' in os.environ:
cls.mgr = mock.MagicMock()
cls.mgr.auth_provider = service_base.AuthProv()
cls.admin_mgr = cls.alt_mgr = cls.mgr
else:
cls.admin_mgr = cls.os_admin
cls.mgr = cls.os_primary
cls.alt_mgr = cls.os_alt
if cls._service == 'workflowv2':
cls.admin_client = mistral_client.MistralClientV2(
cls.admin_mgr.auth_provider, cls._service)
cls.client = mistral_client.MistralClientV2(
cls.mgr.auth_provider, cls._service)
cls.alt_client = mistral_client.MistralClientV2(
cls.alt_mgr.auth_provider, cls._service)
def tearDown(self):
super(TestCase, self).tearDown()
for wb in self.client.workbooks:
self.client.delete_obj('workbooks', wb)
self.client.workbooks = []
class TestCaseAdvanced(TestCase):
@classmethod
def resource_setup(cls):
super(TestCaseAdvanced, cls).resource_setup()
cls.image_ref = CONF.compute.image_ref
cls.flavor_ref = CONF.compute.flavor_ref
def tearDown(self):
for wb in self.client.workbooks:
self.client.delete_obj('workbooks', wb)
self.client.workbooks = []
for ex in self.client.executions:
self.client.delete_obj('executions', ex)
self.client.executions = []
super(TestCaseAdvanced, self).tearDown()

View File

@ -1,21 +0,0 @@
---
version: "2.0"
greeting:
description: "This action says 'Hello'"
tags: [hello]
base: std.echo
base-input:
output: 'Hello, <% $.name %>'
input:
- name
output:
string: <% $ %>
farewell:
base: std.echo
base-input:
output: 'Bye!'
output:
info: <% $ %>

View File

@ -1,6 +0,0 @@
---
version: '2.0'
lowest_level_wf:
tasks:
noop_task:
action: std.noop

View File

@ -1,6 +0,0 @@
---
version: '2.0'
middle_wf:
tasks:
run_workflow_with_name_lowest_level_wf:
workflow: lowest_level_wf

View File

@ -1,6 +0,0 @@
---
version: '2.0'
top_level_wf:
tasks:
run_workflow_with_name_middle_wf:
workflow: middle_wf

View File

@ -1,75 +0,0 @@
---
version: '2.0'
name: action_collection
workflows:
keystone:
type: direct
tasks:
projects_list:
action: keystone.projects_list
publish:
result: <% task().result %>
nova:
type: direct
tasks:
flavors_list:
action: nova.flavors_list
publish:
result: <% task().result %>
glance:
type: direct
tasks:
images_list:
action: glance.images_list
publish:
result: <% task().result %>
heat:
type: direct
tasks:
stacks_list:
action: heat.stacks_list
publish:
result: <% task().result %>
neutron:
type: direct
tasks:
list_subnets:
action: neutron.list_subnets
publish:
result: <% task().result %>
cinder:
type: direct
tasks:
volumes_list:
action: cinder.volumes_list
publish:
result: <% task().result %>
swift:
type: direct
tasks:
get_account:
action: swift.get_account
publish:
result: <% task().result %>
zaqar:
type: direct
tasks:
send_message:
action: zaqar.queue_post
input:
queue_name: test
messages:
body:
type: action_collection.zaqar
payload:
status: <% $.get(status, 'SUCCESS') %>
publish:
result: <% task().result %>

View File

@ -1,11 +0,0 @@
---
version: '2.0'
single_wf:
type: direct
tasks:
hello:
action: std.echo output="Hello"
publish:
result: <% task(hello).result %>

View File

@ -1,12 +0,0 @@
Namespaces:
Greetings:
actions:
hello:
class: std.echo
base-parameters:
output: Hello!
Workflow:
tasks:
hello:
action: Greetings.hello

View File

@ -1,13 +0,0 @@
---
version: '2.0'
name: test
workflows:
test:
type: direct
tasks:
hello:
action: std.echo output="Hello"
publish:
result: <% task(hello).result %>

View File

@ -1,18 +0,0 @@
---
version: "2.0"
name: wb_with_nested_wf
workflows:
wrapping_wf:
type: direct
tasks:
call_inner_wf:
workflow: inner_wf
inner_wf:
type: direct
tasks:
hello:
action: std.echo output="Hello from inner workflow"

View File

@ -1,8 +0,0 @@
---
version: '2.0'
test_action_ex_concurrency:
tasks:
test_with_items:
with-items: index in <% range(2) %>
action: std.echo output='<% $.index %>'

View File

@ -1,11 +0,0 @@
---
version: '2.0'
test_task_ex_concurrency:
tasks:
task1:
action: std.async_noop
timeout: 2
task2:
action: std.async_noop
timeout: 2

View File

@ -1,55 +0,0 @@
---
version: '2.0'
wf:
type: direct
tasks:
hello:
action: std.echo output="Hello"
wait-before: 1
publish:
result: <% task(hello).result %>
wf1:
type: reverse
input:
- farewell
tasks:
addressee:
action: std.echo output="John"
publish:
name: <% task(addressee).result %>
goodbye:
action: std.echo output="<% $.farewell %>, <% $.name %>"
requires: [addressee]
wf2:
type: direct
tasks:
hello:
action: std.echo output="Doe"
subwf1:
type: direct
tasks:
task1:
workflow: subwf2
subwf2:
type: direct
tasks:
task1:
workflow: subwf3
subwf3:
type: direct
tasks:
task1:
action: std.noop

View File

@ -1,145 +0,0 @@
# Copyright 2016 - Nokia, 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 base64
from keystoneclient import service_catalog as ks_service_catalog
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
from six.moves.urllib.parse import urlparse
from tempest.lib import decorators
from mistral_tempest_tests.tests import base
class MultiVimActionsTests(base.TestCase):
_service = 'workflowv2'
@classmethod
def resource_setup(cls):
super(MultiVimActionsTests, cls).resource_setup()
@decorators.attr(type='openstack')
@decorators.idempotent_id('dadc9960-9c03-41a9-9a9d-7e97d527e6dd')
def test_multi_vim_support_target_headers(self):
client_1 = self.alt_client
client_2 = self.client
# Create stack with client2.
result = _execute_action(client_2, _get_create_stack_request())
stack_id = str(
jsonutils.loads(result['output'])['result']['stack']['id']
)
# List stacks with client1, and assert that there is no stack.
result = _execute_action(client_1, _get_list_stack_request())
self.assertEmpty(jsonutils.loads(result['output'])['result'])
# List stacks with client1, but with the target headers of client2,
# and assert the created stack is there.
result = _execute_action(
client_1,
_get_list_stack_request(),
extra_headers=_extract_target_headers_from_client(client_2)
)
self.assertEqual(
stack_id,
str(jsonutils.loads(result['output'])['result'][0]['id'])
)
@decorators.attr(type='openstack')
@decorators.idempotent_id('bc0e9b99-62b0-4d96-95c9-016a3f69b02a')
@decorators.skip_because(bug="1736685")
def test_multi_vim_support_target_headers_and_service_catalog(self):
client_1 = self.alt_client
client_2 = self.client
# List stacks with client1, but with the target headers of client2,
# and additionally with an invalid X-Target-Service-Catalog.
extra_headers = _extract_target_headers_from_client(client_2)
# Use ServiceCatalog to eliminate differences between keystone v2 and
# v3.
token_data = client_2.auth_provider.auth_data[1]
service_catalog = ks_service_catalog.ServiceCatalog.factory(
token_data
).get_data()
for service in service_catalog:
if service['name'] == 'heat':
for ep in service['endpoints']:
if 'publicURL' in ep:
ep['publicURL'] = "invalid"
elif ep['interface'] == 'public':
ep['url'] = "invalid"
break
break
if 'catalog' in token_data:
token_data['catalog'] = service_catalog
else:
token_data['serviceCatalog'] = service_catalog
invalid_service_catalog = {
"X-Target-Service-Catalog": base64.b64encode(
jsonutils.dumps(token_data)
)
}
extra_headers.update(invalid_service_catalog)
result = _execute_action(
client_1,
_get_list_stack_request(),
extra_headers=extra_headers
)
# Assert that the invalid catalog was used.
self.assertIn("Invalid URL", result['output'])
def _extract_target_headers_from_client(client):
u = urlparse(client.auth_provider.auth_url)
v3_auth_url = '{}://{}/identity/v3/'.format(u.scheme, u.netloc)
return {
'X-Target-Auth-Token': client.token,
'X-Target-Auth-Uri': v3_auth_url,
'X-Target-Project-Id': client.tenant_id,
'X-Target-User-Id': client.user_id,
}
def _execute_action(client, request, extra_headers={}):
_, result = client.create_action_execution(
request,
extra_headers=extra_headers
)
return result
def _get_create_stack_request():
stack_name = 'multi_vim_test_stack_{}'.format(
uuidutils.generate_uuid()[:8])
return {
'name': 'heat.stacks_create',
'input': {
'stack_name': stack_name,
"template": {"heat_template_version": "2013-05-23"}
}
}
def _get_list_stack_request():
return {
'name': 'heat.stacks_list',
}

View File

@ -1,112 +0,0 @@
# Copyright 2015 - 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.
from tempest.lib import decorators
from mistral_tempest_tests.tests import base
class OpenStackActionsTestsV2(base.TestCase):
_service = 'workflowv2'
# TODO(akuznetsova): add checks for task result after task_output
# TODO(akuznetsova): refactoring will be finished
@classmethod
def resource_setup(cls):
super(OpenStackActionsTestsV2, cls).resource_setup()
_, cls.wb = cls.client.create_workbook(
'openstack/action_collection_wb.yaml')
@decorators.attr(type='openstack')
@decorators.idempotent_id('9a999fc2-a089-4375-bc69-e1ed85b17a82')
def test_nova_actions(self):
wf_name = self.wb['name'] + '.nova'
_, execution = self.client.create_execution(wf_name)
self.client.wait_execution_success(execution)
executed_task = self.client.get_wf_tasks(wf_name)[-1]
self.assertEqual('SUCCESS', executed_task['state'])
@decorators.attr(type='openstack')
@decorators.idempotent_id('81bdc1c9-cd9a-4c97-b8ce-e44f5211eace')
def test_keystone_actions(self):
wf_name = self.wb['name'] + '.keystone'
_, execution = self.admin_client.create_execution(wf_name)
self.admin_client.wait_execution_success(execution)
executed_task = self.admin_client.get_wf_tasks(wf_name)[-1]
self.assertEqual('SUCCESS', executed_task['state'])
@decorators.attr(type='openstack')
@decorators.idempotent_id('fde681b8-3e1b-4172-a4b8-2fcac1f070d9')
def test_heat_actions(self):
wf_name = self.wb['name'] + '.heat'
_, execution = self.client.create_execution(wf_name)
self.client.wait_execution_success(execution)
executed_task = self.client.get_wf_tasks(wf_name)[-1]
self.assertEqual('SUCCESS', executed_task['state'])
@decorators.attr(type='openstack')
@decorators.idempotent_id('5981360d-f336-45ca-9d38-799c7a8ade26')
def test_glance_actions(self):
wf_name = self.wb['name'] + '.glance'
_, execution = self.client.create_execution(wf_name)
self.client.wait_execution_success(execution)
executed_task = self.client.get_wf_tasks(wf_name)[-1]
self.assertEqual('SUCCESS', executed_task['state'])
@decorators.attr(type='openstack')
@decorators.idempotent_id('a1f71a72-3681-4d32-aad9-117068717b33')
def test_cinder_actions(self):
wf_name = self.wb['name'] + '.cinder'
_, execution = self.client.create_execution(wf_name)
self.client.wait_execution_success(execution)
executed_task = self.client.get_wf_tasks(wf_name)[-1]
self.assertEqual('SUCCESS', executed_task['state'])
@decorators.attr(type='openstack')
@decorators.idempotent_id('586dd973-fc65-40e2-9a85-31418b22473a')
def test_neutron_actions(self):
wf_name = self.wb['name'] + '.neutron'
_, execution = self.client.create_execution(wf_name)
self.client.wait_execution_success(execution)
executed_task = self.client.get_wf_tasks(wf_name)[-1]
self.assertEqual('SUCCESS', executed_task['state'])
@decorators.attr(type='openstack')
@decorators.idempotent_id('985c1051-cf2e-4fd0-8ceb-a9b8110597a1')
def test_swift_actions(self):
wf_name = self.wb['name'] + '.swift'
_, execution = self.client.create_execution(wf_name)
self.client.wait_execution_success(execution)
executed_task = self.client.get_wf_tasks(wf_name)[-1]
self.assertEqual('SUCCESS', executed_task['state'])
@decorators.attr(type='openstack')
@decorators.idempotent_id('6f000ae8-3f41-49bd-b7a7-ad2256d80076')
def test_zaqar_actions(self):
wf_name = self.wb['name'] + '.zaqar'
_, execution = self.client.create_execution(wf_name)
self.client.wait_execution_success(execution)
executed_task = self.client.get_wf_tasks(wf_name)[-1]
self.assertEqual('SUCCESS', executed_task['state'])

View File

@ -1,295 +0,0 @@
# Copyright 2015 - 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 os import path
import time
from oslo_log import log as logging
from paramiko import ssh_exception
from tempest import config
from tempest.lib import decorators
from tempest.lib import exceptions
from mistral_tempest_tests.tests import base
from mistral_tempest_tests.tests import ssh_utils
from mistral_tempest_tests.tests import utils
LOG = logging.getLogger(__name__)
CONF = config.CONF
SSH_KEYS_DIRECTORY = path.expanduser("~/.ssh/")
class SSHActionsTestsV2(base.TestCaseAdvanced):
_service = 'workflowv2'
@classmethod
def _create_security_group_rule_ssh(cls):
sec_groups = (
cls.mgr.compute_security_groups_client.
list_security_groups()
)
sec_groups = sec_groups['security_groups']
default_group = next(
g for g in sec_groups if g['name'] == 'default'
)
rule = (
cls.mgr.compute_security_group_rules_client
.create_security_group_rule(
parent_group_id=default_group['id'],
ip_protocol="tcp",
from_port=22,
to_port=22,
cidr="0.0.0.0/0"
)
)
cls.ssh_rule_id = rule['security_group_rule']['id']
@classmethod
def _create_server(cls, server_name, **kwargs):
return cls.mgr.servers_client.create_server(
name=server_name,
imageRef=CONF.compute.image_ref,
flavorRef=CONF.compute.flavor_ref,
**kwargs
).get('server')
@classmethod
def _associate_floating_ip_to_server(cls, server_id):
fl_ip_client = cls.mgr.compute_floating_ips_client
all_ips = fl_ip_client.list_floating_ips().get(
'floating_ips'
)
free_ips = list(
[fl_ip for fl_ip in all_ips if fl_ip['instance_id'] is None]
)
if free_ips:
ip = free_ips[0]['ip']
else:
# Allocate new floating ip.
ip = fl_ip_client.create_floating_ip()['floating_ip']['ip']
# Associate IP.
fl_ip_client.associate_floating_ip_to_server(
floating_ip=ip,
server_id=server_id
)
return ip
@classmethod
def _wait_until_server_up(cls, server_ip, timeout=120, delay=2):
seconds_remain = timeout
LOG.info("Waiting server SSH [IP=%s]...", server_ip)
while seconds_remain > 0:
try:
ssh_utils.execute_command('cd', server_ip, None)
except ssh_exception.SSHException:
LOG.info("Server %s: SSH service is ready.")
return
except Exception as e:
LOG.info(str(e))
seconds_remain -= delay
time.sleep(delay)
else:
return
raise Exception(
"Failed waiting until server's '%s' SSH is up." % server_ip
)
@classmethod
def _wait_until_server_active(cls, server_id, timeout=60, delay=2):
seconds_remain = timeout
LOG.info("Waiting server [id=%s]...", server_id)
while seconds_remain > 0:
server_info = cls.mgr.servers_client.show_server(server_id)
if server_info['server']['status'] == 'ACTIVE':
return
seconds_remain -= delay
time.sleep(delay)
raise Exception(
"Failed waiting until server %s is active." % server_id
)
@classmethod
def _wait_until_server_delete(cls, server_id, timeout=60, delay=2):
seconds_remain = timeout
LOG.info("Deleting server [id=%s]...", server_id)
while seconds_remain > 0:
try:
cls.mgr.servers_client.show_server(server_id)
seconds_remain -= delay
time.sleep(delay)
except exceptions.NotFound:
return
raise RuntimeError("Server delete timeout!")
@classmethod
def resource_setup(cls):
super(SSHActionsTestsV2, cls).resource_setup()
# Modify security group for accessing VM via SSH.
cls._create_security_group_rule_ssh()
# Create keypair (public and private keys).
cls.private_key, cls.public_key = utils.generate_key_pair()
cls.key_name = 'mistral-functional-tests-key'
# If ZUUL_PROJECT is specified, it means
# tests are running on Jenkins gate.
if os.environ.get('ZUUL_PROJECT'):
cls.key_dir = "/opt/stack/new/.ssh/"
if not path.exists(cls.key_dir):
os.mkdir(cls.key_dir)
else:
cls.key_dir = SSH_KEYS_DIRECTORY
utils.save_text_to(
cls.private_key,
cls.key_dir + cls.key_name,
overwrite=True
)
LOG.info("Private key saved to %s", cls.key_dir + cls.key_name)
# Create keypair in nova.
cls.mgr.keypairs_client.create_keypair(
name=cls.key_name,
public_key=cls.public_key
)
# Start servers and provide key_name.
# Note: start public vm only after starting the guest one,
# so we can track public vm launching using ssh, but can't
# do the same with guest VM.
cls.guest_vm = cls._create_server(
'mistral-guest-vm',
key_name=cls.key_name
)
cls.public_vm = cls._create_server(
'mistral-public-vm',
key_name=cls.key_name
)
cls._wait_until_server_active(cls.public_vm['id'])
cls.public_vm_ip = cls._associate_floating_ip_to_server(
cls.public_vm['id']
)
# Wait until server is up.
cls._wait_until_server_up(cls.public_vm_ip)
# Update servers info.
cls.public_vm = cls.mgr.servers_client.show_server(
cls.public_vm['id']
).get('server')
cls.guest_vm = cls.mgr.servers_client.show_server(
cls.guest_vm['id']
).get('server')
@classmethod
def resource_cleanup(cls):
fl_ip_client = cls.mgr.compute_floating_ips_client
fl_ip_client.disassociate_floating_ip_from_server(
cls.public_vm_ip,
cls.public_vm['id']
)
cls.mgr.servers_client.delete_server(cls.public_vm['id'])
cls.mgr.servers_client.delete_server(cls.guest_vm['id'])
cls._wait_until_server_delete(cls.public_vm['id'])
cls._wait_until_server_delete(cls.guest_vm['id'])
cls.mgr.keypairs_client.delete_keypair(cls.key_name)
cls.mgr.compute_security_group_rules_client.delete_security_group_rule(
cls.ssh_rule_id
)
os.remove(cls.key_dir + cls.key_name)
super(SSHActionsTestsV2, cls).resource_cleanup()
@decorators.attr(type='sanity')
@decorators.idempotent_id('3e12a2ad-5b10-46b0-ae1f-ed34d3cc6ae2')
def test_run_ssh_action(self):
input_data = {
'cmd': 'hostname',
'host': self.public_vm_ip,
'username': CONF.validation.image_ssh_user,
'private_key_filename': self.key_name
}
resp, body = self.client.create_action_execution(
{
'name': 'std.ssh',
'input': json.dumps(input_data)
}
)
self.assertEqual(201, resp.status)
output = json.loads(body['output'])
self.assertIn(self.public_vm['name'], output['result'])
@decorators.attr(type='sanity')
@decorators.idempotent_id('6c09fb04-70b4-43a6-b5f8-a53ca92e66e0')
@decorators.skip_because(bug="1736685")
def test_run_ssh_proxied_action(self):
guest_vm_ip = self.guest_vm['addresses'].popitem()[1][0]['addr']
input_data = {
'cmd': 'hostname',
'host': guest_vm_ip,
'username': CONF.validation.image_ssh_user,
'private_key_filename': self.key_name,
'gateway_host': self.public_vm_ip,
'gateway_username': CONF.validation.image_ssh_user
}
resp, body = self.client.create_action_execution(
{
'name': 'std.ssh_proxied',
'input': json.dumps(input_data)
}
)
self.assertEqual(201, resp.status)
output = json.loads(body['output'])
self.assertIn(self.guest_vm['name'], output['result'])

View File

@ -1,103 +0,0 @@
# Copyright 2014 - 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.
from os import path
from oslo_log import log as logging
import paramiko
import six
KEY_PATH = path.expanduser("~/.ssh/")
LOG = logging.getLogger(__name__)
def _read_paramimko_stream(recv_func):
result = ''
buf = recv_func(1024)
while buf != '':
result += buf
buf = recv_func(1024)
return result
def _to_paramiko_private_key(private_key_filename, password=None):
if '../' in private_key_filename or '..\\' in private_key_filename:
raise OSError(
"Private key filename must not contain '..'. "
"Actual: %s" % private_key_filename
)
private_key_path = KEY_PATH + private_key_filename
return paramiko.RSAKey(
filename=private_key_path,
password=password
)
def _connect(host, username, password=None, pkey=None, proxy=None):
if isinstance(pkey, six.string_types):
pkey = _to_paramiko_private_key(pkey, password)
LOG.debug('Creating SSH connection to %s', host)
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(
host,
username=username,
password=password,
pkey=pkey,
sock=proxy
)
return ssh_client
def _cleanup(ssh_client):
ssh_client.close()
def _execute_command(ssh_client, cmd, get_stderr=False,
raise_when_error=True):
try:
chan = ssh_client.get_transport().open_session()
chan.exec_command(cmd)
# TODO(nmakhotkin): that could hang if stderr buffer overflows
stdout = _read_paramimko_stream(chan.recv)
stderr = _read_paramimko_stream(chan.recv_stderr)
ret_code = chan.recv_exit_status()
if ret_code and raise_when_error:
raise RuntimeError("Cmd: %s\nReturn code: %s\nstdout: %s"
% (cmd, ret_code, stdout))
if get_stderr:
return ret_code, stdout, stderr
else:
return ret_code, stdout
finally:
_cleanup(ssh_client)
def execute_command(cmd, host, username, password=None,
private_key_filename=None, get_stderr=False,
raise_when_error=True):
ssh_client = _connect(host, username, password, private_key_filename)
LOG.debug("Executing command %s", cmd)
return _execute_command(ssh_client, cmd, get_stderr, raise_when_error)

View File

@ -1,152 +0,0 @@
# Copyright 2013 - Mirantis, Inc.
# Copyright 2015 - Huawei Technologies Co. Ltd
# Copyright 2016 - Brocade Communications Systems, 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 contextlib
import json
import os
import shutil
import tempfile
from oslo_concurrency import processutils
class NotDefined(object):
"""Marker of an empty value.
In a number of cases None can't be used to express the semantics of
a not defined value because None is just a normal value rather than
a value set to denote that it's not defined. This class can be used
in such cases instead of None.
"""
pass
def get_dict_from_string(string, delimiter=','):
if not string:
return {}
kv_dicts = []
for kv_pair_str in string.split(delimiter):
kv_str = kv_pair_str.strip()
kv_list = kv_str.split('=')
if len(kv_list) > 1:
try:
value = json.loads(kv_list[1])
except ValueError:
value = kv_list[1]
kv_dicts += [{kv_list[0]: value}]
else:
kv_dicts += [kv_list[0]]
return get_dict_from_entries(kv_dicts)
def get_dict_from_entries(entries):
"""Transforms a list of entries into dictionary.
:param entries: A list of entries.
If an entry is a dictionary the method simply updates the result
dictionary with its content.
If an entry is not a dict adds {entry, NotDefined} into the result.
"""
result = {}
for e in entries:
if isinstance(e, dict):
result.update(e)
else:
# NOTE(kong): we put NotDefined here as the value of
# param without value specified, to distinguish from
# the valid values such as None, ''(empty string), etc.
result[e] = NotDefined
return result
@contextlib.contextmanager
def tempdir(**kwargs):
argdict = kwargs.copy()
if 'dir' not in argdict:
argdict['dir'] = '/tmp/'
tmpdir = tempfile.mkdtemp(**argdict)
try:
yield tmpdir
finally:
try:
shutil.rmtree(tmpdir)
except OSError as e:
raise OSError(
"Failed to delete temp dir %(dir)s (reason: %(reason)s)" %
{'dir': tmpdir, 'reason': e}
)
def save_text_to(text, file_path, overwrite=False):
if os.path.exists(file_path) and not overwrite:
raise OSError(
"Cannot save data to file. File %s already exists."
)
with open(file_path, 'w') as f:
f.write(text)
def generate_key_pair(key_length=2048):
"""Create RSA key pair with specified number of bits in key.
Returns tuple of private and public keys.
"""
with tempdir() as tmpdir:
keyfile = os.path.join(tmpdir, 'tempkey')
args = [
'ssh-keygen',
'-q', # quiet
'-N', '', # w/o passphrase
'-t', 'rsa', # create key of rsa type
'-f', keyfile, # filename of the key file
'-C', 'Generated-by-Mistral' # key comment
]
if key_length is not None:
args.extend(['-b', key_length])
processutils.execute(*args)
if not os.path.exists(keyfile):
# raise exc.DataAccessException(
# "Private key file hasn't been created"
# )
raise OSError("Private key file hasn't been created")
private_key = open(keyfile).read()
public_key_path = keyfile + '.pub'
if not os.path.exists(public_key_path):
# raise exc.DataAccessException(
# "Public key file hasn't been created"
# )
raise OSError("Private key file hasn't been created")
public_key = open(public_key_path).read()
return private_key, public_key

View File

@ -23,6 +23,18 @@
chdir: '{{ ansible_user_dir }}/workspace'
environment: '{{ zuul | zuul_legacy_vars }}'
- shell:
cmd: |
set -e
set -x
cat << 'EOF' >>"/tmp/dg-local.conf"
[[local|localrc]]
TEMPEST_PLUGINS='/opt/stack/new/mistral-tempest-plugin'
EOF
executable: /bin/bash
chdir: '{{ ansible_user_dir }}/workspace'
environment: '{{ zuul | zuul_legacy_vars }}'
- shell:
cmd: |
set -e
@ -38,11 +50,13 @@
fi
export ENABLED_SERVICES=heat,h-api,h-api-cfn,h-api-cw,h-eng,tempest
export DEVSTACK_GATE_TEMPEST=1
export PROJECTS="openstack/zaqar $PROJECTS"
export PROJECTS="openstack/python-zaqarclient $PROJECTS"
export PROJECTS="openstack/heat $PROJECTS"
export PROJECTS="openstack/mistral $PROJECTS"
export PROJECTS="openstack/mistral-dashboard $PROJECTS"
export PROJECTS="openstack/mistral-tempest-plugin $PROJECTS"
export DEVSTACK_LOCAL_CONFIG="enable_plugin mistral https://git.openstack.org/openstack/mistral"
export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_plugin heat git://git.openstack.org/openstack/heat"
export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_plugin zaqar git://git.openstack.org/openstack/zaqar"

View File

@ -23,6 +23,18 @@
chdir: '{{ ansible_user_dir }}/workspace'
environment: '{{ zuul | zuul_legacy_vars }}'
- shell:
cmd: |
set -e
set -x
cat << 'EOF' >>"/tmp/dg-local.conf"
[[local|localrc]]
TEMPEST_PLUGINS='/opt/stack/new/mistral-tempest-plugin'
EOF
executable: /bin/bash
chdir: '{{ ansible_user_dir }}/workspace'
environment: '{{ zuul | zuul_legacy_vars }}'
- shell:
cmd: |
set -e
@ -38,11 +50,13 @@
fi
export ENABLED_SERVICES=heat,h-api,h-api-cfn,h-api-cw,h-eng,tempest
export DEVSTACK_GATE_TEMPEST=1
export PROJECTS="openstack/zaqar $PROJECTS"
export PROJECTS="openstack/python-zaqarclient $PROJECTS"
export PROJECTS="openstack/heat $PROJECTS"
export PROJECTS="openstack/mistral $PROJECTS"
export PROJECTS="openstack/mistral-dashboard $PROJECTS"
export PROJECTS="openstack/mistral-tempest-plugin $PROJECTS"
export DEVSTACK_LOCAL_CONFIG="enable_plugin mistral https://git.openstack.org/openstack/mistral"
export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_plugin heat git://git.openstack.org/openstack/heat"
export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_plugin zaqar git://git.openstack.org/openstack/zaqar"

View File

@ -23,6 +23,18 @@
chdir: '{{ ansible_user_dir }}/workspace'
environment: '{{ zuul | zuul_legacy_vars }}'
- shell:
cmd: |
set -e
set -x
cat << 'EOF' >>"/tmp/dg-local.conf"
[[local|localrc]]
TEMPEST_PLUGINS='/opt/stack/new/mistral-tempest-plugin'
EOF
executable: /bin/bash
chdir: '{{ ansible_user_dir }}/workspace'
environment: '{{ zuul | zuul_legacy_vars }}'
- shell:
cmd: |
set -e
@ -38,11 +50,13 @@
fi
export ENABLED_SERVICES=heat,h-api,h-api-cfn,h-api-cw,h-eng,tempest
export DEVSTACK_GATE_TEMPEST=1
export PROJECTS="openstack/zaqar $PROJECTS"
export PROJECTS="openstack/python-zaqarclient $PROJECTS"
export PROJECTS="openstack/heat $PROJECTS"
export PROJECTS="openstack/mistral $PROJECTS"
export PROJECTS="openstack/mistral-dashboard $PROJECTS"
export PROJECTS="openstack/mistral-tempest-plugin $PROJECTS"
export DEVSTACK_LOCAL_CONFIG="enable_plugin mistral https://git.openstack.org/openstack/mistral"
export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_plugin heat git://git.openstack.org/openstack/heat"
export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_plugin zaqar git://git.openstack.org/openstack/zaqar"

View File

@ -56,9 +56,6 @@ oslo.config.opts.defaults =
oslo.policy.policies =
mistral = mistral.policies:list_rules
tempest.test_plugins =
mistral_test = mistral_tempest_tests.plugin:MistralTempestPlugin
mistral.actions =
std.async_noop = mistral.actions.std_actions:AsyncNoOpAction
std.noop = mistral.actions.std_actions:NoOpAction

View File

@ -37,7 +37,6 @@ basepython = python2.7
commands =
doc8 doc/source
flake8 {posargs} . {toxinidir}/tools/get_action_list.py {toxinidir}/tools/sync_db.py
check-uuid --package mistral_tempest_tests
[testenv:cover]
# Also do not run test_coverage_ext tests while gathering coverage as those