Passed
Push — master ( f1fe9e...5c5de8 )
by
unknown
03:44
created

ActionExecutionsController.delete()   C

Complexity

Conditions 8

Size

Total Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
dl 0
loc 51
rs 5.2591
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 httplib
19
import sys
20
import traceback
21
import itertools
22
23
import six
24
import jsonschema
25
from oslo_config import cfg
26
from six.moves import http_client
27
28
from st2api.controllers.base import BaseRestControllerMixin
29
from st2api.controllers.resource import ResourceController
30
from st2api.controllers.v1.executionviews import ExecutionViewsController
31
from st2api.controllers.v1.executionviews import SUPPORTED_FILTERS
32
from st2common import log as logging
33
from st2common.constants import action as action_constants
34
from st2common.exceptions import actionrunner as runner_exc
35
from st2common.exceptions import apivalidation as validation_exc
36
from st2common.exceptions import param as param_exc
37
from st2common.exceptions import trace as trace_exc
38
from st2common.models.api.action import LiveActionAPI
39
from st2common.models.api.action import LiveActionCreateAPI
40
from st2common.models.api.base import cast_argument_value
41
from st2common.models.api.execution import ActionExecutionAPI
42
from st2common.models.api.execution import ActionExecutionOutputAPI
43
from st2common.models.db.auth import UserDB
44
from st2common.persistence.liveaction import LiveAction
45
from st2common.persistence.execution import ActionExecution
46
from st2common.persistence.execution import ActionExecutionOutput
47
from st2common.router import abort
48
from st2common.router import Response
49
from st2common.services import action as action_service
50
from st2common.services import executions as execution_service
51
from st2common.services import trace as trace_service
52
from st2common.services import rbac as rbac_service
53
from st2common.util import isotime
54
from st2common.util import action_db as action_utils
55
from st2common.util import param as param_utils
56
from st2common.util.jsonify import try_loads
57
from st2common.rbac.types import PermissionType
58
from st2common.rbac import utils as rbac_utils
59
from st2common.rbac.utils import assert_user_has_resource_db_permission
60
from st2common.rbac.utils import assert_user_is_admin_if_user_query_param_is_provided
61
from st2common.stream.listener import get_listener
62
63
__all__ = [
64
    'ActionExecutionsController'
65
]
66
67
LOG = logging.getLogger(__name__)
68
69
# Note: We initialize filters here and not in the constructor
70
SUPPORTED_EXECUTIONS_FILTERS = copy.deepcopy(SUPPORTED_FILTERS)
71
SUPPORTED_EXECUTIONS_FILTERS.update({
72
    'timestamp_gt': 'start_timestamp.gt',
73
    'timestamp_lt': 'start_timestamp.lt'
74
})
75
76
MONITOR_THREAD_EMPTY_Q_SLEEP_TIME = 5
77
MONITOR_THREAD_NO_WORKERS_SLEEP_TIME = 1
78
79
80
class ActionExecutionsControllerMixin(BaseRestControllerMixin):
81
    """
82
    Mixin class with shared methods.
83
    """
84
85
    model = ActionExecutionAPI
86
    access = ActionExecution
87
88
    # A list of attributes which can be specified using ?exclude_attributes filter
89
    valid_exclude_attributes = [
90
        'result',
91
        'trigger_instance'
92
    ]
93
94
    def _handle_schedule_execution(self, liveaction_api, requester_user, context_string=None,
95
                                   show_secrets=False):
96
        """
97
        :param liveaction: LiveActionAPI object.
98
        :type liveaction: :class:`LiveActionAPI`
99
        """
100
101
        if not requester_user:
102
            requester_user = UserDB(cfg.CONF.system_user.user)
103
104
        # Assert action ref is valid
105
        action_ref = liveaction_api.action
106
        action_db = action_utils.get_action_by_ref(action_ref)
107
108
        if not action_db:
109
            message = 'Action "%s" cannot be found.' % action_ref
110
            LOG.warning(message)
111
            abort(http_client.BAD_REQUEST, message)
112
113
        # Assert the permissions
114
        assert_user_has_resource_db_permission(user_db=requester_user, resource_db=action_db,
115
                                               permission_type=PermissionType.ACTION_EXECUTE)
