summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhparekh <hardik.parekh@nectechnologies.in>2016-01-20 15:23:19 +0900
committerhparekh <hardik.parekh@nectechnologies.in>2016-01-29 11:04:03 +0900
commit568bbf53844cc0dbee3bed0f7039a47f25943f6e (patch)
treed8575bd1be12e31428be0fa8c01c541819e10f03
parent39a025fce45dd5d5721d6263439b70c48359d798 (diff)
Removed mistral/tests/functional
All the changes has been merged to mistral_tempest_tests. Also scrits has been changed. Change-Id: I6c514a3c75f1b6e3b94b0e9b0e542697b68d9a02 Partially-Implements: blueprint mistral-tempest-plugin
Notes
Notes (review): Code-Review+2: Winson Chan <wcchan@stackstorm.com> Code-Review+1: Lingxian Kong <anlin.kong@gmail.com> Code-Review+2: Nikolay Mahotkin <nmakhotkin@mirantis.com> Code-Review+1: Ghanshyam Mann <ghanshyam.mann@nectechnologies.in> Workflow+1: Nikolay Mahotkin <nmakhotkin@mirantis.com> Verified+2: Jenkins Submitted-by: Jenkins Submitted-at: Wed, 03 Feb 2016 10:12:01 +0000 Reviewed-on: https://review.openstack.org/270021 Project: openstack/mistral Branch: refs/heads/master
-rwxr-xr-xfunctionaltests/run_tests.sh2
-rw-r--r--mistral/tests/functional/__init__.py0
-rw-r--r--mistral/tests/functional/api/__init__.py0
-rw-r--r--mistral/tests/functional/api/v2/__init__.py0
-rw-r--r--mistral/tests/functional/api/v2/test_mistral_basic_v2.py1156
-rw-r--r--mistral/tests/functional/base.py309
-rw-r--r--mistral/tests/functional/engine/__init__.py0
-rw-r--r--mistral/tests/functional/engine/actions/__init__.py0
-rw-r--r--mistral/tests/functional/engine/actions/v2/__init__.py0
-rw-r--r--mistral/tests/functional/engine/actions/v2/test_openstack_actions.py86
-rw-r--r--mistral/tests/functional/engine/actions/v2/test_ssh_actions.py272
-rw-r--r--mistral_tempest_tests/services/base.py16
-rw-r--r--mistral_tempest_tests/tests/api/v2/test_mistral_basic_v2.py5
-rw-r--r--mistral_tempest_tests/tests/scenario/engine/actions/v2/test_ssh_actions.py33
-rwxr-xr-xrun_functional_tests.sh2
15 files changed, 32 insertions, 1849 deletions
diff --git a/functionaltests/run_tests.sh b/functionaltests/run_tests.sh
index 99410e3..162ea39 100755
--- a/functionaltests/run_tests.sh
+++ b/functionaltests/run_tests.sh
@@ -38,4 +38,4 @@ MISTRALCLIENT_DIR=/opt/stack/new/python-mistralclient
38export PYTHONPATH=$PYTHONPATH:$TEMPEST_DIR 38export PYTHONPATH=$PYTHONPATH:$TEMPEST_DIR
39 39
40pwd 40pwd
41nosetests -sv mistral/tests/functional/ 41nosetests -sv mistral_tempest_tests/tests/
diff --git a/mistral/tests/functional/__init__.py b/mistral/tests/functional/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/mistral/tests/functional/__init__.py
+++ /dev/null
diff --git a/mistral/tests/functional/api/__init__.py b/mistral/tests/functional/api/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/mistral/tests/functional/api/__init__.py
+++ /dev/null
diff --git a/mistral/tests/functional/api/v2/__init__.py b/mistral/tests/functional/api/v2/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/mistral/tests/functional/api/v2/__init__.py
+++ /dev/null
diff --git a/mistral/tests/functional/api/v2/test_mistral_basic_v2.py b/mistral/tests/functional/api/v2/test_mistral_basic_v2.py
deleted file mode 100644
index 5904129..0000000
--- a/mistral/tests/functional/api/v2/test_mistral_basic_v2.py
+++ /dev/null
@@ -1,1156 +0,0 @@
1# Copyright 2014 Mirantis, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15import json
16
17from oslo_log import log as logging
18import six
19from tempest import test
20from tempest_lib import decorators
21from tempest_lib import exceptions
22
23from mistral.tests.functional import base
24from mistral import utils
25
26
27LOG = logging.getLogger(__name__)
28
29
30class WorkbookTestsV2(base.TestCase):
31
32 _service = 'workflowv2'
33
34 def tearDown(self):
35 for wf in self.client.workflows:
36 self.client.delete_obj('workflows', wf)
37 self.client.workflows = []
38
39 super(WorkbookTestsV2, self).tearDown()
40
41 @test.attr(type='smoke')
42 def test_get_list_workbooks(self):
43 resp, body = self.client.get_list_obj('workbooks')
44
45 self.assertEqual(200, resp.status)
46 self.assertEqual([], body['workbooks'])
47
48 @test.attr(type='sanity')
49 def test_create_and_delete_workbook(self):
50 resp, body = self.client.create_workbook('wb_v2.yaml')
51 name = body['name']
52 self.assertEqual(201, resp.status)
53
54 resp, body = self.client.get_list_obj('workbooks')
55 self.assertEqual(200, resp.status)
56 self.assertEqual(name, body['workbooks'][0]['name'])
57
58 self.client.delete_obj('workbooks', name)
59 self.client.workbooks.remove(name)
60
61 _, body = self.client.get_list_obj('workbooks')
62 self.assertEqual([], body['workbooks'])
63
64 @test.attr(type='sanity')
65 def test_get_workbook(self):
66 _, body = self.client.create_workbook('wb_v2.yaml')
67 name = body['name']
68
69 resp, body = self.client.get_object('workbooks', name)
70 self.assertEqual(200, resp.status)
71 self.assertEqual(name, body['name'])
72
73 @test.attr(type='sanity')
74 def test_update_workbook(self):
75 _, body = self.client.create_workbook('wb_v2.yaml')
76 name = body['name']
77 resp, body = self.client.update_request('workbooks', 'wb_v2.yaml')
78
79 self.assertEqual(200, resp.status)
80 self.assertEqual(name, body['name'])
81
82 @test.attr(type='sanity')
83 def test_get_workbook_definition(self):
84 _, body = self.client.create_workbook('wb_v2.yaml')
85 name = body['name']
86 resp, body = self.client.get_definition('workbooks', name)
87
88 self.assertEqual(200, resp.status)
89 self.assertIsNotNone(body)
90
91 @test.attr(type='negative')
92 def test_get_nonexistent_workbook_definition(self):
93 self.assertRaises(exceptions.NotFound,
94 self.client.get_definition,
95 'workbooks', 'nonexist')
96
97 @test.attr(type='negative')
98 def test_get_nonexistent_workbook(self):
99 self.assertRaises(exceptions.NotFound, self.client.get_object,
100 'workbooks', 'nonexist')
101
102 @test.attr(type='negative')
103 def test_double_create_workbook(self):
104 _, body = self.client.create_workbook('wb_v2.yaml')
105 name = body['name']
106 self.assertRaises(exceptions.Conflict,
107 self.client.create_workbook,
108 'wb_v2.yaml')
109
110 self.client.delete_obj('workbooks', name)
111 self.client.workbooks.remove(name)
112 _, body = self.client.get_list_obj('workbooks')
113
114 self.assertEqual([], body['workbooks'])
115
116 @test.attr(type='negative')
117 def test_create_wb_with_invalid_def(self):
118 self.assertRaises(
119 exceptions.BadRequest,
120 self.client.create_workbook,
121 'wb_v1.yaml'
122 )
123
124 @test.attr(type='negative')
125 def test_update_wb_with_invalid_def(self):
126 self.assertRaises(
127 exceptions.BadRequest,
128 self.client.update_request,
129 'workbooks',
130 'wb_v1.yaml'
131 )
132
133
134class WorkflowTestsV2(base.TestCase):
135
136 _service = 'workflowv2'
137
138 def tearDown(self):
139 for wf in self.client.workflows:
140 self.client.delete_obj('workflows', wf)
141 self.client.workflows = []
142
143 super(WorkflowTestsV2, self).tearDown()
144
145 @test.attr(type='smoke')
146 def test_get_list_workflows(self):
147 resp, body = self.client.get_list_obj('workflows')
148 self.assertEqual(200, resp.status)
149
150 names = [wf['name'] for wf in body['workflows']]
151
152 self.assertIn('std.create_instance', names)
153
154 self.assertNotIn('next', body)
155
156 @test.attr(type='smoke')
157 def test_get_list_workflows_with_fields(self):
158 resp, body = self.client.get_list_obj('workflows?fields=name')
159
160 self.assertEqual(200, resp.status)
161
162 for wf in body['workflows']:
163 self.assertListEqual(['id', 'name'], wf.keys())
164
165 @test.attr(type='smoke')
166 def test_get_list_workflows_with_pagination(self):
167 resp, body = self.client.get_list_obj(
168 'workflows?limit=1&sort_keys=name&sort_dirs=desc'
169 )
170
171 self.assertEqual(200, resp.status)
172 self.assertEqual(1, len(body['workflows']))
173 self.assertIn('next', body)
174
175 name_1 = body['workflows'][0].get('name')
176 next = body.get('next')
177
178 param_dict = utils.get_dict_from_string(
179 next.split('?')[1],
180 delimiter='&'
181 )
182
183 expected_sub_dict = {
184 'limit': 1,
185 'sort_keys': 'name',
186 'sort_dirs': 'desc'
187 }
188
189 self.assertDictContainsSubset(expected_sub_dict, param_dict)
190
191 # Query again using 'next' hint
192 url_param = next.split('/')[-1]
193 resp, body = self.client.get_list_obj(url_param)
194
195 self.assertEqual(200, resp.status)
196 self.assertEqual(1, len(body['workflows']))
197
198 name_2 = body['workflows'][0].get('name')
199
200 self.assertGreater(name_1, name_2)
201
202 @test.attr(type='negative')
203 def test_get_list_workflows_nonexist_sort_dirs(self):
204 context = self.assertRaises(
205 exceptions.BadRequest,
206 self.client.get_list_obj,
207 'workflows?limit=1&sort_keys=id&sort_dirs=nonexist'
208 )
209
210 self.assertIn(
211 'Unknown sort direction',
212 context.resp_body.get('faultstring')
213 )
214
215 @test.attr(type='negative')
216 def test_get_list_workflows_invalid_limit(self):
217 context = self.assertRaises(
218 exceptions.BadRequest,
219 self.client.get_list_obj,
220 'workflows?limit=-1&sort_keys=id&sort_dirs=asc'
221 )
222
223 self.assertIn(
224 'Limit must be positive',
225 context.resp_body.get('faultstring')
226 )
227
228 @test.attr(type='negative')
229 def test_get_list_workflows_duplicate_sort_keys(self):
230 context = self.assertRaises(
231 exceptions.BadRequest,
232 self.client.get_list_obj,
233 'workflows?limit=1&sort_keys=id,id&sort_dirs=asc,asc'
234 )
235
236 self.assertIn(
237 'Length of sort_keys must be equal or greater than sort_dirs',
238 context.resp_body.get('faultstring')
239 )
240
241 @test.attr(type='sanity')
242 def test_create_and_delete_workflow(self):
243 resp, body = self.client.create_workflow('wf_v2.yaml')
244 name = body['workflows'][0]['name']
245
246 self.assertEqual(201, resp.status)
247
248 resp, body = self.client.get_list_obj('workflows')
249 self.assertEqual(200, resp.status)
250
251 names = [wf['name'] for wf in body['workflows']]
252 self.assertIn(name, names)
253
254 self.client.delete_obj('workflows', name)
255 self.client.workflows.remove(name)
256
257 _, body = self.client.get_list_obj('workflows')
258
259 names = [wf['name'] for wf in body['workflows']]
260 self.assertNotIn(name, names)
261
262 @test.attr(type='sanity')
263 def test_get_workflow(self):
264 _, body = self.client.create_workflow('wf_v2.yaml')
265 name = body['workflows'][0]['name']
266
267 resp, body = self.client.get_object('workflows', name)
268
269 self.assertEqual(200, resp.status)
270 self.assertEqual(name, body['name'])
271
272 @test.attr(type='sanity')
273 def test_update_workflow(self):
274 _, body = self.client.create_workflow('wf_v2.yaml')
275 name = body['workflows'][0]['name']
276
277 resp, body = self.client.update_request('workflows', 'wf_v2.yaml')
278
279 self.assertEqual(200, resp.status)
280 self.assertEqual(name, body['workflows'][0]['name'])
281
282 @test.attr(type='sanity')
283 def test_get_workflow_definition(self):
284 _, body = self.client.create_workflow('wf_v2.yaml')
285 name = body['workflows'][0]['name']
286
287 resp, body = self.client.get_definition('workflows', name)
288
289 self.assertEqual(200, resp.status)
290 self.assertIsNotNone(body)
291
292 @test.attr(type='sanity')
293 def test_get_workflow_uploaded_in_wb(self):
294 _, body = self.client.create_workbook('wb_v2.yaml')
295 wb_name = body['name']
296
297 _, body = self.client.get_list_obj('workflows')
298 wf_names = [wf['name'] for wf in body['workflows']
299 if wf['name'].startswith(wb_name)]
300
301 self.assertNotEmpty(wf_names)
302
303 @test.attr(type='negative')
304 def test_get_nonexistent_workflow_definition(self):
305 self.assertRaises(exceptions.NotFound,
306 self.client.get_definition,
307 'workflows', 'nonexist')
308
309 @test.attr(type='negative')
310 def test_get_nonexistent_workflow(self):
311 self.assertRaises(exceptions.NotFound, self.client.get_object,
312 'workflows', 'nonexist')
313
314 @test.attr(type='negative')
315 def test_double_create_workflows(self):
316 _, body = self.client.create_workflow('wf_v2.yaml')
317 self.assertRaises(exceptions.Conflict,
318 self.client.create_workflow,
319 'wf_v2.yaml')
320
321 @test.attr(type='negative')
322 def test_create_wf_with_invalid_def(self):
323 self.assertRaises(exceptions.BadRequest,
324 self.client.create_workflow,
325 'wb_v1.yaml')
326
327 @test.attr(type='negative')
328 def test_update_wf_with_invalid_def(self):
329 self.assertRaises(exceptions.BadRequest,
330 self.client.update_request,
331 'workflows', 'wb_v1.yaml')
332
333 @test.attr(type='negative')
334 def test_delete_wf_with_trigger_associate(self):
335 tr_name = 'trigger'
336 resp, body = self.client.create_workflow('wf_v2.yaml')
337 name = body['workflows'][0]['name']
338 resp, body = self.client.create_cron_trigger(
339 tr_name, name, None, '5 * * * *')
340
341 try:
342 self.assertRaises(
343 exceptions.BadRequest,
344 self.client.delete_obj,
345 'workflows',
346 name
347 )
348 finally:
349 self.client.delete_obj('cron_triggers', tr_name)
350 self.client.triggers.remove(tr_name)
351
352 @test.attr(type='negative')
353 def test_delete_wf_with_trigger_associate_in_other_tenant(self):
354 tr_name = 'trigger'
355 _, body = self.client.create_workflow('wf_v2.yaml', scope='public')
356 name = body['workflows'][0]['name']
357 resp, body = self.alt_client.create_cron_trigger(
358 tr_name,
359 name,
360 None,
361 '5 * * * *'
362 )
363
364 try:
365 exception = self.assertRaises(
366 exceptions.BadRequest,
367 self.client.delete_obj,
368 'workflows',
369 name
370 )
371
372 self.assertIn(
373 "Can't delete workflow that has triggers associated",
374 exception.resp_body['faultstring']
375 )
376 finally:
377 self.alt_client.delete_obj('cron_triggers', tr_name)
378 self.alt_client.triggers.remove(tr_name)
379
380 @test.attr(type='negative')
381 def test_delete_nonexistent_wf(self):
382 self.assertRaises(exceptions.NotFound,
383 self.client.delete_obj,
384 'workflows', 'nonexist')
385
386
387class ExecutionTestsV2(base.TestCase):
388
389 _service = 'workflowv2'
390
391 def setUp(self):
392 super(ExecutionTestsV2, self).setUp()
393
394 _, body = self.client.create_workflow('wf_v2.yaml')
395
396 self.direct_wf_name = 'wf'
397 self.direct_wf2_name = 'wf2'
398 reverse_wfs = [wf for wf in body['workflows'] if wf['name'] == 'wf1']
399 self.reverse_wf = reverse_wfs[0]
400
401 def tearDown(self):
402 for wf in self.client.workflows:
403 self.client.delete_obj('workflows', wf)
404 self.client.workflows = []
405
406 for ex in self.client.executions:
407 self.client.delete_obj('executions', ex)
408 self.client.executions = []
409
410 super(ExecutionTestsV2, self).tearDown()
411
412 @test.attr(type='smoke')
413 def test_get_list_executions(self):
414 resp, body = self.client.get_list_obj('executions')
415 self.assertEqual(200, resp.status)
416 self.assertNotIn('next', body)
417
418 @test.attr(type='smoke')
419 def test_get_list_executions_with_pagination(self):
420 resp, body = self.client.create_execution(self.direct_wf_name)
421 exec_id_1 = body['id']
422
423 self.assertEqual(201, resp.status)
424
425 resp, body = self.client.create_execution(self.direct_wf2_name)
426 exec_id_2 = body['id']
427
428 self.assertEqual(201, resp.status)
429
430 resp, body = self.client.get_list_obj('executions')
431
432 self.assertIn(exec_id_1, [ex['id'] for ex in body['executions']])
433 self.assertIn(exec_id_2, [ex['id'] for ex in body['executions']])
434
435 resp, body = self.client.get_list_obj(
436 'executions?limit=1&sort_keys=workflow_name&sort_dirs=asc'
437 )
438
439 self.assertEqual(200, resp.status)
440 self.assertEqual(1, len(body['executions']))
441 self.assertIn('next', body)
442
443 workflow_name_1 = body['executions'][0].get('workflow_name')
444 next = body.get('next')
445 param_dict = utils.get_dict_from_string(
446 next.split('?')[1],
447 delimiter='&'
448 )
449
450 expected_dict = {
451 'limit': 1,
452 'sort_keys': 'workflow_name',
453 'sort_dirs': 'asc',
454 }
455
456 self.assertTrue(
457 set(expected_dict.items()).issubset(set(param_dict.items()))
458 )
459
460 # Query again using 'next' link
461 url_param = next.split('/')[-1]
462 resp, body = self.client.get_list_obj(url_param)
463
464 self.assertEqual(200, resp.status)
465 self.assertEqual(1, len(body['executions']))
466
467 workflow_name_2 = body['executions'][0].get('workflow_name')
468
469 self.assertGreater(workflow_name_2, workflow_name_1)
470
471 @test.attr(type='sanity')
472 def test_create_execution_for_direct_wf(self):
473 resp, body = self.client.create_execution(self.direct_wf_name)
474 exec_id = body['id']
475 self.assertEqual(201, resp.status)
476 self.assertEqual(self.direct_wf_name, body['workflow_name'])
477 self.assertEqual('RUNNING', body['state'])
478
479 resp, body = self.client.get_list_obj('executions')
480 self.assertIn(exec_id,
481 [ex_id['id'] for ex_id in body['executions']])
482
483 @test.attr(type='sanity')
484 def test_create_execution_for_reverse_wf(self):
485 resp, body = self.client.create_execution(
486 self.reverse_wf['name'],
487 {self.reverse_wf['input']: "Bye"},
488 {"task_name": "goodbye"})
489
490 exec_id = body['id']
491 self.assertEqual(201, resp.status)
492 self.assertEqual(self.reverse_wf['name'], body['workflow_name'])
493 self.assertEqual('RUNNING', body['state'])
494
495 resp, body = self.client.get_list_obj('executions')
496 self.assertIn(exec_id,
497 [ex_id['id'] for ex_id in body['executions']])
498
499 resp, body = self.client.get_object('executions', exec_id)
500 # TODO(nmakhotkin): Fix this loop. It is infinite now.
501 while body['state'] != 'SUCCESS':
502 resp, body = self.client.get_object('executions', exec_id)
503 self.assertEqual(200, resp.status)
504 self.assertEqual('SUCCESS', body['state'])
505
506 @test.attr(type='sanity')
507 def test_get_execution(self):
508 _, execution = self.client.create_execution(self.direct_wf_name)
509
510 resp, body = self.client.get_object('executions', execution['id'])
511
512 del execution['state']
513 del body['state']
514
515 self.assertEqual(200, resp.status)
516 self.assertEqual(execution['id'], body['id'])
517
518 @test.attr(type='sanity')
519 def test_update_execution_pause(self):
520 _, execution = self.client.create_execution(self.direct_wf_name)
521 resp, body = self.client.update_execution(
522 execution['id'], '{"state": "PAUSED"}')
523
524 self.assertEqual(200, resp.status)
525 self.assertEqual('PAUSED', body['state'])
526
527 @test.attr(type='sanity')
528 def test_update_execution_description(self):
529 _, execution = self.client.create_execution(self.direct_wf_name)
530 resp, body = self.client.update_execution(
531 execution['id'], '{"description": "description"}')
532
533 self.assertEqual(200, resp.status)
534 self.assertEqual('description', body['description'])
535
536 @test.attr(type='sanity')
537 def test_update_execution_fail(self):
538 _, execution = self.client.create_execution(self.direct_wf_name)
539 resp, body = self.client.update_execution(
540 execution['id'], '{"state": "ERROR", "state_info": "Forced"}')
541
542 self.assertEqual(200, resp.status)
543 self.assertEqual('ERROR', body['state'])
544 self.assertEqual('Forced', body['state_info'])
545
546 @test.attr(type='negative')
547 def test_get_nonexistent_execution(self):
548 self.assertRaises(exceptions.NotFound, self.client.get_object,
549 'executions', '1a2b3c')
550
551 @test.attr(type='negative')
552 def test_update_nonexistent_execution(self):
553 put_body = '{"state": "STOPPED"}'
554
555 self.assertRaises(exceptions.NotFound,
556 self.client.update_execution,
557 '1a2b3c', put_body)
558
559 @test.attr(type='negative')
560 def test_delete_nonexistent_execution(self):
561 self.assertRaises(exceptions.NotFound,
562 self.client.delete_obj,
563 'executions', 'nonexist')
564
565 @test.attr(type='negative')
566 def test_create_ex_for_nonexistent_wf(self):
567 self.assertRaises(exceptions.NotFound,
568 self.client.create_execution,
569 'nonexist')
570
571 @test.attr(type='negative')
572 def test_create_execution_for_reverse_wf_invalid_start_task(self):
573 _, wf_ex = self.client.create_execution(
574 self.reverse_wf['name'],
575 {
576 self.reverse_wf['input']: "Bye"},
577 {
578 "task_name": "nonexist"
579 }
580 )
581
582 self.assertEqual("ERROR", wf_ex['state'])
583 self.assertIn("Invalid task name", wf_ex['state_info'])
584
585 @test.attr(type='negative')
586 def test_create_execution_forgot_input_params(self):
587 self.assertRaises(exceptions.BadRequest,
588 self.client.create_execution,
589 self.reverse_wf['name'],
590 params={"task_name": "nonexist"})
591
592 @test.attr(type='sanity')
593 def test_action_ex_concurrency(self):
594 resp, wf = self.client.create_workflow("wf_action_ex_concurrency.yaml")
595 self.assertEqual(201, resp.status)
596
597 wf_name = wf['workflows'][0]['name']
598 resp, execution = self.client.create_execution(wf_name)
599
600 self.assertEqual(201, resp.status)
601 self.assertEqual('RUNNING', execution['state'])
602
603 self.client.wait_execution_success(execution)
604
605 @test.attr(type='sanity')
606 def test_task_ex_concurrency(self):
607 resp, wf = self.client.create_workflow("wf_task_ex_concurrency.yaml")
608 self.assertEqual(201, resp.status)
609
610 wf_name = wf['workflows'][0]['name']
611 resp, execution = self.client.create_execution(wf_name)
612
613 self.assertEqual(201, resp.status)
614 self.assertEqual('RUNNING', execution['state'])
615
616 self.client.wait_execution(execution, target_state='ERROR')
617
618
619class CronTriggerTestsV2(base.TestCase):
620
621 _service = 'workflowv2'
622
623 def setUp(self):
624 super(CronTriggerTestsV2, self).setUp()
625
626 _, body = self.client.create_workflow('wf_v2.yaml')
627 self.wf_name = body['workflows'][0]['name']
628
629 def tearDown(self):
630
631 for tr in self.client.triggers:
632 self.client.delete_obj('cron_triggers', tr)
633 self.client.triggers = []
634
635 for wf in self.client.workflows:
636 self.client.delete_obj('workflows', wf)
637 self.client.workflows = []
638
639 super(CronTriggerTestsV2, self).tearDown()
640
641 @test.attr(type='smoke')
642 def test_get_list_cron_triggers(self):
643 resp, body = self.client.get_list_obj('cron_triggers')
644
645 self.assertEqual(200, resp.status)
646 self.assertEqual([], body['cron_triggers'])
647
648 @test.attr(type='sanity')
649 def test_create_and_delete_cron_triggers(self):
650 tr_name = 'trigger'
651
652 resp, body = self.client.create_cron_trigger(
653 tr_name, self.wf_name, None, '5 * * * *')
654 self.assertEqual(201, resp.status)
655 self.assertEqual(tr_name, body['name'])
656
657 resp, body = self.client.get_list_obj('cron_triggers')
658 self.assertEqual(200, resp.status)
659
660 trs_names = [tr['name'] for tr in body['cron_triggers']]
661 self.assertIn(tr_name, trs_names)
662
663 self.client.delete_obj('cron_triggers', tr_name)
664 self.client.triggers.remove(tr_name)
665
666 _, body = self.client.get_list_obj('cron_triggers')
667
668 trs_names = [tr['name'] for tr in body['cron_triggers']]
669 self.assertNotIn(tr_name, trs_names)
670
671 @test.attr(type='sanity')
672 def test_create_and_delete_oneshot_cron_triggers(self):
673 tr_name = 'trigger'
674
675 resp, body = self.client.create_cron_trigger(
676 tr_name, self.wf_name, None, None, "4242-12-25 13:37")
677 self.assertEqual(201, resp.status)
678 self.assertEqual(tr_name, body['name'])
679 self.assertEqual("4242-12-25 13:37:00", body['next_execution_time'])
680
681 resp, body = self.client.get_list_obj('cron_triggers')
682 self.assertEqual(200, resp.status)
683
684 trs_names = [tr['name'] for tr in body['cron_triggers']]
685 self.assertIn(tr_name, trs_names)
686
687 self.client.delete_obj('cron_triggers', tr_name)
688 self.client.triggers.remove(tr_name)
689
690 _, body = self.client.get_list_obj('cron_triggers')
691
692 trs_names = [tr['name'] for tr in body['cron_triggers']]
693 self.assertNotIn(tr_name, trs_names)
694
695 @test.attr(type='sanity')
696 def test_create_two_cron_triggers_for_one_wf(self):
697 tr_name_1 = 'trigger1'
698 tr_name_2 = 'trigger2'
699
700 resp, body = self.client.create_cron_trigger(
701 tr_name_1, self.wf_name, None, '5 * * * *')
702 self.assertEqual(201, resp.status)
703 self.assertEqual(tr_name_1, body['name'])
704
705 resp, body = self.client.create_cron_trigger(
706 tr_name_2, self.wf_name, None, '15 * * * *')
707 self.assertEqual(201, resp.status)
708 self.assertEqual(tr_name_2, body['name'])
709
710 resp, body = self.client.get_list_obj('cron_triggers')
711 self.assertEqual(200, resp.status)
712
713 trs_names = [tr['name'] for tr in body['cron_triggers']]
714 self.assertIn(tr_name_1, trs_names)
715 self.assertIn(tr_name_2, trs_names)
716
717 @test.attr(type='sanity')
718 def test_get_cron_trigger(self):
719 tr_name = 'trigger'
720 self.client.create_cron_trigger(
721 tr_name, self.wf_name, None, '5 * * * *')
722
723 resp, body = self.client.get_object('cron_triggers', tr_name)
724
725 self.assertEqual(200, resp.status)
726 self.assertEqual(tr_name, body['name'])
727
728 @test.attr(type='negative')
729 def test_create_cron_trigger_nonexistent_wf(self):
730 self.assertRaises(exceptions.NotFound,
731 self.client.create_cron_trigger,
732 'trigger', 'nonexist', None, '5 * * * *')
733
734 @test.attr(type='negative')
735 def test_create_cron_trigger_invalid_count(self):
736 self.assertRaises(exceptions.BadRequest,
737 self.client.create_cron_trigger,
738 'trigger', 'nonexist', None, '5 * * * *', None, "q")
739
740 @test.attr(type='negative')
741 def test_create_cron_trigger_negative_count(self):
742 self.assertRaises(exceptions.BadRequest,
743 self.client.create_cron_trigger,
744 'trigger', 'nonexist', None, '5 * * * *', None, -1)
745
746 @test.attr(type='negative')
747 def test_create_cron_trigger_invalid_first_date(self):
748 self.assertRaises(exceptions.BadRequest,
749 self.client.create_cron_trigger,
750 'trigger', 'nonexist', None, '5 * * * *', "q")
751
752 @test.attr(type='negative')
753 def test_create_cron_trigger_count_only(self):
754 self.assertRaises(exceptions.BadRequest,
755 self.client.create_cron_trigger,
756 'trigger', 'nonexist', None, None, None, "42")
757
758 @test.attr(type='negative')
759 def test_create_cron_trigger_date_and_count_without_pattern(self):
760 self.assertRaises(exceptions.BadRequest,
761 self.client.create_cron_trigger,
762 'trigger', 'nonexist', None, None,
763 "4242-12-25 13:37", "42")
764
765 @test.attr(type='negative')
766 def test_get_nonexistent_cron_trigger(self):
767 self.assertRaises(exceptions.NotFound,
768 self.client.get_object,
769 'cron_triggers', 'trigger')
770
771 @test.attr(type='negative')
772 def test_delete_nonexistent_trigger(self):
773 self.assertRaises(exceptions.NotFound,
774 self.client.delete_obj,
775 'cron_triggers', 'trigger')
776
777 @test.attr(type='negative')
778 def test_create_two_cron_triggers_with_same_name(self):
779 tr_name = 'trigger'
780 self.client.create_cron_trigger(
781 tr_name, self.wf_name, None, '5 * * * *')
782 self.assertRaises(exceptions.Conflict,
783 self.client.create_cron_trigger,
784 tr_name, self.wf_name, None, '5 * * * *')
785
786 @decorators.skip_because(bug="1383146")
787 @test.attr(type='negative')
788 def test_create_two_cron_triggers_with_same_pattern(self):
789 self.client.create_trigger(
790 'trigger1', self.wf_name, None, '5 * * * *')
791 self.assertRaises(exceptions.Conflict,
792 self.client.create_cron_trigger,
793 'trigger2', self.wf_name, None, '5 * * * *')
794
795 @test.attr(type='negative')
796 def test_invalid_cron_pattern_not_enough_params(self):
797 self.assertRaises(exceptions.BadRequest,
798 self.client.create_cron_trigger,
799 'trigger', self.wf_name, None, '5 *')
800
801 @test.attr(type='negative')
802 def test_invalid_cron_pattern_out_of_range(self):
803 self.assertRaises(exceptions.BadRequest,
804 self.client.create_cron_trigger,
805 'trigger', self.wf_name, None, '88 * * * *')
806
807
808class ActionTestsV2(base.TestCase):
809
810 _service = 'workflowv2'
811
812 def get_field_value(self, body, act_name, field):
813 return [body['actions'][i][field]
814 for i in range(len(body['actions']))
815 if body['actions'][i]['name'] == act_name][0]
816
817 def tearDown(self):
818 for act in self.client.actions:
819 self.client.delete_obj('actions', act)
820 self.client.actions = []
821
822 super(ActionTestsV2, self).tearDown()
823
824 @test.attr(type='smoke')
825 def test_get_list_actions(self):
826 resp, body = self.client.get_list_obj('actions')
827
828 self.assertEqual(200, resp.status)
829 self.assertNotEqual([], body['actions'])
830 self.assertNotIn('next', body)
831
832 @test.attr(type='smoke')
833 def test_get_list_actions_with_pagination(self):
834 resp, body = self.client.get_list_obj(
835 'actions?limit=1&sort_keys=name&sort_dirs=desc'
836 )
837
838 self.assertEqual(200, resp.status)
839 self.assertEqual(1, len(body['actions']))
840 self.assertIn('next', body)
841
842 name_1 = body['actions'][0].get('name')
843 next = body.get('next')
844
845 param_dict = utils.get_dict_from_string(
846 next.split('?')[1],
847 delimiter='&'
848 )
849
850 expected_sub_dict = {
851 'limit': 1,
852 'sort_keys': 'name',
853 'sort_dirs': 'desc'
854 }
855
856 self.assertDictContainsSubset(expected_sub_dict, param_dict)
857
858 # Query again using 'next' hint
859 url_param = next.split('/')[-1]
860 resp, body = self.client.get_list_obj(url_param)
861
862 self.assertEqual(200, resp.status)
863 self.assertEqual(1, len(body['actions']))
864
865 name_2 = body['actions'][0].get('name')
866
867 self.assertGreater(name_1, name_2)
868
869 @test.attr(type='negative')
870 def test_get_list_actions_nonexist_sort_dirs(self):
871 context = self.assertRaises(
872 exceptions.BadRequest,
873 self.client.get_list_obj,
874 'actions?limit=1&sort_keys=id&sort_dirs=nonexist'
875 )
876
877 self.assertIn(
878 'Unknown sort direction',
879 context.resp_body.get('faultstring')
880 )
881
882 @test.attr(type='negative')
883 def test_get_list_actions_invalid_limit(self):
884 context = self.assertRaises(
885 exceptions.BadRequest,
886 self.client.get_list_obj,
887 'actions?limit=-1&sort_keys=id&sort_dirs=asc'
888 )
889
890 self.assertIn(
891 'Limit must be positive',
892 context.resp_body.get('faultstring')
893 )
894
895 @test.attr(type='negative')
896 def test_get_list_actions_duplicate_sort_keys(self):
897 context = self.assertRaises(
898 exceptions.BadRequest,
899 self.client.get_list_obj,
900 'actions?limit=1&sort_keys=id,id&sort_dirs=asc,asc'
901 )
902
903 self.assertIn(
904 'Length of sort_keys must be equal or greater than sort_dirs',
905 context.resp_body.get('faultstring')
906 )
907
908 @test.attr(type='sanity')
909 def test_create_and_delete_few_actions(self):
910 resp, body = self.client.create_action('action_v2.yaml')
911 self.assertEqual(201, resp.status)
912
913 created_acts = [action['name'] for action in body['actions']]
914
915 resp, body = self.client.get_list_obj('actions')
916 self.assertEqual(200, resp.status)
917
918 actions = [action['name'] for action in body['actions']]
919
920 for act in created_acts:
921 self.assertIn(act, actions)
922 self.client.delete_obj('actions', act)
923
924 _, body = self.client.get_list_obj('actions')
925 actions = [action['name'] for action in body['actions']]
926
927 for act in created_acts:
928 self.assertNotIn(act, actions)
929 self.client.actions.remove(act)
930
931 @test.attr(type='sanity')
932 def test_get_action(self):
933 _, body = self.client.create_action('action_v2.yaml')
934 action_name = body['actions'][0]['name']
935 resp, body = self.client.get_object('actions', action_name)
936
937 self.assertEqual(200, resp.status)
938 self.assertEqual(action_name, body['name'])
939
940 @test.attr(type='sanity')
941 def test_update_action(self):
942 _, body = self.client.create_action('action_v2.yaml')
943 action = body['actions'][0]['name']
944
945 act_created_at = self.get_field_value(
946 body=body, act_name=action, field='created_at')
947
948 self.assertNotIn('updated at', body['actions'])
949
950 resp, body = self.client.update_request('actions', 'action_v2.yaml')
951 self.assertEqual(200, resp.status)
952
953 actions = [act['name'] for act in body['actions']]
954 self.assertIn(action, actions)
955
956 updated_act_created_at = self.get_field_value(
957 body=body, act_name=action, field='created_at')
958
959 self.assertEqual(act_created_at.split(".")[0], updated_act_created_at)
960 self.assertTrue(all(['updated_at' in item
961 for item in body['actions']]))
962
963 @test.attr(type='sanity')
964 def test_get_action_definition(self):
965 _, body = self.client.create_action('action_v2.yaml')
966 act_name = body['actions'][0]['name']
967
968 resp, body = self.client.get_definition('actions', act_name)
969 self.assertEqual(200, resp.status)
970 self.assertIsNotNone(body)
971 self.assertIn(act_name, body)
972
973 @test.attr(type='negative')
974 def test_get_nonexistent_action(self):
975 self.assertRaises(
976 exceptions.NotFound,
977 self.client.get_object,
978 'actions', 'nonexist'
979 )
980
981 @test.attr(type='negative')
982 def test_double_creation(self):
983 self.client.create_action('action_v2.yaml')
984
985 self.assertRaises(
986 exceptions.Conflict,
987 self.client.create_action,
988 'action_v2.yaml'
989 )
990
991 @test.attr(type='negative')
992 def test_create_action_invalid_def(self):
993 self.assertRaises(
994 exceptions.BadRequest,
995 self.client.create_action,
996 'wb_v2.yaml'
997 )
998
999 @test.attr(type='negative')
1000 def test_update_action_invalid_def(self):
1001 self.assertRaises(
1002 exceptions.BadRequest,
1003 self.client.update_request,
1004 'actions', 'wb_v2.yaml'
1005 )
1006
1007 @test.attr(type='negative')
1008 def test_delete_nonexistent_action(self):
1009 self.assertRaises(
1010 exceptions.NotFound,
1011 self.client.delete_obj,
1012 'actions', 'nonexist'
1013 )
1014
1015 @test.attr(type='negative')
1016 def test_delete_standard_action(self):
1017 self.assertRaises(
1018 exceptions.BadRequest,
1019 self.client.delete_obj,
1020 'actions', 'nova.servers_create'
1021 )
1022
1023
1024class TasksTestsV2(base.TestCase):
1025
1026 _service = 'workflowv2'
1027
1028 def setUp(self):
1029 super(TasksTestsV2, self).setUp()
1030
1031 _, body = self.client.create_workflow('wf_v2.yaml')
1032 self.direct_wf_name = body['workflows'][0]['name']
1033 _, execution = self.client.create_execution(self.direct_wf_name)
1034
1035 def tearDown(self):
1036 for wf in self.client.workflows:
1037 self.client.delete_obj('workflows', wf)
1038 self.client.workflows = []
1039
1040 for wf in self.client.executions:
1041 self.client.delete_obj('executions', wf)
1042 self.client.executions = []
1043
1044 super(TasksTestsV2, self).tearDown()
1045
1046 @test.attr(type='smoke')
1047 def test_get_tasks_list(self):
1048 resp, body = self.client.get_list_obj('tasks')
1049
1050 self.assertEqual(200, resp.status)
1051 self.assertNotEmpty(body['tasks'])
1052
1053 @test.attr(type='sanity')
1054 def test_get_task(self):
1055 resp, body = self.client.get_list_obj('tasks')
1056
1057 self.assertEqual(200, resp.status)
1058 self.assertEqual(
1059 self.direct_wf_name, body['tasks'][-1]['workflow_name']
1060 )
1061
1062
1063class ActionExecutionTestsV2(base.TestCase):
1064 _service = 'workflowv2'
1065
1066 @classmethod
1067 def resource_cleanup(cls):
1068 for action_ex in cls.client.action_executions:
1069 try:
1070 cls.client.delete_obj('action_executions', action_ex)
1071 except Exception as e:
1072 LOG.exception('Exception raised when deleting '
1073 'action_executions %s, error message: %s.'
1074 % (action_ex, six.text_type(e)))
1075
1076 cls.client.action_executions = []
1077
1078 super(ActionExecutionTestsV2, cls).resource_cleanup()
1079
1080 @test.attr(type='sanity')
1081 def test_run_action_execution(self):
1082 resp, body = self.client.create_action_execution(
1083 {
1084 'name': 'std.echo',
1085 'input': '{"output": "Hello, Mistral!"}'
1086 }
1087 )
1088
1089 self.assertEqual(201, resp.status)
1090 output = json.loads(body['output'])
1091 self.assertDictEqual(
1092 {'result': 'Hello, Mistral!'},
1093 output
1094 )
1095
1096 @test.attr(type='sanity')
1097 def test_run_action_std_http(self):
1098 resp, body = self.client.create_action_execution(
1099 {
1100 'name': 'std.http',
1101 'input': '{"url": "http://wiki.openstack.org"}'
1102 }
1103 )
1104
1105 self.assertEqual(201, resp.status)
1106 output = json.loads(body['output'])
1107 self.assertTrue(output['result']['status'] in range(200, 307))
1108
1109 @test.attr(type='sanity')
1110 def test_run_action_std_http_error(self):
1111 resp, body = self.client.create_action_execution(
1112 {
1113 'name': 'std.http',
1114 'input': '{"url": "http://www.google.ru/not-found-test"}'
1115 }
1116 )
1117
1118 self.assertEqual(201, resp.status)
1119 output = json.loads(body['output'])
1120 self.assertEqual(404, output['result']['status'])
1121
1122 @test.attr(type='sanity')
1123 def test_create_action_execution(self):
1124 resp, body = self.client.create_action_execution(
1125 {
1126 'name': 'std.echo',
1127 'input': '{"output": "Hello, Mistral!"}',
1128 'params': '{"save_result": true}'
1129 }
1130 )
1131
1132 self.assertEqual(201, resp.status)
1133 self.assertEqual('RUNNING', body['state'])
1134
1135 # We must reread action execution in order to get actual
1136 # state and output.
1137 body = self.client.wait_execution_success(
1138 body,
1139 url='action_executions'
1140 )
1141 output = json.loads(body['output'])
1142
1143 self.assertEqual('SUCCESS', body['state'])
1144 self.assertDictEqual(
1145 {'result': 'Hello, Mistral!'},
1146 output
1147 )
1148
1149 @test.attr(type='negative')
1150 def test_delete_nonexistent_action_execution(self):
1151 self.assertRaises(
1152 exceptions.NotFound,
1153 self.client.delete_obj,
1154 'action_executions',
1155 'nonexist'
1156 )
diff --git a/mistral/tests/functional/base.py b/mistral/tests/functional/base.py
deleted file mode 100644
index 4df8d37..0000000
--- a/mistral/tests/functional/base.py
+++ /dev/null
@@ -1,309 +0,0 @@
1# Copyright 2013 Mirantis, Inc. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15import json
16import os
17import time
18
19import mock
20import six
21
22from tempest import clients
23from tempest import config
24from tempest import test as test
25from tempest_lib import auth
26from tempest_lib.common import rest_client
27from tempest_lib import exceptions
28
29
30CONF = config.CONF
31
32
33def get_resource(path):
34 main_package = 'mistral/tests'
35 dir_path = __file__[0:__file__.find(main_package) + len(main_package) + 1]
36
37 return open(dir_path + 'resources/' + path).read()
38
39
40def find_items(items, **props):
41 def _matches(item, **props):
42 for prop_name, prop_val in six.iteritems(props):
43 if item[prop_name] != prop_val:
44 return False
45
46 return True
47
48 filtered = list(filter(lambda item: _matches(item, **props), items))
49
50 if len(filtered) == 1:
51 return filtered[0]
52
53 return filtered
54
55
56class MistralClientBase(rest_client.RestClient):
57 def __init__(self, auth_provider, service_type):
58 super(MistralClientBase, self).__init__(
59 auth_provider=auth_provider,
60 service=service_type,
61 region=CONF.identity.region
62 )
63
64 if service_type not in ('workflow', 'workflowv2'):
65 msg = "Invalid parameter 'service_type'. "
66 raise exceptions.UnprocessableEntity(msg)
67
68 self.endpoint_url = 'publicURL'
69
70 self.workbooks = []
71 self.executions = []
72 self.workflows = []
73 self.triggers = []
74 self.actions = []
75 self.action_executions = []
76
77 def get_list_obj(self, name):
78 resp, body = self.get(name)
79
80 return resp, json.loads(body)
81
82 def delete_obj(self, obj, name):
83 return self.delete('{obj}/{name}'.format(obj=obj, name=name))
84
85 def get_object(self, obj, id):
86 resp, body = self.get('{obj}/{id}'.format(obj=obj, id=id))
87
88 return resp, json.loads(body)
89
90 def wait_execution_success(self, ex_body, timeout=180, url='executions'):
91 return self.wait_execution(ex_body, timeout=timeout, url=url)
92
93 def wait_execution(self, ex_body, timeout=180, url='executions',
94 target_state='SUCCESS'):
95 start_time = time.time()
96
97 expected_states = [target_state, 'RUNNING']
98
99 while ex_body['state'] != target_state:
100 if time.time() - start_time > timeout:
101 msg = ("Execution exceeds timeout {0} "
102 "to change state to {1}. "
103 "Execution: {2}".format(timeout, target_state, ex_body))
104 raise exceptions.TimeoutException(msg)
105
106 _, ex_body = self.get_object(url, ex_body['id'])
107
108 if ex_body['state'] not in expected_states:
109 msg = ("Execution state %s is not in expected "
110 "states: %s" % (ex_body['state'], expected_states))
111 raise exceptions.TempestException(msg)
112
113 time.sleep(1)
114
115 return ex_body
116
117
118class MistralClientV2(MistralClientBase):
119
120 def post_request(self, url, file_name):
121 headers = {"headers": "Content-Type:text/plain"}
122
123 return self.post(url, get_resource(file_name), headers=headers)
124
125 def post_json(self, url, obj):
126 headers = {"Content-Type": "application/json"}
127
128 return self.post(url, json.dumps(obj), headers=headers)
129
130 def update_request(self, url, file_name):
131 headers = {"headers": "Content-Type:text/plain"}
132
133 resp, body = self.put(url, get_resource(file_name), headers=headers)
134
135 return resp, json.loads(body)
136
137 def get_definition(self, item, name):
138 resp, body = self.get("%s/%s" % (item, name))
139
140 return resp, json.loads(body)['definition']
141
142 def create_workbook(self, yaml_file):
143 resp, body = self.post_request('workbooks', yaml_file)
144
145 wb_name = json.loads(body)['name']
146 self.workbooks.append(wb_name)
147
148 _, wfs = self.get_list_obj('workflows')
149
150 for wf in wfs['workflows']:
151 if wf['name'].startswith(wb_name):
152 self.workflows.append(wf['name'])
153
154 return resp, json.loads(body)
155
156 def create_workflow(self, yaml_file, scope=None):
157 if scope:
158 resp, body = self.post_request('workflows?scope=public', yaml_file)
159 else:
160 resp, body = self.post_request('workflows', yaml_file)
161
162 for wf in json.loads(body)['workflows']:
163 self.workflows.append(wf['name'])
164
165 return resp, json.loads(body)
166
167 def create_execution(self, wf_name, wf_input=None, params=None):
168 body = {"workflow_name": "%s" % wf_name}
169
170 if wf_input:
171 body.update({'input': json.dumps(wf_input)})
172 if params:
173 body.update({'params': json.dumps(params)})
174
175 resp, body = self.post('executions', json.dumps(body))
176
177 self.executions.append(json.loads(body)['id'])
178
179 return resp, json.loads(body)
180
181 def update_execution(self, execution_id, put_body):
182 resp, body = self.put('executions/%s' % execution_id, put_body)
183
184 return resp, json.loads(body)
185
186 def create_cron_trigger(self, name, wf_name, wf_input=None, pattern=None,
187 first_time=None, count=None):
188 post_body = {
189 'name': name,
190 'workflow_name': wf_name,
191 'pattern': pattern,
192 'remaining_executions': count,
193 'first_execution_time': first_time
194 }
195
196 if wf_input:
197 post_body.update({'workflow_input': json.dumps(wf_input)})
198
199 rest, body = self.post('cron_triggers', json.dumps(post_body))
200
201 self.triggers.append(name)
202
203 return rest, json.loads(body)
204
205 def create_action(self, yaml_file):
206 resp, body = self.post_request('actions', yaml_file)
207
208 self.actions.extend(
209 [action['name'] for action in json.loads(body)['actions']])
210
211 return resp, json.loads(body)
212
213 def get_wf_tasks(self, wf_name):
214 all_tasks = self.get_list_obj('tasks')[1]['tasks']
215
216 return [t for t in all_tasks if t['workflow_name'] == wf_name]
217
218 def create_action_execution(self, request_body):
219 resp, body = self.post_json('action_executions', request_body)
220
221 params = json.loads(request_body.get('params', '{}'))
222 if params.get('save_result', False):
223 self.action_executions.append(json.loads(body)['id'])
224
225 return resp, json.loads(body)
226
227
228class AuthProv(auth.KeystoneV2AuthProvider):
229 def __init__(self):
230 self.alt_part = None
231
232 def auth_request(self, method, url, *args, **kwargs):
233 req_url, headers, body = super(AuthProv, self).auth_request(
234 method, url, *args, **kwargs)
235 return 'http://localhost:8989/{0}/{1}'.format(
236 os.environ['VERSION'], url), headers, body
237
238 def get_auth(self):
239 return 'mock_str', 'mock_str'
240
241 def base_url(self, *args, **kwargs):
242 return ''
243
244
245class TestCase(test.BaseTestCase):
246
247 credentials = ['primary', 'alt']
248
249 @classmethod
250 def resource_setup(cls):
251 """Client authentication.
252
253 This method allows to initialize authentication before
254 each test case and define parameters of Mistral API Service.
255 """
256 super(TestCase, cls).resource_setup()
257
258 if 'WITHOUT_AUTH' in os.environ:
259 cls.mgr = mock.MagicMock()
260 cls.mgr.auth_provider = AuthProv()
261 cls.alt_mgr = cls.mgr
262 else:
263 cls.mgr = cls.manager
264 cls.alt_mgr = cls.alt_manager
265
266 if cls._service == 'workflowv2':
267 cls.client = MistralClientV2(
268 cls.mgr.auth_provider, cls._service)
269 cls.alt_client = MistralClientV2(
270 cls.alt_mgr.auth_provider, cls._service)
271
272 def setUp(self):
273 super(TestCase, self).setUp()
274
275 def tearDown(self):
276 super(TestCase, self).tearDown()
277
278 for wb in self.client.workbooks:
279 self.client.delete_obj('workbooks', wb)
280
281 self.client.workbooks = []
282
283
284class TestCaseAdvanced(TestCase):
285 @classmethod
286 def resource_setup(cls):
287 super(TestCaseAdvanced, cls).resource_setup()
288
289 cls.server_client = clients.ServersClient(
290 cls.mgr.auth_provider,
291 "compute",
292 region=CONF.identity.region
293 )
294
295 cls.image_ref = CONF.compute.image_ref
296 cls.flavor_ref = CONF.compute.flavor_ref
297
298 def tearDown(self):
299 for wb in self.client.workbooks:
300 self.client.delete_obj('workbooks', wb)
301
302 self.client.workbooks = []
303
304 for ex in self.client.executions:
305 self.client.delete_obj('executions', ex)
306
307 self.client.executions = []
308
309 super(TestCaseAdvanced, self).tearDown()
diff --git a/mistral/tests/functional/engine/__init__.py b/mistral/tests/functional/engine/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/mistral/tests/functional/engine/__init__.py
+++ /dev/null
diff --git a/mistral/tests/functional/engine/actions/__init__.py b/mistral/tests/functional/engine/actions/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/mistral/tests/functional/engine/actions/__init__.py
+++ /dev/null
diff --git a/mistral/tests/functional/engine/actions/v2/__init__.py b/mistral/tests/functional/engine/actions/v2/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/mistral/tests/functional/engine/actions/v2/__init__.py
+++ /dev/null
diff --git a/mistral/tests/functional/engine/actions/v2/test_openstack_actions.py b/mistral/tests/functional/engine/actions/v2/test_openstack_actions.py
deleted file mode 100644
index b7cfe26..0000000
--- a/mistral/tests/functional/engine/actions/v2/test_openstack_actions.py
+++ /dev/null
@@ -1,86 +0,0 @@
1# Copyright 2015 - Mirantis, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15from tempest import test
16
17from mistral.tests.functional import base
18
19
20class OpenStackActionsTestsV2(base.TestCase):
21
22 _service = 'workflowv2'
23
24 # TODO(akuznetsova): add checks for task result after task_output
25 # TODO(akuznetsova): refactoring will be finished
26
27 @classmethod
28 def resource_setup(cls):
29 super(OpenStackActionsTestsV2, cls).resource_setup()
30
31 _, cls.wb = cls.client.create_workbook(
32 'openstack/action_collection_wb.yaml')
33
34 @test.attr(type='openstack')
35 def test_nova_actions(self):
36 wf_name = self.wb['name'] + '.nova'
37 _, execution = self.client.create_execution(wf_name)
38 self.client.wait_execution_success(execution)
39 executed_task = self.client.get_wf_tasks(wf_name)[-1]
40
41 self.assertEqual('SUCCESS', executed_task['state'])
42
43 @test.attr(type='openstack')
44 def test_keystone_actions(self):
45 wf_name = self.wb['name'] + '.keystone'
46 _, execution = self.client.create_execution(wf_name)
47 self.client.wait_execution_success(execution)
48 executed_task = self.client.get_wf_tasks(wf_name)[-1]
49
50 self.assertEqual('SUCCESS', executed_task['state'])
51
52 @test.attr(type='openstack')
53 def test_heat_actions(self):
54 wf_name = self.wb['name'] + '.heat'
55 _, execution = self.client.create_execution(wf_name)
56 self.client.wait_execution_success(execution)
57 executed_task = self.client.get_wf_tasks(wf_name)[-1]
58
59 self.assertEqual('SUCCESS', executed_task['state'])
60
61 @test.attr(type='openstack')
62 def test_glance_actions(self):
63 wf_name = self.wb['name'] + '.glance'
64 _, execution = self.client.create_execution(wf_name)
65 self.client.wait_execution_success(execution)
66 executed_task = self.client.get_wf_tasks(wf_name)[-1]
67
68 self.assertEqual('SUCCESS', executed_task['state'])
69
70 @test.attr(type='openstack')
71 def test_cinder_actions(self):
72 wf_name = self.wb['name'] + '.cinder'
73 _, execution = self.client.create_execution(wf_name)
74 self.client.wait_execution_success(execution)
75 executed_task = self.client.get_wf_tasks(wf_name)[-1]
76
77 self.assertEqual('SUCCESS', executed_task['state'])
78
79 @test.attr(type='openstack')
80 def test_neutron_actions(self):
81 wf_name = self.wb['name'] + '.neutron'
82 _, execution = self.client.create_execution(wf_name)
83 self.client.wait_execution_success(execution)
84 executed_task = self.client.get_wf_tasks(wf_name)[-1]
85
86 self.assertEqual('SUCCESS', executed_task['state'])
diff --git a/mistral/tests/functional/engine/actions/v2/test_ssh_actions.py b/mistral/tests/functional/engine/actions/v2/test_ssh_actions.py
deleted file mode 100644
index 55f38a1..0000000
--- a/mistral/tests/functional/engine/actions/v2/test_ssh_actions.py
+++ /dev/null
@@ -1,272 +0,0 @@
1# Copyright 2015 - Mirantis, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import json
16import os
17from os import path
18import time
19
20from oslo_log import log as logging
21from paramiko import ssh_exception
22from tempest import config
23from tempest import test
24
25from mistral.tests.functional import base
26from mistral import utils
27from mistral.utils import ssh_utils
28
29
30LOG = logging.getLogger(__name__)
31CONF = config.CONF
32SSH_KEYS_DIRECTORY = path.expanduser("~/.ssh/")
33
34
35class SSHActionsTestsV2(base.TestCaseAdvanced):
36
37 _service = 'workflowv2'
38
39 @classmethod
40 def _create_security_group_rule_ssh(cls):
41 sec_groups = (
42 cls.mgr.compute_security_groups_client.
43 list_security_groups()
44 )
45 sec_groups = sec_groups['security_groups']
46
47 default_group = next(
48 g for g in sec_groups if g['name'] == 'default'
49 )
50
51 rule = (
52 cls.mgr.compute_security_group_rules_client
53 .create_security_group_rule(
54 parent_group_id=default_group['id'],
55 ip_protocol="tcp",
56 from_port=22,
57 to_port=22,
58 cidr="0.0.0.0/0"
59 )
60 )
61
62 cls.ssh_rule_id = rule['security_group_rule']['id']
63
64 @classmethod
65 def _create_server(cls, server_name, **kwargs):
66 return cls.server_client.create_server(
67 name=server_name,
68 imageRef=CONF.compute.image_ref,
69 flavorRef=CONF.compute.flavor_ref,
70 **kwargs
71 ).get('server')
72
73 @classmethod
74 def _associate_floating_ip_to_server(cls, server_id):
75 fl_ip_client = cls.mgr.compute_floating_ips_client
76
77 all_ips = fl_ip_client.list_floating_ips().get(
78 'floating_ips'
79 )
80 free_ips = list(
81 filter(lambda fl_ip: fl_ip['instance_id'] is None, all_ips)
82 )
83
84 if free_ips:
85 ip = free_ips[0]['ip']
86 else:
87 # Allocate new floating ip.
88 ip = fl_ip_client.create_floating_ip()['floating_ip']['ip']
89
90 # Associate IP.
91 fl_ip_client.associate_floating_ip_to_server(
92 floating_ip=ip,
93 server_id=server_id
94 )
95
96 return ip
97
98 @classmethod
99 def _wait_until_server_up(cls, server_ip, timeout=120, delay=2):
100 seconds_remain = timeout
101
102 LOG.info("Waiting server SSH [IP=%s]..." % server_ip)
103
104 while seconds_remain > 0:
105 try:
106 ssh_utils.execute_command('cd', server_ip, None)
107 except ssh_exception.SSHException:
108 LOG.info("Server %s: SSH service is ready.")
109 return
110 except Exception as e:
111 LOG.info(str(e))
112 seconds_remain -= delay
113 time.sleep(delay)
114 else:
115 return
116
117 raise Exception(
118 "Failed waiting until server's '%s' SSH is up." % server_ip
119 )
120
121 @classmethod
122 def _wait_until_server_active(cls, server_id, timeout=60, delay=2):
123 seconds_remain = timeout
124
125 LOG.info("Waiting server [id=%s]..." % server_id)
126
127 while seconds_remain > 0:
128 server_info = cls.server_client.show_server(server_id)
129 if server_info['server']['status'] == 'ACTIVE':
130 return
131
132 seconds_remain -= delay
133 time.sleep(delay)
134
135 raise Exception(
136 "Failed waiting until server %s is active." % server_id
137 )
138
139 @classmethod
140 def resource_setup(cls):
141 super(SSHActionsTestsV2, cls).resource_setup()
142
143 # Modify security group for accessing VM via SSH.
144 cls._create_security_group_rule_ssh()
145
146 # Create keypair (public and private keys).
147 cls.private_key, cls.public_key = utils.generate_key_pair()
148 cls.key_name = 'mistral-functional-tests-key'
149
150 # If ZUUL_PROJECT is specified, it means
151 # tests are running on Jenkins gate.
152
153 if os.environ.get('ZUUL_PROJECT'):
154 cls.key_dir = "/opt/stack/new/.ssh/"
155
156 if not path.exists(cls.key_dir):
157 os.mkdir(cls.key_dir)
158 else:
159 cls.key_dir = SSH_KEYS_DIRECTORY
160
161 utils.save_text_to(
162 cls.private_key,
163 cls.key_dir + cls.key_name,
164 overwrite=True
165 )
166
167 LOG.info(
168 "Private key saved to %s" % cls.key_dir + cls.key_name
169 )
170
171 # Create keypair in nova.
172 cls.mgr.keypairs_client.create_keypair(
173 name=cls.key_name,
174 public_key=cls.public_key
175 )
176
177 # Start servers and provide key_name.
178 # Note: start public vm only after starting the guest one,
179 # so we can track public vm launching using ssh, but can't
180 # do the same with guest VM.
181 cls.guest_vm = cls._create_server(
182 'mistral-guest-vm',
183 key_name=cls.key_name
184 )
185 cls.public_vm = cls._create_server(
186 'mistral-public-vm',
187 key_name=cls.key_name
188 )
189
190 cls._wait_until_server_active(cls.public_vm['id'])
191
192 cls.public_vm_ip = cls._associate_floating_ip_to_server(
193 cls.public_vm['id']
194 )
195
196 # Wait until server is up.
197 cls._wait_until_server_up(cls.public_vm_ip)
198
199 # Update servers info.
200 cls.public_vm = cls.server_client.show_server(
201 cls.public_vm['id']
202 ).get('server')
203
204 cls.guest_vm = cls.server_client.show_server(
205 cls.guest_vm['id']
206 ).get('server')
207
208 @classmethod
209 def resource_cleanup(cls):
210 fl_ip_client = cls.mgr.compute_floating_ips_client
211 fl_ip_client.disassociate_floating_ip_from_server(
212 cls.public_vm_ip,
213 cls.public_vm['id']
214 )
215 cls.server_client.delete_server(cls.public_vm['id'])
216 cls.server_client.delete_server(cls.guest_vm['id'])
217 cls.mgr.keypairs_client.delete_keypair(cls.key_name)
218
219 cls.mgr.compute_security_group_rules_client.delete_security_group_rule(
220 cls.ssh_rule_id
221 )
222 os.remove(cls.key_dir + cls.key_name)
223
224 super(SSHActionsTestsV2, cls).resource_cleanup()
225
226 @test.attr(type='sanity')
227 def test_run_ssh_action(self):
228 input_data = {
229 'cmd': 'hostname',
230 'host': self.public_vm_ip,
231 'username': CONF.validation.image_ssh_user,
232 'private_key_filename': self.key_name
233 }
234
235 resp, body = self.client.create_action_execution(
236 {
237 'name': 'std.ssh',
238 'input': json.dumps(input_data)
239 }
240 )
241
242 self.assertEqual(201, resp.status)
243
244 output = json.loads(body['output'])
245
246 self.assertIn(self.public_vm['name'], output['result'])
247
248 @test.attr(type='sanity')
249 def test_run_ssh_proxied_action(self):
250 guest_vm_ip = self.guest_vm['addresses'].popitem()[1][0]['addr']
251
252 input_data = {
253 'cmd': 'hostname',
254 'host': guest_vm_ip,
255 'username': CONF.validation.image_ssh_user,
256 'private_key_filename': self.key_name,
257 'gateway_host': self.public_vm_ip,
258 'gateway_username': CONF.validation.image_ssh_user
259 }
260
261 resp, body = self.client.create_action_execution(
262 {
263 'name': 'std.ssh_proxied',
264 'input': json.dumps(input_data)
265 }
266 )
267
268 self.assertEqual(201, resp.status)
269
270 output = json.loads(body['output'])
271
272 self.assertIn(self.guest_vm['name'], output['result'])
diff --git a/mistral_tempest_tests/services/base.py b/mistral_tempest_tests/services/base.py
index 815333c..790f6b8 100644
--- a/mistral_tempest_tests/services/base.py
+++ b/mistral_tempest_tests/services/base.py
@@ -20,7 +20,6 @@ import mock
20import six 20import six
21 21
22from tempest import clients 22from tempest import clients
23from tempest.common import credentials_factory as creds
24from tempest import config 23from tempest import config
25from tempest import test as test 24from tempest import test as test
26from tempest_lib import auth 25from tempest_lib import auth
@@ -244,6 +243,9 @@ class AuthProv(auth.KeystoneV2AuthProvider):
244 243
245 244
246class TestCase(test.BaseTestCase): 245class TestCase(test.BaseTestCase):
246
247 credentials = ['primary', 'alt']
248
247 @classmethod 249 @classmethod
248 def resource_setup(cls): 250 def resource_setup(cls):
249 """Client authentication. 251 """Client authentication.
@@ -256,16 +258,10 @@ class TestCase(test.BaseTestCase):
256 if 'WITHOUT_AUTH' in os.environ: 258 if 'WITHOUT_AUTH' in os.environ:
257 cls.mgr = mock.MagicMock() 259 cls.mgr = mock.MagicMock()
258 cls.mgr.auth_provider = AuthProv() 260 cls.mgr.auth_provider = AuthProv()
261 cls.alt_mgr = cls.mgr
259 else: 262 else:
260 cls.creds = creds.get_configured_credentials( 263 cls.mgr = cls.manager
261 credential_type='user' 264 cls.alt_mgr = cls.alt_manager
262 )
263 cls.mgr = clients.Manager(cls.creds)
264
265 cls.alt_creds = creds.get_configured_credentials(
266 credential_type='alt_user'
267 )
268 cls.alt_mgr = clients.Manager(cls.alt_creds)
269 265
270 if cls._service == 'workflowv2': 266 if cls._service == 'workflowv2':
271 cls.client = MistralClientV2( 267 cls.client = MistralClientV2(
diff --git a/mistral_tempest_tests/tests/api/v2/test_mistral_basic_v2.py b/mistral_tempest_tests/tests/api/v2/test_mistral_basic_v2.py
index f546e95..14ebd04 100644
--- a/mistral_tempest_tests/tests/api/v2/test_mistral_basic_v2.py
+++ b/mistral_tempest_tests/tests/api/v2/test_mistral_basic_v2.py
@@ -382,9 +382,8 @@ class WorkflowTestsV2(base.TestCase):
382 name 382 name
383 ) 383 )
384 384
385 self.assertEqual( 385 self.assertIn(
386 "Can't delete workflow that has triggers " + 386 "Can't delete workflow that has triggers associated",
387 "[workflow_name=wf2],[cron_trigger_name(s)=trigger]",
388 exception.resp_body['faultstring'] 387 exception.resp_body['faultstring']
389 ) 388 )
390 finally: 389 finally:
diff --git a/mistral_tempest_tests/tests/scenario/engine/actions/v2/test_ssh_actions.py b/mistral_tempest_tests/tests/scenario/engine/actions/v2/test_ssh_actions.py
index 933af01..6848428 100644
--- a/mistral_tempest_tests/tests/scenario/engine/actions/v2/test_ssh_actions.py
+++ b/mistral_tempest_tests/tests/scenario/engine/actions/v2/test_ssh_actions.py
@@ -38,19 +38,25 @@ class SSHActionsTestsV2(base.TestCaseAdvanced):
38 38
39 @classmethod 39 @classmethod
40 def _create_security_group_rule_ssh(cls): 40 def _create_security_group_rule_ssh(cls):
41 sec_groups = cls.mgr.security_groups_client.list_security_groups() 41 sec_groups = (
42 cls.mgr.compute_security_groups_client.
43 list_security_groups()
44 )
42 sec_groups = sec_groups['security_groups'] 45 sec_groups = sec_groups['security_groups']
43 46
44 default_group = next( 47 default_group = next(
45 g for g in sec_groups if g['name'] == 'default' 48 g for g in sec_groups if g['name'] == 'default'
46 ) 49 )
47 50
48 rule = cls.mgr.security_group_rules_client.create_security_group_rule( 51 rule = (
49 parent_group_id=default_group['id'], 52 cls.mgr.compute_security_group_rules_client
50 ip_protocol="tcp", 53 .create_security_group_rule(
51 from_port=22, 54 parent_group_id=default_group['id'],
52 to_port=22, 55 ip_protocol="tcp",
53 cidr="0.0.0.0/0" 56 from_port=22,
57 to_port=22,
58 cidr="0.0.0.0/0"
59 )
54 ) 60 )
55 61
56 cls.ssh_rule_id = rule['security_group_rule']['id'] 62 cls.ssh_rule_id = rule['security_group_rule']['id']
@@ -201,11 +207,16 @@ class SSHActionsTestsV2(base.TestCaseAdvanced):
201 207
202 @classmethod 208 @classmethod
203 def resource_cleanup(cls): 209 def resource_cleanup(cls):
210 fl_ip_client = cls.mgr.compute_floating_ips_client
211 fl_ip_client.disassociate_floating_ip_from_server(
212 cls.public_vm_ip,
213 cls.public_vm['id']
214 )
204 cls.server_client.delete_server(cls.public_vm['id']) 215 cls.server_client.delete_server(cls.public_vm['id'])
205 cls.server_client.delete_server(cls.guest_vm['id']) 216 cls.server_client.delete_server(cls.guest_vm['id'])
206 cls.mgr.keypairs_client.delete_keypair(cls.key_name) 217 cls.mgr.keypairs_client.delete_keypair(cls.key_name)
207 218
208 cls.mgr.security_group_rules_client.delete_security_group_rule( 219 cls.mgr.compute_security_group_rules_client.delete_security_group_rule(
209 cls.ssh_rule_id 220 cls.ssh_rule_id
210 ) 221 )
211 os.remove(cls.key_dir + cls.key_name) 222 os.remove(cls.key_dir + cls.key_name)
@@ -217,7 +228,7 @@ class SSHActionsTestsV2(base.TestCaseAdvanced):
217 input_data = { 228 input_data = {
218 'cmd': 'hostname', 229 'cmd': 'hostname',
219 'host': self.public_vm_ip, 230 'host': self.public_vm_ip,
220 'username': CONF.scenario.ssh_user, 231 'username': CONF.validation.image_ssh_user,
221 'private_key_filename': self.key_name 232 'private_key_filename': self.key_name
222 } 233 }
223 234
@@ -241,10 +252,10 @@ class SSHActionsTestsV2(base.TestCaseAdvanced):
241 input_data = { 252 input_data = {
242 'cmd': 'hostname', 253 'cmd': 'hostname',
243 'host': guest_vm_ip, 254 'host': guest_vm_ip,
244 'username': CONF.scenario.ssh_user, 255 'username': CONF.validation.image_ssh_user,
245 'private_key_filename': self.key_name, 256 'private_key_filename': self.key_name,
246 'gateway_host': self.public_vm_ip, 257 'gateway_host': self.public_vm_ip,
247 'gateway_username': CONF.scenario.ssh_user 258 'gateway_username': CONF.validation.image_ssh_user
248 } 259 }
249 260
250 resp, body = self.client.create_action_execution( 261 resp, body = self.client.create_action_execution(
diff --git a/run_functional_tests.sh b/run_functional_tests.sh
index 5c5886a..1632fdc 100755
--- a/run_functional_tests.sh
+++ b/run_functional_tests.sh
@@ -14,7 +14,7 @@ function pre_hook() {
14function run_tests_by_version() { 14function run_tests_by_version() {
15 echo "$(tput setaf 4)Running integration API and workflow execution tests for v$1$(tput sgr 0)" 15 echo "$(tput setaf 4)Running integration API and workflow execution tests for v$1$(tput sgr 0)"
16 export VERSION="v$1" 16 export VERSION="v$1"
17 nosetests -v mistral/tests/functional/api/v$1/ 17 nosetests -v mistral_tempest_tests/tests/api/v$1/
18 unset VERSION 18 unset VERSION
19} 19}
20 20