545a34c751
Now that we no longer support py27, we can use the standard library unittest.mock module instead of the third party mock lib. Change-Id: I6a4524c3e0458747646995615535e85cde2e155f Signed-off-by: Sean McGinnis <sean.mcginnis@gmail.com>
700 lines
22 KiB
Python
700 lines
22 KiB
Python
# Copyright 2015 - Mirantis, Inc.
|
|
# 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 copy
|
|
import datetime
|
|
import json
|
|
from unittest import mock
|
|
|
|
from oslo_config import cfg
|
|
import oslo_messaging
|
|
from oslo_messaging import exceptions as oslo_exc
|
|
import sqlalchemy as sa
|
|
|
|
from mistral.api.controllers.v2 import action_execution
|
|
from mistral.api.controllers.v2 import resources
|
|
from mistral.db.v2 import api as db_api
|
|
from mistral.db.v2.sqlalchemy import models
|
|
from mistral import exceptions as exc
|
|
from mistral.rpc import clients as rpc_clients
|
|
from mistral.rpc.oslo import oslo_client
|
|
from mistral.tests.unit.api import base
|
|
from mistral.utils import rest_utils
|
|
from mistral.workflow import states
|
|
from mistral_lib import actions as ml_actions
|
|
|
|
# This line is needed for correct initialization of messaging config.
|
|
oslo_messaging.get_rpc_transport(cfg.CONF)
|
|
|
|
|
|
ACTION_EX_DB = models.ActionExecution(
|
|
id='123',
|
|
workflow_name='flow',
|
|
task_execution=models.TaskExecution(name='task1'),
|
|
task_execution_id='333',
|
|
state=states.SUCCESS,
|
|
state_info=states.SUCCESS,
|
|
tags=['foo', 'fee'],
|
|
name='std.echo',
|
|
description='something',
|
|
accepted=True,
|
|
input={},
|
|
output={},
|
|
created_at=datetime.datetime(1970, 1, 1),
|
|
updated_at=datetime.datetime(1970, 1, 1)
|
|
)
|
|
|
|
AD_HOC_ACTION_EX_DB = models.ActionExecution(
|
|
id='123',
|
|
state=states.SUCCESS,
|
|
state_info=states.SUCCESS,
|
|
tags=['foo', 'fee'],
|
|
name='std.echo',
|
|
description='something',
|
|
accepted=True,
|
|
input={},
|
|
output={},
|
|
created_at=datetime.datetime(1970, 1, 1),
|
|
updated_at=datetime.datetime(1970, 1, 1)
|
|
)
|
|
|
|
AD_HOC_ACTION_EX_ERROR = models.ActionExecution(
|
|
id='123',
|
|
state=states.ERROR,
|
|
state_info=states.ERROR,
|
|
tags=['foo', 'fee'],
|
|
name='std.echo',
|
|
description='something',
|
|
accepted=True,
|
|
input={},
|
|
output={},
|
|
created_at=datetime.datetime(1970, 1, 1),
|
|
updated_at=datetime.datetime(1970, 1, 1)
|
|
)
|
|
|
|
AD_HOC_ACTION_EX_CANCELLED = models.ActionExecution(
|
|
id='123',
|
|
state=states.CANCELLED,
|
|
state_info=states.CANCELLED,
|
|
tags=['foo', 'fee'],
|
|
name='std.echo',
|
|
description='something',
|
|
accepted=True,
|
|
input={},
|
|
output={},
|
|
created_at=datetime.datetime(1970, 1, 1),
|
|
updated_at=datetime.datetime(1970, 1, 1)
|
|
)
|
|
|
|
ACTION_EX_DB_NOT_COMPLETE = models.ActionExecution(
|
|
id='123',
|
|
state=states.RUNNING,
|
|
state_info=states.RUNNING,
|
|
tags=['foo', 'fee'],
|
|
name='std.echo',
|
|
description='something',
|
|
accepted=False,
|
|
input={},
|
|
output={},
|
|
created_at=datetime.datetime(1970, 1, 1),
|
|
updated_at=datetime.datetime(1970, 1, 1)
|
|
)
|
|
|
|
ACTION_EX = {
|
|
'id': '123',
|
|
'workflow_name': 'flow',
|
|
'task_execution_id': '333',
|
|
'task_name': 'task1',
|
|
'state': 'SUCCESS',
|
|
'state_info': 'SUCCESS',
|
|
'tags': ['foo', 'fee'],
|
|
'name': 'std.echo',
|
|
'description': 'something',
|
|
'accepted': True,
|
|
'input': '{}',
|
|
'output': '{}',
|
|
'created_at': '1970-01-01 00:00:00',
|
|
'updated_at': '1970-01-01 00:00:00'
|
|
}
|
|
|
|
UPDATED_ACTION_EX_DB = copy.copy(ACTION_EX_DB).to_dict()
|
|
UPDATED_ACTION_EX_DB['state'] = 'SUCCESS'
|
|
UPDATED_ACTION_EX_DB['task_name'] = 'task1'
|
|
UPDATED_ACTION = copy.deepcopy(ACTION_EX)
|
|
UPDATED_ACTION['state'] = 'SUCCESS'
|
|
UPDATED_ACTION_OUTPUT = UPDATED_ACTION['output']
|
|
|
|
CANCELLED_ACTION_EX_DB = copy.copy(ACTION_EX_DB).to_dict()
|
|
CANCELLED_ACTION_EX_DB['state'] = 'CANCELLED'
|
|
CANCELLED_ACTION_EX_DB['task_name'] = 'task1'
|
|
CANCELLED_ACTION = copy.deepcopy(ACTION_EX)
|
|
CANCELLED_ACTION['state'] = 'CANCELLED'
|
|
|
|
PAUSED_ACTION_EX_DB = copy.copy(ACTION_EX_DB).to_dict()
|
|
PAUSED_ACTION_EX_DB['state'] = 'PAUSED'
|
|
PAUSED_ACTION_EX_DB['task_name'] = 'task1'
|
|
PAUSED_ACTION = copy.deepcopy(ACTION_EX)
|
|
PAUSED_ACTION['state'] = 'PAUSED'
|
|
|
|
RUNNING_ACTION_EX_DB = copy.copy(ACTION_EX_DB).to_dict()
|
|
RUNNING_ACTION_EX_DB['state'] = 'RUNNING'
|
|
RUNNING_ACTION_EX_DB['task_name'] = 'task1'
|
|
RUNNING_ACTION = copy.deepcopy(ACTION_EX)
|
|
RUNNING_ACTION['state'] = 'RUNNING'
|
|
|
|
ERROR_ACTION_EX = copy.copy(ACTION_EX_DB).to_dict()
|
|
ERROR_ACTION_EX['state'] = 'ERROR'
|
|
ERROR_ACTION_EX['task_name'] = 'task1'
|
|
ERROR_ACTION = copy.deepcopy(ACTION_EX)
|
|
ERROR_ACTION['state'] = 'ERROR'
|
|
ERROR_ACTION_RES = ERROR_ACTION['output']
|
|
|
|
ERROR_OUTPUT = "Fake error, it is a test"
|
|
ERROR_ACTION_EX_WITH_OUTPUT = copy.copy(ACTION_EX_DB).to_dict()
|
|
ERROR_ACTION_EX_WITH_OUTPUT['state'] = 'ERROR'
|
|
ERROR_ACTION_EX_WITH_OUTPUT['task_name'] = 'task1'
|
|
ERROR_ACTION_EX_WITH_OUTPUT['output'] = {"output": ERROR_OUTPUT}
|
|
ERROR_ACTION_WITH_OUTPUT = copy.deepcopy(ACTION_EX)
|
|
ERROR_ACTION_WITH_OUTPUT['state'] = 'ERROR'
|
|
ERROR_ACTION_WITH_OUTPUT['output'] = (
|
|
'{"output": "%s"}' % ERROR_OUTPUT
|
|
)
|
|
ERROR_ACTION_RES_WITH_OUTPUT = {"output": ERROR_OUTPUT}
|
|
|
|
DEFAULT_ERROR_OUTPUT = "Unknown error"
|
|
ERROR_ACTION_EX_FOR_EMPTY_OUTPUT = copy.copy(ACTION_EX_DB).to_dict()
|
|
ERROR_ACTION_EX_FOR_EMPTY_OUTPUT['state'] = 'ERROR'
|
|
ERROR_ACTION_EX_FOR_EMPTY_OUTPUT['task_name'] = 'task1'
|
|
ERROR_ACTION_EX_FOR_EMPTY_OUTPUT['output'] = {"output": DEFAULT_ERROR_OUTPUT}
|
|
ERROR_ACTION_FOR_EMPTY_OUTPUT = copy.deepcopy(ERROR_ACTION)
|
|
ERROR_ACTION_FOR_EMPTY_OUTPUT['output'] = (
|
|
'{"output": "%s"}' % DEFAULT_ERROR_OUTPUT
|
|
)
|
|
|
|
ERROR_ACTION_WITH_NONE_OUTPUT = copy.deepcopy(ERROR_ACTION)
|
|
ERROR_ACTION_WITH_NONE_OUTPUT['output'] = None
|
|
|
|
|
|
BROKEN_ACTION = copy.deepcopy(ACTION_EX)
|
|
BROKEN_ACTION['output'] = 'string not escaped'
|
|
|
|
MOCK_ACTION = mock.MagicMock(return_value=ACTION_EX_DB)
|
|
MOCK_ACTION_NOT_COMPLETE = mock.MagicMock(
|
|
return_value=ACTION_EX_DB_NOT_COMPLETE
|
|
)
|
|
|
|
MOCK_ACTION_COMPLETE_ERROR = mock.MagicMock(
|
|
return_value=AD_HOC_ACTION_EX_ERROR
|
|
)
|
|
MOCK_ACTION_COMPLETE_CANCELLED = mock.MagicMock(
|
|
return_value=AD_HOC_ACTION_EX_CANCELLED
|
|
)
|
|
|
|
MOCK_AD_HOC_ACTION = mock.MagicMock(return_value=AD_HOC_ACTION_EX_DB)
|
|
MOCK_ACTIONS = mock.MagicMock(return_value=[ACTION_EX_DB])
|
|
MOCK_EMPTY = mock.MagicMock(return_value=[])
|
|
MOCK_NOT_FOUND = mock.MagicMock(side_effect=exc.DBEntityNotFoundError())
|
|
MOCK_DELETE = mock.MagicMock(return_value=None)
|
|
|
|
ACTION_EX_DB_WITH_PROJECT_ID = AD_HOC_ACTION_EX_DB.get_clone()
|
|
ACTION_EX_DB_WITH_PROJECT_ID.project_id = '<default-project>'
|
|
|
|
|
|
class TestActionExecutionsController(base.APITest):
|
|
def setUp(self):
|
|
super(TestActionExecutionsController, self).setUp()
|
|
|
|
self.addCleanup(
|
|
cfg.CONF.set_default,
|
|
'allow_action_execution_deletion',
|
|
False,
|
|
group='api'
|
|
)
|
|
|
|
@mock.patch.object(db_api, 'get_action_execution', MOCK_ACTION)
|
|
def test_get(self):
|
|
resp = self.app.get('/v2/action_executions/123')
|
|
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertDictEqual(ACTION_EX, resp.json)
|
|
|
|
@mock.patch.object(db_api, 'get_action_execution')
|
|
def test_get_operational_error(self, mocked_get):
|
|
mocked_get.side_effect = [
|
|
# Emulating DB OperationalError
|
|
sa.exc.OperationalError('Mock', 'mock', 'mock'),
|
|
ACTION_EX_DB # Successful run
|
|
]
|
|
|
|
resp = self.app.get('/v2/action_executions/123')
|
|
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertDictEqual(ACTION_EX, resp.json)
|
|
|
|
def test_basic_get(self):
|
|
resp = self.app.get('/v2/action_executions/')
|
|
self.assertEqual(200, resp.status_int)
|
|
|
|
@mock.patch.object(db_api, 'get_action_execution', MOCK_NOT_FOUND)
|
|
def test_get_not_found(self):
|
|
resp = self.app.get('/v2/action_executions/123', expect_errors=True)
|
|
|
|
self.assertEqual(404, resp.status_int)
|
|
|
|
@mock.patch.object(db_api, 'get_action_execution',
|
|
return_value=ACTION_EX_DB_WITH_PROJECT_ID)
|
|
def test_get_within_project_id(self, mock_get):
|
|
resp = self.app.get('/v2/action_executions/123')
|
|
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertTrue('project_id' in resp.json)
|
|
|
|
@mock.patch.object(oslo_client.OsloRPCClient, 'sync_call',
|
|
mock.MagicMock(side_effect=oslo_exc.MessagingTimeout))
|
|
def test_post_timeout(self):
|
|
self.override_config('rpc_response_timeout', 100)
|
|
|
|
resp = self.app.post_json(
|
|
'/v2/action_executions',
|
|
{
|
|
'name': 'std.sleep',
|
|
'input': {'seconds': 120}
|
|
},
|
|
expect_errors=True
|
|
)
|
|
|
|
error_msg = resp.json['faultstring']
|
|
self.assertEqual(error_msg,
|
|
'This rpc call "start_action" took longer than '
|
|
'configured 100 seconds.')
|
|
|
|
@mock.patch.object(rpc_clients.EngineClient, 'start_action')
|
|
def test_post(self, f):
|
|
f.return_value = ACTION_EX_DB.to_dict()
|
|
|
|
resp = self.app.post_json(
|
|
'/v2/action_executions',
|
|
{
|
|
'name': 'std.echo',
|
|
'input': "{}",
|
|
'params': '{"save_result": true, "run_sync": true}'
|
|
}
|
|
)
|
|
|
|
self.assertEqual(201, resp.status_int)
|
|
|
|
action_exec = copy.deepcopy(ACTION_EX)
|
|
del action_exec['task_name']
|
|
|
|
self.assertDictEqual(action_exec, resp.json)
|
|
|
|
f.assert_called_once_with(
|
|
action_exec['name'],
|
|
json.loads(action_exec['input']),
|
|
description=None,
|
|
save_result=True,
|
|
run_sync=True,
|
|
namespace=''
|
|
)
|
|
|
|
@mock.patch.object(rpc_clients.EngineClient, 'start_action')
|
|
def test_post_with_timeout(self, f):
|
|
f.return_value = ACTION_EX_DB.to_dict()
|
|
|
|
resp = self.app.post_json(
|
|
'/v2/action_executions',
|
|
{
|
|
'name': 'std.echo',
|
|
'input': "{}",
|
|
'params': '{"timeout": 2}'
|
|
}
|
|
)
|
|
|
|
self.assertEqual(201, resp.status_int)
|
|
|
|
action_exec = copy.deepcopy(ACTION_EX)
|
|
del action_exec['task_name']
|
|
|
|
self.assertDictEqual(action_exec, resp.json)
|
|
|
|
f.assert_called_once_with(
|
|
action_exec['name'],
|
|
json.loads(action_exec['input']),
|
|
description=None,
|
|
timeout=2,
|
|
namespace=''
|
|
)
|
|
|
|
@mock.patch.object(rpc_clients.EngineClient, 'start_action')
|
|
def test_post_json(self, f):
|
|
f.return_value = ACTION_EX_DB.to_dict()
|
|
|
|
resp = self.app.post_json(
|
|
'/v2/action_executions',
|
|
{
|
|
'name': 'std.echo',
|
|
'input': {},
|
|
'params': '{"save_result": true}'
|
|
}
|
|
)
|
|
|
|
self.assertEqual(201, resp.status_int)
|
|
|
|
action_exec = copy.deepcopy(ACTION_EX)
|
|
del action_exec['task_name']
|
|
|
|
self.assertDictEqual(action_exec, resp.json)
|
|
|
|
f.assert_called_once_with(
|
|
action_exec['name'],
|
|
json.loads(action_exec['input']),
|
|
description=None,
|
|
save_result=True,
|
|
namespace=''
|
|
)
|
|
|
|
@mock.patch.object(rpc_clients.EngineClient, 'start_action')
|
|
def test_post_without_input(self, f):
|
|
f.return_value = ACTION_EX_DB.to_dict()
|
|
f.return_value['output'] = {'result': '123'}
|
|
|
|
resp = self.app.post_json(
|
|
'/v2/action_executions',
|
|
{'name': 'nova.servers_list'}
|
|
)
|
|
|
|
self.assertEqual(201, resp.status_int)
|
|
self.assertEqual('{"result": "123"}', resp.json['output'])
|
|
|
|
f.assert_called_once_with('nova.servers_list', {},
|
|
description=None,
|
|
namespace='')
|
|
|
|
def test_post_bad_result(self):
|
|
resp = self.app.post_json(
|
|
'/v2/action_executions',
|
|
{'input': 'null'},
|
|
expect_errors=True
|
|
)
|
|
|
|
self.assertEqual(400, resp.status_int)
|
|
|
|
def test_post_bad_input(self):
|
|
resp = self.app.post_json(
|
|
'/v2/action_executions',
|
|
{'input': None},
|
|
expect_errors=True
|
|
)
|
|
|
|
self.assertEqual(400, resp.status_int)
|
|
|
|
def test_post_bad_json_input(self):
|
|
resp = self.app.post_json(
|
|
'/v2/action_executions',
|
|
{'input': 2},
|
|
expect_errors=True
|
|
)
|
|
|
|
self.assertEqual(400, resp.status_int)
|
|
|
|
@mock.patch.object(rpc_clients.EngineClient, 'on_action_complete')
|
|
def test_put(self, f):
|
|
f.return_value = UPDATED_ACTION_EX_DB
|
|
|
|
resp = self.app.put_json('/v2/action_executions/123', UPDATED_ACTION)
|
|
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertDictEqual(UPDATED_ACTION, resp.json)
|
|
|
|
f.assert_called_once_with(
|
|
UPDATED_ACTION['id'],
|
|
ml_actions.Result(data=ACTION_EX_DB.output)
|
|
)
|
|
|
|
@mock.patch.object(rpc_clients.EngineClient, 'on_action_complete')
|
|
def test_put_error_with_output(self, f):
|
|
f.return_value = ERROR_ACTION_EX_WITH_OUTPUT
|
|
|
|
resp = self.app.put_json(
|
|
'/v2/action_executions/123',
|
|
ERROR_ACTION_WITH_OUTPUT
|
|
)
|
|
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertDictEqual(ERROR_ACTION_WITH_OUTPUT, resp.json)
|
|
|
|
f.assert_called_once_with(
|
|
ERROR_ACTION_WITH_OUTPUT['id'],
|
|
ml_actions.Result(error=ERROR_ACTION_RES_WITH_OUTPUT)
|
|
)
|
|
|
|
@mock.patch.object(rpc_clients.EngineClient, 'on_action_complete')
|
|
def test_put_error_with_unknown_reason(self, f):
|
|
f.return_value = ERROR_ACTION_EX_FOR_EMPTY_OUTPUT
|
|
resp = self.app.put_json('/v2/action_executions/123', ERROR_ACTION)
|
|
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertDictEqual(ERROR_ACTION_FOR_EMPTY_OUTPUT, resp.json)
|
|
|
|
f.assert_called_once_with(
|
|
ERROR_ACTION_FOR_EMPTY_OUTPUT['id'],
|
|
ml_actions.Result(error=DEFAULT_ERROR_OUTPUT)
|
|
)
|
|
|
|
@mock.patch.object(rpc_clients.EngineClient, 'on_action_complete')
|
|
def test_put_error_with_unknown_reason_output_none(self, f):
|
|
f.return_value = ERROR_ACTION_EX_FOR_EMPTY_OUTPUT
|
|
resp = self.app.put_json(
|
|
'/v2/action_executions/123',
|
|
ERROR_ACTION_WITH_NONE_OUTPUT
|
|
)
|
|
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertDictEqual(ERROR_ACTION_FOR_EMPTY_OUTPUT, resp.json)
|
|
|
|
f.assert_called_once_with(
|
|
ERROR_ACTION_FOR_EMPTY_OUTPUT['id'],
|
|
ml_actions.Result(error=DEFAULT_ERROR_OUTPUT)
|
|
)
|
|
|
|
@mock.patch.object(rpc_clients.EngineClient, 'on_action_complete')
|
|
def test_put_cancelled(self, on_action_complete_mock_func):
|
|
on_action_complete_mock_func.return_value = CANCELLED_ACTION_EX_DB
|
|
|
|
resp = self.app.put_json('/v2/action_executions/123', CANCELLED_ACTION)
|
|
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertDictEqual(CANCELLED_ACTION, resp.json)
|
|
|
|
on_action_complete_mock_func.assert_called_once_with(
|
|
CANCELLED_ACTION['id'],
|
|
ml_actions.Result(cancel=True)
|
|
)
|
|
|
|
@mock.patch.object(rpc_clients.EngineClient, 'on_action_update')
|
|
def test_put_paused(self, on_action_update_mock_func):
|
|
on_action_update_mock_func.return_value = PAUSED_ACTION_EX_DB
|
|
|
|
resp = self.app.put_json('/v2/action_executions/123', PAUSED_ACTION)
|
|
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertDictEqual(PAUSED_ACTION, resp.json)
|
|
|
|
on_action_update_mock_func.assert_called_once_with(
|
|
PAUSED_ACTION['id'],
|
|
PAUSED_ACTION['state']
|
|
)
|
|
|
|
@mock.patch.object(rpc_clients.EngineClient, 'on_action_update')
|
|
def test_put_resume(self, on_action_update_mock_func):
|
|
on_action_update_mock_func.return_value = RUNNING_ACTION_EX_DB
|
|
|
|
resp = self.app.put_json('/v2/action_executions/123', RUNNING_ACTION)
|
|
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertDictEqual(RUNNING_ACTION, resp.json)
|
|
|
|
on_action_update_mock_func.assert_called_once_with(
|
|
RUNNING_ACTION['id'],
|
|
RUNNING_ACTION['state']
|
|
)
|
|
|
|
@mock.patch.object(
|
|
rpc_clients.EngineClient,
|
|
'on_action_complete',
|
|
MOCK_NOT_FOUND
|
|
)
|
|
def test_put_no_action_ex(self):
|
|
resp = self.app.put_json(
|
|
'/v2/action_executions/123',
|
|
UPDATED_ACTION,
|
|
expect_errors=True
|
|
)
|
|
|
|
self.assertEqual(404, resp.status_int)
|
|
|
|
def test_put_bad_state(self):
|
|
action = copy.deepcopy(ACTION_EX)
|
|
action['state'] = 'DELAYED'
|
|
|
|
resp = self.app.put_json(
|
|
'/v2/action_executions/123',
|
|
action,
|
|
expect_errors=True
|
|
)
|
|
|
|
self.assertEqual(400, resp.status_int)
|
|
self.assertIn('Expected one of', resp.json['faultstring'])
|
|
|
|
def test_put_bad_result(self):
|
|
resp = self.app.put_json(
|
|
'/v2/action_executions/123',
|
|
BROKEN_ACTION,
|
|
expect_errors=True
|
|
)
|
|
|
|
self.assertEqual(400, resp.status_int)
|
|
|
|
@mock.patch.object(rpc_clients.EngineClient, 'on_action_complete')
|
|
def test_put_without_result(self, f):
|
|
action_ex = copy.deepcopy(UPDATED_ACTION)
|
|
del action_ex['output']
|
|
|
|
f.return_value = UPDATED_ACTION_EX_DB
|
|
|
|
resp = self.app.put_json('/v2/action_executions/123', action_ex)
|
|
|
|
self.assertEqual(200, resp.status_int)
|
|
|
|
@mock.patch.object(db_api, 'get_action_executions', MOCK_ACTIONS)
|
|
def test_get_all(self):
|
|
resp = self.app.get('/v2/action_executions')
|
|
|
|
self.assertEqual(200, resp.status_int)
|
|
|
|
self.assertEqual(1, len(resp.json['action_executions']))
|
|
self.assertDictEqual(ACTION_EX, resp.json['action_executions'][0])
|
|
|
|
@mock.patch.object(db_api, 'get_action_executions')
|
|
def test_get_all_operational_error(self, mocked_get_all):
|
|
mocked_get_all.side_effect = [
|
|
# Emulating DB OperationalError
|
|
sa.exc.OperationalError('Mock', 'mock', 'mock'),
|
|
[ACTION_EX_DB] # Successful run
|
|
]
|
|
|
|
resp = self.app.get('/v2/action_executions')
|
|
|
|
self.assertEqual(200, resp.status_int)
|
|
|
|
self.assertEqual(1, len(resp.json['action_executions']))
|
|
self.assertDictEqual(ACTION_EX, resp.json['action_executions'][0])
|
|
|
|
@mock.patch.object(rest_utils, 'get_all',
|
|
return_value=resources.ActionExecutions())
|
|
def test_get_all_without_output(self, mock_get_all):
|
|
resp = self.app.get('/v2/action_executions')
|
|
|
|
args, kwargs = mock_get_all.call_args
|
|
resource_function = kwargs['resource_function']
|
|
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual(
|
|
action_execution._get_action_execution_resource_for_list,
|
|
resource_function
|
|
)
|
|
|
|
@mock.patch.object(rest_utils, 'get_all',
|
|
return_value=resources.ActionExecutions())
|
|
def test_get_all_with_output(self, mock_get_all):
|
|
resp = self.app.get('/v2/action_executions?include_output=true')
|
|
|
|
args, kwargs = mock_get_all.call_args
|
|
resource_function = kwargs['resource_function']
|
|
|
|
self.assertEqual(200, resp.status_int)
|
|
self.assertEqual(
|
|
action_execution._get_action_execution_resource,
|
|
resource_function
|
|
)
|
|
|
|
@mock.patch.object(db_api, 'get_action_executions', MOCK_EMPTY)
|
|
def test_get_all_empty(self):
|
|
resp = self.app.get('/v2/action_executions')
|
|
|
|
self.assertEqual(200, resp.status_int)
|
|
|
|
self.assertEqual(0, len(resp.json['action_executions']))
|
|
|
|
@mock.patch.object(db_api, 'get_action_execution', MOCK_AD_HOC_ACTION)
|
|
@mock.patch.object(db_api, 'delete_action_execution', MOCK_DELETE)
|
|
def test_delete(self):
|
|
cfg.CONF.set_default('allow_action_execution_deletion', True, 'api')
|
|
|
|
resp = self.app.delete('/v2/action_executions/123')
|
|
|
|
self.assertEqual(204, resp.status_int)
|
|
|
|
@mock.patch.object(db_api, 'get_action_execution', MOCK_NOT_FOUND)
|
|
def test_delete_not_found(self):
|
|
cfg.CONF.set_default('allow_action_execution_deletion', True, 'api')
|
|
|
|
resp = self.app.delete('/v2/action_executions/123', expect_errors=True)
|
|
|
|
self.assertEqual(404, resp.status_int)
|
|
|
|
def test_delete_not_allowed(self):
|
|
resp = self.app.delete('/v2/action_executions/123', expect_errors=True)
|
|
|
|
self.assertEqual(403, resp.status_int)
|
|
self.assertIn(
|
|
"Action execution deletion is not allowed",
|
|
resp.body.decode()
|
|
)
|
|
|
|
@mock.patch.object(db_api, 'get_action_execution', MOCK_ACTION)
|
|
def test_delete_action_execution_with_task(self):
|
|
cfg.CONF.set_default('allow_action_execution_deletion', True, 'api')
|
|
|
|
resp = self.app.delete('/v2/action_executions/123', expect_errors=True)
|
|
|
|
self.assertEqual(403, resp.status_int)
|
|
self.assertIn(
|
|
"Only ad-hoc action execution can be deleted",
|
|
resp.body.decode()
|
|
)
|
|
|
|
@mock.patch.object(
|
|
db_api,
|
|
'get_action_execution',
|
|
MOCK_ACTION_NOT_COMPLETE
|
|
)
|
|
def test_delete_action_execution_not_complete(self):
|
|
cfg.CONF.set_default('allow_action_execution_deletion', True, 'api')
|
|
|
|
resp = self.app.delete('/v2/action_executions/123', expect_errors=True)
|
|
|
|
self.assertEqual(403, resp.status_int)
|
|
self.assertIn(
|
|
"Only completed action execution can be deleted",
|
|
resp.body.decode()
|
|
)
|
|
|
|
@mock.patch.object(
|
|
db_api,
|
|
'get_action_execution',
|
|
MOCK_ACTION_COMPLETE_ERROR
|
|
)
|
|
@mock.patch.object(db_api, 'delete_action_execution', MOCK_DELETE)
|
|
def test_delete_action_execution_complete_error(self):
|
|
cfg.CONF.set_default('allow_action_execution_deletion', True, 'api')
|
|
|
|
resp = self.app.delete('/v2/action_executions/123', expect_errors=True)
|
|
|
|
self.assertEqual(204, resp.status_int)
|
|
|
|
@mock.patch.object(
|
|
db_api,
|
|
'get_action_execution',
|
|
MOCK_ACTION_COMPLETE_CANCELLED
|
|
)
|
|
@mock.patch.object(db_api, 'delete_action_execution', MOCK_DELETE)
|
|
def test_delete_action_execution_complete_cancelled(self):
|
|
cfg.CONF.set_default('allow_action_execution_deletion', True, 'api')
|
|
|
|
resp = self.app.delete('/v2/action_executions/123', expect_errors=True)
|
|
|
|
self.assertEqual(204, resp.status_int)
|