116
117
        # Validate that the authenticated user is admin if user query param is provided
118
        user = liveaction_api.user or requester_user.name
119
        assert_user_is_admin_if_user_query_param_is_provided(user_db=requester_user,
120
                                                             user=user)
121
122
        try:
123
            return self._schedule_execution(liveaction=liveaction_api,
124
                                            requester_user=requester_user,
125
                                            user=user,
126
                                            context_string=context_string,
127
                                            show_secrets=show_secrets,
128
                                            pack=action_db.pack)
129
        except ValueError as e:
130
            LOG.exception('Unable to execute action.')
131
            abort(http_client.BAD_REQUEST, str(e))
132
        except jsonschema.ValidationError as e:
133
            LOG.exception('Unable to execute action. Parameter validation failed.')
134
            abort(http_client.BAD_REQUEST, re.sub("u'([^']*)'", r"'\1'", e.message))
135
        except trace_exc.TraceNotFoundException as e:
136
            abort(http_client.BAD_REQUEST, str(e))
137
        except validation_exc.ValueValidationException as e:
138
            raise e
139
        except Exception as e:
140
            LOG.exception('Unable to execute action. Unexpected error encountered.')
141
            abort(http_client.INTERNAL_SERVER_ERROR, str(e))
142
143
    def _schedule_execution(self,
144
                            liveaction,
145
                            requester_user,
146
                            user=None,
147
                            context_string=None,
148
                            show_secrets=False,
149
                            pack=None):
150
        # Initialize execution context if it does not exist.
151
        if not hasattr(liveaction, 'context'):
152
            liveaction.context = dict()
153
154
        liveaction.context['user'] = user
155
        liveaction.context['pack'] = pack
156
        LOG.debug('User is: %s' % liveaction.context['user'])
157
158
        # Retrieve other st2 context from request header.
159
        if context_string:
160
            context = try_loads(context_string)
161
            if not isinstance(context, dict):
162
                raise ValueError('Unable to convert st2-context from the headers into JSON.')
163
            liveaction.context.update(context)
164
165
        # Include RBAC context (if RBAC is available and enabled)
166
        if cfg.CONF.rbac.enable:
167
            user_db = UserDB(name=user)
168
            role_dbs = rbac_service.get_roles_for_user(user_db=user_db, include_remote=True)
169
            roles = [role_db.name for role_db in role_dbs]
170
            liveaction.context['rbac'] = {
171
                'user': user,
172
                'roles': roles
173
            }
174
175
        # Schedule the action execution.
176
        liveaction_db = LiveActionAPI.to_model(liveaction)
177
        liveaction_db, actionexecution_db = action_service.create_request(liveaction_db)
178
179
        action_db = action_utils.get_action_by_ref(liveaction_db.action)
180
        runnertype_db = action_utils.get_runnertype_by_name(action_db.runner_type['name'])
181
182
        try:
183
            liveaction_db.parameters = param_utils.render_live_params(
184
                runnertype_db.runner_parameters, action_db.parameters, liveaction_db.parameters,
185
                liveaction_db.context)
186
        except param_exc.ParamException:
187
            # By this point the execution is already in the DB therefore need to mark it failed.
188
            _, e, tb = sys.exc_info()
189
            action_service.update_status(
190
                liveaction=liveaction_db,
191
                new_status=action_constants.LIVEACTION_STATUS_FAILED,
192
                result={'error': str(e), 'traceback': ''.join(traceback.format_tb(tb, 20))})
193
            # Might be a good idea to return the actual ActionExecution rather than bubble up
194
            # the execption.
195
            raise validation_exc.ValueValidationException(str(e))
196
197
        liveaction_db = LiveAction.add_or_update(liveaction_db, publish=False)
198
199
        _, actionexecution_db = action_service.publish_request(liveaction_db, actionexecution_db)
200
        mask_secrets = self._get_mask_secrets(requester_user, show_secrets=show_secrets)
201
        execution_api = ActionExecutionAPI.from_model(actionexecution_db, mask_secrets=mask_secrets)
202
203
        return Response(json=execution_api, status=http_client.CREATED)
