Test Failed
Push — master ( 0496d3...626f66 )
by W
01:28
created

st2common/st2common/services/inquiry.py (9 issues)

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
from __future__ import absolute_import
17
18
import copy
19
import six
20
21
from oslo_config import cfg
22
23
from st2actions.container import base as container
24
from st2common.models.db import auth as auth_db_models
25
from st2common.constants import action as action_constants
26
from st2common.exceptions import inquiry as inquiry_exceptions
27
from st2common import log as logging
28
from st2common.persistence import liveaction as lv_db_access
29
from st2common.rbac import utils as rbac_utils
30
from st2common.services import action as action_service
31
from st2common.services import executions as execution_service
32
from st2common.services import workflows as workflow_service
33
from st2common.util import action_db as action_utils
34
from st2common.util import date as date_utils
35
from st2common.util import schema as schema_utils
36
from st2common.util import system_info as sys_info_utils
37
38
39
LOG = logging.getLogger(__name__)
40
41
42
def check_inquiry(inquiry):
43
    LOG.debug('Checking action execution "%s" to see if is an inquiry.' % str(inquiry.id))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
44
45
    if inquiry.runner.get('name') != 'inquirer':
46
        raise inquiry_exceptions.InvalidInquiryInstance(str(inquiry.id))
47
48
    LOG.debug('Checking if the inquiry "%s" has timed out.' % str(inquiry.id))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
49
50
    if inquiry.status == action_constants.LIVEACTION_STATUS_TIMED_OUT:
51
        raise inquiry_exceptions.InquiryTimedOut(str(inquiry.id))
52
53
    LOG.debug('Checking if the inquiry "%s" is responded.' % str(inquiry.id))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
54
55
    if inquiry.status != action_constants.LIVEACTION_STATUS_PENDING:
56
        raise inquiry_exceptions.InquiryAlreadyResponded(str(inquiry.id))
57
58
59
def check_permission(inquiry, requester):
60
    # Normalize user object.
61
    user_db = (
62
        auth_db_models.UserDB(requester)
63
        if isinstance(requester, six.string_types)
64
        else requester
65
    )
66
67
    # Deny by default
68
    roles_passed = False
69
    users_passed = False
70
71
    # Determine role-level permissions
72
    roles = getattr(inquiry, 'roles', [])
73
74
    if not roles:
75
        # No roles definition so we treat it as a pass
76
        roles_passed = True
77
78
    for role in roles:
79
        user_has_role = rbac_utils.user_has_role(user_db, role)
80
81
        LOG.debug('Checking user %s is in role %s - %s' % (user_db, role, user_has_role))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
82
83
        if user_has_role:
84
            roles_passed = True
85
            break
86
87
    # Determine user-level permissions
88
    users = getattr(inquiry, 'users', [])
89
    if not users or user_db.name in users:
90
        users_passed = True
91
92
    # Thow exception if either permission check failed.
93
    if not roles_passed or not users_passed:
94
        raise inquiry_exceptions.InquiryResponseUnauthorized(str(inquiry.id), requester)
95
96
97
def validate_response(inquiry, response):
98
    schema = inquiry.schema
99
100
    LOG.debug('Validating inquiry response: %s against schema: %s' % (response, schema))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
101
102
    try:
103
        schema_utils.validate(
104
            instance=response,
105
            schema=schema,
106
            cls=schema_utils.CustomValidator,
107
            use_default=True,
108
            allow_default_none=True
109
        )
110
    except Exception as e:
111
        msg = 'Response for inquiry "%s" did not pass schema validation.'
112
        LOG.exception(msg % str(inquiry.id))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
113
        raise inquiry_exceptions.InvalidInquiryResponse(str(inquiry.id), str(e))
114
115
116
def respond(inquiry, response, requester=None):
117
    # Set requester to system user is not provided.
118
    if not requester:
119
        requester = cfg.CONF.system_user.user
120
121
    # Retrieve the liveaction from the database.
122
    liveaction_db = lv_db_access.LiveAction.get_by_id(inquiry.liveaction.get('id'))
123
124
    # Resume the parent workflow first. If the action execution for the inquiry is updated first,
125
    # it triggers handling of the action execution completion which will interact with the paused
126
    # parent workflow. The resuming logic that is executed here will then race with the completion
127
    # of the inquiry action execution, which will randomly result in the parent workflow stuck in
128
    # paused state.
129
    if liveaction_db.context.get('parent'):
130
        LOG.debug('Resuming workflow parent(s) for inquiry "%s".' % str(inquiry.id))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
131
132
        # For action execution under Action Chain and Mistral workflows, request the entire
133
        # workflow to resume. Orquesta handles resume differently and so does not require root
134
        # to resume. Orquesta allows for specifc branches to resume while other is paused. When
135
        # there is no other paused branches, the conductor will resume the rest of the workflow.
136
        resume_target = (
137
            action_service.get_parent_liveaction(liveaction_db)
138
            if workflow_service.is_action_execution_under_workflow_context(liveaction_db)
139
            else action_service.get_root_liveaction(liveaction_db)
140
        )
141
142
        if resume_target.status in action_constants.LIVEACTION_PAUSE_STATES:
143
            action_service.request_resume(resume_target, requester)
144
145
    # Succeed the liveaction and update result with the inquiry response.
146
    LOG.debug('Updating response for inquiry "%s".' % str(inquiry.id))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
147
148
    result = copy.deepcopy(inquiry.result)
149
    result['response'] = response
150
151
    liveaction_db = action_utils.update_liveaction_status(
152
        status=action_constants.LIVEACTION_STATUS_SUCCEEDED,
153
        end_timestamp=date_utils.get_datetime_utc_now(),
154
        runner_info=sys_info_utils.get_process_info(),
155
        result=result,
156
        liveaction_id=str(liveaction_db.id)
157
    )
158
159
    # Sync the liveaction with the corresponding action execution.
160
    execution_service.update_execution(liveaction_db)
161
162
    # Invoke inquiry post run to trigger a callback to parent workflow.
163
    LOG.debug('Invoking post run for inquiry "%s".' % str(inquiry.id))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
164
    runner_container = container.get_runner_container()
165
    action_db = action_utils.get_action_by_ref(liveaction_db.action)
166
    runnertype_db = action_utils.get_runnertype_by_name(action_db.runner_type['name'])
167
    runner = runner_container._get_runner(runnertype_db, action_db, liveaction_db)
168
    runner.post_run(status=action_constants.LIVEACTION_STATUS_SUCCEEDED, result=result)
169
170
    return liveaction_db
171