blob: c2e82d417ac5224115d374a1c0adbee72615e407 [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 Dmitriev7cab5482019-05-22 15:40:14 +0300417 r"mkdir /root/\$(hostname -f)/;"
418 r"rsync -aruv /var/log/ /root/\$(hostname -f)/;"
419 r"dpkg -l > /root/\$(hostname -f)/dump_dpkg_l.txt;"
420 r"df -h > /root/\$(hostname -f)/dump_df.txt;"
421 r"mount > /root/\$(hostname -f)/dump_mount.txt;"
422 r"blkid -o list > /root/\$(hostname -f)/dump_blkid_o_list.txt;"
423 r"iptables -t nat -S > "
424 r" /root/\$(hostname -f)/dump_iptables_nat.txt;"
425 r"iptables -S > /root/\$(hostname -f)/dump_iptables.txt;"
426 r"ps auxwwf > /root/\$(hostname -f)/dump_ps.txt;"
427 r"docker images > /root/\$(hostname -f)/dump_docker_images.txt;"
428 r"docker ps > /root/\$(hostname -f)/dump_docker_ps.txt;"
429 r"docker service ls > "
430 r" /root/\$(hostname -f)/dump_docker_services_ls.txt;"
431 r"for SERVICE in \$(docker service ls | awk '{ print \$2 }'); "
432 r" do docker service ps --no-trunc 2>&1 \$SERVICE >> "
433 r" /root/\$(hostname -f)/dump_docker_service_ps.txt;"
434 r" done;"
435 r"for SERVICE in \$(docker service ls | awk '{ print \$2 }'); "
436 r" do timeout 30 docker service logs --no-trunc 2>&1 \$SERVICE > "
437 r" /root/\$(hostname -f)/dump_docker_service_\${SERVICE}_logs;"
438 r" done;"
439 r"vgdisplay > /root/\$(hostname -f)/dump_vgdisplay.txt;"
440 r"lvdisplay > /root/\$(hostname -f)/dump_lvdisplay.txt;"
441 r"ip a > /root/\$(hostname -f)/dump_ip_a.txt;"
442 r"ip r > /root/\$(hostname -f)/dump_ip_r.txt;"
443 r"netstat -anp > /root/\$(hostname -f)/dump_netstat.txt;"
444 r"brctl show > /root/\$(hostname -f)/dump_brctl_show.txt;"
445 r"arp -an > /root/\$(hostname -f)/dump_arp.txt;"
446 r"uname -a > /root/\$(hostname -f)/dump_uname_a.txt;"
447 r"lsmod > /root/\$(hostname -f)/dump_lsmod.txt;"
448 r"cat /proc/interrupts > "
449 r" /root/\$(hostname -f)/dump_interrupts.txt;"
450 r"cat /etc/*-release > /root/\$(hostname -f)/dump_release.txt;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200451 # OpenStack specific, will fail on other nodes
Dennis Dmitriev21369672018-03-24 14:50:18 +0200452 # "rabbitmqctl report > "
453 # " /root/\$(hostname -f)/dump_rabbitmqctl.txt;"
Tatyana Leontovichab47e162017-10-06 16:53:30 +0300454
Dennis Dmitriev21369672018-03-24 14:50:18 +0200455 # "ceph health > /root/\$(hostname -f)/dump_ceph_health.txt;"
456 # "ceph -s > /root/\$(hostname -f)/dump_ceph_s.txt;"
457 # "ceph osd tree > /root/\$(hostname -f)/dump_ceph_osd_tree.txt;"
Dennis Dmitriev0bc485b2017-12-13 12:49:54 +0200458
Dennis Dmitriev21369672018-03-24 14:50:18 +0200459 # "for ns in \$(ip netns list);"
460 # " do echo Namespace: \${ns}; ip netns exec \${ns} ip a;"
461 # "done > /root/\$(hostname -f)/dump_ip_a_ns.txt;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200462
Dennis Dmitriev21369672018-03-24 14:50:18 +0200463 # "for ns in \$(ip netns list);"
464 # " do echo Namespace: \${ns}; ip netns exec \${ns} ip r;"
465 # "done > /root/\$(hostname -f)/dump_ip_r_ns.txt;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200466
Dennis Dmitriev21369672018-03-24 14:50:18 +0200467 # "for ns in \$(ip netns list);"
468 # " do echo Namespace: \${ns}; ip netns exec \${ns} netstat -anp;"
469 # "done > /root/\$(hostname -f)/dump_netstat_ns.txt;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200470
Dennis Dmitriev7cab5482019-05-22 15:40:14 +0300471 r"/usr/bin/haproxy-status.sh > "
472 r" /root/\$(hostname -f)/dump_haproxy.txt;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200473
474 # Archive the files
Dennis Dmitriev7cab5482019-05-22 15:40:14 +0300475 r"cd /root/; tar --absolute-names --warning=no-file-changed "
476 r" -czf \$(hostname -f).tar.gz ./\$(hostname -f)/;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200477 )
478
479 master_host = self.__config.salt.salt_master_host
480 with self.remote(host=master_host) as master:
Dennis Dmitrievb85793a2019-01-18 14:11:23 +0000481 LOG.info("Make sure that 'rsync' is installed on all nodes")
482 master.check_call("salt '*' cmd.run "
483 " 'apt-get -qq install -y rsync'",
484 raise_on_err=False,
485 timeout=240)
486
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200487 # dump files
488 LOG.info("Archive artifacts on all nodes")
Dennis Dmitrievc83b3d42018-03-16 00:59:18 +0200489 master.check_call('salt "*" cmd.run "{0}"'.format(dump_commands),
Dennis Dmitrievbc229552018-07-24 13:54:59 +0300490 raise_on_err=False,
491 timeout=600)
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200492
493 # create target dir for archives
Dennis Dmitriev9cd5c132018-12-12 17:10:47 +0200494 master.check_call("mkdir -p /root/dump/")
495
496 saltkeys_res = master.check_call(
497 "salt-key --list all --out=yaml", verbose=True)
498
499 saltkeys_all = yaml.load(saltkeys_res.stdout_str)
500 minions = saltkeys_all['minions']
501
502 # add nodes registered self.config_ssh,
503 # to get logs from nodes without salt minions
504 for node in self.config_ssh:
505 # If there is no any minion which name starts
506 # with the same hostname as node['node_name']
507 if not any(minion.startswith(node['node_name'])
508 for minion in minions):
509 # Use IP address from node['host'] to access the node
510 # because cfg01 node may not know it's hostname.
511 # Note: SSH public key from system.openssh.server.team.lab
512 # should already be configured on that node
513 # in order to access the node from cfg01
514 minions.append(str(node['host']))
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200515
516 # get archived artifacts to the master node
Dennis Dmitriev9cd5c132018-12-12 17:10:47 +0200517 for minion in minions:
518 LOG.info("Getting archived artifacts from the minion {0}"
519 .format(minion))
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200520 master.check_call("rsync -aruv {0}:/root/*.tar.gz "
Dennis Dmitriev9cd5c132018-12-12 17:10:47 +0200521 "/root/dump/".format(minion.strip()),
Dennis Dmitriev8feb2522018-03-28 19:17:04 +0300522 raise_on_err=False,
523 timeout=120)
Dennis Dmitriev0bc485b2017-12-13 12:49:54 +0200524
Dennis Dmitriev9cd5c132018-12-12 17:10:47 +0200525 destination_name = '/tmp/{0}_dump.tar.gz'.format(artifact_name)
526 # Archive the artifacts from all minions
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200527 master.check_call(
528 'cd /root/dump/;'
529 'tar --absolute-names --warning=no-file-changed -czf '
Dennis Dmitriev9cd5c132018-12-12 17:10:47 +0200530 ' {0} ./'.format(destination_name), verbose=True)
Tatyana Leontovichab47e162017-10-06 16:53:30 +0300531
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200532 # Download the artifact to the host
Dennis Dmitriev2d643bc2017-12-04 12:23:47 +0200533 LOG.info("Downloading the artifact {0}".format(destination_name))
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200534 master.download(destination=destination_name, target=os.getcwd())
Dennis Dmitriev2d643bc2017-12-04 12:23:47 +0200535
536 def delayed_call(
537 self, cmd,
538 node_name=None, host=None, address_pool=None,
539 verbose=True, timeout=5,
540 delay_min=None, delay_max=None):
541 """Delayed call of the specified command in background
542
543 :param delay_min: minimum delay in minutes before run
544 the command
545 :param delay_max: maximum delay in minutes before run
546 the command
547 The command will be started at random time in the range
548 from delay_min to delay_max in minutes from 'now'
549 using the command 'at'.
550
551 'now' is rounded to integer by 'at' command, i.e.:
552 now(28 min 59 sec) == 28 min 00 sec.
553
554 So, if delay_min=1 , the command may start in range from
555 1 sec to 60 sec.
556
557 If delay_min and delay_max are None, then the command will
558 be executed in the background right now.
559 """
560 time_min = delay_min or delay_max
561 time_max = delay_max or delay_min
562
563 delay = None
564 if time_min is not None and time_max is not None:
565 delay = random.randint(time_min, time_max)
566
567 delay_str = ''
568 if delay:
569 delay_str = " + {0} min".format(delay)
570
571 delay_cmd = "cat << EOF | at now {0}\n{1}\nEOF".format(delay_str, cmd)
572
573 self.check_call(delay_cmd, node_name=node_name, host=host,
574 address_pool=address_pool, verbose=verbose,
575 timeout=timeout)
576
577 def get_target_node_names(self, target='gtw01.'):
578 """Get all node names which names starts with <target>"""
579 return [node_name for node_name
580 in self.node_names()
581 if node_name.startswith(target)]
Dennis Dmitriev1566e3f2019-01-11 17:35:43 +0200582
583 def get_target_minion_ids(self, target='gtw01.'):
584 """Get all minion ids which names starts with <target>"""
585 return [minion_id for minion_id
586 in self.minion_ids()
587 if minion_id.startswith(target)]