204
205
    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...
206
        """
207
        Retrieve result object for the provided action execution.
208
209
        :param id: Action execution ID.
210
        :type id: ``str``
211
212
        :rtype: ``dict``
213
        """
214
        fields = ['result']
215
        action_exec_db = self.access.impl.model.objects.filter(id=id).only(*fields).get()
216
        return action_exec_db.result
217
218
    def _get_children(self, id_, requester_user, depth=-1, result_fmt=None,
219
                      show_secrets=False):
220
        # make sure depth is int. Url encoding will make it a string and needs to
221
        # be converted back in that case.
222
        depth = int(depth)
223
        LOG.debug('retrieving children for id: %s with depth: %s', id_, depth)
224
        descendants = execution_service.get_descendants(actionexecution_id=id_,
225
                                                        descendant_depth=depth,
226
                                                        result_fmt=result_fmt)
227
228
        mask_secrets = self._get_mask_secrets(requester_user, show_secrets=show_secrets)
229
        return [self.model.from_model(descendant, mask_secrets=mask_secrets) for
230
                descendant in descendants]
231
232
233
class BaseActionExecutionNestedController(ActionExecutionsControllerMixin, ResourceController):
234
    # Note: We need to override "get_one" and "get_all" to return 404 since nested controller
235
    # don't implement thos methods
236
237
    # ResourceController attributes
238
    query_options = {}
239
    supported_filters = {}
240
241
    def get_all(self):
242
        abort(httplib.NOT_FOUND)
243
244
    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...
245
        abort(httplib.NOT_FOUND)
246
247
248
class ActionExecutionChildrenController(BaseActionExecutionNestedController):
249
    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...
250
        """
251
        Retrieve children for the provided action execution.
252
253
        :rtype: ``list``
254
        """
255
256
        instance = self._get_by_id(resource_id=id)
257
258
        permission_type = PermissionType.EXECUTION_VIEW
259
        rbac_utils.assert_user_has_resource_db_permission(user_db=requester_user,
260
                                                          resource_db=instance,
261
                                                          permission_type=permission_type)
262
263
        return self._get_children(id_=id, depth=depth, result_fmt=result_fmt,
264
                                  requester_user=requester_user, show_secrets=show_secrets)
265
266
267
class ActionExecutionAttributeController(BaseActionExecutionNestedController):
268
    valid_exclude_attributes = ['action__pack', 'action__uid'] + \
269
        ActionExecutionsControllerMixin.valid_exclude_attributes
270
271
    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...
272
        """
273
        Retrieve a particular attribute for the provided action execution.
274
275
        Handles requests:
276
277
            GET /executions/<id>/attribute/<attribute name>
278
279
        :rtype: ``dict``
280
        """
281
        fields = [attribute, 'action__pack', 'action__uid']
282
        fields = self._validate_exclude_fields(fields)
283
        action_exec_db = self.access.impl.model.objects.filter(id=id).only(*fields).get()
284
285
        permission_type = PermissionType.EXECUTION_VIEW
286
        rbac_utils.assert_user_has_resource_db_permission(user_db=requester_user,
287
                                                          resource_db=action_exec_db,
288
                                                          permission_type=permission_type)
289
290
        result = getattr(action_exec_db, attribute, None)
291
        return result
292
293
294
class ActionExecutionOutputController(ActionExecutionsControllerMixin, ResourceController):
295
    supported_filters = {
296
        'output_type': 'output_type'
297
    }
298
    exclude_fields = []
299
300
    CLOSE_STREAM_LIVEACTION_STATES = action_constants.LIVEACTION_COMPLETED_STATES + [
301
        action_constants.LIVEACTION_STATUS_PAUSING,
302
        action_constants.LIVEACTION_STATUS_RESUMING
303
    ]
304
305
    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...
306
        # Special case for id == "last"
307
        if id == 'last':
308
            execution_db = ActionExecution.query().order_by('-id').limit(1).first()
309
        else:
310
            execution_db = self._get_one_by_id(id=id, requester_user=requester_user,
311
                                               permission_type=PermissionType.EXECUTION_VIEW)
312
313
        execution_id = str(execution_db.id)
