blob: 5f919cebb3b9152b7491d945f4f9bc9006d37090 [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
Tatyana Leontovichab47e162017-10-06 16:53:30 +030026from tcp_tests.helpers import ext
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030027from tcp_tests.helpers import utils
28
29LOG = logger.logger
30
31
32class UnderlaySSHManager(object):
33 """Keep the list of SSH access credentials to Underlay nodes.
34
35 This object is initialized using config.underlay.ssh.
36
37 :param config_ssh: JSONList of SSH access credentials for nodes:
38 [
39 {
40 node_name: node1,
41 address_pool: 'public-pool01',
42 host: ,
43 port: ,
44 keys: [],
45 keys_source_host: None,
46 login: ,
47 password: ,
Dennis Dmitriev474e3f72016-10-21 16:46:09 +030048 roles: [],
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030049 },
50 {
51 node_name: node1,
52 address_pool: 'private-pool01',
53 host:
54 port:
55 keys: []
56 keys_source_host: None,
57 login:
58 password:
Dennis Dmitriev474e3f72016-10-21 16:46:09 +030059 roles: [],
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030060 },
61 {
62 node_name: node2,
63 address_pool: 'public-pool01',
64 keys_source_host: node1
65 ...
66 }
67 ,
68 ...
69 ]
70
71 self.node_names(): list of node names registered in underlay.
72 self.remote(): SSHClient object by a node name (w/wo address pool)
73 or by a hostname.
74 """
Dennis Dmitriev2a13a132016-11-04 00:56:23 +020075 __config = None
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030076 config_ssh = None
77 config_lvm = None
78
Dennis Dmitriev2a13a132016-11-04 00:56:23 +020079 def __init__(self, config):
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030080 """Read config.underlay.ssh object
81
82 :param config_ssh: dict
83 """
Dennis Dmitriev2a13a132016-11-04 00:56:23 +020084 self.__config = config
Dennis Dmitriev6f59add2016-10-18 13:45:27 +030085 if self.config_ssh is None:
86 self.config_ssh = []
87
88 if self.config_lvm is None:
89 self.config_lvm = {}
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'],
102 'host': ssh['host'],
103 'login': ssh['login'],
104 'password': ssh['password'],
105 # Optional keys:
106 'address_pool': ssh.get('address_pool', None),
107 'port': ssh.get('port', None),
108 'keys': ssh.get('keys', []),
Dennis Dmitriev474e3f72016-10-21 16:46:09 +0300109 'roles': ssh.get('roles', []),
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300110 }
111
112 if 'keys_source_host' in ssh:
113 node_name = ssh['keys_source_host']
114 remote = self.remote(node_name)
115 keys = self.__get_keys(remote)
116 ssh_data['keys'].extend(keys)
117
118 self.config_ssh.append(ssh_data)
119
120 def remove_config_ssh(self, config_ssh):
121 if config_ssh is None:
122 config_ssh = []
123
124 for ssh in config_ssh:
125 ssh_data = {
126 # Required keys:
127 'node_name': ssh['node_name'],
128 'host': ssh['host'],
129 'login': ssh['login'],
130 'password': ssh['password'],
131 # Optional keys:
132 'address_pool': ssh.get('address_pool', None),
133 'port': ssh.get('port', None),
134 'keys': ssh.get('keys', []),
Dennis Dmitriev474e3f72016-10-21 16:46:09 +0300135 'roles': ssh.get('roles', []),
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300136 }
137 self.config_ssh.remove(ssh_data)
138
139 def __get_keys(self, remote):
140 keys = []
141 remote.execute('cd ~')
142 key_string = './.ssh/id_rsa'
143 if remote.exists(key_string):
144 with remote.open(key_string) as f:
145 keys.append(rsakey.RSAKey.from_private_key(f))
146 return keys
147
Tatyana Leontovichecd491d2017-09-13 13:51:12 +0300148 def __ssh_data(self, node_name=None, host=None, address_pool=None,
149 node_role=None):
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300150
151 ssh_data = None
152
153 if host is not None:
154 for ssh in self.config_ssh:
155 if host == ssh['host']:
156 ssh_data = ssh
157 break
158
159 elif node_name is not None:
160 for ssh in self.config_ssh:
161 if node_name == ssh['node_name']:
162 if address_pool is not None:
163 if address_pool == ssh['address_pool']:
164 ssh_data = ssh
165 break
166 else:
167 ssh_data = ssh
Tatyana Leontovichecd491d2017-09-13 13:51:12 +0300168 elif node_role is not None:
169 for ssh in self.config_ssh:
170 if node_role in ssh['roles']:
171 if address_pool is not None:
172 if address_pool == ssh['address_pool']:
173 ssh_data = ssh
174 break
175 else:
176 ssh_data = ssh
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300177 if ssh_data is None:
Dmitry Tyzhnenkob610afd2018-02-19 15:43:45 +0200178 LOG.debug("config_ssh - {}".format(self.config_ssh))
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300179 raise Exception('Auth data for node was not found using '
180 'node_name="{}" , host="{}" , address_pool="{}"'
181 .format(node_name, host, address_pool))
182 return ssh_data
183
184 def node_names(self):
185 """Get list of node names registered in config.underlay.ssh"""
186
187 names = [] # List is used to keep the original order of names
188 for ssh in self.config_ssh:
189 if ssh['node_name'] not in names:
190 names.append(ssh['node_name'])
191 return names
192
193 def enable_lvm(self, lvmconfig):
194 """Method for enabling lvm oh hosts in environment
195
196 :param lvmconfig: dict with ids or device' names of lvm storage
197 :raises: devops.error.DevopsCalledProcessError,
198 devops.error.TimeoutError, AssertionError, ValueError
199 """
200 def get_actions(lvm_id):
201 return [
202 "systemctl enable lvm2-lvmetad.service",
203 "systemctl enable lvm2-lvmetad.socket",
204 "systemctl start lvm2-lvmetad.service",
205 "systemctl start lvm2-lvmetad.socket",
206 "pvcreate {} && pvs".format(lvm_id),
207 "vgcreate default {} && vgs".format(lvm_id),
208 "lvcreate -L 1G -T default/pool && lvs",
209 ]
210 lvmpackages = ["lvm2", "liblvm2-dev", "thin-provisioning-tools"]
211 for node_name in self.node_names():
212 lvm = lvmconfig.get(node_name, None)
213 if not lvm:
214 continue
215 if 'id' in lvm:
216 lvmdevice = '/dev/disk/by-id/{}'.format(lvm['id'])
217 elif 'device' in lvm:
218 lvmdevice = '/dev/{}'.format(lvm['device'])
219 else:
220 raise ValueError("Unknown LVM device type")
221 if lvmdevice:
222 self.apt_install_package(
223 packages=lvmpackages, node_name=node_name, verbose=True)
224 for command in get_actions(lvmdevice):
225 self.sudo_check_call(command, node_name=node_name,
226 verbose=True)
227 self.config_lvm = dict(lvmconfig)
228
229 def host_by_node_name(self, node_name, address_pool=None):
230 ssh_data = self.__ssh_data(node_name=node_name,
231 address_pool=address_pool)
232 return ssh_data['host']
233
Tatyana Leontovichecd491d2017-09-13 13:51:12 +0300234 def host_by_node_role(self, node_role, address_pool=None):
235 ssh_data = self.__ssh_data(node_role=node_role,
236 address_pool=address_pool)
237 return ssh_data['host']
238
Dmitry Tyzhnenko35413c02018-03-05 14:12:37 +0200239 def remote(self, node_name=None, host=None, address_pool=None,
240 username=None):
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300241 """Get SSHClient by a node name or hostname.
242
243 One of the following arguments should be specified:
244 - host (str): IP address or hostname. If specified, 'node_name' is
245 ignored.
246 - node_name (str): Name of the node stored to config.underlay.ssh
247 - address_pool (str): optional for node_name.
248 If None, use the first matched node_name.
249 """
250 ssh_data = self.__ssh_data(node_name=node_name, host=host,
251 address_pool=address_pool)
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200252 ssh_auth = ssh_client.SSHAuth(
Dmitry Tyzhnenko35413c02018-03-05 14:12:37 +0200253 username=username or ssh_data['login'],
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200254 password=ssh_data['password'],
255 keys=[rsakey.RSAKey(file_obj=StringIO.StringIO(key))
256 for key in ssh_data['keys']])
257
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300258 return ssh_client.SSHClient(
259 host=ssh_data['host'],
260 port=ssh_data['port'] or 22,
Dmitry Tyzhnenko5a5d8da2017-12-14 14:14:42 +0200261 auth=ssh_auth)
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300262
Dennis Dmitriev2dfb8ef2017-07-21 20:19:38 +0300263 def local(self):
264 """Get Subprocess instance for local operations like:
265
266 underlay.local.execute(command, verbose=False, timeout=None)
267 underlay.local.check_call(
268 command, verbose=False, timeout=None,
269 error_info=None, expected=None, raise_on_err=True)
270 underlay.local.check_stderr(
271 command, verbose=False, timeout=None,
272 error_info=None, raise_on_err=True)
273 """
274 return subprocess_runner.Subprocess()
275
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300276 def check_call(
277 self, cmd,
278 node_name=None, host=None, address_pool=None,
279 verbose=False, timeout=None,
280 error_info=None,
281 expected=None, raise_on_err=True):
282 """Execute command on the node_name/host and check for exit code
283
284 :type cmd: str
285 :type node_name: str
286 :type host: str
287 :type verbose: bool
288 :type timeout: int
289 :type error_info: str
290 :type expected: list
291 :type raise_on_err: bool
292 :rtype: list stdout
293 :raises: devops.error.DevopsCalledProcessError
294 """
295 remote = self.remote(node_name=node_name, host=host,
296 address_pool=address_pool)
297 return remote.check_call(
298 command=cmd, verbose=verbose, timeout=timeout,
299 error_info=error_info, expected=expected,
300 raise_on_err=raise_on_err)
301
302 def apt_install_package(self, packages=None, node_name=None, host=None,
303 **kwargs):
304 """Method to install packages on ubuntu nodes
305
306 :type packages: list
307 :type node_name: str
308 :type host: str
309 :raises: devops.error.DevopsCalledProcessError,
310 devops.error.TimeoutError, AssertionError, ValueError
311
312 Other params of check_call and sudo_check_call are allowed
313 """
314 expected = kwargs.pop('expected', None)
315 if not packages or not isinstance(packages, list):
316 raise ValueError("packages list should be provided!")
317 install = "apt-get install -y {}".format(" ".join(packages))
318 # Should wait until other 'apt' jobs are finished
319 pgrep_expected = [0, 1]
320 pgrep_command = "pgrep -a -f apt"
321 helpers.wait(
322 lambda: (self.check_call(
323 pgrep_command, expected=pgrep_expected, host=host,
324 node_name=node_name, **kwargs).exit_code == 1
325 ), interval=30, timeout=1200,
326 timeout_msg="Timeout reached while waiting for apt lock"
327 )
328 # Install packages
329 self.sudo_check_call("apt-get update", node_name=node_name, host=host,
330 **kwargs)
331 self.sudo_check_call(install, expected=expected, node_name=node_name,
332 host=host, **kwargs)
333
334 def sudo_check_call(
335 self, cmd,
336 node_name=None, host=None, address_pool=None,
337 verbose=False, timeout=None,
338 error_info=None,
339 expected=None, raise_on_err=True):
340 """Execute command with sudo on node_name/host and check for exit code
341
342 :type cmd: str
343 :type node_name: str
344 :type host: str
345 :type verbose: bool
346 :type timeout: int
347 :type error_info: str
348 :type expected: list
349 :type raise_on_err: bool
350 :rtype: list stdout
351 :raises: devops.error.DevopsCalledProcessError
352 """
353 remote = self.remote(node_name=node_name, host=host,
354 address_pool=address_pool)
355 with remote.get_sudo(remote):
356 return remote.check_call(
357 command=cmd, verbose=verbose, timeout=timeout,
358 error_info=error_info, expected=expected,
359 raise_on_err=raise_on_err)
360
361 def dir_upload(self, host, source, destination):
362 """Upload local directory content to remote host
363
364 :param host: str, remote node name
365 :param source: str, local directory path
366 :param destination: str, local directory path
367 """
368 with self.remote(node_name=host) as remote:
369 remote.upload(source, destination)
370
Dennis Dmitriev0f08d9a2017-12-19 02:27:59 +0200371 def get_random_node(self, node_names=None):
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300372 """Get random node name
373
Dennis Dmitriev0f08d9a2017-12-19 02:27:59 +0200374 :param node_names: list of strings
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300375 :return: str, name of node
376 """
Dennis Dmitriev0f08d9a2017-12-19 02:27:59 +0200377 return random.choice(node_names or self.node_names())
Dennis Dmitriev6f59add2016-10-18 13:45:27 +0300378
379 def yaml_editor(self, file_path, node_name=None, host=None,
380 address_pool=None):
381 """Returns an initialized YamlEditor instance for context manager
382
383 Usage (with 'underlay' fixture):
384
385 # Local YAML file
386 with underlay.yaml_editor('/path/to/file') as editor:
387 editor.content[key] = "value"
388
389 # Remote YAML file on TCP host
390 with underlay.yaml_editor('/path/to/file',
391 host=config.tcp.tcp_host) as editor:
392 editor.content[key] = "value"
393 """
394 # Local YAML file
395 if node_name is None and host is None:
396 return utils.YamlEditor(file_path=file_path)
397
398 # Remote YAML file
399 ssh_data = self.__ssh_data(node_name=node_name, host=host,
400 address_pool=address_pool)
401 return utils.YamlEditor(
402 file_path=file_path,
403 host=ssh_data['host'],
404 port=ssh_data['port'] or 22,
405 username=ssh_data['login'],
406 password=ssh_data['password'],
407 private_keys=ssh_data['keys'])
Dennis Dmitriev010f4cd2016-11-01 20:43:51 +0200408
Dennis Dmitriev99b26fe2017-04-26 12:34:44 +0300409 def read_template(self, file_path):
410 """Read yaml as a jinja template"""
411 options = {
412 'config': self.__config,
413 }
414 template = utils.render_template(file_path, options=options)
415 return yaml.load(template)
Tatyana Leontovichab47e162017-10-06 16:53:30 +0300416
417 def get_logs(self, artifact_name,
418 node_role=ext.UNDERLAY_NODE_ROLES.salt_master):
Dennis Dmitriev21369672018-03-24 14:50:18 +0200419
420 # Prefix each '$' symbol with backslash '\' to disable
421 # early interpolation of environment variables on cfg01 node only
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200422 dump_commands = (
Dennis Dmitriev21369672018-03-24 14:50:18 +0200423 "mkdir /root/\$(hostname -f)/;"
424 "rsync -aruv /var/log/ /root/\$(hostname -f)/;"
425 "dpkg -l > /root/\$(hostname -f)/dump_dpkg_l.txt;"
426 "df -h > /root/\$(hostname -f)/dump_df.txt;"
427 "mount > /root/\$(hostname -f)/dump_mount.txt;"
428 "blkid -o list > /root/\$(hostname -f)/dump_blkid_o_list.txt;"
429 "iptables -t nat -S > /root/\$(hostname -f)/dump_iptables_nat.txt;"
430 "iptables -S > /root/\$(hostname -f)/dump_iptables.txt;"
431 "ps auxwwf > /root/\$(hostname -f)/dump_ps.txt;"
432 "docker images > /root/\$(hostname -f)/dump_docker_images.txt;"
433 "docker ps > /root/\$(hostname -f)/dump_docker_ps.txt;"
Mikhail Ivanov82da3392018-03-15 22:19:26 +0400434 "docker service ls > "
Dennis Dmitriev21369672018-03-24 14:50:18 +0200435 " /root/\$(hostname -f)/dump_docker_services_ls.txt;"
436 "for SERVICE in \$(docker service ls | awk '{ print $2 }'); "
437 " do docker service ps --no-trunc 2>&1 \$SERVICE >> "
438 " /root/\$(hostname -f)/dump_docker_service_ps.txt;"
Dennis Dmitriev01d5e372018-03-15 23:29:29 +0200439 " done;"
Dennis Dmitriev21369672018-03-24 14:50:18 +0200440 "for SERVICE in \$(docker service ls | awk '{ print $2 }'); "
441 " do docker service logs 2>&1 \$SERVICE > "
442 " /root/\$(hostname -f)/dump_docker_service_\${SERVICE}_logs;"
Dennis Dmitriev01d5e372018-03-15 23:29:29 +0200443 " done;"
Dennis Dmitriev21369672018-03-24 14:50:18 +0200444 "vgdisplay > /root/\$(hostname -f)/dump_vgdisplay.txt;"
445 "lvdisplay > /root/\$(hostname -f)/dump_lvdisplay.txt;"
446 "ip a > /root/\$(hostname -f)/dump_ip_a.txt;"
447 "ip r > /root/\$(hostname -f)/dump_ip_r.txt;"
448 "netstat -anp > /root/\$(hostname -f)/dump_netstat.txt;"
449 "brctl show > /root/\$(hostname -f)/dump_brctl_show.txt;"
450 "arp -an > /root/\$(hostname -f)/dump_arp.txt;"
451 "uname -a > /root/\$(hostname -f)/dump_uname_a.txt;"
452 "lsmod > /root/\$(hostname -f)/dump_lsmod.txt;"
453 "cat /proc/interrupts > /root/\$(hostname -f)/dump_interrupts.txt;"
454 "cat /etc/*-release > /root/\$(hostname -f)/dump_release.txt;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200455 # OpenStack specific, will fail on other nodes
Dennis Dmitriev21369672018-03-24 14:50:18 +0200456 # "rabbitmqctl report > "
457 # " /root/\$(hostname -f)/dump_rabbitmqctl.txt;"
Tatyana Leontovichab47e162017-10-06 16:53:30 +0300458
Dennis Dmitriev21369672018-03-24 14:50:18 +0200459 # "ceph health > /root/\$(hostname -f)/dump_ceph_health.txt;"
460 # "ceph -s > /root/\$(hostname -f)/dump_ceph_s.txt;"
461 # "ceph osd tree > /root/\$(hostname -f)/dump_ceph_osd_tree.txt;"
Dennis Dmitriev0bc485b2017-12-13 12:49:54 +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 a;"
465 # "done > /root/\$(hostname -f)/dump_ip_a_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} ip r;"
469 # "done > /root/\$(hostname -f)/dump_ip_r_ns.txt;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200470
Dennis Dmitriev21369672018-03-24 14:50:18 +0200471 # "for ns in \$(ip netns list);"
472 # " do echo Namespace: \${ns}; ip netns exec \${ns} netstat -anp;"
473 # "done > /root/\$(hostname -f)/dump_netstat_ns.txt;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200474
475 "/usr/bin/haproxy-status.sh > "
Dennis Dmitriev21369672018-03-24 14:50:18 +0200476 " /root/\$(hostname -f)/dump_haproxy.txt;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200477
478 # Archive the files
479 "cd /root/; tar --absolute-names --warning=no-file-changed "
Dennis Dmitriev21369672018-03-24 14:50:18 +0200480 " -czf \$(hostname -f).tar.gz ./\$(hostname -f)/;"
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200481 )
482
483 master_host = self.__config.salt.salt_master_host
484 with self.remote(host=master_host) as master:
485 # dump files
486 LOG.info("Archive artifacts on all nodes")
Dennis Dmitrievc83b3d42018-03-16 00:59:18 +0200487 master.check_call('salt "*" cmd.run "{0}"'.format(dump_commands),
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200488 raise_on_err=False)
489
490 # create target dir for archives
491 master.check_call("mkdir /root/dump/")
492
493 # get archived artifacts to the master node
494 for node in self.config_ssh:
495 LOG.info("Getting archived artifacts from the node {0}"
Dennis Dmitriev0bc485b2017-12-13 12:49:54 +0200496 .format(node['node_name']))
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200497 master.check_call("rsync -aruv {0}:/root/*.tar.gz "
498 "/root/dump/".format(node['node_name']),
Dennis Dmitriev8feb2522018-03-28 19:17:04 +0300499 raise_on_err=False,
500 timeout=120)
Dennis Dmitriev0bc485b2017-12-13 12:49:54 +0200501
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200502 destination_name = '/root/{0}_dump.tar.gz'.format(artifact_name)
503 # Archive the artifacts from all nodes
504 master.check_call(
505 'cd /root/dump/;'
506 'tar --absolute-names --warning=no-file-changed -czf '
507 ' {0} ./'.format(destination_name))
Tatyana Leontovichab47e162017-10-06 16:53:30 +0300508
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200509 # Download the artifact to the host
Dennis Dmitriev2d643bc2017-12-04 12:23:47 +0200510 LOG.info("Downloading the artifact {0}".format(destination_name))
Dennis Dmitrievf0b2afe2018-02-28 13:25:02 +0200511 master.download(destination=destination_name, target=os.getcwd())
Dennis Dmitriev2d643bc2017-12-04 12:23:47 +0200512
513 def delayed_call(
514 self, cmd,
515 node_name=None, host=None, address_pool=None,
516 verbose=True, timeout=5,
517 delay_min=None, delay_max=None):
518 """Delayed call of the specified command in background
519
520 :param delay_min: minimum delay in minutes before run
521 the command
522 :param delay_max: maximum delay in minutes before run
523 the command
524 The command will be started at random time in the range
525 from delay_min to delay_max in minutes from 'now'
526 using the command 'at'.
527
528 'now' is rounded to integer by 'at' command, i.e.:
529 now(28 min 59 sec) == 28 min 00 sec.
530
531 So, if delay_min=1 , the command may start in range from
532 1 sec to 60 sec.
533
534 If delay_min and delay_max are None, then the command will
535 be executed in the background right now.
536 """
537 time_min = delay_min or delay_max
538 time_max = delay_max or delay_min
539
540 delay = None
541 if time_min is not None and time_max is not None:
542 delay = random.randint(time_min, time_max)
543
544 delay_str = ''
545 if delay:
546 delay_str = " + {0} min".format(delay)
547
548 delay_cmd = "cat << EOF | at now {0}\n{1}\nEOF".format(delay_str, cmd)
549
550 self.check_call(delay_cmd, node_name=node_name, host=host,
551 address_pool=address_pool, verbose=verbose,
552 timeout=timeout)
553
554 def get_target_node_names(self, target='gtw01.'):
555 """Get all node names which names starts with <target>"""
556 return [node_name for node_name
557 in self.node_names()
558 if node_name.startswith(target)]