Passed
Push — develop ( fdd284...466107 )
by Plexxi
07:24 queued 03:29
created

RemoteScriptRunner._get_remote_action()   B

Complexity

Conditions 2

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 2
dl 0
loc 32
rs 8.8571
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 os
17
import sys
18
import traceback
19
import uuid
20
21
from oslo_config import cfg
22
23
from st2common import log as logging
24
from st2actions.runners.ssh.paramiko_ssh_runner import RUNNER_REMOTE_DIR
25
from st2actions.runners.ssh.paramiko_ssh_runner import BaseParallelSSHRunner
26
from st2common.models.system.paramiko_script_action import ParamikoRemoteScriptAction
27
28
__all__ = [
29
    'get_runner',
30
31
    'ParamikoRemoteScriptRunner',
32
]
33
34
LOG = logging.getLogger(__name__)
35
36
37
def get_runner():
38
    return ParamikoRemoteScriptRunner(str(uuid.uuid4()))
39
40
41
class ParamikoRemoteScriptRunner(BaseParallelSSHRunner):
42
    def run(self, action_parameters):
43
        remote_action = self._get_remote_action(action_parameters)
44
45
        LOG.debug('Executing remote action.', extra={'_action_params': remote_action})
46
        result = self._run(remote_action)
47
        LOG.debug('Executed remote action.', extra={'_result': result})
48
        status = self._get_result_status(result, cfg.CONF.ssh_runner.allow_partial_failure)
49
50
        return (status, result, None)
51
52
    def _run(self, remote_action):
53
        try:
54
            copy_results = self._copy_artifacts(remote_action)
55
        except:
56
            # If for whatever reason there is a top level exception,
57
            # we just bail here.
58
            error = 'Failed copying content to remote boxes.'
59
            LOG.exception(error)
60
            _, ex, tb = sys.exc_info()
61
            copy_results = self._generate_error_results(' '.join([error, str(ex)]), tb)
62
            return copy_results
63
64
        try:
65
            exec_results = self._run_script_on_remote_host(remote_action)
66
            try:
67
                remote_dir = remote_action.get_remote_base_dir()
68
                LOG.debug('Deleting remote execution dir.', extra={'_remote_dir': remote_dir})
69
                delete_results = self._parallel_ssh_client.delete_dir(path=remote_dir,
70
                                                                      force=True)
71
                LOG.debug('Deleted remote execution dir.', extra={'_result': delete_results})
72
            except:
73
                LOG.exception('Failed deleting remote dir.', extra={'_remote_dir': remote_dir})
74
            finally:
75
                return exec_results
76
        except:
77
            error = 'Failed executing script on remote boxes.'
78
            LOG.exception(error, extra={'_action_params': remote_action})
79
            _, ex, tb = sys.exc_info()
80
            exec_results = self._generate_error_results(' '.join([error, str(ex)]), tb)
81
            return exec_results
82
83
    def _copy_artifacts(self, remote_action):
84
        # First create remote execution directory.
85
        remote_dir = remote_action.get_remote_base_dir()
86
        LOG.debug('Creating remote execution dir.', extra={'_path': remote_dir})
87
        mkdir_result = self._parallel_ssh_client.mkdir(path=remote_action.get_remote_base_dir())
88
89
        # Copy the script to remote dir in remote host.
90
        local_script_abs_path = remote_action.get_local_script_abs_path()
91
        remote_script_abs_path = remote_action.get_remote_script_abs_path()
92
        file_mode = 0744
93
        extra = {'_local_script': local_script_abs_path, '_remote_script': remote_script_abs_path,
94
                 'mode': file_mode}
95
        LOG.debug('Copying local script to remote box.', extra=extra)
96
        put_result_1 = self._parallel_ssh_client.put(local_path=local_script_abs_path,
97
                                                     remote_path=remote_script_abs_path,
98
                                                     mirror_local_mode=False, mode=file_mode)
99
100
        # If `lib` exist for the script, copy that to remote host.
101
        local_libs_path = remote_action.get_local_libs_path_abs()
102
        if os.path.exists(local_libs_path):
103
            extra = {'_local_libs': local_libs_path, '_remote_path': remote_dir}
104
            LOG.debug('Copying libs to remote host.', extra=extra)
105
            put_result_2 = self._parallel_ssh_client.put(local_path=local_libs_path,
106
                                                         remote_path=remote_dir,
107
                                                         mirror_local_mode=True)
108
109
        result = mkdir_result or put_result_1 or put_result_2
110
        return result
111
112
    def _run_script_on_remote_host(self, remote_action):
113
        command = remote_action.get_full_command_string()
114
        LOG.info('Command to run: %s', command)
115
        results = self._parallel_ssh_client.run(command, timeout=remote_action.get_timeout())
116
        LOG.debug('Results from script: %s', results)
117
        return results
118
119
    def _get_remote_action(self, action_parameters):
120
        # remote script actions without entry_point don't make sense, user probably wanted to use
121
        # "remote-shell-cmd" action
122
        if not self.entry_point:
123
            msg = ('Action "%s" is missing "entry_point" attribute. Perhaps wanted to use '
124
                   '"remote-shell-script" runner?' % (self.action_name))
125
            raise Exception(msg)
126
127
        script_local_path_abs = self.entry_point
128
        pos_args, named_args = self._get_script_args(action_parameters)
129
        named_args = self._transform_named_args(named_args)
130
        env_vars = self._get_env_vars()
131
        remote_dir = self.runner_parameters.get(RUNNER_REMOTE_DIR,
132
                                                cfg.CONF.ssh_runner.remote_dir)
133
        remote_dir = os.path.join(remote_dir, self.liveaction_id)
134
        return ParamikoRemoteScriptAction(self.action_name,
135
                                          str(self.liveaction_id),
136
                                          script_local_path_abs,
137
                                          self.libs_dir_path,
138
                                          named_args=named_args,
139
                                          positional_args=pos_args,
140
                                          env_vars=env_vars,
141
                                          on_behalf_user=self._on_behalf_user,
142
                                          user=self._username,
143
                                          password=self._password,
144
                                          private_key=self._private_key,
145
                                          remote_dir=remote_dir,
146
                                          hosts=self._hosts,
147
                                          parallel=self._parallel,
148
                                          sudo=self._sudo,
149
                                          timeout=self._timeout,
150
                                          cwd=self._cwd)
151
152
    @staticmethod
153
    def _generate_error_results(error, tb):
154
        error_dict = {
155
            'error': error,
156
            'traceback': ''.join(traceback.format_tb(tb, 20)) if tb else '',
157
            'failed': True,
158
            'succeeded': False,
159
            'return_code': 255
160
        }
161
        return error_dict
162