Sean Dague | 556add5 | 2013-07-19 14:28:44 -0400 | [diff] [blame] | 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may |
| 2 | # not use this file except in compliance with the License. You may obtain |
| 3 | # a copy of the License at |
| 4 | # |
| 5 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 6 | # |
| 7 | # Unless required by applicable law or agreed to in writing, software |
| 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| 10 | # License for the specific language governing permissions and limitations |
| 11 | # under the License. |
| 12 | |
Attila Fazekas | a23f500 | 2012-10-23 19:32:45 +0200 | [diff] [blame] | 13 | import re |
Matthew Treinish | a83a16e | 2012-12-07 13:44:02 -0500 | [diff] [blame] | 14 | import time |
| 15 | |
David Kranz | 968f1b3 | 2015-06-18 16:58:18 -0400 | [diff] [blame] | 16 | from oslo_log import log as logging |
Matthew Treinish | 96e9e88 | 2014-06-09 18:37:19 -0400 | [diff] [blame] | 17 | |
Sean Dague | 86bd842 | 2013-12-20 09:56:44 -0500 | [diff] [blame] | 18 | from tempest import config |
Ken'ichi Ohmichi | d25a1a3 | 2017-03-01 13:40:35 -0800 | [diff] [blame] | 19 | from tempest.lib.common.utils.linux import remote_client |
Andrea Frittoli (andreaf) | db9672e | 2016-02-23 14:07:24 -0500 | [diff] [blame] | 20 | import tempest.lib.exceptions |
Daryl Walleck | 98e66dd | 2012-06-21 04:58:39 -0500 | [diff] [blame] | 21 | |
Sean Dague | 86bd842 | 2013-12-20 09:56:44 -0500 | [diff] [blame] | 22 | CONF = config.CONF |
| 23 | |
David Kranz | 968f1b3 | 2015-06-18 16:58:18 -0400 | [diff] [blame] | 24 | LOG = logging.getLogger(__name__) |
| 25 | |
Daryl Walleck | 6b9b288 | 2012-04-08 21:43:39 -0500 | [diff] [blame] | 26 | |
Ken'ichi Ohmichi | d25a1a3 | 2017-03-01 13:40:35 -0800 | [diff] [blame] | 27 | class RemoteClient(remote_client.RemoteClient): |
Andrea Frittoli (andreaf) | 5f5e4fc | 2016-04-29 16:00:17 -0500 | [diff] [blame] | 28 | |
Ken'ichi Ohmichi | d25a1a3 | 2017-03-01 13:40:35 -0800 | [diff] [blame] | 29 | # TODO(oomichi): Make this class deprecated after migrating |
| 30 | # necessary methods to tempest.lib and cleaning |
| 31 | # unnecessary methods up from this class. |
Andrea Frittoli (andreaf) | 5f5e4fc | 2016-04-29 16:00:17 -0500 | [diff] [blame] | 32 | def __init__(self, ip_address, username, password=None, pkey=None, |
| 33 | server=None, servers_client=None): |
| 34 | """Executes commands in a VM over ssh |
| 35 | |
| 36 | :param ip_address: IP address to ssh to |
| 37 | :param username: ssh username |
| 38 | :param password: ssh password (optional) |
| 39 | :param pkey: ssh public key (optional) |
| 40 | :param server: server dict, used for debugging purposes |
| 41 | :param servers_client: servers client, used for debugging purposes |
| 42 | """ |
Ken'ichi Ohmichi | d25a1a3 | 2017-03-01 13:40:35 -0800 | [diff] [blame] | 43 | super(RemoteClient, self).__init__( |
| 44 | ip_address, username, password=password, pkey=pkey, |
| 45 | server=server, servers_client=servers_client, |
| 46 | ssh_timeout=CONF.validation.ssh_timeout, |
| 47 | connect_timeout=CONF.validation.connect_timeout, |
| 48 | console_output_enabled=CONF.compute_feature_enabled.console_output, |
| 49 | ssh_shell_prologue=CONF.validation.ssh_shell_prologue, |
| 50 | ping_count=CONF.validation.ping_count, |
| 51 | ping_size=CONF.validation.ping_size) |
Daryl Walleck | 6b9b288 | 2012-04-08 21:43:39 -0500 | [diff] [blame] | 52 | |
Emily Hugenbruch | 877811f | 2017-03-24 14:32:03 -0400 | [diff] [blame] | 53 | # Note that this method will not work on SLES11 guests, as they do |
| 54 | # not support the TYPE column on lsblk |
Evgeny Antyshev | 4894a91 | 2016-11-21 12:17:18 +0000 | [diff] [blame] | 55 | def get_disks(self): |
| 56 | # Select root disk devices as shown by lsblk |
| 57 | command = 'lsblk -lb --nodeps' |
Elena Ezhova | 91db24e | 2014-02-28 20:47:10 +0400 | [diff] [blame] | 58 | output = self.exec_command(command) |
Evgeny Antyshev | 4894a91 | 2016-11-21 12:17:18 +0000 | [diff] [blame] | 59 | selected = [] |
| 60 | pos = None |
| 61 | for l in output.splitlines(): |
| 62 | if pos is None and l.find("TYPE") > 0: |
| 63 | pos = l.find("TYPE") |
| 64 | # Show header line too |
| 65 | selected.append(l) |
| 66 | # lsblk lists disk type in a column right-aligned with TYPE |
Masayuki Igawa | 0a5d606 | 2017-03-27 12:22:33 +0900 | [diff] [blame] | 67 | elif pos is not None and pos > 0 and l[pos:pos + 4] == "disk": |
Evgeny Antyshev | 4894a91 | 2016-11-21 12:17:18 +0000 | [diff] [blame] | 68 | selected.append(l) |
| 69 | |
Masayuki Igawa | 0a5d606 | 2017-03-27 12:22:33 +0900 | [diff] [blame] | 70 | if selected: |
| 71 | return "\n".join(selected) |
| 72 | else: |
shangxiaobj | 284d311 | 2017-08-13 23:37:34 -0700 | [diff] [blame] | 73 | msg = "'TYPE' column is required but the output doesn't have it: " |
Masayuki Igawa | 0a5d606 | 2017-03-27 12:22:33 +0900 | [diff] [blame] | 74 | raise tempest.lib.exceptions.TempestException(msg + output) |
Daryl Walleck | 98e66dd | 2012-06-21 04:58:39 -0500 | [diff] [blame] | 75 | |
| 76 | def get_boot_time(self): |
Vincent Untz | 3c0b5b9 | 2014-01-18 10:56:00 +0100 | [diff] [blame] | 77 | cmd = 'cut -f1 -d. /proc/uptime' |
Elena Ezhova | 91db24e | 2014-02-28 20:47:10 +0400 | [diff] [blame] | 78 | boot_secs = self.exec_command(cmd) |
Vincent Untz | 3c0b5b9 | 2014-01-18 10:56:00 +0100 | [diff] [blame] | 79 | boot_time = time.time() - int(boot_secs) |
| 80 | return time.localtime(boot_time) |
Attila Fazekas | a23f500 | 2012-10-23 19:32:45 +0200 | [diff] [blame] | 81 | |
| 82 | def write_to_console(self, message): |
| 83 | message = re.sub("([$\\`])", "\\\\\\\\\\1", message) |
| 84 | # usually to /dev/ttyS0 |
| 85 | cmd = 'sudo sh -c "echo \\"%s\\" >/dev/console"' % message |
Elena Ezhova | 91db24e | 2014-02-28 20:47:10 +0400 | [diff] [blame] | 86 | return self.exec_command(cmd) |
Yair Fried | 5f670ab | 2013-12-09 09:26:51 +0200 | [diff] [blame] | 87 | |
Yair Fried | bc46f59 | 2015-11-18 16:29:34 +0200 | [diff] [blame] | 88 | def get_mac_address(self, nic=""): |
| 89 | show_nic = "show {nic} ".format(nic=nic) if nic else "" |
| 90 | cmd = "ip addr %s| awk '/ether/ {print $2}'" % show_nic |
| 91 | return self.exec_command(cmd).strip().lower() |
Yair Fried | 3097dc1 | 2014-01-26 08:46:43 +0200 | [diff] [blame] | 92 | |
Evgeny Antyshev | 9b77ae5 | 2016-02-16 09:48:57 +0000 | [diff] [blame] | 93 | def get_nic_name_by_mac(self, address): |
| 94 | cmd = "ip -o link | awk '/%s/ {print $2}'" % address |
| 95 | nic = self.exec_command(cmd) |
James Page | a936627 | 2017-06-09 12:05:09 +0100 | [diff] [blame] | 96 | return nic.strip().strip(":").split('@')[0].lower() |
Evgeny Antyshev | 9b77ae5 | 2016-02-16 09:48:57 +0000 | [diff] [blame] | 97 | |
| 98 | def get_nic_name_by_ip(self, address): |
Evgeny Antyshev | f58ab6d | 2015-04-15 08:23:05 +0000 | [diff] [blame] | 99 | cmd = "ip -o addr | awk '/%s/ {print $2}'" % address |
Daniel Mellado | 9e3e106 | 2015-08-06 18:07:05 +0200 | [diff] [blame] | 100 | nic = self.exec_command(cmd) |
James Page | a936627 | 2017-06-09 12:05:09 +0100 | [diff] [blame] | 101 | return nic.strip().strip(":").split('@')[0].lower() |
Yair Fried | 413bf2d | 2014-11-19 17:07:11 +0200 | [diff] [blame] | 102 | |
Yair Fried | 413bf2d | 2014-11-19 17:07:11 +0200 | [diff] [blame] | 103 | def get_dns_servers(self): |
| 104 | cmd = 'cat /etc/resolv.conf' |
| 105 | resolve_file = self.exec_command(cmd).strip().split('\n') |
| 106 | entries = (l.split() for l in resolve_file) |
| 107 | dns_servers = [l[1] for l in entries |
| 108 | if len(l) and l[0] == 'nameserver'] |
| 109 | return dns_servers |
| 110 | |
Yair Fried | 413bf2d | 2014-11-19 17:07:11 +0200 | [diff] [blame] | 111 | def _renew_lease_udhcpc(self, fixed_ip=None): |
| 112 | """Renews DHCP lease via udhcpc client. """ |
| 113 | file_path = '/var/run/udhcpc.' |
Evgeny Antyshev | 9b77ae5 | 2016-02-16 09:48:57 +0000 | [diff] [blame] | 114 | nic_name = self.get_nic_name_by_ip(fixed_ip) |
Yair Fried | 413bf2d | 2014-11-19 17:07:11 +0200 | [diff] [blame] | 115 | pid = self.exec_command('cat {path}{nic}.pid'. |
| 116 | format(path=file_path, nic=nic_name)) |
| 117 | pid = pid.strip() |
Ken'ichi Ohmichi | 4e5a69e | 2017-03-01 18:15:29 -0800 | [diff] [blame] | 118 | cmd = 'sudo /bin/kill -{sig} {pid}'.format(pid=pid, sig='USR1') |
| 119 | self.exec_command(cmd) |
Yair Fried | 413bf2d | 2014-11-19 17:07:11 +0200 | [diff] [blame] | 120 | |
| 121 | def _renew_lease_dhclient(self, fixed_ip=None): |
| 122 | """Renews DHCP lease via dhclient client. """ |
Itzik Brown | ffb1402 | 2015-03-23 17:03:55 +0200 | [diff] [blame] | 123 | cmd = "sudo /sbin/dhclient -r && sudo /sbin/dhclient" |
Yair Fried | 413bf2d | 2014-11-19 17:07:11 +0200 | [diff] [blame] | 124 | self.exec_command(cmd) |
| 125 | |
Ken'ichi Ohmichi | 4e33785 | 2017-03-01 12:04:23 -0800 | [diff] [blame] | 126 | def renew_lease(self, fixed_ip=None, dhcp_client='udhcpc'): |
Yair Fried | 413bf2d | 2014-11-19 17:07:11 +0200 | [diff] [blame] | 127 | """Wrapper method for renewing DHCP lease via given client |
| 128 | |
| 129 | Supporting: |
| 130 | * udhcpc |
| 131 | * dhclient |
| 132 | """ |
| 133 | # TODO(yfried): add support for dhcpcd |
Takashi NATSUME | 6d5a2b4 | 2015-09-08 11:27:49 +0900 | [diff] [blame] | 134 | supported_clients = ['udhcpc', 'dhclient'] |
Takashi NATSUME | 6d5a2b4 | 2015-09-08 11:27:49 +0900 | [diff] [blame] | 135 | if dhcp_client not in supported_clients: |
Matthew Treinish | 4217a70 | 2016-10-07 17:27:11 -0400 | [diff] [blame] | 136 | raise tempest.lib.exceptions.InvalidConfiguration( |
| 137 | '%s DHCP client unsupported' % dhcp_client) |
Yair Fried | 413bf2d | 2014-11-19 17:07:11 +0200 | [diff] [blame] | 138 | if dhcp_client == 'udhcpc' and not fixed_ip: |
| 139 | raise ValueError("need to set 'fixed_ip' for udhcpc client") |
Joe Gordon | 28788b4 | 2015-02-25 12:42:37 -0800 | [diff] [blame] | 140 | return getattr(self, '_renew_lease_' + dhcp_client)(fixed_ip=fixed_ip) |
Alexander Gubanov | abd154c | 2015-09-23 23:24:06 +0300 | [diff] [blame] | 141 | |
| 142 | def mount(self, dev_name, mount_path='/mnt'): |
| 143 | cmd_mount = 'sudo mount /dev/%s %s' % (dev_name, mount_path) |
| 144 | self.exec_command(cmd_mount) |
| 145 | |
| 146 | def umount(self, mount_path='/mnt'): |
| 147 | self.exec_command('sudo umount %s' % mount_path) |
| 148 | |
| 149 | def make_fs(self, dev_name, fs='ext4'): |
| 150 | cmd_mkfs = 'sudo /usr/sbin/mke2fs -t %s /dev/%s' % (fs, dev_name) |
Sean Dague | 57c6655 | 2016-02-08 08:51:13 -0500 | [diff] [blame] | 151 | try: |
| 152 | self.exec_command(cmd_mkfs) |
Andrea Frittoli (andreaf) | db9672e | 2016-02-23 14:07:24 -0500 | [diff] [blame] | 153 | except tempest.lib.exceptions.SSHExecCommandFailed: |
Sean Dague | 57c6655 | 2016-02-08 08:51:13 -0500 | [diff] [blame] | 154 | LOG.error("Couldn't mke2fs") |
| 155 | cmd_why = 'sudo ls -lR /dev' |
Jordan Pittier | 525ec71 | 2016-12-07 17:51:26 +0100 | [diff] [blame] | 156 | LOG.info("Contents of /dev: %s", self.exec_command(cmd_why)) |
Sean Dague | 57c6655 | 2016-02-08 08:51:13 -0500 | [diff] [blame] | 157 | raise |