blob: 105c34d129f7061066982f2c7ae646f9ab7c0da4 [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 """
Hanna Arhipovac2cb6a52021-10-20 14:30:05 +0300289 LOG.info("Executing {} on {}".format(cmd, node_name))
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300290 remote = self.remote(node_name=node_name, host=host,
291 address_pool=address_pool)
292 return remote.check_call(
293 command=cmd, verbose=verbose, timeout=timeout,
294 error_info=error_info, expected=expected,
295 raise_on_err=raise_on_err)
296
297 def apt_install_package(self, packages=None, node_name=None, host=None,
298 **kwargs):
299 """Method to install packages on ubuntu nodes
300
301 :type packages: list
302 :type node_name: str
303 :type host: str
304 :raises: devops.error.DevopsCalledProcessError,
305 devops.error.TimeoutError, AssertionError, ValueError
306
307 Other params of check_call and sudo_check_call are allowed
308 """
309 expected = kwargs.pop('expected', None)
310 if not packages or not isinstance(packages, list):
311 raise ValueError("packages list should be provided!")
312 install = "apt-get install -y {}".format(" ".join(packages))
313 # Should wait until other 'apt' jobs are finished
314 pgrep_expected = [0, 1]
315 pgrep_command = "pgrep -a -f apt"
316 helpers.wait(
317 lambda: (self.check_call(
318 pgrep_command, expected=pgrep_expected, host=host,
319 node_name=node_name, **kwargs).exit_code == 1
320 ), interval=30, timeout=1200,
321 timeout_msg="Timeout reached while waiting for apt lock"
322 )
323 # Install packages
324 self.sudo_check_call("apt-get update", node_name=node_name, host=host,
325 **kwargs)
326 self.sudo_check_call(install, expected=expected, node_name=node_name,
327 host=host, **kwargs)
328
329 def sudo_check_call(
330 self, cmd,
331 node_name=None, host=None, address_pool=None,
332 verbose=False, timeout=None,
333 error_info=None,
334 expected=None, raise_on_err=True):
335 """Execute command with sudo on node_name/host and check for exit code
336
337 :type cmd: str
338 :type node_name: str
339 :type host: str
340 :type verbose: bool
341 :type timeout: int
342 :type error_info: str
343 :type expected: list
344 :type raise_on_err: bool
345 :rtype: list stdout
346 :raises: devops.error.DevopsCalledProcessError
347 """
348 remote = self.remote(node_name=node_name, host=host,
349 address_pool=address_pool)
Dennis Dmitriev3ec2e532018-06-08 04:33:34 +0300350 with remote.sudo(enforce=True):
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300351 return remote.check_call(
352 command=cmd, verbose=verbose, timeout=timeout,
353 error_info=error_info, expected=expected,
354 raise_on_err=raise_on_err)
355
356 def dir_upload(self, host, source, destination):
357 """Upload local directory content to remote host
358
359 :param host: str, remote node name
360 :param source: str, local directory path
361 :param destination: str, local directory path
362 """
363 with self.remote(node_name=host) as remote:
364 remote.upload(source, destination)
365
Dennis Dmitriev0f08d9a2017-12-19 02:27:59 +0200366 def get_random_node(self, node_names=None):
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300367 """Get random node name
368
Dennis Dmitriev0f08d9a2017-12-19 02:27:59 +0200369 :param node_names: list of strings
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300370 :return: str, name of node
371 """
Dennis Dmitriev0f08d9a2017-12-19 02:27:59 +0200372 return random.choice(node_names or self.node_names())
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300373
374 def yaml_editor(self, file_path, node_name=None, host=None,
375 address_pool=None):
376 """Returns an initialized YamlEditor instance for context manager
377
378 Usage (with 'underlay' fixture):
379
380 # Local YAML file
381 with underlay.yaml_editor('/path/to/file') as editor:
382 editor.content[key] = "value"
383
384 # Remote YAML file on TCP host
385 with underlay.yaml_editor('/path/to/file',
386 host=config.tcp.tcp_host) as editor:
387 editor.content[key] = "value"
388 """
389 # Local YAML file
390 if node_name is None and host is None:
391 return utils.YamlEditor(file_path=file_path)
392
393 # Remote YAML file
394 ssh_data = self.__ssh_data(node_name=node_name, host=host,
395 address_pool=address_pool)
396 return utils.YamlEditor(
397 file_path=file_path,
398 host=ssh_data['host'],
399 port=ssh_data['port'] or 22,
400 username=ssh_data['login'],
401 password=ssh_data['password'],
402 private_keys=ssh_data['keys'])
Dennis Dmitriev010f4cd2016-11-01 20:43:51 +0200403
Dennis Dmitriev99b26fe2017-04-26 12:34:44 +0300404 def read_template(self, file_path):
405 """Read yaml as a jinja template"""
406 options = {
407 'config': self.__config,
408 }
409 template = utils.render_template(file_path, options=options)
410 return yaml.load(template)
Tatyana Leontovichab47e162017-10-06 16:53:30 +0300411
412 def get_logs(self, artifact_name,
413 node_role=ext.UNDERLAY_NODE_ROLES.salt_master):
Dennis Dmitriev21369672018-03-24 14:50:18 +0200414
415 # Prefix each '$' symbol with backslash '\' to disable
416 # early interpolation of environment variables on cfg01 node only
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200417 dump_commands = (
Dennis Dmitriev7cab5482019-05-22 15:40:14 +0300418 r"mkdir /root/\$(hostname -f)/;"
419 r"rsync -aruv /var/log/ /root/\$(hostname -f)/;"
420 r"dpkg -l > /root/\$(hostname -f)/dump_dpkg_l.txt;"
421 r"df -h > /root/\$(hostname -f)/dump_df.txt;"
422 r"mount > /root/\$(hostname -f)/dump_mount.txt;"
423 r"blkid -o list > /root/\$(hostname -f)/dump_blkid_o_list.txt;"
424 r"iptables -t nat -S > "
425 r" /root/\$(hostname -f)/dump_iptables_nat.txt;"
426 r"iptables -S > /root/\$(hostname -f)/dump_iptables.txt;"
427 r"ps auxwwf > /root/\$(hostname -f)/dump_ps.txt;"
428 r"docker images > /root/\$(hostname -f)/dump_docker_images.txt;"
429 r"docker ps > /root/\$(hostname -f)/dump_docker_ps.txt;"
430 r"docker service ls > "
431 r" /root/\$(hostname -f)/dump_docker_services_ls.txt;"
432 r"for SERVICE in \$(docker service ls | awk '{ print \$2 }'); "
433 r" do docker service ps --no-trunc 2>&1 \$SERVICE >> "
434 r" /root/\$(hostname -f)/dump_docker_service_ps.txt;"
435 r" done;"
436 r"for SERVICE in \$(docker service ls | awk '{ print \$2 }'); "
437 r" do timeout 30 docker service logs --no-trunc 2>&1 \$SERVICE > "
438 r" /root/\$(hostname -f)/dump_docker_service_\${SERVICE}_logs;"
439 r" done;"
440 r"vgdisplay > /root/\$(hostname -f)/dump_vgdisplay.txt;"
441 r"lvdisplay > /root/\$(hostname -f)/dump_lvdisplay.txt;"
442 r"ip a > /root/\$(hostname -f)/dump_ip_a.txt;"
443 r"ip r > /root/\$(hostname -f)/dump_ip_r.txt;"
444 r"netstat -anp > /root/\$(hostname -f)/dump_netstat.txt;"
445 r"brctl show > /root/\$(hostname -f)/dump_brctl_show.txt;"
446 r"arp -an > /root/\$(hostname -f)/dump_arp.txt;"
447 r"uname -a > /root/\$(hostname -f)/dump_uname_a.txt;"
448 r"lsmod > /root/\$(hostname -f)/dump_lsmod.txt;"
449 r"cat /proc/interrupts > "
450 r" /root/\$(hostname -f)/dump_interrupts.txt;"
451 r"cat /etc/*-release > /root/\$(hostname -f)/dump_release.txt;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200452 # OpenStack specific, will fail on other nodes
Dennis Dmitriev21369672018-03-24 14:50:18 +0200453 # "rabbitmqctl report > "
454 # " /root/\$(hostname -f)/dump_rabbitmqctl.txt;"
Tatyana Leontovichab47e162017-10-06 16:53:30 +0300455
Dennis Dmitriev21369672018-03-24 14:50:18 +0200456 # "ceph health > /root/\$(hostname -f)/dump_ceph_health.txt;"
457 # "ceph -s > /root/\$(hostname -f)/dump_ceph_s.txt;"
458 # "ceph osd tree > /root/\$(hostname -f)/dump_ceph_osd_tree.txt;"
Dennis Dmitriev0bc485b2017-12-13 12:49:54 +0200459
Dennis Dmitriev21369672018-03-24 14:50:18 +0200460 # "for ns in \$(ip netns list);"
461 # " do echo Namespace: \${ns}; ip netns exec \${ns} ip a;"
462 # "done > /root/\$(hostname -f)/dump_ip_a_ns.txt;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200463
Dennis Dmitriev21369672018-03-24 14:50:18 +0200464 # "for ns in \$(ip netns list);"
465 # " do echo Namespace: \${ns}; ip netns exec \${ns} ip r;"
466 # "done > /root/\$(hostname -f)/dump_ip_r_ns.txt;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200467
Dennis Dmitriev21369672018-03-24 14:50:18 +0200468 # "for ns in \$(ip netns list);"
469 # " do echo Namespace: \${ns}; ip netns exec \${ns} netstat -anp;"
470 # "done > /root/\$(hostname -f)/dump_netstat_ns.txt;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200471
Dennis Dmitriev7cab5482019-05-22 15:40:14 +0300472 r"/usr/bin/haproxy-status.sh > "
473 r" /root/\$(hostname -f)/dump_haproxy.txt;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200474
475 # Archive the files
Dennis Dmitriev7cab5482019-05-22 15:40:14 +0300476 r"cd /root/; tar --absolute-names --warning=no-file-changed "
477 r" -czf \$(hostname -f).tar.gz ./\$(hostname -f)/;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200478 )
479
480 master_host = self.__config.salt.salt_master_host
481 with self.remote(host=master_host) as master:
Dennis Dmitrievb85793a2019-01-18 14:11:23 +0000482 LOG.info("Make sure that 'rsync' is installed on all nodes")
483 master.check_call("salt '*' cmd.run "
484 " 'apt-get -qq install -y rsync'",
485 raise_on_err=False,
486 timeout=240)
487
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200488 # dump files
489 LOG.info("Archive artifacts on all nodes")
Dennis Dmitrievc83b3d42018-03-16 00:59:18 +0200490 master.check_call('salt "*" cmd.run "{0}"'.format(dump_commands),
Dennis Dmitrievbc229552018-07-24 13:54:59 +0300491 raise_on_err=False,
492 timeout=600)
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200493
494 # create target dir for archives
Dennis Dmitriev9cd5c132018-12-12 17:10:47 +0200495 master.check_call("mkdir -p /root/dump/")
496
497 saltkeys_res = master.check_call(
498 "salt-key --list all --out=yaml", verbose=True)
499
500 saltkeys_all = yaml.load(saltkeys_res.stdout_str)
501 minions = saltkeys_all['minions']
502
503 # add nodes registered self.config_ssh,
504 # to get logs from nodes without salt minions
505 for node in self.config_ssh:
506 # If there is no any minion which name starts
507 # with the same hostname as node['node_name']
508 if not any(minion.startswith(node['node_name'])
509 for minion in minions):
510 # Use IP address from node['host'] to access the node
511 # because cfg01 node may not know it's hostname.
512 # Note: SSH public key from system.openssh.server.team.lab
513 # should already be configured on that node
514 # in order to access the node from cfg01
515 minions.append(str(node['host']))
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200516
517 # get archived artifacts to the master node
Dennis Dmitriev9cd5c132018-12-12 17:10:47 +0200518 for minion in minions:
519 LOG.info("Getting archived artifacts from the minion {0}"
520 .format(minion))
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200521 master.check_call("rsync -aruv {0}:/root/*.tar.gz "
Dennis Dmitriev9cd5c132018-12-12 17:10:47 +0200522 "/root/dump/".format(minion.strip()),
Dennis Dmitriev8feb2522018-03-28 19:17:04 +0300523 raise_on_err=False,
524 timeout=120)
Dennis Dmitriev0bc485b2017-12-13 12:49:54 +0200525
Dennis Dmitriev9cd5c132018-12-12 17:10:47 +0200526 destination_name = '/tmp/{0}_dump.tar.gz'.format(artifact_name)
527 # Archive the artifacts from all minions
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200528 master.check_call(
529 'cd /root/dump/;'
530 'tar --absolute-names --warning=no-file-changed -czf '
Dennis Dmitriev9cd5c132018-12-12 17:10:47 +0200531 ' {0} ./'.format(destination_name), verbose=True)
Tatyana Leontovichab47e162017-10-06 16:53:30 +0300532
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200533 # Download the artifact to the host
Dennis Dmitriev2d643bc2017-12-04 12:23:47 +0200534 LOG.info("Downloading the artifact {0}".format(destination_name))
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200535 master.download(destination=destination_name, target=os.getcwd())
Dennis Dmitriev2d643bc2017-12-04 12:23:47 +0200536
537 def delayed_call(
538 self, cmd,
539 node_name=None, host=None, address_pool=None,
540 verbose=True, timeout=5,
541 delay_min=None, delay_max=None):
542 """Delayed call of the specified command in background
543
544 :param delay_min: minimum delay in minutes before run
545 the command
546 :param delay_max: maximum delay in minutes before run
547 the command
548 The command will be started at random time in the range
549 from delay_min to delay_max in minutes from 'now'
550 using the command 'at'.
551
552 'now' is rounded to integer by 'at' command, i.e.:
553 now(28 min 59 sec) == 28 min 00 sec.
554
555 So, if delay_min=1 , the command may start in range from
556 1 sec to 60 sec.
557
558 If delay_min and delay_max are None, then the command will
559 be executed in the background right now.
560 """
561 time_min = delay_min or delay_max
562 time_max = delay_max or delay_min
563
564 delay = None
565 if time_min is not None and time_max is not None:
566 delay = random.randint(time_min, time_max)
567
568 delay_str = ''
569 if delay:
570 delay_str = " + {0} min".format(delay)
571
572 delay_cmd = "cat << EOF | at now {0}\n{1}\nEOF".format(delay_str, cmd)
573
574 self.check_call(delay_cmd, node_name=node_name, host=host,
575 address_pool=address_pool, verbose=verbose,
576 timeout=timeout)
577
578 def get_target_node_names(self, target='gtw01.'):
579 """Get all node names which names starts with <target>"""
580 return [node_name for node_name
581 in self.node_names()
582 if node_name.startswith(target)]
Dennis Dmitriev1566e3f2019-01-11 17:35:43 +0200583
584 def get_target_minion_ids(self, target='gtw01.'):
585 """Get all minion ids which names starts with <target>"""
586 return [minion_id for minion_id
587 in self.minion_ids()
588 if minion_id.startswith(target)]