blob: aea3111853b8f447e7894f417c8a69ac9d6331a8 [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
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300136def delete_file(conn, path):
137 sftp = conn.open_sftp()
138 sftp.remove(path)
139 sftp.close()
140
141
koder aka kdanilove06762a2015-03-22 23:32:09 +0200142def copy_paths(conn, paths):
143 sftp = conn.open_sftp()
144 try:
145 for src, dst in paths.items():
146 try:
147 if os.path.isfile(src):
148 ssh_copy_file(sftp, src, dst)
149 elif os.path.isdir(src):
150 put_dir_recursively(sftp, src, dst)
151 else:
152 templ = "Can't copy {0!r} - " + \
153 "it neither a file not a directory"
154 msg = templ.format(src)
155 raise OSError(msg)
156 except Exception as exc:
157 tmpl = "Scp {0!r} => {1!r} failed - {2!r}"
158 msg = tmpl.format(src, dst, exc)
159 raise OSError(msg)
160 finally:
161 sftp.close()
162
163
164class ConnCreds(object):
koder aka kdanilov2c473092015-03-29 17:12:13 +0300165 conn_uri_attrs = ("user", "passwd", "host", "port", "path")
166
koder aka kdanilove06762a2015-03-22 23:32:09 +0200167 def __init__(self):
koder aka kdanilov2c473092015-03-29 17:12:13 +0300168 for name in self.conn_uri_attrs:
koder aka kdanilove06762a2015-03-22 23:32:09 +0200169 setattr(self, name, None)
170
171
172uri_reg_exprs = []
173
174
175class URIsNamespace(object):
176 class ReParts(object):
177 user_rr = "[^:]*?"
178 host_rr = "[^:]*?"
179 port_rr = "\\d+"
180 key_file_rr = "[^:@]*"
181 passwd_rr = ".*?"
182
183 re_dct = ReParts.__dict__
184
185 for attr_name, val in re_dct.items():
186 if attr_name.endswith('_rr'):
187 new_rr = "(?P<{0}>{1})".format(attr_name[:-3], val)
188 setattr(ReParts, attr_name, new_rr)
189
190 re_dct = ReParts.__dict__
191
192 templs = [
193 "^{host_rr}$",
194 "^{user_rr}@{host_rr}::{key_file_rr}$",
195 "^{user_rr}@{host_rr}:{port_rr}:{key_file_rr}$",
196 "^{user_rr}:{passwd_rr}@@{host_rr}$",
197 "^{user_rr}:{passwd_rr}@@{host_rr}:{port_rr}$",
198 ]
199
200 for templ in templs:
201 uri_reg_exprs.append(templ.format(**re_dct))
202
203
204def parse_ssh_uri(uri):
205 # user:passwd@@ip_host:port
206 # user:passwd@@ip_host
207 # user@ip_host:port
208 # user@ip_host
209 # ip_host:port
210 # ip_host
211 # user@ip_host:port:path_to_key_file
212 # user@ip_host::path_to_key_file
213 # ip_host:port:path_to_key_file
214 # ip_host::path_to_key_file
215
216 res = ConnCreds()
217 res.port = "22"
218 res.key_file = None
219 res.passwd = None
220
221 for rr in uri_reg_exprs:
222 rrm = re.match(rr, uri)
223 if rrm is not None:
224 res.__dict__.update(rrm.groupdict())
225 return res
koder aka kdanilov652cd802015-04-13 12:21:07 +0300226
koder aka kdanilove06762a2015-03-22 23:32:09 +0200227 raise ValueError("Can't parse {0!r} as ssh uri value".format(uri))
228
229
230def connect(uri):
231 creds = parse_ssh_uri(uri)
232 creds.port = int(creds.port)
233 return ssh_connect(creds)
234
235
koder aka kdanilov652cd802015-04-13 12:21:07 +0300236all_sessions_lock = threading.Lock()
237all_sessions = []
koder aka kdanilove06762a2015-03-22 23:32:09 +0200238
koder aka kdanilove06762a2015-03-22 23:32:09 +0200239
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300240def run_over_ssh(conn, cmd, stdin_data=None, timeout=60, nolog=False, node=None):
koder aka kdanilov652cd802015-04-13 12:21:07 +0300241 "should be replaces by normal implementation, with select"
242 transport = conn.get_transport()
243 session = transport.open_session()
koder aka kdanilove06762a2015-03-22 23:32:09 +0200244
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300245 if node is None:
246 node = ""
247
koder aka kdanilov652cd802015-04-13 12:21:07 +0300248 with all_sessions_lock:
249 all_sessions.append(session)
koder aka kdanilove06762a2015-03-22 23:32:09 +0200250
koder aka kdanilov652cd802015-04-13 12:21:07 +0300251 try:
252 session.set_combine_stderr(True)
koder aka kdanilove06762a2015-03-22 23:32:09 +0200253
koder aka kdanilov652cd802015-04-13 12:21:07 +0300254 stime = time.time()
koder aka kdanilove06762a2015-03-22 23:32:09 +0200255
koder aka kdanilov652cd802015-04-13 12:21:07 +0300256 if not nolog:
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300257 logger.debug("SSH:{0} Exec {1!r}".format(node, cmd))
koder aka kdanilove06762a2015-03-22 23:32:09 +0200258
koder aka kdanilov652cd802015-04-13 12:21:07 +0300259 session.exec_command(cmd)
koder aka kdanilove06762a2015-03-22 23:32:09 +0200260
koder aka kdanilov652cd802015-04-13 12:21:07 +0300261 if stdin_data is not None:
262 session.sendall(stdin_data)
koder aka kdanilove06762a2015-03-22 23:32:09 +0200263
koder aka kdanilov652cd802015-04-13 12:21:07 +0300264 session.settimeout(1)
265 session.shutdown_write()
266 output = ""
267
268 while True:
269 try:
270 ndata = session.recv(1024)
271 output += ndata
272 if "" == ndata:
273 break
274 except socket.timeout:
275 pass
276
277 if time.time() - stime > timeout:
278 raise OSError(output + "\nExecution timeout")
279
280 code = session.recv_exit_status()
281 finally:
282 session.close()
283
284 if code != 0:
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300285 templ = "SSH:{0} Cmd {1!r} failed with code {2}. Output: {3}"
286 raise OSError(templ.format(node, cmd, code, output))
koder aka kdanilov652cd802015-04-13 12:21:07 +0300287
288 return output
289
290
291def close_all_sessions():
292 with all_sessions_lock:
293 for session in all_sessions:
294 try:
295 session.sendall('\x03')
296 session.close()
297 except:
298 pass