Completed
Pull Request — master (#2357)
by Manas
07:53
created

st2actions.runners.cloudslang.MistralRunner   B

Complexity

Total Complexity 39

Size/Duplication

Total Lines 245
Duplicated Lines 0 %
Metric Value
dl 0
loc 245
rs 8.2857
wmc 39

6 Methods

Rating   Name   Duplication   Size   Complexity  
B st2actions.runners.cloudslang.CloudSlangRunner._write_inputs_to_a_temp_file() 0 25 3
A st2actions.runners.cloudslang.CloudSlangRunner.__init__() 0 2 1
A st2actions.runners.cloudslang.CloudSlangRunner._prepare_command() 0 16 3
B st2actions.runners.cloudslang.CloudSlangRunner._run_cli_command() 0 24 4
B st2actions.runners.cloudslang.CloudSlangRunner.run() 0 21 5
A st2actions.runners.cloudslang.CloudSlangRunner.pre_run() 0 7 1
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 uuid
17
import os
18
import tempfile
19
import yaml
20
21
from oslo_config import cfg
22
from eventlet.green import subprocess
23
24
from st2common.util.green.shell import run_command
25
from st2common.util.shell import kill_process
26
from st2common.util.shell import quote_unix
27
from st2common import log as logging
28
from st2actions.runners import ActionRunner
29
from st2common.constants.action import LIVEACTION_STATUS_SUCCEEDED
30
from st2common.constants.action import LIVEACTION_STATUS_FAILED
31
from st2common.constants.runners import LOCAL_RUNNER_DEFAULT_ACTION_TIMEOUT
32
import st2common.util.jsonify as jsonify
33
34
35
LOG = logging.getLogger(__name__)
36
37
# constants to lookup in runner_parameters.
38
RUNNER_INPUTS = 'inputs'
39
RUNNER_TIMEOUT = 'timeout'
40
41
42
def get_runner():
43
    return CloudSlangRunner(str(uuid.uuid4()))
44
45
46
class CloudSlangRunner(ActionRunner):
47
    """
48
    Runner which executes cloudslang flows and operations as single action
49
    """
50
    KEYS_TO_TRANSFORM = ['stdout', 'stderr']
51
52
    def __init__(self, runner_id):
53
        super(CloudSlangRunner, self).__init__(runner_id=runner_id)
54
55
    def pre_run(self):
56
        self._flow_path = self.entry_point
57
        self._user = cfg.CONF.system_user.user
58
        self._cloudslang_home = cfg.CONF.cloudslang.home_dir
59
        self._inputs = self.runner_parameters.get(RUNNER_INPUTS, None)
60
        self._timeout = self.runner_parameters.get(RUNNER_TIMEOUT,
61
                                                   LOCAL_RUNNER_DEFAULT_ACTION_TIMEOUT)
62
63
    def run(self, action_parameters):
64
        # Note: "inputs" runner parameter has precedence over action parameters
65
        if self._inputs:
66
            inputs = self._inputs
67
        elif action_parameters:
68
            inputs = action_parameters
69
        else:
70
            inputs = None
71
72
        inputs_file_path = self._write_inputs_to_a_temp_file(inputs=inputs)
73
        has_inputs = (inputs_file_path is not None)
74
75
        try:
76
            command = self._prepare_command(has_inputs=has_inputs,
77
                                            inputs_file_path=inputs_file_path)
78
79
            result, status = self._run_cli_command(command)
80
            return (status, jsonify.json_loads(result, CloudSlangRunner.KEYS_TO_TRANSFORM), None)
81
        finally:
82
            if inputs_file_path and os.path.isfile(inputs_file_path):
83
                os.remove(inputs_file_path)
84
85
    def _write_inputs_to_a_temp_file(self, inputs):
86
        """
87
        Serialize inputs dictionary as YAML and write it in a temporary file.
88
89
        :param inputs: Inputs dictionary.
90
        :type inputs: ``dict``
91
92
        :return: Path to the temporary file.
93
        :rtype: ``str``
94
        """
95
        if not inputs:
96
            return None
97
98
        LOG.debug('Inputs dict: %s', inputs)
99
100
        inputs_file = tempfile.NamedTemporaryFile(delete=False)
101
        inputs_file_path = inputs_file.name
102
        yaml_inputs = yaml.safe_dump(inputs, default_flow_style=False)
103
104
        with open(inputs_file_path, 'w') as fp:
105
            fp.write(yaml_inputs)
106
107
        LOG.debug('YAML serialized inputs: %s', yaml_inputs)
108
109
        return inputs_file_path
110
111
    def _prepare_command(self, has_inputs, inputs_file_path):
112
        LOG.debug('CloudSlang home: %s', self._cloudslang_home)
113
114
        cloudslang_binary = os.path.join(self._cloudslang_home, 'bin/cslang')
115
        LOG.debug('Using CloudSlang binary: %s', cloudslang_binary)
116
117
        command_args = ['--f', self._flow_path,
118
                        '--cp', self._cloudslang_home]
119
120
        if has_inputs:
121
            command_args += ['--if', inputs_file_path]
122
123
        command = cloudslang_binary + " run " + " ".join([quote_unix(arg) for arg in command_args])
124
        LOG.info('Executing action via CloudSlangRunner: %s', self.runner_id)
125
        LOG.debug('Command is: %s', command)
126
        return command
127
128
    def _run_cli_command(self, command):
129
        exit_code, stdout, stderr, timed_out = run_command(
130
            cmd=command, stdin=None,
131
            stdout=subprocess.PIPE, stderr=subprocess.PIPE,
132
            shell=True, timeout=self._timeout, kill_func=kill_process)
133
134
        error = None
135
        if timed_out:
136
            error = 'Action failed to complete in %s seconds' % self._timeout
137
            exit_code = -9
138
139
        succeeded = (exit_code == 0)
140
        result = {
141
            'failed': not succeeded,
142
            'succeeded': succeeded,
143
            'return_code': exit_code,
144
            'stdout': stdout,
145
            'stderr': stderr
146
        }
147
        if error:
148
            result['error'] = error
149
150
        status = LIVEACTION_STATUS_SUCCEEDED if succeeded else LIVEACTION_STATUS_FAILED
151
        return result, status
152