314
315
        query_filters = {}
316
        if output_type:
317
            query_filters['output_type'] = output_type
318
319
        def existing_output_iter():
320
            # Consume and return all of the existing lines
321
            # pylint: disable=no-member
322
            output_dbs = ActionExecutionOutput.query(execution_id=execution_id, **query_filters)
323
324
            # Note: We return all at once instead of yield line by line to avoid multiple socket
325
            # writes and to achieve better performance
326
            output = ''.join([output_db.data for output_db in output_dbs])
327
            yield six.binary_type(output.encode('utf-8'))
328
329
        def new_output_iter():
330
            def noop_gen():
331
                yield ''
332
333
            # Bail out if execution has already completed / been paused
334
            if execution_db.status in self.CLOSE_STREAM_LIVEACTION_STATES:
335
                return noop_gen()
336
337
            # Wait for and return any new line which may come in
338
            execution_ids = [execution_id]
339
            listener = get_listener(name='execution_output')  # pylint: disable=no-member
340
            gen = listener.generator(execution_ids=execution_ids)
341
342
            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...
343
                for pack in gen:
344
                    if not pack:
345
                        continue
346
                    else:
347
                        (_, model_api) = pack
348
349
                        # Note: gunicorn wsgi handler expect bytes, not unicode
350
                        # pylint: disable=no-member
351
                        if isinstance(model_api, ActionExecutionOutputAPI):
352
                            if output_type and model_api.output_type != output_type:
353
                                continue
354
355
                            yield six.binary_type(model_api.data.encode('utf-8'))
356
                        elif isinstance(model_api, ActionExecutionAPI):
357
                            if model_api.status in self.CLOSE_STREAM_LIVEACTION_STATES:
358
                                yield six.binary_type('')
359
                                break
360
                        else:
361
                            LOG.debug('Unrecognized message type: %s' % (model_api))
362
363
            gen = format(gen)
364
            return gen
365
366
        def make_response():
367
            app_iter = itertools.chain(existing_output_iter(), new_output_iter())
368
            res = Response(content_type='text/plain', app_iter=app_iter)
369
            return res
370
371
        res = make_response()
372
        return res
373
374
375
class ActionExecutionReRunController(ActionExecutionsControllerMixin, ResourceController):
376
    supported_filters = {}
377
    exclude_fields = [
378
        'result',
379
        'trigger_instance'
380
    ]
381
382
    class ExecutionSpecificationAPI(object):
383
        def __init__(self, parameters=None, tasks=None, reset=None, user=None):
384
            self.parameters = parameters or {}
385
            self.tasks = tasks or []
386
            self.reset = reset or []
387
            self.user = user
388
389
        def validate(self):
390
            if (self.tasks or self.reset) and self.parameters:
391
                raise ValueError('Parameters override is not supported when '
392
                                 're-running task(s) for a workflow.')
393
394
            if self.parameters:
395
                assert isinstance(self.parameters, dict)
396
397
            if self.tasks:
398
                assert isinstance(self.tasks, list)
399
400
            if self.reset:
401
                assert isinstance(self.reset, list)
402
403
            if list(set(self.reset) - set(self.tasks)):
404
                raise ValueError('List of tasks to reset does not match the tasks to rerun.')
405
406
            return self
407
408
    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...
409
        """
410
        Re-run the provided action execution optionally specifying override parameters.
411
412
        Handles requests:
413
414
            POST /executions/<id>/re_run
415
        """
416
417
        if (spec_api.tasks or spec_api.reset) and spec_api.parameters:
418
            raise ValueError('Parameters override is not supported when '
419
                             're-running task(s) for a workflow.')
420
421
        if spec_api.parameters:
422
            assert isinstance(spec_api.parameters, dict)
423
424
        if spec_api.tasks:
425
            assert isinstance(spec_api.tasks, list)
426
427
        if spec_api.reset:
428
            assert isinstance(spec_api.reset, list)
429
430
        if list(set(spec_api.reset) - set(spec_api.tasks)):
431
            raise ValueError('List of tasks to reset does not match the tasks to rerun.')
432
433
        no_merge = cast_argument_value(value_type=bool, value=no_merge)
