blob: 03265c14c43af2fb92363f3b90de9f2fd5456081 [file] [log] [blame]
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +03001
2import time
3
4from tcp_tests import logger
5from tcp_tests.helpers.log_helpers import pretty_repr
6
7LOG = logger.logger
8
9
10class ExecuteCommandsMixin(object):
11 """docstring for ExecuteCommands"""
12
Dmitry Tyzhnenkobc0f8262017-04-28 15:39:26 +030013 __config = None
14 __underlay = None
15
16 def __init__(self, config, underlay):
17 self.__config = config
18 self.__underlay = underlay
19 super(ExecuteCommandsMixin, self).__init__()
20
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030021 def ensure_running_service(self, service_name, host, check_cmd,
22 state_running='start/running'):
23 """Check if the service_name running or try to restart it
24
25 :param service_name: name of the service that will be checked
26 :param node_name: node on which the service will be checked
27 :param check_cmd: shell command to ensure that the service is running
28 :param state_running: string for check the service state
29 """
30 cmd = "service {0} status | grep -q '{1}'".format(
31 service_name, state_running)
Dmitry Tyzhnenkobc0f8262017-04-28 15:39:26 +030032 with self.__underlay.remote(host=host) as remote:
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +030033 result = remote.execute(cmd)
34 if result.exit_code != 0:
35 LOG.info("{0} is not in running state on the node {1},"
36 " trying to start".format(service_name, host))
37 cmd = ("service {0} stop;"
38 " sleep 3; killall -9 {0};"
39 "service {0} start; sleep 5;"
40 .format(service_name))
41 remote.execute(cmd)
42
43 remote.execute(check_cmd)
44 remote.execute(check_cmd)
45
46 def execute_commands(self, commands, label="Command"):
47 """Execute a sequence of commands
48
49 Main propose is to implement workarounds for salt formulas like:
50 - exit_code == 0 when there are actual failures
51 - salt_master and/or salt_minion stop working after executing a formula
52 - a formula fails at first run, but completes at next runs
53
54 :param label: label of the current sequence of the commands, for log
55 :param commands: list of dicts with the following data:
56 commands = [
57 ...
58 {
59 # Required:
60 'cmd': 'shell command(s) to run',
61 'node_name': 'name of the node to run the command(s)',
62 # Optional:
63 'description': 'string with a readable command description',
64 'retry': {
65 'count': int, # How many times should be run the command
66 # until success
67 'delay': int, # Delay between tries in seconds
68 },
69 'skip_fail': bool # If True - continue with the next step
70 # without failure even if count number
71 # is reached.
72 # If False - rise an exception (default)
73 },
74 ...
75 ]
76 """
77 for n, step in enumerate(commands):
78 # Required fields
79 cmd = step.get('cmd')
80 do = step.get('do')
81 # node_name = step.get('node_name')
82 # Optional fields
83 description = step.get('description', cmd)
84 # retry = step.get('retry', {'count': 1, 'delay': 1})
85 # retry_count = retry.get('count', 1)
86 # retry_delay = retry.get('delay', 1)
87 # skip_fail = step.get('skip_fail', False)
88
89 msg = "[ {0} #{1} ] {2}".format(label, n + 1, description)
90 LOG.info("\n\n{0}\n{1}".format(msg, '=' * len(msg)))
91
92 if cmd:
93 self.execute_command(step)
94 elif do:
95 self.command2(step)
96
97 def execute_command(self, step):
98 # Required fields
99 cmd = step.get('cmd')
100 node_name = step.get('node_name')
101 # Optional fields
102 description = step.get('description', cmd)
103 retry = step.get('retry', {'count': 1, 'delay': 1})
104 retry_count = retry.get('count', 1)
105 retry_delay = retry.get('delay', 1)
106 skip_fail = step.get('skip_fail', False)
107
Dmitry Tyzhnenkobc0f8262017-04-28 15:39:26 +0300108 with self.__underlay.remote(node_name=node_name) as remote:
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +0300109
110 for x in range(retry_count, 0, -1):
111 time.sleep(3)
112 result = remote.execute(cmd, verbose=True)
113
114 # Workaround of exit code 0 from salt in case of failures
115 failed = 0
116 for s in result['stdout']:
117 if s.startswith("Failed:"):
118 failed += int(s.split("Failed:")[1])
119
120 if result.exit_code != 0:
121 time.sleep(retry_delay)
122 LOG.info(
123 " === RETRY ({0}/{1}) ========================="
124 .format(x - 1, retry_count))
125 elif failed != 0:
126 LOG.error(
127 " === SALT returned exit code = 0 while "
128 "there are failed modules! ===")
129 LOG.info(
130 " === RETRY ({0}/{1}) ======================="
131 .format(x - 1, retry_count))
132 else:
Dmitry Tyzhnenkobc0f8262017-04-28 15:39:26 +0300133 if self.__config.salt.salt_master_host != '0.0.0.0':
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +0300134 # Workarounds for crashed services
135 self.ensure_running_service(
136 "salt-master",
Dmitry Tyzhnenkobc0f8262017-04-28 15:39:26 +0300137 self.__config.salt.salt_master_host,
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +0300138 "salt-call pillar.items",
139 'active (running)') # Hardcoded for now
140 self.ensure_running_service(
141 "salt-minion",
Dmitry Tyzhnenkobc0f8262017-04-28 15:39:26 +0300142 self.__config.salt.salt_master_host,
Dmitry Tyzhnenko2b730a02017-04-07 19:31:32 +0300143 "salt 'cfg01*' pillar.items",
144 "active (running)") # Hardcoded for now
145 break
146
147 if x == 1 and skip_fail is False:
148 # In the last retry iteration, raise an exception
149 raise Exception("Step '{0}' failed"
150 .format(description))
151
152 def command2(self, step):
153 # Required fields
154 do = step['do']
155 target = step['target']
156 state = step.get('state')
157 states = step.get('states')
158 # Optional fields
159 args = step.get('args')
160 kwargs = step.get('kwargs')
161 description = step.get('description', do)
162 retry = step.get('retry', {'count': 1, 'delay': 1})
163 retry_count = retry.get('count', 1)
164 retry_delay = retry.get('delay', 1)
165 skip_fail = step.get('skip_fail', False)
166
167 if not bool(state) ^ bool(states):
168 raise ValueError("You should use state or states in step")
169
170 for x in range(retry_count, 0, -1):
171 time.sleep(3)
172
173 method = getattr(self._salt, self._salt._map[do])
174 command_ret = method(tgt=target, state=state or states,
175 args=args, kwargs=kwargs)
176 command_ret = command_ret if \
177 isinstance(command_ret, list) else [command_ret]
178 results = [(r['return'][0], f) for r, f in command_ret]
179
180 # FIMME: Change to debug level
181 LOG.info(" === States output =======================\n"
182 "{}\n"
183 " =========================================".format(
184 pretty_repr([r for r, f in results])))
185
186 all_fails = [f for r, f in results if f]
187 if all_fails:
188 LOG.error("States finished with failures.\n{}".format(
189 all_fails))
190 time.sleep(retry_delay)
191 LOG.info(" === RETRY ({0}/{1}) ========================="
192 .format(x - 1, retry_count))
193 else:
194 break
195
196 if x == 1 and skip_fail is False:
197 # In the last retry iteration, raise an exception
198 raise Exception("Step '{0}' failed"
199 .format(description))