blob: 7085be49fa8b5fa41737aaede3c6946b3c09b358 [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 kdanilov416b87a2015-05-12 00:26:04 +03003import errno
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +03004import random
koder aka kdanilov652cd802015-04-13 12:21:07 +03005import socket
koder aka kdanilov0c598a12015-04-21 03:01:40 +03006import shutil
koder aka kdanilove06762a2015-03-22 23:32:09 +02007import logging
8import os.path
koder aka kdanilov3a6633e2015-03-26 18:20:00 +02009import getpass
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030010import StringIO
koder aka kdanilov652cd802015-04-13 12:21:07 +030011import threading
koder aka kdanilov0c598a12015-04-21 03:01:40 +030012import subprocess
koder aka kdanilov652cd802015-04-13 12:21:07 +030013
koder aka kdanilov3a6633e2015-03-26 18:20:00 +020014import paramiko
koder aka kdanilove06762a2015-03-22 23:32:09 +020015
koder aka kdanilove06762a2015-03-22 23:32:09 +020016
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +030017logger = logging.getLogger("wally")
koder aka kdanilove06762a2015-03-22 23:32:09 +020018
19
koder aka kdanilov0c598a12015-04-21 03:01:40 +030020class Local(object):
21 "placeholder for local node"
22 @classmethod
23 def open_sftp(cls):
koder aka kdanilovafd98742015-04-24 01:27:22 +030024 return cls()
koder aka kdanilov0c598a12015-04-21 03:01:40 +030025
26 @classmethod
27 def mkdir(cls, remotepath, mode=None):
28 os.mkdir(remotepath)
29 if mode is not None:
30 os.chmod(remotepath, mode)
31
32 @classmethod
33 def put(cls, localfile, remfile):
koder aka kdanilov4d4771c2015-04-23 01:32:02 +030034 dirname = os.path.dirname(remfile)
35 if not os.path.exists(dirname):
36 os.makedirs(dirname)
koder aka kdanilov0c598a12015-04-21 03:01:40 +030037 shutil.copyfile(localfile, remfile)
38
39 @classmethod
40 def chmod(cls, path, mode):
41 os.chmod(path, mode)
42
43 @classmethod
44 def copytree(cls, src, dst):
45 shutil.copytree(src, dst)
46
47 @classmethod
48 def remove(cls, path):
49 os.unlink(path)
50
51 @classmethod
52 def close(cls):
53 pass
54
55 @classmethod
56 def open(cls, *args, **kwarhgs):
57 return open(*args, **kwarhgs)
58
koder aka kdanilove2de58c2015-04-24 22:59:36 +030059 @classmethod
60 def stat(cls, path):
61 return os.stat(path)
62
koder aka kdanilov783b4542015-04-23 18:57:04 +030063 def __enter__(self):
64 return self
65
66 def __exit__(self, x, y, z):
67 return False
68
koder aka kdanilov0c598a12015-04-21 03:01:40 +030069
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030070NODE_KEYS = {}
71
72
koder aka kdanilov416b87a2015-05-12 00:26:04 +030073def exists(sftp, path):
74 """os.path.exists for paramiko's SCP object
75 """
76 try:
77 sftp.stat(path)
78 return True
79 except IOError as e:
80 if e.errno == errno.ENOENT:
81 return False
82 raise
83
84
koder aka kdanilovf86d7af2015-05-06 04:01:54 +030085def set_key_for_node(host_port, key):
86 sio = StringIO.StringIO(key)
87 NODE_KEYS[host_port] = paramiko.RSAKey.from_private_key(sio)
88 sio.close()
89
90
koder aka kdanilov6b1341a2015-04-21 22:44:21 +030091def ssh_connect(creds, conn_timeout=60):
koder aka kdanilov0c598a12015-04-21 03:01:40 +030092 if creds == 'local':
koder aka kdanilov416b87a2015-05-12 00:26:04 +030093 return Local()
koder aka kdanilov0c598a12015-04-21 03:01:40 +030094
koder aka kdanilov46d4f392015-04-24 11:35:00 +030095 tcp_timeout = 15
96 banner_timeout = 30
97
koder aka kdanilov3a6633e2015-03-26 18:20:00 +020098 ssh = paramiko.SSHClient()
99 ssh.load_host_keys('/dev/null')
100 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
101 ssh.known_hosts = None
koder aka kdanilov168f6092015-04-19 02:33:38 +0300102
koder aka kdanilov6b1341a2015-04-21 22:44:21 +0300103 etime = time.time() + conn_timeout
104
105 while True:
koder aka kdanilov3a6633e2015-03-26 18:20:00 +0200106 try:
koder aka kdanilov46d4f392015-04-24 11:35:00 +0300107 tleft = etime - time.time()
108 c_tcp_timeout = min(tcp_timeout, tleft)
109 c_banner_timeout = min(banner_timeout, tleft)
110
koder aka kdanilov3a6633e2015-03-26 18:20:00 +0200111 if creds.passwd is not None:
112 ssh.connect(creds.host,
koder aka kdanilov46d4f392015-04-24 11:35:00 +0300113 timeout=c_tcp_timeout,
koder aka kdanilova4a570f2015-04-23 22:11:40 +0300114 username=creds.user,
koder aka kdanilov3a6633e2015-03-26 18:20:00 +0200115 password=creds.passwd,
116 port=creds.port,
117 allow_agent=False,
koder aka kdanilov46d4f392015-04-24 11:35:00 +0300118 look_for_keys=False,
119 banner_timeout=c_banner_timeout)
120 elif creds.key_file is not None:
koder aka kdanilov3a6633e2015-03-26 18:20:00 +0200121 ssh.connect(creds.host,
koder aka kdanilova4a570f2015-04-23 22:11:40 +0300122 username=creds.user,
koder aka kdanilov46d4f392015-04-24 11:35:00 +0300123 timeout=c_tcp_timeout,
koder aka kdanilov3a6633e2015-03-26 18:20:00 +0200124 key_filename=creds.key_file,
125 look_for_keys=False,
koder aka kdanilov46d4f392015-04-24 11:35:00 +0300126 port=creds.port,
127 banner_timeout=c_banner_timeout)
koder aka kdanilovf86d7af2015-05-06 04:01:54 +0300128 elif (creds.host, creds.port) in NODE_KEYS:
129 ssh.connect(creds.host,
130 username=creds.user,
131 timeout=c_tcp_timeout,
132 pkey=NODE_KEYS[(creds.host, creds.port)],
133 look_for_keys=False,
134 port=creds.port,
135 banner_timeout=c_banner_timeout)
koder aka kdanilov46d4f392015-04-24 11:35:00 +0300136 else:
137 key_file = os.path.expanduser('~/.ssh/id_rsa')
138 ssh.connect(creds.host,
139 username=creds.user,
140 timeout=c_tcp_timeout,
141 key_filename=key_file,
142 look_for_keys=False,
143 port=creds.port,
144 banner_timeout=c_banner_timeout)
koder aka kdanilov3a6633e2015-03-26 18:20:00 +0200145 return ssh
koder aka kdanilov3a6633e2015-03-26 18:20:00 +0200146 except paramiko.PasswordRequiredException:
147 raise
koder aka kdanilov46d4f392015-04-24 11:35:00 +0300148 except (socket.error, paramiko.SSHException):
koder aka kdanilov6b1341a2015-04-21 22:44:21 +0300149 if time.time() > etime:
koder aka kdanilov3a6633e2015-03-26 18:20:00 +0200150 raise
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300151 time.sleep(1)
koder aka kdanilov3a6633e2015-03-26 18:20:00 +0200152
153
koder aka kdanilov4d4771c2015-04-23 01:32:02 +0300154def save_to_remote(sftp, path, content):
155 with sftp.open(path, "wb") as fd:
156 fd.write(content)
157
158
159def read_from_remote(sftp, path):
160 with sftp.open(path, "rb") as fd:
161 return fd.read()
162
163
koder aka kdanilove06762a2015-03-22 23:32:09 +0200164def normalize_dirpath(dirpath):
165 while dirpath.endswith("/"):
166 dirpath = dirpath[:-1]
167 return dirpath
168
169
koder aka kdanilov2c473092015-03-29 17:12:13 +0300170ALL_RWX_MODE = ((1 << 9) - 1)
171
172
173def ssh_mkdir(sftp, remotepath, mode=ALL_RWX_MODE, intermediate=False):
koder aka kdanilove06762a2015-03-22 23:32:09 +0200174 remotepath = normalize_dirpath(remotepath)
175 if intermediate:
176 try:
177 sftp.mkdir(remotepath, mode=mode)
koder aka kdanilov4d4771c2015-04-23 01:32:02 +0300178 except (IOError, OSError):
koder aka kdanilov168f6092015-04-19 02:33:38 +0300179 upper_dir = remotepath.rsplit("/", 1)[0]
180
181 if upper_dir == '' or upper_dir == '/':
182 raise
183
184 ssh_mkdir(sftp, upper_dir, mode=mode, intermediate=True)
koder aka kdanilove06762a2015-03-22 23:32:09 +0200185 return sftp.mkdir(remotepath, mode=mode)
186 else:
187 sftp.mkdir(remotepath, mode=mode)
188
189
190def ssh_copy_file(sftp, localfile, remfile, preserve_perm=True):
191 sftp.put(localfile, remfile)
192 if preserve_perm:
koder aka kdanilov2c473092015-03-29 17:12:13 +0300193 sftp.chmod(remfile, os.stat(localfile).st_mode & ALL_RWX_MODE)
koder aka kdanilove06762a2015-03-22 23:32:09 +0200194
195
196def put_dir_recursively(sftp, localpath, remotepath, preserve_perm=True):
197 "upload local directory to remote recursively"
198
199 # hack for localhost connection
200 if hasattr(sftp, "copytree"):
201 sftp.copytree(localpath, remotepath)
202 return
203
204 assert remotepath.startswith("/"), "%s must be absolute path" % remotepath
205
206 # normalize
207 localpath = normalize_dirpath(localpath)
208 remotepath = normalize_dirpath(remotepath)
209
210 try:
211 sftp.chdir(remotepath)
212 localsuffix = localpath.rsplit("/", 1)[1]
213 remotesuffix = remotepath.rsplit("/", 1)[1]
214 if localsuffix != remotesuffix:
215 remotepath = os.path.join(remotepath, localsuffix)
216 except IOError:
217 pass
218
219 for root, dirs, fls in os.walk(localpath):
220 prefix = os.path.commonprefix([localpath, root])
221 suffix = root.split(prefix, 1)[1]
222 if suffix.startswith("/"):
223 suffix = suffix[1:]
224
225 remroot = os.path.join(remotepath, suffix)
226
227 try:
228 sftp.chdir(remroot)
229 except IOError:
230 if preserve_perm:
koder aka kdanilov2c473092015-03-29 17:12:13 +0300231 mode = os.stat(root).st_mode & ALL_RWX_MODE
koder aka kdanilove06762a2015-03-22 23:32:09 +0200232 else:
koder aka kdanilov2c473092015-03-29 17:12:13 +0300233 mode = ALL_RWX_MODE
koder aka kdanilove06762a2015-03-22 23:32:09 +0200234 ssh_mkdir(sftp, remroot, mode=mode, intermediate=True)
235 sftp.chdir(remroot)
236
237 for f in fls:
238 remfile = os.path.join(remroot, f)
239 localfile = os.path.join(root, f)
240 ssh_copy_file(sftp, localfile, remfile, preserve_perm)
241
242
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300243def delete_file(conn, path):
244 sftp = conn.open_sftp()
245 sftp.remove(path)
246 sftp.close()
247
248
koder aka kdanilove06762a2015-03-22 23:32:09 +0200249def copy_paths(conn, paths):
250 sftp = conn.open_sftp()
251 try:
252 for src, dst in paths.items():
253 try:
254 if os.path.isfile(src):
255 ssh_copy_file(sftp, src, dst)
256 elif os.path.isdir(src):
257 put_dir_recursively(sftp, src, dst)
258 else:
259 templ = "Can't copy {0!r} - " + \
260 "it neither a file not a directory"
koder aka kdanilov168f6092015-04-19 02:33:38 +0300261 raise OSError(templ.format(src))
koder aka kdanilove06762a2015-03-22 23:32:09 +0200262 except Exception as exc:
263 tmpl = "Scp {0!r} => {1!r} failed - {2!r}"
koder aka kdanilov168f6092015-04-19 02:33:38 +0300264 raise OSError(tmpl.format(src, dst, exc))
koder aka kdanilove06762a2015-03-22 23:32:09 +0200265 finally:
266 sftp.close()
267
268
269class ConnCreds(object):
koder aka kdanilov2c473092015-03-29 17:12:13 +0300270 conn_uri_attrs = ("user", "passwd", "host", "port", "path")
271
koder aka kdanilove06762a2015-03-22 23:32:09 +0200272 def __init__(self):
koder aka kdanilov2c473092015-03-29 17:12:13 +0300273 for name in self.conn_uri_attrs:
koder aka kdanilove06762a2015-03-22 23:32:09 +0200274 setattr(self, name, None)
275
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300276 def __str__(self):
277 return str(self.__dict__)
278
koder aka kdanilove06762a2015-03-22 23:32:09 +0200279
280uri_reg_exprs = []
281
282
283class URIsNamespace(object):
284 class ReParts(object):
285 user_rr = "[^:]*?"
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300286 host_rr = "[^:@]*?"
koder aka kdanilove06762a2015-03-22 23:32:09 +0200287 port_rr = "\\d+"
288 key_file_rr = "[^:@]*"
289 passwd_rr = ".*?"
290
291 re_dct = ReParts.__dict__
292
293 for attr_name, val in re_dct.items():
294 if attr_name.endswith('_rr'):
295 new_rr = "(?P<{0}>{1})".format(attr_name[:-3], val)
296 setattr(ReParts, attr_name, new_rr)
297
298 re_dct = ReParts.__dict__
299
300 templs = [
301 "^{host_rr}$",
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300302 "^{host_rr}:{port_rr}$",
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300303 "^{host_rr}::{key_file_rr}$",
304 "^{host_rr}:{port_rr}:{key_file_rr}$",
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300305 "^{user_rr}@{host_rr}$",
306 "^{user_rr}@{host_rr}:{port_rr}$",
koder aka kdanilove06762a2015-03-22 23:32:09 +0200307 "^{user_rr}@{host_rr}::{key_file_rr}$",
308 "^{user_rr}@{host_rr}:{port_rr}:{key_file_rr}$",
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300309 "^{user_rr}:{passwd_rr}@{host_rr}$",
310 "^{user_rr}:{passwd_rr}@{host_rr}:{port_rr}$",
koder aka kdanilove06762a2015-03-22 23:32:09 +0200311 ]
312
313 for templ in templs:
314 uri_reg_exprs.append(templ.format(**re_dct))
315
316
317def parse_ssh_uri(uri):
koder aka kdanilov7e0f7cf2015-05-01 17:24:35 +0300318 # user:passwd@ip_host:port
319 # user:passwd@ip_host
koder aka kdanilove06762a2015-03-22 23:32:09 +0200320 # user@ip_host:port
321 # user@ip_host
322 # ip_host:port
323 # ip_host
324 # user@ip_host:port:path_to_key_file
325 # user@ip_host::path_to_key_file
326 # ip_host:port:path_to_key_file
327 # ip_host::path_to_key_file
328
koder aka kdanilov4d4771c2015-04-23 01:32:02 +0300329 if uri.startswith("ssh://"):
330 uri = uri[len("ssh://"):]
331
koder aka kdanilove06762a2015-03-22 23:32:09 +0200332 res = ConnCreds()
333 res.port = "22"
334 res.key_file = None
335 res.passwd = None
koder aka kdanilova4a570f2015-04-23 22:11:40 +0300336 res.user = getpass.getuser()
koder aka kdanilove06762a2015-03-22 23:32:09 +0200337
338 for rr in uri_reg_exprs:
339 rrm = re.match(rr, uri)
340 if rrm is not None:
341 res.__dict__.update(rrm.groupdict())
342 return res
koder aka kdanilov652cd802015-04-13 12:21:07 +0300343
koder aka kdanilove06762a2015-03-22 23:32:09 +0200344 raise ValueError("Can't parse {0!r} as ssh uri value".format(uri))
345
346
koder aka kdanilov168f6092015-04-19 02:33:38 +0300347def connect(uri, **params):
koder aka kdanilov0c598a12015-04-21 03:01:40 +0300348 if uri == 'local':
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300349 return Local()
koder aka kdanilov0c598a12015-04-21 03:01:40 +0300350
koder aka kdanilove06762a2015-03-22 23:32:09 +0200351 creds = parse_ssh_uri(uri)
352 creds.port = int(creds.port)
koder aka kdanilov168f6092015-04-19 02:33:38 +0300353 return ssh_connect(creds, **params)
koder aka kdanilove06762a2015-03-22 23:32:09 +0200354
355
koder aka kdanilov652cd802015-04-13 12:21:07 +0300356all_sessions_lock = threading.Lock()
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300357all_sessions = {}
358
359
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300360class BGSSHTask(object):
361 def __init__(self, node, use_sudo):
362 self.node = node
363 self.pid = None
364 self.use_sudo = use_sudo
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300365
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300366 def start(self, orig_cmd, **params):
367 uniq_name = 'test'
368 cmd = "screen -S {0} -d -m {1}".format(uniq_name, orig_cmd)
369 run_over_ssh(self.node.connection, cmd,
370 timeout=10, node=self.node.get_conn_id(),
371 **params)
372 processes = run_over_ssh(self.node.connection, "ps aux", nolog=True)
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300373
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300374 for proc in processes.split("\n"):
375 if orig_cmd in proc and "SCREEN" not in proc:
376 self.pid = proc.split()[1]
377 break
378 else:
379 self.pid = -1
380
381 def check_running(self):
382 assert self.pid is not None
383 try:
384 run_over_ssh(self.node.connection,
385 "ls /proc/{0}".format(self.pid),
386 timeout=10, nolog=True)
387 return True
388 except OSError:
389 return False
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300390
391 def kill(self, soft=True, use_sudo=True):
392 assert self.pid is not None
393 try:
394 if soft:
395 cmd = "kill {0}"
396 else:
397 cmd = "kill -9 {0}"
398
399 if self.use_sudo:
400 cmd = "sudo " + cmd
401
402 run_over_ssh(self.node.connection,
403 cmd.format(self.pid), nolog=True)
404 return True
405 except OSError:
406 return False
407
408 def wait(self, soft_timeout, timeout):
409 end_of_wait_time = timeout + time.time()
410 soft_end_of_wait_time = soft_timeout + time.time()
411 time_till_check = random.randint(5, 10)
412
koder aka kdanilovfd2cfa52015-05-20 03:17:42 +0300413 time_till_first_check = random.randint(2, 6)
414 time.sleep(time_till_first_check)
415 if not self.check_running():
416 return True
417
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300418 while self.check_running() and time.time() < soft_end_of_wait_time:
419 time.sleep(soft_end_of_wait_time - time.time())
420
421 while end_of_wait_time > time.time():
422 time.sleep(time_till_check)
423 if not self.check_running():
424 break
425 else:
426 self.kill()
427 time.sleep(3)
428 if self.check_running():
429 self.kill(soft=False)
430 return False
431 return True
koder aka kdanilove06762a2015-03-22 23:32:09 +0200432
koder aka kdanilove06762a2015-03-22 23:32:09 +0200433
koder aka kdanilovcff7b2e2015-04-18 20:48:15 +0300434def run_over_ssh(conn, cmd, stdin_data=None, timeout=60,
435 nolog=False, node=None):
koder aka kdanilov652cd802015-04-13 12:21:07 +0300436 "should be replaces by normal implementation, with select"
koder aka kdanilov0c598a12015-04-21 03:01:40 +0300437
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300438 if isinstance(conn, Local):
koder aka kdanilov0c598a12015-04-21 03:01:40 +0300439 if not nolog:
440 logger.debug("SSH:local Exec {0!r}".format(cmd))
441 proc = subprocess.Popen(cmd, shell=True,
442 stdin=subprocess.PIPE,
443 stdout=subprocess.PIPE,
444 stderr=subprocess.STDOUT)
445
446 stdoutdata, _ = proc.communicate(input=stdin_data)
koder aka kdanilov0c598a12015-04-21 03:01:40 +0300447 if proc.returncode != 0:
448 templ = "SSH:{0} Cmd {1!r} failed with code {2}. Output: {3}"
449 raise OSError(templ.format(node, cmd, proc.returncode, stdoutdata))
450
451 return stdoutdata
452
koder aka kdanilov652cd802015-04-13 12:21:07 +0300453 transport = conn.get_transport()
454 session = transport.open_session()
koder aka kdanilove06762a2015-03-22 23:32:09 +0200455
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300456 if node is None:
457 node = ""
458
koder aka kdanilov652cd802015-04-13 12:21:07 +0300459 with all_sessions_lock:
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300460 all_sessions[id(session)] = session
koder aka kdanilove06762a2015-03-22 23:32:09 +0200461
koder aka kdanilov652cd802015-04-13 12:21:07 +0300462 try:
463 session.set_combine_stderr(True)
koder aka kdanilove06762a2015-03-22 23:32:09 +0200464
koder aka kdanilov652cd802015-04-13 12:21:07 +0300465 stime = time.time()
koder aka kdanilove06762a2015-03-22 23:32:09 +0200466
koder aka kdanilov652cd802015-04-13 12:21:07 +0300467 if not nolog:
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300468 logger.debug("SSH:{0} Exec {1!r}".format(node, cmd))
koder aka kdanilove06762a2015-03-22 23:32:09 +0200469
koder aka kdanilov652cd802015-04-13 12:21:07 +0300470 session.exec_command(cmd)
koder aka kdanilove06762a2015-03-22 23:32:09 +0200471
koder aka kdanilov652cd802015-04-13 12:21:07 +0300472 if stdin_data is not None:
473 session.sendall(stdin_data)
koder aka kdanilove06762a2015-03-22 23:32:09 +0200474
koder aka kdanilov652cd802015-04-13 12:21:07 +0300475 session.settimeout(1)
476 session.shutdown_write()
477 output = ""
478
479 while True:
480 try:
481 ndata = session.recv(1024)
482 output += ndata
483 if "" == ndata:
484 break
485 except socket.timeout:
486 pass
487
488 if time.time() - stime > timeout:
489 raise OSError(output + "\nExecution timeout")
490
491 code = session.recv_exit_status()
492 finally:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300493 found = False
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300494 with all_sessions_lock:
koder aka kdanilov4af1c1d2015-05-18 15:48:58 +0300495 if id(session) in all_sessions:
496 found = True
497 del all_sessions[id(session)]
498
499 if found:
500 session.close()
koder aka kdanilov652cd802015-04-13 12:21:07 +0300501
502 if code != 0:
koder aka kdanilov4500a5f2015-04-17 16:55:17 +0300503 templ = "SSH:{0} Cmd {1!r} failed with code {2}. Output: {3}"
504 raise OSError(templ.format(node, cmd, code, output))
koder aka kdanilov652cd802015-04-13 12:21:07 +0300505
506 return output
507
508
509def close_all_sessions():
510 with all_sessions_lock:
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300511 for session in all_sessions.values():
koder aka kdanilov652cd802015-04-13 12:21:07 +0300512 try:
513 session.sendall('\x03')
514 session.close()
515 except:
516 pass
koder aka kdanilov416b87a2015-05-12 00:26:04 +0300517 all_sessions.clear()