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

remote_runner/remote_script_runner.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
from __future__ import absolute_import
17
import os
18
import sys
19
import traceback
20
import uuid
21
22
from oslo_config import cfg
23
24
from st2common import log as logging
25
from st2common.runners.paramiko_ssh_runner import RUNNER_REMOTE_DIR
26
from st2common.runners.paramiko_ssh_runner import BaseParallelSSHRunner
27
from st2common.runners.base import get_metadata as get_runner_metadata
28
from st2common.models.system.paramiko_script_action import ParamikoRemoteScriptAction
29
30
__all__ = [
31
    'ParamikoRemoteScriptRunner',
32
33
    'get_runner',
34
    'get_metadata'
35
]
36
37
LOG = logging.getLogger(__name__)
38
39
40
class ParamikoRemoteScriptRunner(BaseParallelSSHRunner):
41
    def run(self, action_parameters):
42
        remote_action = self._get_remote_action(action_parameters)
43
44
        LOG.debug('Executing remote action.', extra={'_action_params': remote_action})
45
        result = self._run(remote_action)
46
        LOG.debug('Executed remote action.', extra={'_result': result})
47
        status = self._get_result_status(result, cfg.CONF.ssh_runner.allow_partial_failure)
48
49
        return (status, result, None)
50
51
    def _run(self, remote_action):
52
        try:
53
            copy_results = self._copy_artifacts(remote_action)
54
        except:
55
            # If for whatever reason there is a top level exception,
56
            # we just bail here.
57
            error = 'Failed copying content to remote boxes.'
58
            LOG.exception(error)
59
            _, ex, tb = sys.exc_info()
60
            copy_results = self._generate_error_results(' '.join([error, str(ex)]), tb)
61
            return copy_results
62
63
        try:
64
            exec_results = self._run_script_on_remote_host(remote_action)
65
            try:
66
                remote_dir = remote_action.get_remote_base_dir()
67
                LOG.debug('Deleting remote execution dir.', extra={'_remote_dir': remote_dir})
68
                delete_results = self._parallel_ssh_client.delete_dir(path=remote_dir,
69
                                                                      force=True)
70
                LOG.debug('Deleted remote execution dir.', extra={'_result': delete_results})
71
            except:
72
                LOG.exception('Failed deleting remote dir.', extra={'_remote_dir': remote_dir})
73
            finally:
74
                return exec_results
0 ignored issues
show
Bug Best Practice introduced by
return statements in finally blocks should be avoided.

Placing a return statement inside finally will swallow all exceptions that may have been thrown in the try block.

Loading history...
75
        except:
76
            error = 'Failed executing script on remote boxes.'
77
            LOG.exception(error, extra={'_action_params': remote_action})
78
            _, ex, tb = sys.exc_info()
79
            exec_results = self._generate_error_results(' '.join([error, str(ex)]), tb)
80
            return exec_results
81
82
    def _copy_artifacts(self, remote_action):
83
        # First create remote execution directory.
84
        remote_dir = remote_action.get_remote_base_dir()
85
        LOG.debug('Creating remote execution dir.', extra={'_path': remote_dir})
86
        mkdir_result = self._parallel_ssh_client.mkdir(path=remote_action.get_remote_base_dir())
87
88
        # Copy the script to remote dir in remote host.
89
        local_script_abs_path = remote_action.get_local_script_abs_path()
90
        remote_script_abs_path = remote_action.get_remote_script_abs_path()
91
        file_mode = 0o744
92
        extra = {'_local_script': local_script_abs_path, '_remote_script': remote_script_abs_path,
93
                 'mode': file_mode}
94
        LOG.debug('Copying local script to remote box.', extra=extra)
95
        put_result_1 = self._parallel_ssh_client.put(local_path=local_script_abs_path,
96
                                                     remote_path=remote_script_abs_path,
97
                                                     mirror_local_mode=False, mode=file_mode)
98
99
        # If `lib` exist for the script, copy that to remote host.
100
        local_libs_path = remote_action.get_local_libs_path_abs()
101
        if os.path.exists(local_libs_path):
102
            extra = {'_local_libs': local_libs_path, '_remote_path': remote_dir}
103
            LOG.debug('Copying libs to remote host.', extra=extra)
104
            put_result_2 = self._parallel_ssh_client.put(local_path=local_libs_path,
105
                                                         remote_path=remote_dir,
106
                                                         mirror_local_mode=True)
107
108
        result = mkdir_result or put_result_1 or put_result_2
109
        return result
110
111
    def _run_script_on_remote_host(self, remote_action):
112
        command = remote_action.get_full_command_string()
113
        LOG.info('Command to run: %s', command)
114
        results = self._parallel_ssh_client.run(command, timeout=remote_action.get_timeout())
115
        LOG.debug('Results from script: %s', results)
116
        return results
117
118
    def _get_remote_action(self, action_parameters):
119
        # remote script actions without entry_point don't make sense, user probably wanted to use
120
        # "remote-shell-cmd" action
121
        if not self.entry_point:
122
            msg = ('Action "%s" is missing "entry_point" attribute. Perhaps wanted to use '
123
                   '"remote-shell-script" runner?' % (self.action_name))
124
            raise Exception(msg)
125
126
        script_local_path_abs = self.entry_point
127
        pos_args, named_args = self._get_script_args(action_parameters)
128
        named_args = self._transform_named_args(named_args)
129
        env_vars = self._get_env_vars()
130
        remote_dir = self.runner_parameters.get(RUNNER_REMOTE_DIR,
131
                                                cfg.CONF.ssh_runner.remote_dir)
132
        remote_dir = os.path.join(remote_dir, self.liveaction_id)
133
        return ParamikoRemoteScriptAction(self.action_name,
134
                                          str(self.liveaction_id),
135
                                          script_local_path_abs,
136
                                          self.libs_dir_path,
137
                                          named_args=named_args,
138
                                          positional_args=pos_args,
139
                                          env_vars=env_vars,
140
                                          on_behalf_user=self._on_behalf_user,
141
                                          user=self._username,
142
                                          password=self._password,
143
                                          private_key=self._private_key,
144
                                          remote_dir=remote_dir,
145
                                          hosts=self._hosts,
146
                                          parallel=self._parallel,
147
                                          sudo=self._sudo,
148
                                          sudo_password=self._sudo_password,
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
163
164
def get_runner():
165
    return ParamikoRemoteScriptRunner(str(uuid.uuid4()))
166
167
168
def get_metadata():
169
    return get_runner_metadata('remote_script_runner')
170