blob: c4a18e822fe8ce1b9bab72802d7fc9cc789f3316 [file] [log] [blame]
koder aka kdanilove06762a2015-03-22 23:32:09 +02001import re
koder aka kdanilov3a6633e2015-03-26 18:20:00 +02002import time
koder aka kdanilov652cd802015-04-13 12:21:07 +03003import socket
koder aka kdanilove06762a2015-03-22 23:32:09 +02004import logging
5import os.path
koder aka kdanilov3a6633e2015-03-26 18:20:00 +02006import getpass
koder aka kdanilov652cd802015-04-13 12:21:07 +03007import threading
koder aka kdanilove06762a2015-03-22 23:32:09 +02008
koder aka kdanilov652cd802015-04-13 12:21:07 +03009
koder aka kdanilov3a6633e2015-03-26 18:20:00 +020010import paramiko
koder aka kdanilove06762a2015-03-22 23:32:09 +020011
koder aka kdanilove06762a2015-03-22 23:32:09 +020012
13logger = logging.getLogger("io-perf-tool")
koder aka kdanilove06762a2015-03-22 23:32:09 +020014
15
koder aka kdanilov3a6633e2015-03-26 18:20:00 +020016def ssh_connect(creds, retry_count=60, timeout=1):
17 ssh = paramiko.SSHClient()
18 ssh.load_host_keys('/dev/null')
19 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
20 ssh.known_hosts = None
21 for i in range(retry_count):
22 try:
23 if creds.user is None:
24 user = getpass.getuser()
25 else:
26 user = creds.user
27
28 if creds.passwd is not None:
29 ssh.connect(creds.host,
30 username=user,
31 password=creds.passwd,
32 port=creds.port,
33 allow_agent=False,
34 look_for_keys=False)
35 return ssh
36
37 if creds.key_file is not None:
38 ssh.connect(creds.host,
39 username=user,
40 key_filename=creds.key_file,
41 look_for_keys=False,
42 port=creds.port)
43 return ssh
44
45 key_file = os.path.expanduser('~/.ssh/id_rsa')
46 ssh.connect(creds.host,
47 username=user,
48 key_filename=key_file,
49 look_for_keys=False,
50 port=creds.port)
51 return ssh
52 # raise ValueError("Wrong credentials {0}".format(creds.__dict__))
53 except paramiko.PasswordRequiredException:
54 raise
55 except socket.error:
56 if i == retry_count - 1:
57 raise
58 time.sleep(timeout)
59
60
koder aka kdanilove06762a2015-03-22 23:32:09 +020061def normalize_dirpath(dirpath):
62 while dirpath.endswith("/"):
63 dirpath = dirpath[:-1]
64 return dirpath
65
66
koder aka kdanilov2c473092015-03-29 17:12:13 +030067ALL_RWX_MODE = ((1 << 9) - 1)
68
69
70def ssh_mkdir(sftp, remotepath, mode=ALL_RWX_MODE, intermediate=False):
koder aka kdanilove06762a2015-03-22 23:32:09 +020071 remotepath = normalize_dirpath(remotepath)
72 if intermediate:
73 try:
74 sftp.mkdir(remotepath, mode=mode)
75 except IOError:
76 ssh_mkdir(sftp, remotepath.rsplit("/", 1)[0], mode=mode,
77 intermediate=True)
78 return sftp.mkdir(remotepath, mode=mode)
79 else:
80 sftp.mkdir(remotepath, mode=mode)
81
82
83def ssh_copy_file(sftp, localfile, remfile, preserve_perm=True):
84 sftp.put(localfile, remfile)
85 if preserve_perm:
koder aka kdanilov2c473092015-03-29 17:12:13 +030086 sftp.chmod(remfile, os.stat(localfile).st_mode & ALL_RWX_MODE)
koder aka kdanilove06762a2015-03-22 23:32:09 +020087
88
89def put_dir_recursively(sftp, localpath, remotepath, preserve_perm=True):
90 "upload local directory to remote recursively"
91
92 # hack for localhost connection
93 if hasattr(sftp, "copytree"):
94 sftp.copytree(localpath, remotepath)
95 return
96
97 assert remotepath.startswith("/"), "%s must be absolute path" % remotepath
98
99 # normalize
100 localpath = normalize_dirpath(localpath)
101 remotepath = normalize_dirpath(remotepath)
102
103 try:
104 sftp.chdir(remotepath)
105 localsuffix = localpath.rsplit("/", 1)[1]
106 remotesuffix = remotepath.rsplit("/", 1)[1]
107 if localsuffix != remotesuffix:
108 remotepath = os.path.join(remotepath, localsuffix)
109 except IOError:
110 pass
111
112 for root, dirs, fls in os.walk(localpath):
113 prefix = os.path.commonprefix([localpath, root])
114 suffix = root.split(prefix, 1)[1]
115 if suffix.startswith("/"):
116 suffix = suffix[1:]
117
118 remroot = os.path.join(remotepath, suffix)
119
120 try:
121 sftp.chdir(remroot)
122 except IOError:
123 if preserve_perm:
koder aka kdanilov2c473092015-03-29 17:12:13 +0300124 mode = os.stat(root).st_mode & ALL_RWX_MODE
koder aka kdanilove06762a2015-03-22 23:32:09 +0200125 else:
koder aka kdanilov2c473092015-03-29 17:12:13 +0300126 mode = ALL_RWX_MODE
koder aka kdanilove06762a2015-03-22 23:32:09 +0200127 ssh_mkdir(sftp, remroot, mode=mode, intermediate=True)
128 sftp.chdir(remroot)
129
130 for f in fls:
131 remfile = os.path.join(remroot, f)
132 localfile = os.path.join(root, f)
133 ssh_copy_file(sftp, localfile, remfile, preserve_perm)
134
135
136def copy_paths(conn, paths):
137 sftp = conn.open_sftp()
138 try:
139 for src, dst in paths.items():
140 try:
141 if os.path.isfile(src):
142 ssh_copy_file(sftp, src, dst)
143 elif os.path.isdir(src):
144 put_dir_recursively(sftp, src, dst)
145 else:
146 templ = "Can't copy {0!r} - " + \
147 "it neither a file not a directory"
148 msg = templ.format(src)
149 raise OSError(msg)
150 except Exception as exc:
151 tmpl = "Scp {0!r} => {1!r} failed - {2!r}"
152 msg = tmpl.format(src, dst, exc)
153 raise OSError(msg)
154 finally:
155 sftp.close()
156
157
158class ConnCreds(object):
koder aka kdanilov2c473092015-03-29 17:12:13 +0300159 conn_uri_attrs = ("user", "passwd", "host", "port", "path")
160
koder aka kdanilove06762a2015-03-22 23:32:09 +0200161 def __init__(self):
koder aka kdanilov2c473092015-03-29 17:12:13 +0300162 for name in self.conn_uri_attrs:
koder aka kdanilove06762a2015-03-22 23:32:09 +0200163 setattr(self, name, None)
164
165
166uri_reg_exprs = []
167
168
169class URIsNamespace(object):
170 class ReParts(object):
171 user_rr = "[^:]*?"
172 host_rr = "[^:]*?"
173 port_rr = "\\d+"
174 key_file_rr = "[^:@]*"
175 passwd_rr = ".*?"
176
177 re_dct = ReParts.__dict__
178
179 for attr_name, val in re_dct.items():
180 if attr_name.endswith('_rr'):
181 new_rr = "(?P<{0}>{1})".format(attr_name[:-3], val)
182 setattr(ReParts, attr_name, new_rr)
183
184 re_dct = ReParts.__dict__
185
186 templs = [
187 "^{host_rr}$",
188 "^{user_rr}@{host_rr}::{key_file_rr}$",
189 "^{user_rr}@{host_rr}:{port_rr}:{key_file_rr}$",
190 "^{user_rr}:{passwd_rr}@@{host_rr}$",
191 "^{user_rr}:{passwd_rr}@@{host_rr}:{port_rr}$",
192 ]
193
194 for templ in templs:
195 uri_reg_exprs.append(templ.format(**re_dct))
196
197
198def parse_ssh_uri(uri):
199 # user:passwd@@ip_host:port
200 # user:passwd@@ip_host
201 # user@ip_host:port
202 # user@ip_host
203 # ip_host:port
204 # ip_host
205 # user@ip_host:port:path_to_key_file
206 # user@ip_host::path_to_key_file
207 # ip_host:port:path_to_key_file
208 # ip_host::path_to_key_file
209
210 res = ConnCreds()
211 res.port = "22"
212 res.key_file = None
213 res.passwd = None
214
215 for rr in uri_reg_exprs:
216 rrm = re.match(rr, uri)
217 if rrm is not None:
218 res.__dict__.update(rrm.groupdict())
219 return res
koder aka kdanilov652cd802015-04-13 12:21:07 +0300220
koder aka kdanilove06762a2015-03-22 23:32:09 +0200221 raise ValueError("Can't parse {0!r} as ssh uri value".format(uri))
222
223
224def connect(uri):
225 creds = parse_ssh_uri(uri)
226 creds.port = int(creds.port)
227 return ssh_connect(creds)
228
229
koder aka kdanilov652cd802015-04-13 12:21:07 +0300230all_sessions_lock = threading.Lock()
231all_sessions = []
koder aka kdanilove06762a2015-03-22 23:32:09 +0200232
koder aka kdanilove06762a2015-03-22 23:32:09 +0200233
koder aka kdanilov652cd802015-04-13 12:21:07 +0300234def run_over_ssh(conn, cmd, stdin_data=None, timeout=60, nolog=False):
235 "should be replaces by normal implementation, with select"
236 transport = conn.get_transport()
237 session = transport.open_session()
koder aka kdanilove06762a2015-03-22 23:32:09 +0200238
koder aka kdanilov652cd802015-04-13 12:21:07 +0300239 with all_sessions_lock:
240 all_sessions.append(session)
koder aka kdanilove06762a2015-03-22 23:32:09 +0200241
koder aka kdanilov652cd802015-04-13 12:21:07 +0300242 try:
243 session.set_combine_stderr(True)
koder aka kdanilove06762a2015-03-22 23:32:09 +0200244
koder aka kdanilov652cd802015-04-13 12:21:07 +0300245 stime = time.time()
koder aka kdanilove06762a2015-03-22 23:32:09 +0200246
koder aka kdanilov652cd802015-04-13 12:21:07 +0300247 if not nolog:
248 logger.debug("SSH: Exec {1!r}".format(conn, cmd))
koder aka kdanilove06762a2015-03-22 23:32:09 +0200249
koder aka kdanilov652cd802015-04-13 12:21:07 +0300250 session.exec_command(cmd)
koder aka kdanilove06762a2015-03-22 23:32:09 +0200251
koder aka kdanilov652cd802015-04-13 12:21:07 +0300252 if stdin_data is not None:
253 session.sendall(stdin_data)
koder aka kdanilove06762a2015-03-22 23:32:09 +0200254
koder aka kdanilov652cd802015-04-13 12:21:07 +0300255 session.settimeout(1)
256 session.shutdown_write()
257 output = ""
258
259 while True:
260 try:
261 ndata = session.recv(1024)
262 output += ndata
263 if "" == ndata:
264 break
265 except socket.timeout:
266 pass
267
268 if time.time() - stime > timeout:
269 raise OSError(output + "\nExecution timeout")
270
271 code = session.recv_exit_status()
272 finally:
273 session.close()
274
275 if code != 0:
276 templ = "Cmd {0!r} failed with code {1}. Output: {2}"
277 raise OSError(templ.format(cmd, code, output))
278
279 return output
280
281
282def close_all_sessions():
283 with all_sessions_lock:
284 for session in all_sessions:
285 try:
286 session.sendall('\x03')
287 session.close()
288 except:
289 pass