blob: cf1fb2be87072706c106a98304c24d38b3fca4da [file] [log] [blame]
Dennis Dmitriev6f59add2016-10-18 13:45:27 +03001# Copyright 2016 Mirantis, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
Tatyana Leontovichab47e162017-10-06 16:53:30 +030015import os
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030016import random
Artem Panchenkodb0a97f2017-06-27 19:09:13 +030017import StringIO
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030018
19from devops.helpers import helpers
20from devops.helpers import ssh_client
Dennis Dmitriev2dfb8ef2017-07-21 20:19:38 +030021from devops.helpers import subprocess_runner
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030022from paramiko import rsakey
Dennis Dmitriev99b26fe2017-04-26 12:34:44 +030023import yaml
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030024
25from tcp_tests import logger
Dennis Dmitrievd2604512018-06-04 05:34:44 +030026from tcp_tests import settings
Tatyana Leontovichab47e162017-10-06 16:53:30 +030027from tcp_tests.helpers import ext
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030028from tcp_tests.helpers import utils
29
30LOG = logger.logger
31
32
33class UnderlaySSHManager(object):
34 """Keep the list of SSH access credentials to Underlay nodes.
35
36 This object is initialized using config.underlay.ssh.
37
38 :param config_ssh: JSONList of SSH access credentials for nodes:
39 [
40 {
41 node_name: node1,
Dennis Dmitriev83cc1d52018-11-09 15:35:30 +020042 minion_id: node1.local,
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030043 address_pool: 'public-pool01',
44 host: ,
45 port: ,
46 keys: [],
47 keys_source_host: None,
48 login: ,
49 password: ,
Dennis Dmitriev474e3f72016-10-21 16:46:09 +030050 roles: [],
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030051 },
52 {
53 node_name: node1,
Dennis Dmitriev83cc1d52018-11-09 15:35:30 +020054 minion_id: node1.local,
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030055 address_pool: 'private-pool01',
56 host:
57 port:
58 keys: []
59 keys_source_host: None,
60 login:
61 password:
Dennis Dmitriev474e3f72016-10-21 16:46:09 +030062 roles: [],
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030063 },
64 {
65 node_name: node2,
Dennis Dmitriev83cc1d52018-11-09 15:35:30 +020066 minion_id: node2.local,
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030067 address_pool: 'public-pool01',
68 keys_source_host: node1
69 ...
70 }
71 ,
72 ...
73 ]
74
75 self.node_names(): list of node names registered in underlay.
76 self.remote(): SSHClient object by a node name (w/wo address pool)
77 or by a hostname.
78 """
Dennis Dmitriev2a13a132016-11-04 00:56:23 +020079 __config = None
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030080 config_ssh = None
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030081
Dennis Dmitriev2a13a132016-11-04 00:56:23 +020082 def __init__(self, config):
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030083 """Read config.underlay.ssh object
84
85 :param config_ssh: dict
86 """
Dennis Dmitriev2a13a132016-11-04 00:56:23 +020087 self.__config = config
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030088 if self.config_ssh is None:
89 self.config_ssh = []
90
Dennis Dmitriev2a13a132016-11-04 00:56:23 +020091 self.add_config_ssh(self.__config.underlay.ssh)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030092
93 def add_config_ssh(self, config_ssh):
94
95 if config_ssh is None:
96 config_ssh = []
97
98 for ssh in config_ssh:
99 ssh_data = {
100 # Required keys:
101 'node_name': ssh['node_name'],
Dennis Dmitriev83cc1d52018-11-09 15:35:30 +0200102 'minion_id': ssh['minion_id'],
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300103 'host': ssh['host'],
104 'login': ssh['login'],
105 'password': ssh['password'],
106 # Optional keys:
107 'address_pool': ssh.get('address_pool', None),
108 'port': ssh.get('port', None),
109 'keys': ssh.get('keys', []),
Dennis Dmitriev474e3f72016-10-21 16:46:09 +0300110 'roles': ssh.get('roles', []),
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300111 }
112
113 if 'keys_source_host' in ssh:
114 node_name = ssh['keys_source_host']
115 remote = self.remote(node_name)
116 keys = self.__get_keys(remote)
117 ssh_data['keys'].extend(keys)
118
119 self.config_ssh.append(ssh_data)
120
121 def remove_config_ssh(self, config_ssh):
122 if config_ssh is None:
123 config_ssh = []
124
125 for ssh in config_ssh:
126 ssh_data = {
127 # Required keys:
128 'node_name': ssh['node_name'],
Dennis Dmitriev83cc1d52018-11-09 15:35:30 +0200129 'minion_id': ssh['minion_id'],
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300130 'host': ssh['host'],
131 'login': ssh['login'],
132 'password': ssh['password'],
133 # Optional keys:
134 'address_pool': ssh.get('address_pool', None),
135 'port': ssh.get('port', None),
136 'keys': ssh.get('keys', []),
Dennis Dmitriev474e3f72016-10-21 16:46:09 +0300137 'roles': ssh.get('roles', []),
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300138 }
139 self.config_ssh.remove(ssh_data)
140
141 def __get_keys(self, remote):
142 keys = []
143 remote.execute('cd ~')
144 key_string = './.ssh/id_rsa'
145 if remote.exists(key_string):
146 with remote.open(key_string) as f:
147 keys.append(rsakey.RSAKey.from_private_key(f))
148 return keys
149
Tatyana Leontovichecd491d2017-09-13 13:51:12 +0300150 def __ssh_data(self, node_name=None, host=None, address_pool=None,
Dennis Dmitriev83cc1d52018-11-09 15:35:30 +0200151 node_role=None, minion_id=None):
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300152
153 ssh_data = None
154
155 if host is not None:
156 for ssh in self.config_ssh:
157 if host == ssh['host']:
158 ssh_data = ssh
159 break
160
161 elif node_name is not None:
162 for ssh in self.config_ssh:
163 if node_name == ssh['node_name']:
164 if address_pool is not None:
165 if address_pool == ssh['address_pool']:
166 ssh_data = ssh
167 break
168 else:
169 ssh_data = ssh
Tatyana Leontovichecd491d2017-09-13 13:51:12 +0300170 elif node_role is not None:
171 for ssh in self.config_ssh:
172 if node_role in ssh['roles']:
173 if address_pool is not None:
174 if address_pool == ssh['address_pool']:
175 ssh_data = ssh
176 break
177 else:
178 ssh_data = ssh
Dennis Dmitriev83cc1d52018-11-09 15:35:30 +0200179 elif minion_id is not None:
180 for ssh in self.config_ssh:
181 if minion_id == ssh['minion_id']:
182 if address_pool is not None:
183 if address_pool == ssh['address_pool']:
184 ssh_data = ssh
185 break
186 else:
187 ssh_data = ssh
188
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300189 if ssh_data is None:
Dmitry Tyzhnenkob610afd2018-02-19 15:43:45 +0200190 LOG.debug("config_ssh - {}".format(self.config_ssh))
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300191 raise Exception('Auth data for node was not found using '
192 'node_name="{}" , host="{}" , address_pool="{}"'
193 .format(node_name, host, address_pool))
194 return ssh_data
195
196 def node_names(self):
197 """Get list of node names registered in config.underlay.ssh"""
198
199 names = [] # List is used to keep the original order of names
200 for ssh in self.config_ssh:
201 if ssh['node_name'] not in names:
202 names.append(ssh['node_name'])
203 return names
204
Dennis Dmitriev83cc1d52018-11-09 15:35:30 +0200205 def minion_ids(self):
206 """Get list of minion ids registered in config.underlay.ssh"""
207
208 ids = [] # List is used to keep the original order of ids
209 for ssh in self.config_ssh:
210 if ssh['minion_id'] not in ids:
211 ids.append(ssh['minion_id'])
212 return ids
213
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300214 def host_by_node_name(self, node_name, address_pool=None):
215 ssh_data = self.__ssh_data(node_name=node_name,
216 address_pool=address_pool)
217 return ssh_data['host']
218
Tatyana Leontovichecd491d2017-09-13 13:51:12 +0300219 def host_by_node_role(self, node_role, address_pool=None):
220 ssh_data = self.__ssh_data(node_role=node_role,
221 address_pool=address_pool)
222 return ssh_data['host']
223
Dennis Dmitriev83cc1d52018-11-09 15:35:30 +0200224 def host_by_minion_id(self, minion_id, address_pool=None):
225 ssh_data = self.__ssh_data(minion_id=minion_id,
226 address_pool=address_pool)
227 return ssh_data['host']
228
Dmitry Tyzhnenko35413c02018-03-05 14:12:37 +0200229 def remote(self, node_name=None, host=None, address_pool=None,
230 username=None):
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300231 """Get SSHClient by a node name or hostname.
232
233 One of the following arguments should be specified:
234 - host (str): IP address or hostname. If specified, 'node_name' is
235 ignored.
236 - node_name (str): Name of the node stored to config.underlay.ssh
237 - address_pool (str): optional for node_name.
238 If None, use the first matched node_name.
239 """
240 ssh_data = self.__ssh_data(node_name=node_name, host=host,
241 address_pool=address_pool)
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200242 ssh_auth = ssh_client.SSHAuth(
Dmitry Tyzhnenko35413c02018-03-05 14:12:37 +0200243 username=username or ssh_data['login'],
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200244 password=ssh_data['password'],
245 keys=[rsakey.RSAKey(file_obj=StringIO.StringIO(key))
246 for key in ssh_data['keys']])
247
Dennis Dmitrievd2604512018-06-04 05:34:44 +0300248 client = ssh_client.SSHClient(
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300249 host=ssh_data['host'],
250 port=ssh_data['port'] or 22,
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200251 auth=ssh_auth)
Dennis Dmitrievd2604512018-06-04 05:34:44 +0300252 client._ssh.get_transport().set_keepalive(
253 settings.SSH_SERVER_ALIVE_INTERVAL)
254
255 return client
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300256
Dennis Dmitriev2dfb8ef2017-07-21 20:19:38 +0300257 def local(self):
258 """Get Subprocess instance for local operations like:
259
260 underlay.local.execute(command, verbose=False, timeout=None)
261 underlay.local.check_call(
262 command, verbose=False, timeout=None,
263 error_info=None, expected=None, raise_on_err=True)
264 underlay.local.check_stderr(
265 command, verbose=False, timeout=None,
266 error_info=None, raise_on_err=True)
267 """
268 return subprocess_runner.Subprocess()
269
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300270 def check_call(
271 self, cmd,
272 node_name=None, host=None, address_pool=None,
273 verbose=False, timeout=None,
274 error_info=None,
275 expected=None, raise_on_err=True):
276 """Execute command on the node_name/host and check for exit code
277
278 :type cmd: str
279 :type node_name: str
280 :type host: str
281 :type verbose: bool
282 :type timeout: int
283 :type error_info: str
284 :type expected: list
285 :type raise_on_err: bool
286 :rtype: list stdout
287 :raises: devops.error.DevopsCalledProcessError
288 """
289 remote = self.remote(node_name=node_name, host=host,
290 address_pool=address_pool)
291 return remote.check_call(
292 command=cmd, verbose=verbose, timeout=timeout,
293 error_info=error_info, expected=expected,
294 raise_on_err=raise_on_err)
295
296 def apt_install_package(self, packages=None, node_name=None, host=None,
297 **kwargs):
298 """Method to install packages on ubuntu nodes
299
300 :type packages: list
301 :type node_name: str
302 :type host: str
303 :raises: devops.error.DevopsCalledProcessError,
304 devops.error.TimeoutError, AssertionError, ValueError
305
306 Other params of check_call and sudo_check_call are allowed
307 """
308 expected = kwargs.pop('expected', None)
309 if not packages or not isinstance(packages, list):
310 raise ValueError("packages list should be provided!")
311 install = "apt-get install -y {}".format(" ".join(packages))
312 # Should wait until other 'apt' jobs are finished
313 pgrep_expected = [0, 1]
314 pgrep_command = "pgrep -a -f apt"
315 helpers.wait(
316 lambda: (self.check_call(
317 pgrep_command, expected=pgrep_expected, host=host,
318 node_name=node_name, **kwargs).exit_code == 1
319 ), interval=30, timeout=1200,
320 timeout_msg="Timeout reached while waiting for apt lock"
321 )
322 # Install packages
323 self.sudo_check_call("apt-get update", node_name=node_name, host=host,
324 **kwargs)
325 self.sudo_check_call(install, expected=expected, node_name=node_name,
326 host=host, **kwargs)
327
328 def sudo_check_call(
329 self, cmd,
330 node_name=None, host=None, address_pool=None,
331 verbose=False, timeout=None,
332 error_info=None,
333 expected=None, raise_on_err=True):
334 """Execute command with sudo on node_name/host and check for exit code
335
336 :type cmd: str
337 :type node_name: str
338 :type host: str
339 :type verbose: bool
340 :type timeout: int
341 :type error_info: str
342 :type expected: list
343 :type raise_on_err: bool
344 :rtype: list stdout
345 :raises: devops.error.DevopsCalledProcessError
346 """
347 remote = self.remote(node_name=node_name, host=host,
348 address_pool=address_pool)
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300349 with remote.sudo(enforce=True):
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300350 return remote.check_call(
351 command=cmd, verbose=verbose, timeout=timeout,
352 error_info=error_info, expected=expected,
353 raise_on_err=raise_on_err)
354
355 def dir_upload(self, host, source, destination):
356 """Upload local directory content to remote host
357
358 :param host: str, remote node name
359 :param source: str, local directory path
360 :param destination: str, local directory path
361 """
362 with self.remote(node_name=host) as remote:
363 remote.upload(source, destination)
364
Dennis Dmitriev0f08d9a2017-12-19 02:27:59 +0200365 def get_random_node(self, node_names=None):
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300366 """Get random node name
367
Dennis Dmitriev0f08d9a2017-12-19 02:27:59 +0200368 :param node_names: list of strings
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300369 :return: str, name of node
370 """
Dennis Dmitriev0f08d9a2017-12-19 02:27:59 +0200371 return random.choice(node_names or self.node_names())
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300372
373 def yaml_editor(self, file_path, node_name=None, host=None,
374 address_pool=None):
375 """Returns an initialized YamlEditor instance for context manager
376
377 Usage (with 'underlay' fixture):
378
379 # Local YAML file
380 with underlay.yaml_editor('/path/to/file') as editor:
381 editor.content[key] = "value"
382
383 # Remote YAML file on TCP host
384 with underlay.yaml_editor('/path/to/file',
385 host=config.tcp.tcp_host) as editor:
386 editor.content[key] = "value"
387 """
388 # Local YAML file
389 if node_name is None and host is None:
390 return utils.YamlEditor(file_path=file_path)
391
392 # Remote YAML file
393 ssh_data = self.__ssh_data(node_name=node_name, host=host,
394 address_pool=address_pool)
395 return utils.YamlEditor(
396 file_path=file_path,
397 host=ssh_data['host'],
398 port=ssh_data['port'] or 22,
399 username=ssh_data['login'],
400 password=ssh_data['password'],
401 private_keys=ssh_data['keys'])
Dennis Dmitriev010f4cd2016-11-01 20:43:51 +0200402
Dennis Dmitriev99b26fe2017-04-26 12:34:44 +0300403 def read_template(self, file_path):
404 """Read yaml as a jinja template"""
405 options = {
406 'config': self.__config,
407 }
408 template = utils.render_template(file_path, options=options)
409 return yaml.load(template)
Tatyana Leontovichab47e162017-10-06 16:53:30 +0300410
411 def get_logs(self, artifact_name,
412 node_role=ext.UNDERLAY_NODE_ROLES.salt_master):
Dennis Dmitriev21369672018-03-24 14:50:18 +0200413
414 # Prefix each '$' symbol with backslash '\' to disable
415 # early interpolation of environment variables on cfg01 node only
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200416 dump_commands = (
Dennis Dmitriev21369672018-03-24 14:50:18 +0200417 "mkdir /root/\$(hostname -f)/;"
418 "rsync -aruv /var/log/ /root/\$(hostname -f)/;"
419 "dpkg -l > /root/\$(hostname -f)/dump_dpkg_l.txt;"
420 "df -h > /root/\$(hostname -f)/dump_df.txt;"
421 "mount > /root/\$(hostname -f)/dump_mount.txt;"
422 "blkid -o list > /root/\$(hostname -f)/dump_blkid_o_list.txt;"
423 "iptables -t nat -S > /root/\$(hostname -f)/dump_iptables_nat.txt;"
424 "iptables -S > /root/\$(hostname -f)/dump_iptables.txt;"
425 "ps auxwwf > /root/\$(hostname -f)/dump_ps.txt;"
426 "docker images > /root/\$(hostname -f)/dump_docker_images.txt;"
427 "docker ps > /root/\$(hostname -f)/dump_docker_ps.txt;"
Mikhail Ivanov82da3392018-03-15 22:19:26 +0400428 "docker service ls > "
Dennis Dmitriev21369672018-03-24 14:50:18 +0200429 " /root/\$(hostname -f)/dump_docker_services_ls.txt;"
Dennis Dmitrievefe5c0b2018-10-24 20:35:26 +0300430 "for SERVICE in \$(docker service ls | awk '{ print \$2 }'); "
Dennis Dmitriev21369672018-03-24 14:50:18 +0200431 " do docker service ps --no-trunc 2>&1 \$SERVICE >> "
432 " /root/\$(hostname -f)/dump_docker_service_ps.txt;"
Dennis Dmitriev01d5e372018-03-15 23:29:29 +0200433 " done;"
Dennis Dmitrievefe5c0b2018-10-24 20:35:26 +0300434 "for SERVICE in \$(docker service ls | awk '{ print \$2 }'); "
435 " do timeout 30 docker service logs --no-trunc 2>&1 \$SERVICE > "
Dennis Dmitriev21369672018-03-24 14:50:18 +0200436 " /root/\$(hostname -f)/dump_docker_service_\${SERVICE}_logs;"
Dennis Dmitriev01d5e372018-03-15 23:29:29 +0200437 " done;"
Dennis Dmitriev21369672018-03-24 14:50:18 +0200438 "vgdisplay > /root/\$(hostname -f)/dump_vgdisplay.txt;"
439 "lvdisplay > /root/\$(hostname -f)/dump_lvdisplay.txt;"
440 "ip a > /root/\$(hostname -f)/dump_ip_a.txt;"
441 "ip r > /root/\$(hostname -f)/dump_ip_r.txt;"
442 "netstat -anp > /root/\$(hostname -f)/dump_netstat.txt;"
443 "brctl show > /root/\$(hostname -f)/dump_brctl_show.txt;"
444 "arp -an > /root/\$(hostname -f)/dump_arp.txt;"
445 "uname -a > /root/\$(hostname -f)/dump_uname_a.txt;"
446 "lsmod > /root/\$(hostname -f)/dump_lsmod.txt;"
447 "cat /proc/interrupts > /root/\$(hostname -f)/dump_interrupts.txt;"
448 "cat /etc/*-release > /root/\$(hostname -f)/dump_release.txt;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200449 # OpenStack specific, will fail on other nodes
Dennis Dmitriev21369672018-03-24 14:50:18 +0200450 # "rabbitmqctl report > "
451 # " /root/\$(hostname -f)/dump_rabbitmqctl.txt;"
Tatyana Leontovichab47e162017-10-06 16:53:30 +0300452
Dennis Dmitriev21369672018-03-24 14:50:18 +0200453 # "ceph health > /root/\$(hostname -f)/dump_ceph_health.txt;"
454 # "ceph -s > /root/\$(hostname -f)/dump_ceph_s.txt;"
455 # "ceph osd tree > /root/\$(hostname -f)/dump_ceph_osd_tree.txt;"
Dennis Dmitriev0bc485b2017-12-13 12:49:54 +0200456
Dennis Dmitriev21369672018-03-24 14:50:18 +0200457 # "for ns in \$(ip netns list);"
458 # " do echo Namespace: \${ns}; ip netns exec \${ns} ip a;"
459 # "done > /root/\$(hostname -f)/dump_ip_a_ns.txt;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200460
Dennis Dmitriev21369672018-03-24 14:50:18 +0200461 # "for ns in \$(ip netns list);"
462 # " do echo Namespace: \${ns}; ip netns exec \${ns} ip r;"
463 # "done > /root/\$(hostname -f)/dump_ip_r_ns.txt;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200464
Dennis Dmitriev21369672018-03-24 14:50:18 +0200465 # "for ns in \$(ip netns list);"
466 # " do echo Namespace: \${ns}; ip netns exec \${ns} netstat -anp;"
467 # "done > /root/\$(hostname -f)/dump_netstat_ns.txt;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200468
469 "/usr/bin/haproxy-status.sh > "
Dennis Dmitriev21369672018-03-24 14:50:18 +0200470 " /root/\$(hostname -f)/dump_haproxy.txt;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200471
472 # Archive the files
473 "cd /root/; tar --absolute-names --warning=no-file-changed "
Dennis Dmitriev21369672018-03-24 14:50:18 +0200474 " -czf \$(hostname -f).tar.gz ./\$(hostname -f)/;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200475 )
476
477 master_host = self.__config.salt.salt_master_host
478 with self.remote(host=master_host) as master:
479 # dump files
480 LOG.info("Archive artifacts on all nodes")
Dennis Dmitrievc83b3d42018-03-16 00:59:18 +0200481 master.check_call('salt "*" cmd.run "{0}"'.format(dump_commands),
Dennis Dmitrievbc229552018-07-24 13:54:59 +0300482 raise_on_err=False,
483 timeout=600)
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200484
485 # create target dir for archives
Dennis Dmitriev9cd5c132018-12-12 17:10:47 +0200486 master.check_call("mkdir -p /root/dump/")
487
488 saltkeys_res = master.check_call(
489 "salt-key --list all --out=yaml", verbose=True)
490
491 saltkeys_all = yaml.load(saltkeys_res.stdout_str)
492 minions = saltkeys_all['minions']
493
494 # add nodes registered self.config_ssh,
495 # to get logs from nodes without salt minions
496 for node in self.config_ssh:
497 # If there is no any minion which name starts
498 # with the same hostname as node['node_name']
499 if not any(minion.startswith(node['node_name'])
500 for minion in minions):
501 # Use IP address from node['host'] to access the node
502 # because cfg01 node may not know it's hostname.
503 # Note: SSH public key from system.openssh.server.team.lab
504 # should already be configured on that node
505 # in order to access the node from cfg01
506 minions.append(str(node['host']))
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200507
508 # get archived artifacts to the master node
Dennis Dmitriev9cd5c132018-12-12 17:10:47 +0200509 for minion in minions:
510 LOG.info("Getting archived artifacts from the minion {0}"
511 .format(minion))
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200512 master.check_call("rsync -aruv {0}:/root/*.tar.gz "
Dennis Dmitriev9cd5c132018-12-12 17:10:47 +0200513 "/root/dump/".format(minion.strip()),
Dennis Dmitriev8feb2522018-03-28 19:17:04 +0300514 raise_on_err=False,
515 timeout=120)
Dennis Dmitriev0bc485b2017-12-13 12:49:54 +0200516
Dennis Dmitriev9cd5c132018-12-12 17:10:47 +0200517 destination_name = '/tmp/{0}_dump.tar.gz'.format(artifact_name)
518 # Archive the artifacts from all minions
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200519 master.check_call(
520 'cd /root/dump/;'
521 'tar --absolute-names --warning=no-file-changed -czf '
Dennis Dmitriev9cd5c132018-12-12 17:10:47 +0200522 ' {0} ./'.format(destination_name), verbose=True)
Tatyana Leontovichab47e162017-10-06 16:53:30 +0300523
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200524 # Download the artifact to the host
Dennis Dmitriev2d643bc2017-12-04 12:23:47 +0200525 LOG.info("Downloading the artifact {0}".format(destination_name))
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200526 master.download(destination=destination_name, target=os.getcwd())
Dennis Dmitriev2d643bc2017-12-04 12:23:47 +0200527
528 def delayed_call(
529 self, cmd,
530 node_name=None, host=None, address_pool=None,
531 verbose=True, timeout=5,
532 delay_min=None, delay_max=None):
533 """Delayed call of the specified command in background
534
535 :param delay_min: minimum delay in minutes before run
536 the command
537 :param delay_max: maximum delay in minutes before run
538 the command
539 The command will be started at random time in the range
540 from delay_min to delay_max in minutes from 'now'
541 using the command 'at'.
542
543 'now' is rounded to integer by 'at' command, i.e.:
544 now(28 min 59 sec) == 28 min 00 sec.
545
546 So, if delay_min=1 , the command may start in range from
547 1 sec to 60 sec.
548
549 If delay_min and delay_max are None, then the command will
550 be executed in the background right now.
551 """
552 time_min = delay_min or delay_max
553 time_max = delay_max or delay_min
554
555 delay = None
556 if time_min is not None and time_max is not None:
557 delay = random.randint(time_min, time_max)
558
559 delay_str = ''
560 if delay:
561 delay_str = " + {0} min".format(delay)
562
563 delay_cmd = "cat << EOF | at now {0}\n{1}\nEOF".format(delay_str, cmd)
564
565 self.check_call(delay_cmd, node_name=node_name, host=host,
566 address_pool=address_pool, verbose=verbose,
567 timeout=timeout)
568
569 def get_target_node_names(self, target='gtw01.'):
570 """Get all node names which names starts with <target>"""
571 return [node_name for node_name
572 in self.node_names()
573 if node_name.startswith(target)]