Skip to content

Commit

Permalink
Add --tasks option to execution re-run endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
m4dcoder committed Jan 14, 2016
1 parent 5ab29af commit 6928e7b
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 15 deletions.
18 changes: 13 additions & 5 deletions st2api/st2api/controllers/v1/actionexecutions.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,13 +268,21 @@ class ActionExecutionReRunController(ActionExecutionsControllerMixin, ResourceCo
]

class ExecutionSpecificationAPI(object):
def __init__(self, parameters=None):
def __init__(self, parameters=None, tasks=None):
self.parameters = parameters or {}
self.tasks = tasks or []

def validate(self):
if self.tasks and self.parameters:
raise ValueError('Parameters override is not supported when '
're-running task(s) for a workflow.')

if self.parameters:
assert isinstance(self.parameters, dict)

if self.tasks:
assert isinstance(self.tasks, list)

return self

@jsexpose(body_cls=ExecutionSpecificationAPI, status_code=http_client.CREATED)
Expand All @@ -286,14 +294,14 @@ def post(self, spec, execution_id):
POST /executions/<id>/re_run
"""
parameters = spec.parameters

# Note: We only really need parameters here
existing_execution = self._get_one(id=execution_id, exclude_fields=self.exclude_fields)

if spec.tasks and existing_execution.runner['name'] != 'mistral-v2':
raise ValueError('Task option is only supported for Mistral workflows.')

# Merge in any parameters provided by the user
new_parameters = copy.deepcopy(getattr(existing_execution, 'parameters', {}))
new_parameters.update(parameters)
new_parameters.update(spec.parameters)

# Create object for the new execution
action_ref = existing_execution.action['ref']
Expand Down
101 changes: 100 additions & 1 deletion st2api/tests/unit/controllers/v1/test_executions_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,25 @@
}
}

ACTION_4 = {
'name': 'st2.dummy.action4',
'description': 'another test description',
'enabled': True,
'entry_point': '/tmp/test/workflows/action4.yaml',
'pack': 'starterpack',
'runner_type': 'mistral-v2',
'parameters': {
'a': {
'type': 'string',
'default': 'abc'
},
'b': {
'type': 'number',
'default': 123
}
}
}

LIVE_ACTION_1 = {
'action': 'sixpack.st2.dummy.action1',
'parameters': {
Expand All @@ -123,6 +142,10 @@
}
}

LIVE_ACTION_4 = {
'action': 'starterpack.st2.dummy.action4',
}


class FakeResponse(object):

Expand All @@ -146,21 +169,29 @@ class TestActionExecutionController(FunctionalTest):
return_value=True))
def setUpClass(cls):
super(TestActionExecutionController, cls).setUpClass()

cls.action1 = copy.deepcopy(ACTION_1)
post_resp = cls.app.post_json('/v1/actions', cls.action1)
cls.action1['id'] = post_resp.json['id']

cls.action2 = copy.deepcopy(ACTION_2)
post_resp = cls.app.post_json('/v1/actions', cls.action2)
cls.action2['id'] = post_resp.json['id']

cls.action3 = copy.deepcopy(ACTION_3)
post_resp = cls.app.post_json('/v1/actions', cls.action3)
cls.action3['id'] = post_resp.json['id']

cls.action4 = copy.deepcopy(ACTION_4)
post_resp = cls.app.post_json('/v1/actions', cls.action4)
cls.action4['id'] = post_resp.json['id']

@classmethod
def tearDownClass(cls):
cls.app.delete('/v1/actions/%s' % cls.action1['id'])
cls.app.delete('/v1/actions/%s' % cls.action2['id'])
cls.app.delete('/v1/actions/%s' % cls.action3['id'])
cls.app.delete('/v1/actions/%s' % cls.action4['id'])
super(TestActionExecutionController, cls).tearDownClass()

def test_get_one(self):
Expand Down Expand Up @@ -369,13 +400,81 @@ def test_re_run_failure_parameter_override_invalid_type(self):
self.assertEqual(post_resp.status_int, 201)
execution_id = self._get_actionexecution_id(post_resp)

# Re-run created execution (override parameter with an invalid value)
# Re-run created execution (override parameter and task together)
data = {'parameters': {'a': 1000}}
re_run_resp = self.app.post_json('/v1/executions/%s/re_run' % (execution_id),
data, expect_errors=True)
self.assertEqual(re_run_resp.status_int, 400)
self.assertIn('1000 is not of type \'string\'', re_run_resp.json['faultstring'])

def test_re_run_workflow_success(self):
# Create a new execution
post_resp = self._do_post(LIVE_ACTION_4)
self.assertEqual(post_resp.status_int, 201)
execution_id = self._get_actionexecution_id(post_resp)

# Re-run created execution (tasks option for non workflow)
data = {}
re_run_resp = self.app.post_json('/v1/executions/%s/re_run' % (execution_id),
data, expect_errors=True)

self.assertEqual(re_run_resp.status_int, 201)

def test_re_run_workflow_task_success(self):
# Create a new execution
post_resp = self._do_post(LIVE_ACTION_4)
self.assertEqual(post_resp.status_int, 201)
execution_id = self._get_actionexecution_id(post_resp)

# Re-run created execution (tasks option for non workflow)
data = {'tasks': ['x']}
re_run_resp = self.app.post_json('/v1/executions/%s/re_run' % (execution_id),
data, expect_errors=True)

self.assertEqual(re_run_resp.status_int, 201)

def test_re_run_workflow_tasks_success(self):
# Create a new execution
post_resp = self._do_post(LIVE_ACTION_4)
self.assertEqual(post_resp.status_int, 201)
execution_id = self._get_actionexecution_id(post_resp)

# Re-run created execution (tasks option for non workflow)
data = {'tasks': ['x', 'y']}
re_run_resp = self.app.post_json('/v1/executions/%s/re_run' % (execution_id),
data, expect_errors=True)

self.assertEqual(re_run_resp.status_int, 201)

def test_re_run_failure_tasks_option_for_non_workflow(self):
# Create a new execution
post_resp = self._do_post(LIVE_ACTION_1)
self.assertEqual(post_resp.status_int, 201)
execution_id = self._get_actionexecution_id(post_resp)

# Re-run created execution (tasks option for non workflow)
data = {'tasks': ['x']}
re_run_resp = self.app.post_json('/v1/executions/%s/re_run' % (execution_id),
data, expect_errors=True)

self.assertEqual(re_run_resp.status_int, 400)
self.assertIn('only supported for Mistral workflows', re_run_resp.json['faultstring'])

def test_re_run_workflow_failure_given_both_params_and_tasks(self):
# Create a new execution
post_resp = self._do_post(LIVE_ACTION_4)
self.assertEqual(post_resp.status_int, 201)
execution_id = self._get_actionexecution_id(post_resp)

# Re-run created execution (override parameter with an invalid value)
data = {'parameters': {'a': 'xyz'}, 'tasks': ['x']}
re_run_resp = self.app.post_json('/v1/executions/%s/re_run' % (execution_id),
data, expect_errors=True)

self.assertEqual(re_run_resp.status_int, 500)
self.assertIn('not supported when re-running task(s) for a workflow',
re_run_resp.json['faultstring'])

@staticmethod
def _get_actionexecution_id(resp):
return resp.json['id']
Expand Down
21 changes: 17 additions & 4 deletions st2client/st2client/commands/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,10 @@ def normalize(name, value):
return value

result = {}

if not args.parameters:
return result

for idx in range(len(args.parameters)):
arg = args.parameters[idx]
if '=' in arg:
Expand Down Expand Up @@ -1046,9 +1050,14 @@ def __init__(self, resource, *args, **kwargs):
self.parser.add_argument('id', nargs='?',
metavar='id',
help='ID of action execution to re-run ')
self.parser.add_argument('parameters', nargs='*',
help='List of keyword args, positional args, '
'and optional args for the action.')

group = self.parser.add_mutually_exclusive_group()
group.add_argument('--parameters', nargs='*',
help='List of keyword args, positional args, '
'and optional args for the action.')
group.add_argument('--tasks', nargs='*',
help='Name of the workflow tasks to re-run.')

self.parser.add_argument('-a', '--async',
action='store_true', dest='async',
help='Do not wait for action to finish.')
Expand Down Expand Up @@ -1083,9 +1092,13 @@ def run(self, args, **kwargs):
action_parameters = self._get_action_parameters_from_args(action=action, runner=runner,
args=args)

execution = action_exec_mgr.re_run(execution_id=args.id, parameters=action_parameters,
execution = action_exec_mgr.re_run(execution_id=args.id,
parameters=action_parameters,
tasks=args.tasks,
**kwargs)

execution = self._get_execution_result(execution=execution,
action_exec_mgr=action_exec_mgr,
args=args, **kwargs)

return execution
9 changes: 5 additions & 4 deletions st2client/st2client/models/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,12 +320,13 @@ def __init__(self, resource, endpoint, cacert=None, debug=False):

class LiveActionResourceManager(ResourceManager):
@add_auth_token_to_kwargs_from_env
def re_run(self, execution_id, parameters=None, **kwargs):
def re_run(self, execution_id, parameters=None, tasks=None, **kwargs):
url = '/%s/%s/re_run' % (self.resource.get_url_path_name(), execution_id)

data = {}
if parameters:
data['parameters'] = parameters
data = {
'parameters': parameters,
'tasks': tasks
}

response = self.client.post(url, data, **kwargs)
if response.status_code != 200:
Expand Down
8 changes: 7 additions & 1 deletion st2client/tests/unit/test_shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,20 @@ def test_action_execution(self):
['execution', 'list'],
['execution', 'get', '123'],
['execution', 'get', '123', '-d'],
['execution', 'get', '123', '-k', 'localhost.stdout']
['execution', 'get', '123', '-k', 'localhost.stdout'],
['execution', 're-run', '123'],
['execution', 're-run', '123', '--task', 'x', 'y', 'z'],
['execution', 're-run', '123', '--parameters', 'a=1', 'b=x', 'c=True']
]
self._validate_parser(args_list)

# Test mutually exclusive argument groups
self.assertRaises(SystemExit, self._validate_parser,
[['execution', 'get', '123', '-d', '-k', 'localhost.stdout']])

self.assertRaises(SystemExit, self._validate_parser,
[['execution', 're-run', '123', '--task', 'x', '--parameters', 'y=1']])

def test_key(self):
args_list = [
['key', 'list'],
Expand Down

0 comments on commit 6928e7b

Please sign in to comment.