Test Failed
Pull Request — master (#4197)
by W
03:53
created

ActionExecutionsController   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 256
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 256
rs 9.0399
c 0
b 0
f 0
wmc 42

7 Methods

Rating   Name   Duplication   Size   Complexity  
A get_one() 0 29 3
A _get_action_executions() 0 24 2
A update_status() 0 6 1
F put() 0 84 21
C delete() 0 51 8
B get_all() 0 34 5
A post() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like ActionExecutionsController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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.resource import BaseResourceIsolationControllerMixin
30
from st2api.controllers.v1.execution_views import ExecutionViewsController
31
from st2api.controllers.v1.execution_views 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
        action_db = action_utils.get_action_by_ref(liveaction_db.action)
178
        runnertype_db = action_utils.get_runnertype_by_name(action_db.runner_type['name'])
179
180
        try:
181
            liveaction_db.parameters = param_utils.render_live_params(
182
                runnertype_db.runner_parameters, action_db.parameters, liveaction_db.parameters,
183
                liveaction_db.context)
184
        except param_exc.ParamException:
185
186
            # We still need to create a request, so liveaction_db is assigned an ID
187
            liveaction_db, actionexecution_db = action_service.create_request(liveaction_db)
188
189
            # By this point the execution is already in the DB therefore need to mark it failed.
190
            _, e, tb = sys.exc_info()
191
            action_service.update_status(
192
                liveaction=liveaction_db,
193
                new_status=action_constants.LIVEACTION_STATUS_FAILED,
194
                result={'error': str(e), 'traceback': ''.join(traceback.format_tb(tb, 20))})
195
            # Might be a good idea to return the actual ActionExecution rather than bubble up
196
            # the exception.
197
            raise validation_exc.ValueValidationException(str(e))
198
199
        # The request should be created after the above call to render_live_params
200
        # so any templates in live parameters have a chance to render.
201
        liveaction_db, actionexecution_db = action_service.create_request(liveaction_db)
202
        liveaction_db = LiveAction.add_or_update(liveaction_db, publish=False)
203
204
        _, actionexecution_db = action_service.publish_request(liveaction_db, actionexecution_db)
205
        mask_secrets = self._get_mask_secrets(requester_user, show_secrets=show_secrets)
206
        execution_api = ActionExecutionAPI.from_model(actionexecution_db, mask_secrets=mask_secrets)
207
208
        return Response(json=execution_api, status=http_client.CREATED)
209
210
    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...
211
        """
212
        Retrieve result object for the provided action execution.
213
214
        :param id: Action execution ID.
215
        :type id: ``str``
216
217
        :rtype: ``dict``
218
        """
219
        fields = ['result']
220
        action_exec_db = self.access.impl.model.objects.filter(id=id).only(*fields).get()
221
        return action_exec_db.result
222
223
    def _get_children(self, id_, requester_user, depth=-1, result_fmt=None, 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
        execution_db = self._get_one_by_id(id=id, requester_user=requester_user,
261
                                           permission_type=PermissionType.EXECUTION_VIEW)
262
        id = str(execution_db.id)
263
264
        return self._get_children(id_=id, depth=depth, result_fmt=result_fmt,
265
                                  requester_user=requester_user, show_secrets=show_secrets)
266
267
268
class ActionExecutionAttributeController(BaseActionExecutionNestedController):
269
    valid_exclude_attributes = ['action__pack', 'action__uid'] + \
270
        ActionExecutionsControllerMixin.valid_exclude_attributes
271
272
    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...
273
        """
274
        Retrieve a particular attribute for the provided action execution.
275
276
        Handles requests:
277
278
            GET /executions/<id>/attribute/<attribute name>
279
280
        :rtype: ``dict``
281
        """
282
        fields = [attribute, 'action__pack', 'action__uid']
283
        fields = self._validate_exclude_fields(fields)
284
        action_exec_db = self.access.impl.model.objects.filter(id=id).only(*fields).get()
285
286
        permission_type = PermissionType.EXECUTION_VIEW
287
        rbac_utils.assert_user_has_resource_db_permission(user_db=requester_user,
288
                                                          resource_db=action_exec_db,
289
                                                          permission_type=permission_type)
290
291
        result = getattr(action_exec_db, attribute, None)
292
        return result
293
294
295
class ActionExecutionOutputController(ActionExecutionsControllerMixin, ResourceController):
296
    supported_filters = {
297
        'output_type': 'output_type'
298
    }
299
    exclude_fields = []
300
301
    CLOSE_STREAM_LIVEACTION_STATES = action_constants.LIVEACTION_COMPLETED_STATES + [
302
        action_constants.LIVEACTION_STATUS_PAUSING,
303
        action_constants.LIVEACTION_STATUS_RESUMING
304
    ]
305
306
    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...
307
        # Special case for id == "last"
308
        if id == 'last':
309
            execution_db = ActionExecution.query().order_by('-id').limit(1).first()
310
311
            if not execution_db:
312
                raise ValueError('No executions found in the database')
313
314
            id = str(execution_db.id)
315
316
        execution_db = self._get_one_by_id(id=id, requester_user=requester_user,
317
                                           permission_type=PermissionType.EXECUTION_VIEW)
318
        execution_id = str(execution_db.id)
319
320
        query_filters = {}
321
        if output_type and output_type != 'all':
322
            query_filters['output_type'] = output_type
323
324
        def existing_output_iter():
325
            # Consume and return all of the existing lines
326
            # pylint: disable=no-member
327
            output_dbs = ActionExecutionOutput.query(execution_id=execution_id, **query_filters)
328
329
            # Note: We return all at once instead of yield line by line to avoid multiple socket
330
            # writes and to achieve better performance
331
            output = ''.join([output_db.data for output_db in output_dbs])
332
            yield six.binary_type(output.encode('utf-8'))
333
334
        def new_output_iter():
335
            def noop_gen():
336
                yield six.binary_type('')
337
338
            # Bail out if execution has already completed / been paused
339
            if execution_db.status in self.CLOSE_STREAM_LIVEACTION_STATES:
340
                return noop_gen()
341
342
            # Wait for and return any new line which may come in
343
            execution_ids = [execution_id]
344
            listener = get_listener(name='execution_output')  # pylint: disable=no-member
345
            gen = listener.generator(execution_ids=execution_ids)
346
347
            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...
348
                for pack in gen:
349
                    if not pack:
350
                        continue
351
                    else:
352
                        (_, model_api) = pack
353
354
                        # Note: gunicorn wsgi handler expect bytes, not unicode
355
                        # pylint: disable=no-member
356
                        if isinstance(model_api, ActionExecutionOutputAPI):
357
                            if output_type and model_api.output_type != output_type:
358
                                continue
359
360
                            yield six.binary_type(model_api.data.encode('utf-8'))
361
                        elif isinstance(model_api, ActionExecutionAPI):
362
                            if model_api.status in self.CLOSE_STREAM_LIVEACTION_STATES:
363
                                yield six.binary_type('')
364
                                break
365
                        else:
366
                            LOG.debug('Unrecognized message type: %s' % (model_api))
367
368
            gen = format(gen)
369
            return gen
370
371
        def make_response():
372
            app_iter = itertools.chain(existing_output_iter(), new_output_iter())
373
            res = Response(content_type='text/plain', app_iter=app_iter)
374
            return res
375
376
        res = make_response()
377
        return res
378
379
380
class ActionExecutionReRunController(ActionExecutionsControllerMixin, ResourceController):
381
    supported_filters = {}
382
    exclude_fields = [
383
        'result',
384
        'trigger_instance'
385
    ]
386
387
    class ExecutionSpecificationAPI(object):
388
        def __init__(self, parameters=None, tasks=None, reset=None, user=None):
389
            self.parameters = parameters or {}
390
            self.tasks = tasks or []
391
            self.reset = reset or []
392
            self.user = user
393
394
        def validate(self):
395
            if (self.tasks or self.reset) and self.parameters:
396
                raise ValueError('Parameters override is not supported when '
397
                                 're-running task(s) for a workflow.')
398
399
            if self.parameters:
400
                assert isinstance(self.parameters, dict)
401
402
            if self.tasks:
403
                assert isinstance(self.tasks, list)
404
405
            if self.reset:
406
                assert isinstance(self.reset, list)
407
408
            if list(set(self.reset) - set(self.tasks)):
409
                raise ValueError('List of tasks to reset does not match the tasks to rerun.')
410
411
            return self
412
413
    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...
414
        """
415
        Re-run the provided action execution optionally specifying override parameters.
416
417
        Handles requests:
418
419
            POST /executions/<id>/re_run
420
        """
421
422
        if (spec_api.tasks or spec_api.reset) and spec_api.parameters:
423
            raise ValueError('Parameters override is not supported when '
424
                             're-running task(s) for a workflow.')
425
426
        if spec_api.parameters:
427
            assert isinstance(spec_api.parameters, dict)
428
429
        if spec_api.tasks:
430
            assert isinstance(spec_api.tasks, list)
431
432
        if spec_api.reset:
433
            assert isinstance(spec_api.reset, list)
434
435
        if list(set(spec_api.reset) - set(spec_api.tasks)):
436
            raise ValueError('List of tasks to reset does not match the tasks to rerun.')
437
438
        no_merge = cast_argument_value(value_type=bool, value=no_merge)
439
        existing_execution = self._get_one_by_id(id=id, exclude_fields=self.exclude_fields,
440
                                                 requester_user=requester_user,
441
                                                 permission_type=PermissionType.EXECUTION_VIEW)
442
443
        if spec_api.tasks and existing_execution.runner['name'] != 'mistral-v2':
444
            raise ValueError('Task option is only supported for Mistral workflows.')
445
446
        # Merge in any parameters provided by the user
447
        new_parameters = {}
448
        if not no_merge:
449
            new_parameters.update(getattr(existing_execution, 'parameters', {}))
450
        new_parameters.update(spec_api.parameters)
451
452
        # Create object for the new execution
453
        action_ref = existing_execution.action['ref']
454
455
        # Include additional option(s) for the execution
456
        context = {
457
            're-run': {
458
                'ref': id,
459
            }
460
        }
461
462
        if spec_api.tasks:
463
            context['re-run']['tasks'] = spec_api.tasks
464
465
        if spec_api.reset:
466
            context['re-run']['reset'] = spec_api.reset
467
468
        # Add trace to the new execution
469
        trace = trace_service.get_trace_db_by_action_execution(
470
            action_execution_id=existing_execution.id)
471
472
        if trace:
473
            context['trace_context'] = {'id_': str(trace.id)}
474
475
        new_liveaction_api = LiveActionCreateAPI(action=action_ref,
476
                                                 context=context,
477
                                                 parameters=new_parameters,
478
                                                 user=spec_api.user)
479
480
        return self._handle_schedule_execution(liveaction_api=new_liveaction_api,
481
                                               requester_user=requester_user,
482
                                               show_secrets=show_secrets)
483
484
485
class ActionExecutionsController(BaseResourceIsolationControllerMixin,
486
                                 ActionExecutionsControllerMixin, ResourceController):
487
    """
488
        Implements the RESTful web endpoint that handles
489
        the lifecycle of ActionExecutions in the system.
490
    """
491
492
    # Nested controllers
493
    views = ExecutionViewsController()
494
495
    children = ActionExecutionChildrenController()
496
    attribute = ActionExecutionAttributeController()
497
    re_run = ActionExecutionReRunController()
498
499
    # ResourceController attributes
500
    query_options = {
501
        'sort': ['-start_timestamp', 'action.ref']
502
    }
503
    supported_filters = SUPPORTED_EXECUTIONS_FILTERS
504
    filter_transform_functions = {
505
        'timestamp_gt': lambda value: isotime.parse(value=value),
506
        'timestamp_lt': lambda value: isotime.parse(value=value)
507
    }
508
509
    def get_all(self, requester_user, exclude_attributes=None, sort=None, offset=0, limit=None,
510
                show_secrets=False, include_attributes=None, advanced_filters=None, **raw_filters):
511
        """
512
        List all executions.
513
514
        Handles requests:
515
            GET /executions[?exclude_attributes=result,trigger_instance]
516
517
        :param exclude_attributes: List of attributes to exclude from the object.
518
        :type exclude_attributes: ``list``
519
        """
520
        exclude_fields = self._validate_exclude_fields(exclude_fields=exclude_attributes)
521
522
        # Use a custom sort order when filtering on a timestamp so we return a correct result as
523
        # expected by the user
524
        query_options = None
525
        if raw_filters.get('timestamp_lt', None) or raw_filters.get('sort_desc', None):
526
            query_options = {'sort': ['-start_timestamp', 'action.ref']}
527
        elif raw_filters.get('timestamp_gt', None) or raw_filters.get('sort_asc', None):
528
            query_options = {'sort': ['+start_timestamp', 'action.ref']}
529
530
        from_model_kwargs = {
531
            'mask_secrets': self._get_mask_secrets(requester_user, show_secrets=show_secrets)
532
        }
533
        return self._get_action_executions(exclude_fields=exclude_fields,
534
                                           include_fields=include_attributes,
535
                                           from_model_kwargs=from_model_kwargs,
536
                                           sort=sort,
537
                                           offset=offset,
538
                                           limit=limit,
539
                                           query_options=query_options,
540
                                           raw_filters=raw_filters,
541
                                           advanced_filters=advanced_filters,
542
                                           requester_user=requester_user)
543
544
    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...
545
        """
546
        Retrieve a single execution.
547
548
        Handles requests:
549
            GET /executions/<id>[?exclude_attributes=result,trigger_instance]
550
551
        :param exclude_attributes: List of attributes to exclude from the object.
552
        :type exclude_attributes: ``list``
553
        """
554
        exclude_fields = self._validate_exclude_fields(exclude_fields=exclude_attributes)
555
556
        from_model_kwargs = {
557
            'mask_secrets': self._get_mask_secrets(requester_user, show_secrets=show_secrets)
558
        }
559
560
        # Special case for id == "last"
561
        if id == 'last':
562
            execution_db = ActionExecution.query().order_by('-id').limit(1).only('id').first()
563
564
            if not execution_db:
565
                raise ValueError('No executions found in the database')
566
567
            id = str(execution_db.id)
568
569
        return self._get_one_by_id(id=id, exclude_fields=exclude_fields,
570
                                   requester_user=requester_user,
571
                                   from_model_kwargs=from_model_kwargs,
572
                                   permission_type=PermissionType.EXECUTION_VIEW)
573
574
    def post(self, liveaction_api, requester_user, context_string=None, show_secrets=False):
575
        return self._handle_schedule_execution(liveaction_api=liveaction_api,
576
                                               requester_user=requester_user,
577
                                               context_string=context_string,
578
                                               show_secrets=show_secrets)
579
580
    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...
581
        """
582
        Updates a single execution.
583
584
        Handles requests:
585
            PUT /executions/<id>
586
587
        """
588
        if not requester_user:
589
            requester_user = UserDB(cfg.CONF.system_user.user)
590
591
        from_model_kwargs = {
592
            'mask_secrets': self._get_mask_secrets(requester_user, show_secrets=show_secrets)
593
        }
594
595
        execution_api = self._get_one_by_id(id=id, requester_user=requester_user,
596
                                            from_model_kwargs=from_model_kwargs,
597
                                            permission_type=PermissionType.EXECUTION_STOP)
598
599
        if not execution_api:
600
            abort(http_client.NOT_FOUND, 'Execution with id %s not found.' % id)
601
602
        liveaction_id = execution_api.liveaction['id']
603
        if not liveaction_id:
604
            abort(http_client.INTERNAL_SERVER_ERROR,
605
                  'Execution object missing link to liveaction %s.' % liveaction_id)
606
607
        try:
608
            liveaction_db = LiveAction.get_by_id(liveaction_id)
609
        except:
610
            abort(http_client.INTERNAL_SERVER_ERROR,
611
                  'Execution object missing link to liveaction %s.' % liveaction_id)
612
613
        if liveaction_db.status in action_constants.LIVEACTION_COMPLETED_STATES:
614
            abort(http_client.BAD_REQUEST, 'Execution is already in completed state.')
615
616
        def update_status(liveaction_api, liveaction_db):
617
            status = liveaction_api.status
618
            result = getattr(liveaction_api, 'result', None)
619
            liveaction_db = action_service.update_status(liveaction_db, status, result)
620
            actionexecution_db = ActionExecution.get(liveaction__id=str(liveaction_db.id))
621
            return (liveaction_db, actionexecution_db)
622
623
        try:
624
            if (liveaction_db.status == action_constants.LIVEACTION_STATUS_CANCELING and
625
                    liveaction_api.status == action_constants.LIVEACTION_STATUS_CANCELED):
626
                if action_service.is_children_active(liveaction_id):
627
                    liveaction_api.status = action_constants.LIVEACTION_STATUS_CANCELING
628
                liveaction_db, actionexecution_db = update_status(liveaction_api, liveaction_db)
629
            elif (liveaction_api.status == action_constants.LIVEACTION_STATUS_CANCELING or
630
                    liveaction_api.status == action_constants.LIVEACTION_STATUS_CANCELED):
631
                liveaction_db, actionexecution_db = action_service.request_cancellation(
632
                    liveaction_db, requester_user.name or cfg.CONF.system_user.user)
633
            elif (liveaction_db.status == action_constants.LIVEACTION_STATUS_PAUSING and
634
                    liveaction_api.status == action_constants.LIVEACTION_STATUS_PAUSED):
635
                if action_service.is_children_active(liveaction_id):
636
                    liveaction_api.status = action_constants.LIVEACTION_STATUS_PAUSING
637
                liveaction_db, actionexecution_db = update_status(liveaction_api, liveaction_db)
638
            elif (liveaction_api.status == action_constants.LIVEACTION_STATUS_PAUSING or
639
                    liveaction_api.status == action_constants.LIVEACTION_STATUS_PAUSED):
640
                liveaction_db, actionexecution_db = action_service.request_pause(
641
                    liveaction_db, requester_user.name or cfg.CONF.system_user.user)
642
            elif liveaction_api.status == action_constants.LIVEACTION_STATUS_RESUMING:
643
                liveaction_db, actionexecution_db = action_service.request_resume(
644
                    liveaction_db, requester_user.name or cfg.CONF.system_user.user)
645
            else:
646
                liveaction_db, actionexecution_db = update_status(liveaction_api, liveaction_db)
647
        except runner_exc.InvalidActionRunnerOperationError as e:
648
            LOG.exception('Failed updating liveaction %s. %s', liveaction_db.id, str(e))
649
            abort(http_client.BAD_REQUEST, 'Failed updating execution. %s' % str(e))
650
        except runner_exc.UnexpectedActionExecutionStatusError as e:
651
            LOG.exception('Failed updating liveaction %s. %s', liveaction_db.id, str(e))
652
            abort(http_client.BAD_REQUEST, 'Failed updating execution. %s' % str(e))
653
        except Exception as e:
654
            LOG.exception('Failed updating liveaction %s. %s', liveaction_db.id, str(e))
655
            abort(
656
                http_client.INTERNAL_SERVER_ERROR,
657
                'Failed updating execution due to unexpected error.'
658
            )
659
660
        mask_secrets = self._get_mask_secrets(requester_user, show_secrets=show_secrets)
661
        execution_api = ActionExecutionAPI.from_model(actionexecution_db, mask_secrets=mask_secrets)
662
663
        return execution_api
664
665
    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...
666
        """
667
        Stops a single execution.
668
669
        Handles requests:
670
            DELETE /executions/<id>
671
672
        """
673
        if not requester_user:
674
            requester_user = UserDB(cfg.CONF.system_user.user)
675
676
        from_model_kwargs = {
677
            'mask_secrets': self._get_mask_secrets(requester_user, show_secrets=show_secrets)
678
        }
679
        execution_api = self._get_one_by_id(id=id, requester_user=requester_user,
680
                                            from_model_kwargs=from_model_kwargs,
681
                                            permission_type=PermissionType.EXECUTION_STOP)
682
683
        if not execution_api:
684
            abort(http_client.NOT_FOUND, 'Execution with id %s not found.' % id)
685
686
        liveaction_id = execution_api.liveaction['id']
687
        if not liveaction_id:
688
            abort(http_client.INTERNAL_SERVER_ERROR,
689
                  'Execution object missing link to liveaction %s.' % liveaction_id)
690
691
        try:
692
            liveaction_db = LiveAction.get_by_id(liveaction_id)
693
        except:
694
            abort(http_client.INTERNAL_SERVER_ERROR,
695
                  'Execution object missing link to liveaction %s.' % liveaction_id)
696
697
        if liveaction_db.status == action_constants.LIVEACTION_STATUS_CANCELED:
698
            LOG.info(
699
                'Action %s already in "canceled" state; \
700
                returning execution object.' % liveaction_db.id
701
            )
702
            return execution_api
703
704
        if liveaction_db.status not in action_constants.LIVEACTION_CANCELABLE_STATES:
705
            abort(http_client.OK, 'Action cannot be canceled. State = %s.' % liveaction_db.status)
706
707
        try:
708
            (liveaction_db, execution_db) = action_service.request_cancellation(
709
                liveaction_db, requester_user.name or cfg.CONF.system_user.user)
710
        except:
711
            LOG.exception('Failed requesting cancellation for liveaction %s.', liveaction_db.id)
712
            abort(http_client.INTERNAL_SERVER_ERROR, 'Failed canceling execution.')
713
714
        return ActionExecutionAPI.from_model(execution_db,
715
                                             mask_secrets=from_model_kwargs['mask_secrets'])
716
717
    def _get_action_executions(self, exclude_fields=None, include_fields=None,
718
                               sort=None, offset=0, limit=None, advanced_filters=None,
719
                               query_options=None, raw_filters=None, from_model_kwargs=None,
720
                               requester_user=None):
721
        """
722
        :param exclude_fields: A list of object fields to exclude.
723
        :type exclude_fields: ``list``
724
        """
725
726
        if limit is None:
727
            limit = self.default_limit
728
729
        limit = int(limit)
730
731
        LOG.debug('Retrieving all action executions with filters=%s', raw_filters)
732
        return super(ActionExecutionsController, self)._get_all(exclude_fields=exclude_fields,
733
                                                                from_model_kwargs=from_model_kwargs,
734
                                                                sort=sort,
735
                                                                offset=offset,
736
                                                                limit=limit,
737
                                                                query_options=query_options,
738
                                                                raw_filters=raw_filters,
739
                                                                advanced_filters=advanced_filters,
740
                                                                requester_user=requester_user)
741
742
743
action_executions_controller = ActionExecutionsController()
744
action_execution_output_controller = ActionExecutionOutputController()
745
action_execution_rerun_controller = ActionExecutionReRunController()
746
action_execution_attribute_controller = ActionExecutionAttributeController()
747
action_execution_children_controller = ActionExecutionChildrenController()
748