Test Failed
Push — master ( e380d0...f5671d )
by W
02:58
created

st2api/st2api/controllers/v1/actionexecutions.py (1 issue)

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