Passed
Pull Request — master (#4000)
by W
05:52
created

ActionExecutionsController.update_status()   A

Complexity

Conditions 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
dl 0
loc 6
rs 9.4285
1
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
2
# contributor license agreements.  See the NOTICE file distributed with
3
# this work for additional information regarding copyright ownership.
4
# The ASF licenses this file to You under the Apache License, Version 2.0
5
# (the "License"); you may not use this file except in compliance with
6
# the License.  You may obtain a copy of the License at
7
#
8
#     http://www.apache.org/licenses/LICENSE-2.0
9
#
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS,
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
15
16
import copy
17
import re
18
import sys
19
import traceback
20
import itertools
21
22
import six
23
import jsonschema
24
from oslo_config import cfg
25
from six.moves import http_client
26
27
from st2api.controllers.base import BaseRestControllerMixin
28
from st2api.controllers.resource import ResourceController
29
from st2api.controllers.v1.executionviews import ExecutionViewsController
30
from st2api.controllers.v1.executionviews import SUPPORTED_FILTERS
31
from st2common import log as logging
32
from st2common.constants import action as action_constants
33
from st2common.exceptions import actionrunner as runner_exc
34
from st2common.exceptions import apivalidation as validation_exc
35
from st2common.exceptions import param as param_exc
36
from st2common.exceptions import trace as trace_exc
37
from st2common.models.api.action import LiveActionAPI
38
from st2common.models.api.action import LiveActionCreateAPI
39
from st2common.models.api.base import cast_argument_value
40
from st2common.models.api.execution import ActionExecutionAPI
41
from st2common.models.api.execution import ActionExecutionOutputAPI
42
from st2common.models.db.auth import UserDB
43
from st2common.persistence.liveaction import LiveAction
44
from st2common.persistence.execution import ActionExecution
45
from st2common.persistence.execution import ActionExecutionOutput
46
from st2common.router import abort
47
from st2common.router import Response
48
from st2common.services import action as action_service
49
from st2common.services import executions as execution_service
50
from st2common.services import trace as trace_service
51
from st2common.services import rbac as rbac_service
52
from st2common.util import isotime
53
from st2common.util import action_db as action_utils
54
from st2common.util import param as param_utils
55
from st2common.util.jsonify import try_loads
56
from st2common.rbac.types import PermissionType
57
from st2common.rbac import utils as rbac_utils
58
from st2common.rbac.utils import assert_user_has_resource_db_permission
59
from st2common.rbac.utils import assert_user_is_admin_if_user_query_param_is_provided
60
from st2common.stream.listener import get_listener
61
62
__all__ = [
63
    'ActionExecutionsController'
64
]
65
66
LOG = logging.getLogger(__name__)
67
68
# Note: We initialize filters here and not in the constructor
69
SUPPORTED_EXECUTIONS_FILTERS = copy.deepcopy(SUPPORTED_FILTERS)
70
SUPPORTED_EXECUTIONS_FILTERS.update({
71
    'timestamp_gt': 'start_timestamp.gt',
72
    'timestamp_lt': 'start_timestamp.lt'
73
})
74
75
MONITOR_THREAD_EMPTY_Q_SLEEP_TIME = 5
76
MONITOR_THREAD_NO_WORKERS_SLEEP_TIME = 1
77
78
79
class ActionExecutionsControllerMixin(BaseRestControllerMixin):
80
    """
81
    Mixin class with shared methods.
82
    """
83
84
    model = ActionExecutionAPI
85
    access = ActionExecution
86
87
    # A list of attributes which can be specified using ?exclude_attributes filter
88
    valid_exclude_attributes = [
89
        'result',
90
        'trigger_instance'
91
    ]
92
93
    def _handle_schedule_execution(self, liveaction_api, requester_user, context_string=None,
94
                                   show_secrets=False):
95
        """
96
        :param liveaction: LiveActionAPI object.
97
        :type liveaction: :class:`LiveActionAPI`
98
        """
99
100
        if not requester_user:
101
            requester_user = UserDB(cfg.CONF.system_user.user)
102
103
        # Assert action ref is valid
104
        action_ref = liveaction_api.action
105
        action_db = action_utils.get_action_by_ref(action_ref)
106
107
        if not action_db:
108
            message = 'Action "%s" cannot be found.' % action_ref
109
            LOG.warning(message)
110
            abort(http_client.BAD_REQUEST, message)
111
112
        # Assert the permissions
113
        assert_user_has_resource_db_permission(user_db=requester_user, resource_db=action_db,
114
                                               permission_type=PermissionType.ACTION_EXECUTE)
115
116
        # Validate that the authenticated user is admin if user query param is provided
117
        user = liveaction_api.user or requester_user.name
118
        assert_user_is_admin_if_user_query_param_is_provided(user_db=requester_user,
119
                                                             user=user)
120
121
        try:
122
            return self._schedule_execution(liveaction=liveaction_api,
123
                                            requester_user=requester_user,
124
                                            user=user,
125
                                            context_string=context_string,
126
                                            show_secrets=show_secrets,
127
                                            pack=action_db.pack)
128
        except ValueError as e:
129
            LOG.exception('Unable to execute action.')
130
            abort(http_client.BAD_REQUEST, str(e))
131
        except jsonschema.ValidationError as e:
132
            LOG.exception('Unable to execute action. Parameter validation failed.')
133
            abort(http_client.BAD_REQUEST, re.sub("u'([^']*)'", r"'\1'", e.message))
134
        except trace_exc.TraceNotFoundException as e:
135
            abort(http_client.BAD_REQUEST, str(e))
136
        except validation_exc.ValueValidationException as e:
137
            raise e
138
        except Exception as e:
139
            LOG.exception('Unable to execute action. Unexpected error encountered.')
140
            abort(http_client.INTERNAL_SERVER_ERROR, str(e))
141
142
    def _schedule_execution(self,
143
                            liveaction,
144
                            requester_user,
145
                            user=None,
146
                            context_string=None,
147
                            show_secrets=False,
148
                            pack=None):
149
        # Initialize execution context if it does not exist.
150
        if not hasattr(liveaction, 'context'):
151
            liveaction.context = dict()
152
153
        liveaction.context['user'] = user
154
        liveaction.context['pack'] = pack
155
        LOG.debug('User is: %s' % liveaction.context['user'])
156
157
        # Retrieve other st2 context from request header.
158
        if context_string:
159
            context = try_loads(context_string)
160
            if not isinstance(context, dict):
161
                raise ValueError('Unable to convert st2-context from the headers into JSON.')
162
            liveaction.context.update(context)
163
164
        # Include RBAC context (if RBAC is available and enabled)
165
        if cfg.CONF.rbac.enable:
166
            user_db = UserDB(name=user)
167
            role_dbs = rbac_service.get_roles_for_user(user_db=user_db, include_remote=True)
168
            roles = [role_db.name for role_db in role_dbs]
169
            liveaction.context['rbac'] = {
170
                'user': user,
171
                'roles': roles
172
            }
173
174
        # Schedule the action execution.
175
        liveaction_db = LiveActionAPI.to_model(liveaction)
176
        action_db = action_utils.get_action_by_ref(liveaction_db.action)
177
        runnertype_db = action_utils.get_runnertype_by_name(action_db.runner_type['name'])
178
179
        try:
180
            liveaction_db.parameters = param_utils.render_live_params(
181
                runnertype_db.runner_parameters, action_db.parameters, liveaction_db.parameters,
182
                liveaction_db.context)
183
        except param_exc.ParamException:
184
185
            # We still need to create a request, so liveaction_db is assigned an ID
186
            liveaction_db, actionexecution_db = action_service.create_request(liveaction_db)
187
188
            # By this point the execution is already in the DB therefore need to mark it failed.
189
            _, e, tb = sys.exc_info()
190
            action_service.update_status(
191
                liveaction=liveaction_db,
192
                new_status=action_constants.LIVEACTION_STATUS_FAILED,
193
                result={'error': str(e), 'traceback': ''.join(traceback.format_tb(tb, 20))})
194
            # Might be a good idea to return the actual ActionExecution rather than bubble up
195
            # the exception.
196
            raise validation_exc.ValueValidationException(str(e))
197
198
        # The request should be created after the above call to render_live_params
199
        # so any templates in live parameters have a chance to render.
200
        liveaction_db, actionexecution_db = action_service.create_request(liveaction_db)
201
        liveaction_db = LiveAction.add_or_update(liveaction_db, publish=False)
202
203
        _, actionexecution_db = action_service.publish_request(liveaction_db, actionexecution_db)
204
        mask_secrets = self._get_mask_secrets(requester_user, show_secrets=show_secrets)
205
        execution_api = ActionExecutionAPI.from_model(actionexecution_db, mask_secrets=mask_secrets)
206
207
        return Response(json=execution_api, status=http_client.CREATED)
208
209
    def _get_result_object(self, id):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
210
        """
211
        Retrieve result object for the provided action execution.
212
213
        :param id: Action execution ID.
214
        :type id: ``str``
215
216
        :rtype: ``dict``
217
        """
218
        fields = ['result']
219
        action_exec_db = self.access.impl.model.objects.filter(id=id).only(*fields).get()
220
        return action_exec_db.result
221
222
    def _get_children(self, id_, requester_user, depth=-1, result_fmt=None,
223
                      show_secrets=False):
224
        # make sure depth is int. Url encoding will make it a string and needs to
225
        # be converted back in that case.
226
        depth = int(depth)
227
        LOG.debug('retrieving children for id: %s with depth: %s', id_, depth)
228
        descendants = execution_service.get_descendants(actionexecution_id=id_,
229
                                                        descendant_depth=depth,
230
                                                        result_fmt=result_fmt)
231
232
        mask_secrets = self._get_mask_secrets(requester_user, show_secrets=show_secrets)
233
        return [self.model.from_model(descendant, mask_secrets=mask_secrets) for
234
                descendant in descendants]
235
236
237
class BaseActionExecutionNestedController(ActionExecutionsControllerMixin, ResourceController):
238
    # Note: We need to override "get_one" and "get_all" to return 404 since nested controller
239
    # don't implement thos methods
240
241
    # ResourceController attributes
242
    query_options = {}
243
    supported_filters = {}
244
245
    def get_all(self):
246
        abort(http_client.NOT_FOUND)
247
248
    def get_one(self, id):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
249
        abort(http_client.NOT_FOUND)
250
251
252
class ActionExecutionChildrenController(BaseActionExecutionNestedController):
253
    def get_one(self, id, requester_user, depth=-1, result_fmt=None, show_secrets=False):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
Bug introduced by
Arguments number differs from overridden 'get_one' method
Loading history...
254
        """
255
        Retrieve children for the provided action execution.
256
257
        :rtype: ``list``
258
        """
259
260
        instance = self._get_by_id(resource_id=id)
261
262
        permission_type = PermissionType.EXECUTION_VIEW
263
        rbac_utils.assert_user_has_resource_db_permission(user_db=requester_user,
264
                                                          resource_db=instance,
265
                                                          permission_type=permission_type)
266
267
        return self._get_children(id_=id, depth=depth, result_fmt=result_fmt,
268
                                  requester_user=requester_user, show_secrets=show_secrets)
269
270
271
class ActionExecutionAttributeController(BaseActionExecutionNestedController):
272
    valid_exclude_attributes = ['action__pack', 'action__uid'] + \
273
        ActionExecutionsControllerMixin.valid_exclude_attributes
274
275
    def get(self, id, attribute, requester_user):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
276
        """
277
        Retrieve a particular attribute for the provided action execution.
278
279
        Handles requests:
280
281
            GET /executions/<id>/attribute/<attribute name>
282
283
        :rtype: ``dict``
284
        """
285
        fields = [attribute, 'action__pack', 'action__uid']
286
        fields = self._validate_exclude_fields(fields)
287
        action_exec_db = self.access.impl.model.objects.filter(id=id).only(*fields).get()
288
289
        permission_type = PermissionType.EXECUTION_VIEW
290
        rbac_utils.assert_user_has_resource_db_permission(user_db=requester_user,
291
                                                          resource_db=action_exec_db,
292
                                                          permission_type=permission_type)
293
294
        result = getattr(action_exec_db, attribute, None)
295
        return result
296
297
298
class ActionExecutionOutputController(ActionExecutionsControllerMixin, ResourceController):
299
    supported_filters = {
300
        'output_type': 'output_type'
301
    }
302
    exclude_fields = []
303
304
    CLOSE_STREAM_LIVEACTION_STATES = action_constants.LIVEACTION_COMPLETED_STATES + [
305
        action_constants.LIVEACTION_STATUS_PAUSING,
306
        action_constants.LIVEACTION_STATUS_RESUMING
307
    ]
308
309
    def get_one(self, id, output_type=None, requester_user=None):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
310
        # Special case for id == "last"
311
        if id == 'last':
312
            execution_db = ActionExecution.query().order_by('-id').limit(1).first()
313
        else:
314
            execution_db = self._get_one_by_id(id=id, requester_user=requester_user,
315
                                               permission_type=PermissionType.EXECUTION_VIEW)
316
317
        execution_id = str(execution_db.id)
318
319
        query_filters = {}
320
        if output_type and output_type != 'all':
321
            query_filters['output_type'] = output_type
322
323
        def existing_output_iter():
324
            # Consume and return all of the existing lines
325
            # pylint: disable=no-member
326
            output_dbs = ActionExecutionOutput.query(execution_id=execution_id, **query_filters)
327
328
            # Note: We return all at once instead of yield line by line to avoid multiple socket
329
            # writes and to achieve better performance
330
            output = ''.join([output_db.data for output_db in output_dbs])
331
            yield six.binary_type(output.encode('utf-8'))
332
333
        def new_output_iter():
334
            def noop_gen():
335
                yield six.binary_type('')
336
337
            # Bail out if execution has already completed / been paused
338
            if execution_db.status in self.CLOSE_STREAM_LIVEACTION_STATES:
339
                return noop_gen()
340
341
            # Wait for and return any new line which may come in
342
            execution_ids = [execution_id]
343
            listener = get_listener(name='execution_output')  # pylint: disable=no-member
344
            gen = listener.generator(execution_ids=execution_ids)
345
346
            def format(gen):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in format.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
347
                for pack in gen:
348
                    if not pack:
349
                        continue
350
                    else:
351
                        (_, model_api) = pack
352
353
                        # Note: gunicorn wsgi handler expect bytes, not unicode
354
                        # pylint: disable=no-member
355
                        if isinstance(model_api, ActionExecutionOutputAPI):
356
                            if output_type and model_api.output_type != output_type:
357
                                continue
358
359
                            yield six.binary_type(model_api.data.encode('utf-8'))
360
                        elif isinstance(model_api, ActionExecutionAPI):
361
                            if model_api.status in self.CLOSE_STREAM_LIVEACTION_STATES:
362
                                yield six.binary_type('')
363
                                break
364
                        else:
365
                            LOG.debug('Unrecognized message type: %s' % (model_api))
366
367
            gen = format(gen)
368
            return gen
369
370
        def make_response():
371
            app_iter = itertools.chain(existing_output_iter(), new_output_iter())
372
            res = Response(content_type='text/plain', app_iter=app_iter)
373
            return res
374
375
        res = make_response()
376
        return res
377
378
379
class ActionExecutionReRunController(ActionExecutionsControllerMixin, ResourceController):
380
    supported_filters = {}
381
    exclude_fields = [
382
        'result',
383
        'trigger_instance'
384
    ]
385
386
    class ExecutionSpecificationAPI(object):
387
        def __init__(self, parameters=None, tasks=None, reset=None, user=None):
388
            self.parameters = parameters or {}
389
            self.tasks = tasks or []
390
            self.reset = reset or []
391
            self.user = user
392
393
        def validate(self):
394
            if (self.tasks or self.reset) and self.parameters:
395
                raise ValueError('Parameters override is not supported when '
396
                                 're-running task(s) for a workflow.')
397
398
            if self.parameters:
399
                assert isinstance(self.parameters, dict)
400
401
            if self.tasks:
402
                assert isinstance(self.tasks, list)
403
404
            if self.reset:
405
                assert isinstance(self.reset, list)
406
407
            if list(set(self.reset) - set(self.tasks)):
408
                raise ValueError('List of tasks to reset does not match the tasks to rerun.')
409
410
            return self
411
412
    def post(self, spec_api, id, requester_user, no_merge=False, show_secrets=False):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
413
        """
414
        Re-run the provided action execution optionally specifying override parameters.
415
416
        Handles requests:
417
418
            POST /executions/<id>/re_run
419
        """
420
421
        if (spec_api.tasks or spec_api.reset) and spec_api.parameters:
422
            raise ValueError('Parameters override is not supported when '
423
                             're-running task(s) for a workflow.')
424
425
        if spec_api.parameters:
426
            assert isinstance(spec_api.parameters, dict)
427
428
        if spec_api.tasks:
429
            assert isinstance(spec_api.tasks, list)
430
431
        if spec_api.reset:
432
            assert isinstance(spec_api.reset, list)
433
434
        if list(set(spec_api.reset) - set(spec_api.tasks)):
435
            raise ValueError('List of tasks to reset does not match the tasks to rerun.')
436
437
        no_merge = cast_argument_value(value_type=bool, value=no_merge)
438
        existing_execution = self._get_one_by_id(id=id, exclude_fields=self.exclude_fields,
439
                                                 requester_user=requester_user,
440
                                                 permission_type=PermissionType.EXECUTION_VIEW)
441
442
        if spec_api.tasks and existing_execution.runner['name'] != 'mistral-v2':
443
            raise ValueError('Task option is only supported for Mistral workflows.')
444
445
        # Merge in any parameters provided by the user
446
        new_parameters = {}
447
        if not no_merge:
448
            new_parameters.update(getattr(existing_execution, 'parameters', {}))
449
        new_parameters.update(spec_api.parameters)
450
451
        # Create object for the new execution
452
        action_ref = existing_execution.action['ref']
453
454
        # Include additional option(s) for the execution
455
        context = {
456
            're-run': {
457
                'ref': id,
458
            }
459
        }
460
461
        if spec_api.tasks:
462
            context['re-run']['tasks'] = spec_api.tasks
463
464
        if spec_api.reset:
465
            context['re-run']['reset'] = spec_api.reset
466
467
        # Add trace to the new execution
468
        trace = trace_service.get_trace_db_by_action_execution(
469
            action_execution_id=existing_execution.id)
470
471
        if trace:
472
            context['trace_context'] = {'id_': str(trace.id)}
473
474
        new_liveaction_api = LiveActionCreateAPI(action=action_ref,
475
                                                 context=context,
476
                                                 parameters=new_parameters,
477
                                                 user=spec_api.user)
478
479
        return self._handle_schedule_execution(liveaction_api=new_liveaction_api,
480
                                               requester_user=requester_user,
481
                                               show_secrets=show_secrets)
482
483
484
class ActionExecutionsController(ActionExecutionsControllerMixin, ResourceController):
485
    """
486
        Implements the RESTful web endpoint that handles
487
        the lifecycle of ActionExecutions in the system.
488
    """
489
490
    # Nested controllers
491
    views = ExecutionViewsController()
492
493
    children = ActionExecutionChildrenController()
494
    attribute = ActionExecutionAttributeController()
495
    re_run = ActionExecutionReRunController()
496
497
    # ResourceController attributes
498
    query_options = {
499
        'sort': ['-start_timestamp', 'action.ref']
500
    }
501
    supported_filters = SUPPORTED_EXECUTIONS_FILTERS
502
    filter_transform_functions = {
503
        'timestamp_gt': lambda value: isotime.parse(value=value),
504
        'timestamp_lt': lambda value: isotime.parse(value=value)
505
    }
506
507
    def get_all(self, requester_user, exclude_attributes=None, sort=None, offset=0, limit=None,
508
                show_secrets=False, include_attributes=None, **raw_filters):
509
        """
510
        List all executions.
511
512
        Handles requests:
513
            GET /executions[?exclude_attributes=result,trigger_instance]
514
515
        :param exclude_attributes: List of attributes to exclude from the object.
516
        :type exclude_attributes: ``list``
517
        """
518
        exclude_fields = self._validate_exclude_fields(exclude_fields=exclude_attributes)
519
520
        # Use a custom sort order when filtering on a timestamp so we return a correct result as
521
        # expected by the user
522
        query_options = None
523
        if raw_filters.get('timestamp_lt', None) or raw_filters.get('sort_desc', None):
524
            query_options = {'sort': ['-start_timestamp', 'action.ref']}
525
        elif raw_filters.get('timestamp_gt', None) or raw_filters.get('sort_asc', None):
526
            query_options = {'sort': ['+start_timestamp', 'action.ref']}
527
528
        from_model_kwargs = {
529
            'mask_secrets': self._get_mask_secrets(requester_user, show_secrets=show_secrets)
530
        }
531
        return self._get_action_executions(exclude_fields=exclude_fields,
532
                                           include_fields=include_attributes,
533
                                           from_model_kwargs=from_model_kwargs,
534
                                           sort=sort,
535
                                           offset=offset,
536
                                           limit=limit,
537
                                           query_options=query_options,
538
                                           raw_filters=raw_filters,
539
                                           requester_user=requester_user)
540
541
    def get_one(self, id, requester_user, exclude_attributes=None, show_secrets=False):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
542
        """
543
        Retrieve a single execution.
544
545
        Handles requests:
546
            GET /executions/<id>[?exclude_attributes=result,trigger_instance]
547
548
        :param exclude_attributes: List of attributes to exclude from the object.
549
        :type exclude_attributes: ``list``
550
        """
551
        exclude_fields = self._validate_exclude_fields(exclude_fields=exclude_attributes)
552
553
        from_model_kwargs = {
554
            'mask_secrets': self._get_mask_secrets(requester_user, show_secrets=show_secrets)
555
        }
556
557
        # Special case for id == "last"
558
        if id == 'last':
559
            execution_db = ActionExecution.query().order_by('-id').limit(1).only('id').first()
560
            id = str(execution_db.id)
561
562
        return self._get_one_by_id(id=id, exclude_fields=exclude_fields,
563
                                   requester_user=requester_user,
564
                                   from_model_kwargs=from_model_kwargs,
565
                                   permission_type=PermissionType.EXECUTION_VIEW)
566
567
    def post(self, liveaction_api, requester_user, context_string=None, show_secrets=False):
568
        return self._handle_schedule_execution(liveaction_api=liveaction_api,
569
                                               requester_user=requester_user,
570
                                               context_string=context_string,
571
                                               show_secrets=show_secrets)
572
573
    def put(self, id, liveaction_api, requester_user, show_secrets=False):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
574
        """
575
        Updates a single execution.
576
577
        Handles requests:
578
            PUT /executions/<id>
579
580
        """
581
        if not requester_user:
582
            requester_user = UserDB(cfg.CONF.system_user.user)
583
584
        from_model_kwargs = {
585
            'mask_secrets': self._get_mask_secrets(requester_user, show_secrets=show_secrets)
586
        }
587
588
        execution_api = self._get_one_by_id(id=id, requester_user=requester_user,
589
                                            from_model_kwargs=from_model_kwargs,
590
                                            permission_type=PermissionType.EXECUTION_STOP)
591
592
        if not execution_api:
593
            abort(http_client.NOT_FOUND, 'Execution with id %s not found.' % id)
594
595
        liveaction_id = execution_api.liveaction['id']
596
        if not liveaction_id:
597
            abort(http_client.INTERNAL_SERVER_ERROR,
598
                  'Execution object missing link to liveaction %s.' % liveaction_id)
599
600
        try:
601
            liveaction_db = LiveAction.get_by_id(liveaction_id)
602
        except:
603
            abort(http_client.INTERNAL_SERVER_ERROR,
604
                  'Execution object missing link to liveaction %s.' % liveaction_id)
605
606
        if liveaction_db.status in action_constants.LIVEACTION_COMPLETED_STATES:
607
            abort(http_client.BAD_REQUEST, 'Execution is already in completed state.')
608
609
        def update_status(liveaction_api, liveaction_db):
610
            status = liveaction_api.status
611
            result = getattr(liveaction_api, 'result', None)
612
            liveaction_db = action_service.update_status(liveaction_db, status, result)
613
            actionexecution_db = ActionExecution.get(liveaction__id=str(liveaction_db.id))
614
            return (liveaction_db, actionexecution_db)
615
616
        try:
617
            if (liveaction_db.status == action_constants.LIVEACTION_STATUS_CANCELING and
618
                    liveaction_api.status == action_constants.LIVEACTION_STATUS_CANCELED):
619
                if action_service.is_children_active(liveaction_id):
620
                    liveaction_api.status = action_constants.LIVEACTION_STATUS_CANCELING
621
                liveaction_db, actionexecution_db = update_status(liveaction_api, liveaction_db)
622
            elif (liveaction_api.status == action_constants.LIVEACTION_STATUS_CANCELING or
623
                    liveaction_api.status == action_constants.LIVEACTION_STATUS_CANCELED):
624
                liveaction_db, actionexecution_db = action_service.request_cancellation(
625
                    liveaction_db, requester_user.name or cfg.CONF.system_user.user)
626
            elif (liveaction_db.status == action_constants.LIVEACTION_STATUS_PAUSING and
627
                    liveaction_api.status == action_constants.LIVEACTION_STATUS_PAUSED):
628
                if action_service.is_children_active(liveaction_id):
629
                    liveaction_api.status = action_constants.LIVEACTION_STATUS_PAUSING
630
                liveaction_db, actionexecution_db = update_status(liveaction_api, liveaction_db)
631
            elif (liveaction_api.status == action_constants.LIVEACTION_STATUS_PAUSING or
632
                    liveaction_api.status == action_constants.LIVEACTION_STATUS_PAUSED):
633
                liveaction_db, actionexecution_db = action_service.request_pause(
634
                    liveaction_db, requester_user.name or cfg.CONF.system_user.user)
635
            elif liveaction_api.status == action_constants.LIVEACTION_STATUS_RESUMING:
636
                liveaction_db, actionexecution_db = action_service.request_resume(
637
                    liveaction_db, requester_user.name or cfg.CONF.system_user.user)
638
            else:
639
                liveaction_db, actionexecution_db = update_status(liveaction_api, liveaction_db)
640
        except runner_exc.InvalidActionRunnerOperationError as e:
641
            LOG.exception('Failed updating liveaction %s. %s', liveaction_db.id, str(e))
642
            abort(http_client.BAD_REQUEST, 'Failed updating execution. %s' % str(e))
643
        except runner_exc.UnexpectedActionExecutionStatusError as e:
644
            LOG.exception('Failed updating liveaction %s. %s', liveaction_db.id, str(e))
645
            abort(http_client.BAD_REQUEST, 'Failed updating execution. %s' % str(e))
646
        except Exception as e:
647
            LOG.exception('Failed updating liveaction %s. %s', liveaction_db.id, str(e))
648
            abort(
649
                http_client.INTERNAL_SERVER_ERROR,
650
                'Failed updating execution due to unexpected error.'
651
            )
652
653
        mask_secrets = self._get_mask_secrets(requester_user, show_secrets=show_secrets)
654
        execution_api = ActionExecutionAPI.from_model(actionexecution_db, mask_secrets=mask_secrets)
655
656
        return execution_api
657
658
    def delete(self, id, requester_user, show_secrets=False):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
659
        """
660
        Stops a single execution.
661
662
        Handles requests:
663
            DELETE /executions/<id>
664
665
        """
666
        if not requester_user:
667
            requester_user = UserDB(cfg.CONF.system_user.user)
668
669
        from_model_kwargs = {
670
            'mask_secrets': self._get_mask_secrets(requester_user, show_secrets=show_secrets)
671
        }
672
        execution_api = self._get_one_by_id(id=id, requester_user=requester_user,
673
                                            from_model_kwargs=from_model_kwargs,
674
                                            permission_type=PermissionType.EXECUTION_STOP)
675
676
        if not execution_api:
677
            abort(http_client.NOT_FOUND, 'Execution with id %s not found.' % id)
678
679
        liveaction_id = execution_api.liveaction['id']
680
        if not liveaction_id:
681
            abort(http_client.INTERNAL_SERVER_ERROR,
682
                  'Execution object missing link to liveaction %s.' % liveaction_id)
683
684
        try:
685
            liveaction_db = LiveAction.get_by_id(liveaction_id)
686
        except:
687
            abort(http_client.INTERNAL_SERVER_ERROR,
688
                  'Execution object missing link to liveaction %s.' % liveaction_id)
689
690
        if liveaction_db.status == action_constants.LIVEACTION_STATUS_CANCELED:
691
            LOG.info(
692
                'Action %s already in "canceled" state; \
693
                returning execution object.' % liveaction_db.id
694
            )
695
            return execution_api
696
697
        if liveaction_db.status not in action_constants.LIVEACTION_CANCELABLE_STATES:
698
            abort(http_client.OK, 'Action cannot be canceled. State = %s.' % liveaction_db.status)
699
700
        try:
701
            (liveaction_db, execution_db) = action_service.request_cancellation(
702
                liveaction_db, requester_user.name or cfg.CONF.system_user.user)
703
        except:
704
            LOG.exception('Failed requesting cancellation for liveaction %s.', liveaction_db.id)
705
            abort(http_client.INTERNAL_SERVER_ERROR, 'Failed canceling execution.')
706
707
        return ActionExecutionAPI.from_model(execution_db,
708
                                             mask_secrets=from_model_kwargs['mask_secrets'])
709
710
    def _get_action_executions(self, exclude_fields=None, include_fields=None,
711
                               sort=None, offset=0, limit=None,
712
                               query_options=None, raw_filters=None, from_model_kwargs=None,
713
                               requester_user=None):
714
        """
715
        :param exclude_fields: A list of object fields to exclude.
716
        :type exclude_fields: ``list``
717
        """
718
719
        if limit is None:
720
            limit = self.default_limit
721
722
        limit = int(limit)
723
724
        LOG.debug('Retrieving all action executions with filters=%s', raw_filters)
725
        return super(ActionExecutionsController, self)._get_all(exclude_fields=exclude_fields,
726
                                                                include_fields=include_fields,
727
                                                                from_model_kwargs=from_model_kwargs,
728
                                                                sort=sort,
729
                                                                offset=offset,
730
                                                                limit=limit,
731
                                                                query_options=query_options,
732
                                                                raw_filters=raw_filters,
733
                                                                requester_user=requester_user)
734
735
736
action_executions_controller = ActionExecutionsController()
737
action_execution_output_controller = ActionExecutionOutputController()
738
action_execution_rerun_controller = ActionExecutionReRunController()
739
action_execution_attribute_controller = ActionExecutionAttributeController()
740
action_execution_children_controller = ActionExecutionChildrenController()
741