434
        existing_execution = self._get_one_by_id(id=id, exclude_fields=self.exclude_fields,
435
                                                 requester_user=requester_user,
436
                                                 permission_type=PermissionType.EXECUTION_VIEW)
437
438
        if spec_api.tasks and existing_execution.runner['name'] != 'mistral-v2':
439
            raise ValueError('Task option is only supported for Mistral workflows.')
440
441
        # Merge in any parameters provided by the user
442
        new_parameters = {}
443
        if not no_merge:
444
            new_parameters.update(getattr(existing_execution, 'parameters', {}))
445
        new_parameters.update(spec_api.parameters)
446
447
        # Create object for the new execution
448
        action_ref = existing_execution.action['ref']
449
450
        # Include additional option(s) for the execution
451
        context = {
452
            're-run': {
453
                'ref': id,
454
            }
455
        }
456
457
        if spec_api.tasks:
458
            context['re-run']['tasks'] = spec_api.tasks
459
460
        if spec_api.reset:
461
            context['re-run']['reset'] = spec_api.reset
462
463
        # Add trace to the new execution
464
        trace = trace_service.get_trace_db_by_action_execution(
465
            action_execution_id=existing_execution.id)
466
467
        if trace:
468
            context['trace_context'] = {'id_': str(trace.id)}
469
470
        new_liveaction_api = LiveActionCreateAPI(action=action_ref,
471
                                                 context=context,
472
                                                 parameters=new_parameters,
473
                                                 user=spec_api.user)
474
475
        return self._handle_schedule_execution(liveaction_api=new_liveaction_api,
476
                                               requester_user=requester_user,
477
                                               show_secrets=show_secrets)
478
479
480
class ActionExecutionsController(ActionExecutionsControllerMixin, ResourceController):
481
    """
482
        Implements the RESTful web endpoint that handles
483
        the lifecycle of ActionExecutions in the system.
484
    """
485
486
    # Nested controllers
487
    views = ExecutionViewsController()
488
489
    children = ActionExecutionChildrenController()
490
    attribute = ActionExecutionAttributeController()
491
    re_run = ActionExecutionReRunController()
492
493
    # ResourceController attributes
494
    query_options = {
495
        'sort': ['-start_timestamp', 'action.ref']
496
    }
497
    supported_filters = SUPPORTED_EXECUTIONS_FILTERS
498
    filter_transform_functions = {
499
        'timestamp_gt': lambda value: isotime.parse(value=value),
500
        'timestamp_lt': lambda value: isotime.parse(value=value)
501
    }
502
503
    def get_all(self, requester_user, exclude_attributes=None, sort=None, offset=0, limit=None,
504
                show_secrets=False, **raw_filters):
505
        """
506
        List all executions.
507
508
        Handles requests:
509
            GET /executions[?exclude_attributes=result,trigger_instance]
510
511
        :param exclude_attributes: Comma delimited string of attributes to exclude from the object.
512
        :type exclude_attributes: ``str``
513
        """
514
        if exclude_attributes:
515
            exclude_fields = exclude_attributes.split(',')
516
        else:
517
            exclude_fields = None
518
519
        exclude_fields = self._validate_exclude_fields(exclude_fields=exclude_fields)
520
521
        # Use a custom sort order when filtering on a timestamp so we return a correct result as
522
        # expected by the user
523
        query_options = None
524
        if raw_filters.get('timestamp_lt', None) or raw_filters.get('sort_desc', None):
525
            query_options = {'sort': ['-start_timestamp', 'action.ref']}
526
        elif raw_filters.get('timestamp_gt', None) or raw_filters.get('sort_asc', None):
527
            query_options = {'sort': ['+start_timestamp', 'action.ref']}
528
529
        from_model_kwargs = {
530
            'mask_secrets': self._get_mask_secrets(requester_user, show_secrets=show_secrets)
531
        }
