-
Notifications
You must be signed in to change notification settings - Fork 488
/
remote_client.py
246 lines (209 loc) · 9.74 KB
/
remote_client.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import re
import time
from oslo_log import log as logging
from tempest import config
from tempest.lib.common.utils.linux import remote_client
import tempest.lib.exceptions
CONF = config.CONF
LOG = logging.getLogger(__name__)
class RemoteClient(remote_client.RemoteClient):
# TODO(oomichi): Make this class deprecated after migrating
# necessary methods to tempest.lib and cleaning
# unnecessary methods up from this class.
def __init__(self, ip_address, username, password=None, pkey=None,
server=None, servers_client=None):
"""Executes commands in a VM over ssh
:param ip_address: IP address to ssh to
:param username: ssh username
:param password: ssh password (optional)
:param pkey: ssh public key (optional)
:param server: server dict, used for debugging purposes
:param servers_client: servers client, used for debugging purposes
"""
super(RemoteClient, self).__init__(
ip_address, username, password=password, pkey=pkey,
server=server, servers_client=servers_client,
ssh_timeout=CONF.validation.ssh_timeout,
connect_timeout=CONF.validation.connect_timeout,
console_output_enabled=CONF.compute_feature_enabled.console_output,
ssh_shell_prologue=CONF.validation.ssh_shell_prologue,
ping_count=CONF.validation.ping_count,
ping_size=CONF.validation.ping_size,
ssh_key_type=CONF.validation.ssh_key_type)
# Note that this method will not work on SLES11 guests, as they do
# not support the TYPE column on lsblk
def get_disks(self):
# Select root disk devices as shown by lsblk
command = 'lsblk -lb --nodeps'
output = self.exec_command(command)
selected = []
pos = None
for l in output.splitlines():
if pos is None and l.find("TYPE") > 0:
pos = l.find("TYPE")
# Show header line too
selected.append(l)
# lsblk lists disk type in a column right-aligned with TYPE
elif pos is not None and pos > 0 and l[pos:pos + 4] == "disk":
selected.append(l)
if selected:
return "\n".join(selected)
else:
msg = "'TYPE' column is required but the output doesn't have it: "
raise tempest.lib.exceptions.TempestException(msg + output)
def list_disks(self):
disks_list = self.get_disks()
disks_list = [line[0] for line in
[device_name.split()
for device_name in disks_list.splitlines()][1:]]
return disks_list
def get_boot_time(self):
cmd = 'cut -f1 -d. /proc/uptime'
boot_secs = self.exec_command(cmd)
boot_time = time.time() - int(boot_secs)
return time.localtime(boot_time)
def write_to_console(self, message):
message = re.sub("([$\\`])", "\\\\\\\\\\1", message)
# usually to /dev/ttyS0
cmd = 'sudo sh -c "echo \\"%s\\" >/dev/console"' % message
return self.exec_command(cmd)
def get_mac_address(self, nic=""):
show_nic = "show {nic} ".format(nic=nic) if nic else ""
cmd = "ip addr %s| awk '/ether/ {print $2}'" % show_nic
return self.exec_command(cmd).strip().lower()
def get_nic_name_by_mac(self, address):
cmd = "ip -o link | awk '/%s/ {print $2}'" % address
nic = self.exec_command(cmd)
return nic.strip().strip(":").split('@')[0].lower()
def get_nic_name_by_ip(self, address):
cmd = "ip -o addr | awk '/%s/ {print $2}'" % address
nic = self.exec_command(cmd)
LOG.debug('(get_nic_name_by_ip) Command result: %s', nic)
return nic.strip().strip(":").split('@')[0].lower()
def get_nic_ip_addresses(self, nic_name, ip_version=None):
cmd = "ip "
if ip_version:
cmd += "-%s " % ip_version
cmd += "-o addr | awk '/%s/ {print $4}'" % nic_name
ip_addresses = self.exec_command(cmd)
LOG.debug('(get_nic_ip_address): Command result: %s', ip_addresses)
return ip_addresses.strip().split()
def _get_dns_servers(self):
cmd = 'cat /etc/resolv.conf'
resolve_file = self.exec_command(cmd).strip().split('\n')
entries = (l.split() for l in resolve_file)
dns_servers = [l[1] for l in entries
if len(l) and l[0] == 'nameserver']
return dns_servers
def get_dns_servers(self, timeout=5):
start_time = int(time.time())
dns_servers = []
while True:
dns_servers = self._get_dns_servers()
if dns_servers:
break
LOG.debug("DNS Servers list empty.")
if int(time.time()) - start_time >= timeout:
LOG.debug("DNS Servers list empty after %s.", timeout)
break
return dns_servers
def _renew_lease_udhcpc(self, fixed_ip=None):
"""Renews DHCP lease via udhcpc client. """
file_path = '/var/run/udhcpc.'
nic_name = self.get_nic_name_by_ip(fixed_ip)
pid = self.exec_command('cat {path}{nic}.pid'.
format(path=file_path, nic=nic_name))
pid = pid.strip()
cmd = 'sudo /bin/kill -{sig} {pid}'.format(pid=pid, sig='USR1')
self.exec_command(cmd)
def _renew_lease_dhclient(self, fixed_ip=None):
"""Renews DHCP lease via dhclient client. """
cmd = "sudo /sbin/dhclient -r && sudo /sbin/dhclient"
self.exec_command(cmd)
def _renew_lease_dhcpcd(self, fixed_ip=None):
"""Renews DHCP lease via dhcpcd client. """
cmd = "sudo /sbin/dhcpcd --rebind"
self.exec_command(cmd)
def renew_lease(self, fixed_ip=None, dhcp_client='udhcpc'):
"""Wrapper method for renewing DHCP lease via given client
Supporting:
* udhcpc
* dhclient
* dhcpcd
"""
supported_clients = ['udhcpc', 'dhclient', 'dhcpcd']
if dhcp_client not in supported_clients:
raise tempest.lib.exceptions.InvalidConfiguration(
'%s DHCP client unsupported' % dhcp_client)
if dhcp_client == 'udhcpc' and not fixed_ip:
raise ValueError("need to set 'fixed_ip' for udhcpc client")
return getattr(self, '_renew_lease_' + dhcp_client)(fixed_ip=fixed_ip)
def mount(self, dev_name, mount_path='/mnt'):
cmd_mount = 'sudo mount /dev/%s %s' % (dev_name, mount_path)
self.exec_command(cmd_mount)
def umount(self, mount_path='/mnt'):
self.exec_command('sudo umount %s' % mount_path)
def mkdir(self, dir_path):
self.exec_command('sudo mkdir -p %s' % dir_path)
def make_fs(self, dev_name, fs='ext4'):
cmd_mkfs = 'sudo mkfs -t %s /dev/%s' % (fs, dev_name)
try:
self.exec_command(cmd_mkfs)
except tempest.lib.exceptions.SSHExecCommandFailed:
LOG.error("Couldn't mke2fs")
cmd_why = 'sudo ls -lR /dev'
LOG.info("Contents of /dev: %s", self.exec_command(cmd_why))
raise
def nc_listen_host(self, port=80, protocol='tcp'):
"""Creates persistent nc server listening on the given TCP / UDP port
:port: the port to start listening on.
:protocol: the protocol used by the server. TCP by default.
"""
udp = '-u' if protocol.lower() == 'udp' else ''
cmd = "sudo nc %(udp)s -p %(port)s -lk -e echo foolish &" % {
'udp': udp, 'port': port}
return self.exec_command(cmd)
def nc_host(self, host, port=80, protocol='tcp', expected_response=None):
"""Check connectivity to TCP / UDP port at host via nc
:host: an IP against which the connectivity will be tested.
:port: the port to check connectivity against.
:protocol: the protocol used by nc to send packets. TCP by default.
:expected_response: string representing the expected response
from server.
:raises SSHExecCommandFailed: if an expected response is given and it
does not match the actual server response.
"""
udp = '-u' if protocol.lower() == 'udp' else ''
cmd = 'echo "bar" | nc -w 1 %(udp)s %(host)s %(port)s' % {
'udp': udp, 'host': host, 'port': port}
response = self.exec_command(cmd)
# sending an UDP packet will always succeed. we need to check
# the response.
if (expected_response is not None and
expected_response != response.strip()):
raise tempest.lib.exceptions.SSHExecCommandFailed(
command=cmd, exit_status=0, stdout=response, stderr='')
return response
def icmp_check(self, host, nic=None):
"""Wrapper for icmp connectivity checks"""
return self.ping_host(host, nic=nic)
def udp_check(self, host, **kwargs):
"""Wrapper for udp connectivity checks."""
kwargs.pop('nic', None)
return self.nc_host(host, protocol='udp', expected_response='foolish',
**kwargs)
def tcp_check(self, host, **kwargs):
"""Wrapper for tcp connectivity checks."""
kwargs.pop('nic', None)
return self.nc_host(host, **kwargs)