532
        return self._get_action_executions(exclude_fields=exclude_fields,
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
540
    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...
541
        """
542
        Retrieve a single execution.
543
544
        Handles requests:
545
            GET /executions/<id>[?exclude_attributes=result,trigger_instance]
546
547
        :param exclude_attributes: Comma delimited string of attributes to exclude from the object.
548
        :type exclude_attributes: ``str``
549
        """
550
        if exclude_attributes:
551
            exclude_fields = exclude_attributes.split(',')
552
        else:
553
            exclude_fields = None
554
555
        exclude_fields = self._validate_exclude_fields(exclude_fields=exclude_fields)
556
557
        from_model_kwargs = {
558
            'mask_secrets': self._get_mask_secrets(requester_user, show_secrets=show_secrets)
559
        }
560
561
        # Special case for id == "last"
562
        if id == 'last':
563
            execution_db = ActionExecution.query().order_by('-id').limit(1).only('id').first()
564
            id = str(execution_db.id)
565
566
        return self._get_one_by_id(id=id, exclude_fields=exclude_fields,
567
                                   requester_user=requester_user,
568
                                   from_model_kwargs=from_model_kwargs,
569
                                   permission_type=PermissionType.EXECUTION_VIEW)
570
571
    def post(self, liveaction_api, requester_user, context_string=None, show_secrets=False):
572
        return self._handle_schedule_execution(liveaction_api=liveaction_api,
573
                                               requester_user=requester_user,
574
                                               context_string=context_string,
575
                                               show_secrets=show_secrets)
576
577
    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...
578
        """
579
        Updates a single execution.
580
581
        Handles requests:
582
            PUT /executions/<id>
583
584
        """
585
        if not requester_user:
586
            requester_user = UserDB(cfg.CONF.system_user.user)
587
588
        from_model_kwargs = {
589
            'mask_secrets': self._get_mask_secrets(requester_user, show_secrets=show_secrets)
590
        }
591
592
        execution_api = self._get_one_by_id(id=id, requester_user=requester_user,
593
                                            from_model_kwargs=from_model_kwargs,
594
                                            permission_type=PermissionType.EXECUTION_STOP)
595
596
        if not execution_api:
597
            abort(http_client.NOT_FOUND, 'Execution with id %s not found.' % id)
598
599
        liveaction_id = execution_api.liveaction['id']
600
        if not liveaction_id:
601
            abort(http_client.INTERNAL_SERVER_ERROR,
602
                  'Execution object missing link to liveaction %s.' % liveaction_id)
603
604
        try:
605
            liveaction_db = LiveAction.get_by_id(liveaction_id)
606
        except:
607
            abort(http_client.INTERNAL_SERVER_ERROR,
608
                  'Execution object missing link to liveaction %s.' % liveaction_id)
609
610
        if liveaction_db.status in action_constants.LIVEACTION_COMPLETED_STATES:
611
            abort(http_client.BAD_REQUEST, 'Execution is already in completed state.')
612
613
        if (getattr(liveaction_api, 'result', None) is not None and
614
                liveaction_api.status in [
615
                    action_constants.LIVEACTION_STATUS_PAUSING,
616
                    action_constants.LIVEACTION_STATUS_PAUSED,
617
                    action_constants.LIVEACTION_STATUS_RESUMING]):
618
            abort(http_client.BAD_REQUEST,
619
                  'The result is not applicable for pausing and resuming execution.')
620
621
        try:
622
            if (liveaction_api.status == action_constants.LIVEACTION_STATUS_PAUSING or
623
                    liveaction_api.status == action_constants.LIVEACTION_STATUS_PAUSED):
624
                liveaction_db, actionexecution_db = action_service.request_pause(
625
                    liveaction_db, requester_user.name or cfg.CONF.system_user.user)
626
            elif liveaction_api.status == action_constants.LIVEACTION_STATUS_RESUMING:
627
                liveaction_db, actionexecution_db = action_service.request_resume(
628
                    liveaction_db, requester_user.name or cfg.CONF.system_user.user)
629
            else:
630
                liveaction_db = action_service.update_status(
631
                    liveaction_db,
632
                    liveaction_api.status,
633
                    result=getattr(liveaction_api, 'result', None)
634
                )
635
636
                actionexecution_db = ActionExecution.get(liveaction__id=str(liveaction_db.id))
637
        except runner_exc.InvalidActionRunnerOperationError as e:
638
            LOG.exception('Failed updating liveaction %s. %s', liveaction_db.id, str(e))
639
            abort(http_client.BAD_REQUEST, 'Failed updating execution. %s' % str(e))
640
        except runner_exc.UnexpectedActionExecutionStatusError 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 Exception as e:
644
            LOG.exception('Failed updating liveaction %s. %s', liveaction_db.id, str(e))
645
            abort(
646
                http_client.INTERNAL_SERVER_ERROR,
647
                'Failed updating execution due to unexpected error.'
648
            )
649
650
        mask_secrets = self._get_mask_secrets(requester_user, show_secrets=show_secrets)
651
        execution_api = ActionExecutionAPI.from_model(actionexecution_db, mask_secrets=mask_secrets)
652
653
        return execution_api
654
655
    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...
656
        """
657
        Stops a single execution.
658
659
        Handles requests:
660
            DELETE /executions/<id>
661
662
        """
663
        if not requester_user:
664
            requester_user = UserDB(cfg.CONF.system_user.user)
665
666
        from_model_kwargs = {
667
            'mask_secrets': self._get_mask_secrets(requester_user, show_secrets=show_secrets)
668
        }
669
        execution_api = self._get_one_by_id(id=id, requester_user=requester_user,
670
                                            from_model_kwargs=from_model_kwargs,
671
                                            permission_type=PermissionType.EXECUTION_STOP)
672
673
        if not execution_api:
674
            abort(http_client.NOT_FOUND, 'Execution with id %s not found.' % id)
675
676
        liveaction_id = execution_api.liveaction['id']
677
        if not liveaction_id:
678
            abort(http_client.INTERNAL_SERVER_ERROR,
679
                  'Execution object missing link to liveaction %s.' % liveaction_id)
680
681
        try:
682
            liveaction_db = LiveAction.get_by_id(liveaction_id)
683
        except:
684
            abort(http_client.INTERNAL_SERVER_ERROR,
685
                  'Execution object missing link to liveaction %s.' % liveaction_id)
686
687
        if liveaction_db.status == action_constants.LIVEACTION_STATUS_CANCELED:
688
            LOG.info(
689
                'Action %s already in "canceled" state; \
690
                returning execution object.' % liveaction_db.id
691
            )
692
            return execution_api
693
694
        if liveaction_db.status not in action_constants.LIVEACTION_CANCELABLE_STATES:
695
            abort(http_client.OK, 'Action cannot be canceled. State = %s.' % liveaction_db.status)
696
697
        try:
698
            (liveaction_db, execution_db) = action_service.request_cancellation(
699
                liveaction_db, requester_user.name or cfg.CONF.system_user.user)
700
        except:
701
            LOG.exception('Failed requesting cancellation for liveaction %s.', liveaction_db.id)
702
            abort(http_client.INTERNAL_SERVER_ERROR, 'Failed canceling execution.')
703
704
        return ActionExecutionAPI.from_model(execution_db,
705
                                             mask_secrets=from_model_kwargs['mask_secrets'])
706
707
    def _get_action_executions(self, exclude_fields=None, sort=None, offset=0, limit=None,
708
                               query_options=None, raw_filters=None, from_model_kwargs=None):
709
        """
710
        :param exclude_fields: A list of object fields to exclude.
711
        :type exclude_fields: ``list``
712
        """
713
714
        if limit is None:
715
            limit = self.default_limit
716
717
        limit = int(limit)
718
719
        LOG.debug('Retrieving all action executions with filters=%s', raw_filters)
720
        return super(ActionExecutionsController, self)._get_all(exclude_fields=exclude_fields,
721
                                                                from_model_kwargs=from_model_kwargs,
722
                                                                sort=sort,
723
                                                                offset=offset,
724
                                                                limit=limit,
725
                                                                query_options=query_options,
726
                                                                raw_filters=raw_filters)
727
728
729
action_executions_controller = ActionExecutionsController()
730
action_execution_output_controller = ActionExecutionOutputController()
731
action_execution_rerun_controller = ActionExecutionReRunController()
732
action_execution_attribute_controller = ActionExecutionAttributeController()
733
action_execution_children_controller = ActionExecutionChildrenController()
734