Package x2go :: Package backends :: Package control :: Module _stdout
[frames] | no frames]

Source Code for Module x2go.backends.control._stdout

  1  # -*- coding: utf-8 -*- 
  2   
  3  # Copyright (C) 2010-2011 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de> 
  4  # 
  5  # Python X2go is free software; you can redistribute it and/or modify 
  6  # it under the terms of the GNU General Public License as published by 
  7  # the Free Software Foundation; either version 3 of the License, or 
  8  # (at your option) any later version. 
  9  # 
 10  # Python X2go is distributed in the hope that it will be useful, 
 11  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 13  # GNU General Public License for more details. 
 14  # 
 15  # You should have received a copy of the GNU General Public License 
 16  # along with this program; if not, write to the 
 17  # Free Software Foundation, Inc., 
 18  # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. 
 19   
 20  """\ 
 21  X2goControlSessionSTDOUT class - core functions for handling your individual X2go sessions. 
 22   
 23  This backend handles X2go server implementations that respond via server-side STDOUT. 
 24   
 25  """ 
 26  __NAME__ = 'x2gocontrolsession-pylib' 
 27   
 28  # modules 
 29  import os 
 30  import types 
 31  import paramiko 
 32  import gevent 
 33   
 34  import copy 
 35  import binascii 
 36   
 37  import string 
 38  import random 
 39   
 40  from gevent import socket 
 41   
 42  # Python X2go modules 
 43  import x2go.sshproxy as sshproxy 
 44  import x2go.log as log 
 45  import x2go.utils as utils 
 46  import x2go.x2go_exceptions as x2go_exceptions 
 47  import x2go.defaults as defaults 
 48  import x2go.checkhosts as checkhosts 
 49   
 50  from x2go.backends.terminal import X2goTerminalSession as _X2goTerminalSession 
 51  from x2go.backends.info import X2goServerSessionInfo as _X2goServerSessionInfo 
 52  from x2go.backends.info import X2goServerSessionList as _X2goServerSessionList 
 53  from x2go.backends.proxy import X2goProxy as _X2goProxy 
 54   
 55  from x2go.monkey_patch_paramiko import monkey_patch_paramiko 
 56  monkey_patch_paramiko() 
57 58 -def _rerewrite_blanks(cmd):
59 # X2go run command replace X2GO_SPACE_CHAR string with blanks 60 if cmd: 61 cmd = cmd.replace("X2GO_SPACE_CHAR", " ") 62 return cmd
63
64 -def _rewrite_password(cmd, user=None, password=None):
65 66 # if there is a ,,-u X2GO_USER'' parameter in RDP options then we will replace 67 # it by our X2go session password 68 if cmd and user: 69 cmd = cmd.replace('X2GO_USER', user) 70 # if there is a ,,-p X2GO_PASSWORD'' parameter in RDP options then we will replace 71 # it by our X2go session password 72 if cmd and password: 73 cmd = cmd.replace('X2GO_PASSWORD', password) 74 return cmd
75
76 77 -class X2goControlSessionSTDOUT(paramiko.SSHClient):
78 """\ 79 STILL UNDOCUMENTED 80 81 @param logger: you can pass an L{X2goLogger} object to the 82 L{X2goControlSessionSTDOUT} constructor 83 @type logger: L{X2goLogger} instance 84 @param loglevel: if no L{X2goLogger} object has been supplied a new one will be 85 constructed with the given loglevel 86 @type loglevel: int 87 88 """ 89 associated_terminals = None 90
91 - def __init__(self, 92 profile_name='UNKNOWN', 93 add_to_known_hosts=False, 94 known_hosts=None, 95 terminal_backend=_X2goTerminalSession, 96 info_backend=_X2goServerSessionInfo, 97 list_backend=_X2goServerSessionList, 98 proxy_backend=_X2goProxy, 99 client_rootdir=os.path.join(defaults.LOCAL_HOME, defaults.X2GO_CLIENT_ROOTDIR), 100 sessions_rootdir=os.path.join(defaults.LOCAL_HOME, defaults.X2GO_SESSIONS_ROOTDIR), 101 ssh_rootdir=os.path.join(defaults.LOCAL_HOME, defaults.X2GO_SSH_ROOTDIR), 102 logger=None, loglevel=log.loglevel_DEFAULT, 103 *args, **kwargs):
104 """\ 105 Initialize an X2go session. With the L{X2goControlSessionSTDOUT} class you can start 106 new X2go sessions, resume suspended sessions or suspend resp. terminate 107 currently running sessions on a connected X2go server. 108 109 """ 110 self.associated_terminals = {} 111 self.terminated_terminals = [] 112 113 self.profile_name = profile_name 114 self.add_to_known_hosts = add_to_known_hosts 115 self.known_hosts = known_hosts 116 117 self.hostname = None 118 self.port = None 119 120 self.sshproxy_session = None 121 122 self._session_auth_rsakey = None 123 self._remote_home = None 124 self._remote_group = {} 125 126 self.locked = False 127 128 if logger is None: 129 self.logger = log.X2goLogger(loglevel=loglevel) 130 else: 131 self.logger = copy.deepcopy(logger) 132 self.logger.tag = __NAME__ 133 134 self._terminal_backend = terminal_backend 135 self._info_backend = info_backend 136 self._list_backend = list_backend 137 self._proxy_backend = proxy_backend 138 139 self.client_rootdir = client_rootdir 140 self.sessions_rootdir = sessions_rootdir 141 self.ssh_rootdir = ssh_rootdir 142 143 paramiko.SSHClient.__init__(self, *args, **kwargs) 144 if self.add_to_known_hosts: 145 self.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 146 147 self.session_died = False
148
149 - def load_session_host_keys(self):
150 if self.known_hosts is not None: 151 utils.touch_file(self.known_hosts) 152 self.load_host_keys(self.known_hosts)
153
154 - def __del__(self):
155 156 self.disconnect()
157
158 - def _x2go_sftp_put(self, local_path, remote_path):
159 160 self.logger('sFTP-put: %s -> %s:%s' % (os.path.normpath(local_path), self.get_transport().getpeername(), remote_path), loglevel=log.loglevel_DEBUG) 161 self.sftp_client.put(os.path.normpath(local_path), remote_path)
162
163 - def _x2go_sftp_write(self, remote_path, content):
164 165 self.logger('sFTP-write: opening remote file %s on host %s for writing' % (remote_path, self.get_transport().getpeername()), loglevel=log.loglevel_DEBUG) 166 try: 167 remote_fileobj = self.sftp_client.open(remote_path, 'w') 168 self.logger('sFTP-write: writing content: %s' % content, loglevel=log.loglevel_DEBUG_SFTPXFER) 169 remote_fileobj.write(content) 170 remote_fileobj.close() 171 except SSHException: 172 self.logger('sFTP-write: opening remote file %s on host %s failed' % (remote_path, self.get_transport().getpeername()), loglevel=log.loglevel_WARN)
173
174 - def _x2go_sftp_remove(self, remote_path):
175 176 self.logger('sFTP-write: removing remote file %s on host %s' % (remote_path, self.get_transport().getpeername()), loglevel=log.loglevel_DEBUG) 177 self.sftp_client.remove(remote_path)
178
179 - def _x2go_exec_command(self, cmd_line, loglevel=log.loglevel_INFO, **kwargs):
180 181 while self.locked: 182 gevent.sleep(.1) 183 184 self.locked = True 185 _retval = None 186 187 if type(cmd_line) == types.ListType: 188 cmd = " ".join(cmd_line) 189 else: 190 cmd = cmd_line 191 if self.get_transport() is not None: 192 193 timeout = gevent.Timeout(20) 194 timeout.start() 195 try: 196 self.logger("executing command on X2go server ,,%s'': %s" % (self.profile_name, _rerewrite_blanks(cmd)), loglevel) 197 _retval = self.exec_command(_rewrite_password(cmd, user=self.get_transport().get_username(), password=self._session_password), **kwargs) 198 except AttributeError: 199 self.session_died = True 200 if self.sshproxy_session: 201 self.sshproxy_session.stop_thread() 202 self.locked = False 203 raise x2go_exceptions.X2goControlSessionException('the X2go control session has died unexpectedly') 204 except EOFError: 205 self.session_died = True 206 if self.sshproxy_session: 207 self.sshproxy_session.stop_thread() 208 self.locked = False 209 raise x2go_exceptions.X2goControlSessionException('the X2go control session has died unexpectedly') 210 except x2go_exceptions.SSHException: 211 self.session_died = True 212 if self.sshproxy_session: 213 self.sshproxy_session.stop_thread() 214 self.locked = False 215 raise x2go_exceptions.X2goControlSessionException('the X2go control session has died unexpectedly') 216 except gevent.timeout.Timeout: 217 self.session_died = True 218 if self.sshproxy_session: 219 self.sshproxy_session.stop_thread() 220 self.locked = False 221 raise x2go_exceptions.X2goControlSessionException('the X2go control session command timed out') 222 except socket.error: 223 self.session_died = True 224 if self.sshproxy_session: 225 self.sshproxy_session.stop_thread() 226 self.locked = False 227 raise x2go_exceptions.X2goControlSessionException('the X2go control session has died unexpectedly') 228 finally: 229 self.locked = False 230 timeout.cancel() 231 232 else: 233 self.locked = False 234 raise x2go_exceptions.X2goControlSessionException('the X2go control session is not connected') 235 return _retval
236 237 @property
238 - def _x2go_remote_home(self):
239 240 if self._remote_home is None: 241 (stdin, stdout, stderr) = self._x2go_exec_command('echo $HOME') 242 self._remote_home = stdout.read().split()[0] 243 self.logger('remote user\' home directory: %s' % self._remote_home, loglevel=log.loglevel_DEBUG) 244 return self._remote_home 245 else: 246 return self._remote_home
247
248 - def _x2go_remote_group(self, group):
249 250 if not self._remote_group.has_key(group): 251 (stdin, stdout, stderr) = self._x2go_exec_command('getent group %s | cut -d":" -f4' % group) 252 self._remote_group[group] = stdout.read().split('\n')[0].split(',') 253 self.logger('remote %s group: %s' % (group, self._remote_group[group]), loglevel=log.loglevel_DEBUG) 254 return self._remote_group[group] 255 else: 256 return self._remote_group[group]
257
258 - def is_x2gouser(self, username):
259 ### 260 ### FIXME: 261 ### 262 # discussion about server-side access restriction based on posix group membership or similar currently 263 # in process (as of 20110517, mg) 264 #return username in self._x2go_remote_group('x2gousers') 265 return True
266
268 if self.remote_username() in self._x2go_remote_group('fuse'): 269 return True 270 return False
271
272 - def remote_username(self):
273 """\ 274 Returns the control session's remote username. 275 276 """ 277 if self.get_transport() is not None: 278 return self.get_transport().get_username() 279 else: 280 return None
281 282 @property
284 if self._session_auth_rsakey is None: 285 self._session_auth_rsakey = paramiko.RSAKey.generate(defaults.RSAKEY_STRENGTH) 286 return self._session_auth_rsakey
287
288 - def set_profile_name(self, profile_name):
289 self.profile_name = profile_name
290
291 - def check_host(self, hostname, port=22):
292 """\ 293 Wraps around a Paramiko/SSH host key check. 294 295 """ 296 # trailing whitespace tolerance 297 hostname = hostname.strip() 298 299 # force into IPv4 for localhost connections 300 if hostname in ('localhost', 'localhost.localdomain'): 301 hostname = '127.0.0.1' 302 303 return checkhosts.check_ssh_host_key(self, hostname, port=port)
304
305 - def connect(self, hostname, port=22, username='', password='', pkey=None, 306 use_sshproxy=False, sshproxy_host='', sshproxy_user='', sshproxy_password='', 307 sshproxy_key_filename='', sshproxy_tunnel='', 308 key_filename=None, timeout=None, allow_agent=False, look_for_keys=False, 309 session_instance=None, 310 add_to_known_hosts=False, force_password_auth=False):
311 """\ 312 Connect to an X2go server and authenticate to it. This method is directly 313 inherited from the paramiko.SSHClient module. The features of the Paramiko 314 SSH client connect method are recited here. The parameters C{add_to_known_hosts} 315 and C{force_password_auth} have been added as a parameter for X2go. 316 317 The server's host key 318 is checked against the system host keys (see C{load_system_host_keys}) 319 and any local host keys (C{load_host_keys}). If the server's hostname 320 is not found in either set of host keys, the missing host key policy 321 is used (see C{set_missing_host_key_policy}). The default policy is 322 to reject the key and raise an C{SSHException}. 323 324 Authentication is attempted in the following order of priority: 325 326 - The C{pkey} or C{key_filename} passed in (if any) 327 - Any key we can find through an SSH agent 328 - Any "id_rsa" or "id_dsa" key discoverable in C{~/.ssh/} 329 - Plain username/password auth, if a password was given 330 331 If a private key requires a password to unlock it, and a password is 332 passed in, that password will be used to attempt to unlock the key. 333 334 @param hostname: the server to connect to 335 @type hostname: str 336 @param port: the server port to connect to 337 @type port: int 338 @param username: the username to authenticate as (defaults to the 339 current local username) 340 @type username: str 341 @param password: a password to use for authentication or for unlocking 342 a private key 343 @type password: str 344 @param pkey: an optional private key to use for authentication 345 @type pkey: C{PKey} 346 @param key_filename: the filename, or list of filenames, of optional 347 private key(s) to try for authentication 348 @type key_filename: str or list(str) 349 @param timeout: an optional timeout (in seconds) for the TCP connect 350 @type timeout: float 351 @param allow_agent: set to False to disable connecting to the SSH agent 352 @type allow_agent: C{bool} 353 @param look_for_keys: set to False to disable searching for discoverable 354 private key files in C{~/.ssh/} 355 @type look_for_keys: C{bool} 356 @param add_to_known_hosts: non-paramiko option, if C{True} paramiko.AutoAddPolicy() 357 is used as missing-host-key-policy. If set to C{False} paramiko.RejectPolicy() 358 is used 359 @type add_to_known_hosts: C{bool} 360 @param force_password_auth: non-paramiko option, disable pub/priv key authentication 361 completely, even if the C{pkey} or the C{key_filename} parameter is given 362 @type force_password_auth: C{bool} 363 @param session_instance: an instance L{X2goSession} using this L{X2goControlSessionSTDOUT} 364 instance. 365 @type session_instance: C{instance} 366 367 @raise BadHostKeyException: if the server's host key could not be 368 verified 369 @raise AuthenticationException: if authentication failed 370 @raise SSHException: if there was any other error connecting or 371 establishing an SSH session 372 @raise socket.error: if a socket error occurred while connecting 373 374 """ 375 if use_sshproxy and sshproxy_host and sshproxy_user: 376 try: 377 self.sshproxy_session = sshproxy.X2goSSHProxy(known_hosts=self.known_hosts, 378 sshproxy_host=sshproxy_host, 379 sshproxy_user=sshproxy_user, 380 sshproxy_password=sshproxy_password, 381 sshproxy_key_filename=sshproxy_key_filename, 382 sshproxy_tunnel=sshproxy_tunnel, 383 session_instance=session_instance, 384 logger=self.logger, 385 ) 386 387 except: 388 if self.sshproxy_session: 389 self.sshproxy_session.stop_thread() 390 self.sshproxy_session = None 391 raise 392 393 if self.sshproxy_session is not None: 394 self.sshproxy_session.start() 395 396 # divert port to sshproxy_session's local forwarding port (it might have changed due to 397 # SSH connection errors 398 gevent.sleep(.1) 399 port = self.sshproxy_session.get_local_proxy_port() 400 401 if not add_to_known_hosts and session_instance: 402 self.set_missing_host_key_policy(checkhosts.X2goInteractiveAddPolicy(caller=self, session_instance=session_instance)) 403 404 if add_to_known_hosts: 405 self.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 406 407 # disable pub/priv key authentication if forced 408 if force_password_auth: 409 key_filename = None 410 pkey = None 411 412 # trailing whitespace tolerance in hostname 413 hostname = hostname.strip() 414 415 self.logger('connecting to [%s]:%s' % (hostname, port), loglevel=log.loglevel_NOTICE) 416 417 self.load_session_host_keys() 418 419 _hostname = hostname 420 # enforce IPv4 for localhost address 421 if _hostname in ('localhost', 'localhost.localdomain'): 422 _hostname = '127.0.0.1' 423 424 if (key_filename and os.path.exists(os.path.normpath(key_filename))) or pkey: 425 try: 426 self.logger('trying SSH pub/priv key authentication with server', loglevel=log.loglevel_DEBUG) 427 paramiko.SSHClient.connect(self, _hostname, port=port, username=username, pkey=pkey, 428 key_filename=key_filename, timeout=timeout, allow_agent=allow_agent, 429 look_for_keys=look_for_keys) 430 431 except paramiko.AuthenticationException, e: 432 self.close() 433 if password: 434 self.logger('next auth mechanism we\'ll try is keyboard-interactive authentication', loglevel=log.loglevel_DEBUG) 435 try: 436 paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, 437 timeout=timeout, allow_agent=allow_agent, 438 look_for_keys=look_for_keys) 439 except paramiko.AuthenticationException, e: 440 self.close() 441 if self.sshproxy_session: 442 self.sshproxy_session.stop_thread() 443 raise e 444 except: 445 self.close() 446 if self.sshproxy_session: 447 self.sshproxy_session.stop_thread() 448 raise 449 else: 450 self.close() 451 if self.sshproxy_session: 452 self.sshproxy_session.stop_thread() 453 raise(e) 454 455 except: 456 self.close() 457 if self.sshproxy_session: 458 self.sshproxy_session.stop_thread() 459 raise 460 461 # if there is not private key, we will use the given password, if any 462 else: 463 # create a random password if password is empty to trigger host key validity check 464 if not password: 465 password = "".join([random.choice(string.letters+string.digits) for x in range(1, 20)]) 466 self.logger('performing SSH keyboard-interactive authentication with server', loglevel=log.loglevel_DEBUG) 467 try: 468 paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, 469 timeout=timeout, allow_agent=allow_agent, look_for_keys=look_for_keys) 470 except paramiko.AuthenticationException, e: 471 self.close() 472 if self.sshproxy_session: 473 self.sshproxy_session.stop_thread() 474 raise e 475 except: 476 self.close() 477 if self.sshproxy_session: 478 self.sshproxy_session.stop_thread() 479 raise 480 481 self.set_missing_host_key_policy(paramiko.RejectPolicy()) 482 483 self.hostname = hostname 484 self.port = port 485 486 # if we succeed, we immediately grab us an sFTP client session 487 try: 488 self.sftp_client = self.open_sftp() 489 except: 490 raise x2go_exceptions.X2goControlSessionException('could not invoke server-side SFTP subsystem') 491 492 # preparing reverse tunnels 493 ssh_transport = self.get_transport() 494 ssh_transport.reverse_tunnels = {} 495 496 # mark Paramiko/SSH transport as X2goControlSession 497 ssh_transport._x2go_session_marker = True 498 self._session_password = password 499 500 if self.get_transport(): 501 self.session_died = False 502 503 if not self.home_exists(): 504 raise x2go_exceptions.X2goRemoteHomeException('remote home directory does not exist') 505 506 return (self.get_transport() is not None)
507
508 - def dissociate(self, terminal_session):
509 """\ 510 STILL UNDOCUMENTED 511 512 """ 513 for t_name in self.associated_terminals.keys(): 514 if self.associated_terminals[t_name] == terminal_session: 515 del self.associated_terminals[t_name] 516 if self.terminated_terminals.has_key(t_name): 517 del self.terminated_terminals[t_name]
518
519 - def disconnect(self):
520 """\ 521 STILL UNDOCUMENTED 522 523 """ 524 if self.associated_terminals: 525 t_names = self.associated_terminals.keys() 526 for t_obj in self.associated_terminals.values(): 527 try: 528 if not self.session_died: 529 t_obj.suspend() 530 except x2go_exceptions.X2goTerminalSessionException: 531 pass 532 except x2go_exceptions.X2goControlSessionException: 533 pass 534 t_obj.__del__() 535 for t_name in t_names: 536 try: 537 del self.associated_terminals[t_name] 538 except KeyError: 539 pass 540 541 self._remote_home = None 542 self._remote_group = {} 543 544 self._session_auth_rsakey = None 545 546 try: 547 if self.get_transport() is not None: 548 still_active = self.get_transport().is_active() 549 self.close() 550 if self.sshproxy_session is not None: 551 self.sshproxy_session.stop_thread() 552 return still_active 553 return False 554 except AttributeError: 555 # if the Paramiko _transport object has not yet been initialized, ignore it 556 # but state that this method call did not close the SSH client, but was already closed 557 return False
558 559
560 - def home_exists(self):
561 (_stdin, _stdout, _stderr) = self._x2go_exec_command('stat -tL "%s"' % self._x2go_remote_home, loglevel=log.loglevel_DEBUG) 562 if _stdout.read(): 563 return True 564 return False
565 566
567 - def is_alive(self):
568 if self._x2go_exec_command('echo', loglevel=log.loglevel_DEBUG): 569 return True 570 return False
571
572 - def start(self, **kwargs):
573 """\ 574 Start a new X2go session. 575 576 The L{X2goControlSessionSTDOUT.start()} method accepts any parameter 577 that can be passed to any of the C{X2goTerminalSession} backend class 578 constructors. 579 580 """ 581 return self.resume(**kwargs)
582
583 - def resume(self, session_name=None, session_instance=None, **kwargs):
584 """\ 585 Resume a running/suspended X2go session. 586 587 The L{X2goControlSessionSTDOUT.resume()} method accepts any parameter 588 that can be passed to any of the C{X2goTerminalSession} backend class constructors. 589 590 @return: True if the session could be successfully resumed 591 @rtype: C{bool} 592 593 """ 594 if not self.is_x2gouser(self.get_transport().get_username()): 595 raise x2go_exceptions.X2goUserException('remote user %s is not allowed to run X2go commands' % self.get_transport().get_username()) 596 597 if session_name is not None: 598 session_info = self.list_sessions()[session_name] 599 else: 600 session_info = None 601 602 _terminal = self._terminal_backend(self, 603 profile_name=self.profile_name, 604 session_info=session_info, 605 info_backend=self._info_backend, 606 list_backend=self._list_backend, 607 proxy_backend=self._proxy_backend, 608 client_rootdir=self.client_rootdir, 609 session_instance=session_instance, 610 sessions_rootdir=self.sessions_rootdir, 611 **kwargs) 612 613 _success = False 614 if session_name is not None: 615 try: 616 _success = _terminal.resume() 617 except x2go_exceptions.X2goFwTunnelException: 618 pass 619 620 else: 621 try: 622 _success = _terminal.start() 623 except x2go_exceptions.X2goFwTunnelException: 624 pass 625 626 if _success: 627 while not _terminal.ok(): 628 gevent.sleep(.2) 629 630 if _terminal.ok(): 631 self.associated_terminals[_terminal.get_session_name()] = _terminal 632 self.get_transport().reverse_tunnels[_terminal.get_session_name()] = { 633 'sshfs': (0, None), 634 'snd': (0, None), 635 } 636 637 return _terminal or None 638 639 return None
640
641 - def share_desktop(self, desktop=None, user=None, display=None, share_mode=0, **kwargs):
642 """\ 643 Share another already running desktop session. Desktop sharing can be run 644 in two different modes: view-only and full-access mode. 645 646 @param desktop: desktop ID of a sharable desktop in format <user>@<display> 647 @type desktop: C{str} 648 @param user: user name and display number can be given separately, here give the 649 name of the user who wants to share a session with you. 650 @type user: C{str} 651 @param display: user name and display number can be given separately, here give the 652 number of the display that a user allows you to be shared with. 653 @type display: C{str} 654 @param share_mode: desktop sharing mode, 0 is VIEW-ONLY, 1 is FULL-ACCESS. 655 @type share_mode: C{int} 656 657 @return: True if the session could be successfully shared. 658 @rtype: C{bool} 659 660 """ 661 if desktop: 662 user = desktop.split('@')[0] 663 display = desktop.split('@')[1] 664 if not (user and display): 665 raise x2go_exceptions.X2goDesktopSharingException('Need user name and display number of sharable desktop.') 666 667 cmd = '%sXSHAD%sXSHAD%s' % (share_mode, user, display) 668 669 kwargs['cmd'] = cmd 670 kwargs['session_type'] = 'shared' 671 672 return self.start(**kwargs)
673
674 - def list_desktops(self, raw=False, maxwait=20):
675 """\ 676 List all desktop-like sessions of current user (or of users that have 677 granted desktop sharing) on the connected server. 678 679 @param raw: if C{True}, the raw output of the server-side X2go command 680 C{x2godesktopsharing} is returned. 681 @type raw: C{bool} 682 683 @return: a list of X2go desktops available for sharing 684 @rtype: C{list} 685 686 """ 687 if raw: 688 (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistdesktops") 689 return stdout.read(), stderr.read() 690 691 else: 692 693 # this _success loop will catch errors in case the x2golistsessions output is corrupt 694 # this should not be needed and is a workaround for the current X2go server implementation 695 696 timeout = gevent.Timeout(maxwait) 697 timeout.start() 698 try: 699 (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistdesktops") 700 _stdout_read = stdout.read() 701 _listdesktops = _stdout_read.split('\n') 702 except gevent.timeout.Timeout: 703 # if we do not get a reply here after <maxwait> seconds we will raise a time out, we have to 704 # make sure that we catch this at places where we want to ignore timeouts (e.g. in the 705 # desktop list cache) 706 raise x2go_exceptions.X2goTimeOutException('x2golistdesktop command timed out') 707 finally: 708 timeout.cancel() 709 710 return _listdesktops
711
712 - def list_sessions(self, raw=False):
713 """\ 714 List all sessions of current user on the connected server. 715 716 @param raw: if C{True}, the raw output of the server-side X2go command 717 C{x2golistsessions} is returned. 718 @type raw: C{bool} 719 720 @return: normally an instance of a C{X2goServerSessionList} backend Bis returned. However, 721 if the raw argument is set, the plain text output of the x2golistsessions 722 command is returned 723 @rtype: C{X2goServerSessionList} instance or str 724 725 """ 726 if raw: 727 (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistsessions") 728 return stdout.read(), stderr.read() 729 730 else: 731 732 # this _success loop will catch errors in case the x2golistsessions output is corrupt 733 # this should not be needed and is a workaround for the current X2go server implementation 734 _listsessions = {} 735 _success = False 736 _count = 0 737 _maxwait = 20 738 739 # we will try this 20 times before giving up... we might simply catch the x2golistsessions 740 # output in the middle of creating a session in the database... 741 while not _success and _count < _maxwait: 742 _count += 1 743 try: 744 (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistsessions") 745 _stdout_read = stdout.read() 746 _listsessions = self._list_backend(_stdout_read, info_backend=self._info_backend).sessions 747 _success = True 748 except KeyError: 749 gevent.sleep(1) 750 except IndexError: 751 gevent.sleep(1) 752 except ValueError: 753 gevent.sleep(1) 754 755 if _count >= _maxwait: 756 raise x2go_exceptions.X2goControlSessionException('x2golistsessions command failed after we have tried 20 times') 757 758 # update internal variables when list_sessions() is called 759 for _session_name, _session_info in self.associated_terminals.items(): 760 if _session_name not in _listsessions.keys(): 761 del self.associated_terminals[_session_name] 762 self.terminated_terminals.append(_session_name) 763 elif _session_info.is_suspended(): 764 del self.associated_terminals[_session_name] 765 766 return _listsessions
767
768 - def clean_sessions(self, destroy_terminals=True):
769 """\ 770 Find X2go terminals that have previously been started by the 771 connected user on the remote X2go server and terminate them. 772 773 """ 774 session_list = self.list_sessions() 775 for session_name in session_list.keys(): 776 self.terminate(session_name=session_name, destroy_terminals=destroy_terminals)
777
778 - def is_connected(self):
779 """\ 780 Returns C{True} if this X2go session is connected to the remote server (that 781 is if it has a valid Paramiko Transport object). 782 783 @return: X2go session connected? 784 @rtype: C{bool} 785 786 """ 787 return self.get_transport() is not None and self.get_transport().is_authenticated()
788
789 - def is_running(self, session_name):
790 """\ 791 Returns C{True} if the given X2go session is in running state, 792 C{False} else. 793 794 @param session_name: X2go name of the session to be queried 795 @type session_name: str 796 797 @return: X2go session running? 798 @rtype: C{bool} 799 800 """ 801 session_infos = self.list_sessions() 802 if session_name in session_infos.keys(): 803 return session_infos[session_name].is_running() 804 return None
805
806 - def is_suspended(self, session_name):
807 """\ 808 Returns C{True} if the given X2go session is in suspended state, 809 C{False} else. 810 811 @return: X2go session suspended? 812 @rtype: C{bool} 813 814 """ 815 session_infos = self.list_sessions() 816 if session_name in session_infos.keys(): 817 return session_infos[session_name].is_suspended() 818 return None
819
820 - def has_terminated(self, session_name):
821 """\ 822 Returns C{True} if this X2go session is not in the session list on the 823 connected server, C{False} else. 824 825 Of course, if this command is called before session startup, it will also 826 return C{True}. 827 828 @return: X2go session has terminate? 829 @rtype: C{bool} 830 831 """ 832 session_infos = self.list_sessions() 833 if session_name not in session_infos.keys(): 834 if session_name in self.terminated_terminals: 835 return True 836 else: 837 # do a post-mortem tidy up 838 if session_name in self.associated_terminals.keys(): 839 self.terminate(session_name) 840 return True 841 return False
842
843 - def suspend(self, session_name):
844 """\ 845 Suspend either this or another available X2go session on the connected 846 server. 847 848 If L{session_name} is given, L{X2goControlSessionSTDOUT.suspend()} tries to suspend the 849 corresponding session. 850 851 @param session_name: X2go name of the session to be suspended 852 @type session_name: str 853 854 @return: True if the session could be successfully suspended 855 @rtype: C{bool} 856 857 """ 858 _ret = False 859 _session_names = [ t.get_session_name() for t in self.associated_terminals.values() ] 860 if session_name in _session_names: 861 862 self.logger('suspending associated terminal session: %s' % session_name, loglevel=log.loglevel_DEBUG) 863 (stdin, stdout, stderr) = self._x2go_exec_command("x2gosuspend-session %s" % session_name, loglevel=log.loglevel_DEBUG) 864 dummy_stdout = stdout.read() 865 dummy_stderr = stderr.read() 866 if self.associated_terminals.has_key(session_name): 867 if self.associated_terminals[session_name] is not None: 868 self.associated_terminals[session_name].__del__() 869 try: del self.associated_terminals[session_name] 870 except KeyError: pass 871 _ret = True 872 873 else: 874 875 self.logger('suspending non-associated terminal session: %s' % session_name, loglevel=log.loglevel_DEBUG) 876 (stdin, stdout, stderr) = self._x2go_exec_command("x2gosuspend-session %s" % session_name, loglevel=log.loglevel_DEBUG) 877 dummy_stdout = stdout.read() 878 dummy_stderr = stderr.read() 879 _ret = True 880 881 return _ret
882
883 - def terminate(self, session_name, destroy_terminals=True):
884 """\ 885 Terminate either this or another available X2go session on the connected 886 server. 887 888 If L{session_name} is given, L{X2goControlSessionSTDOUT.terminate()} tries to terminate the 889 corresponding session. 890 891 @param session_name: X2go name of the session to be terminated 892 @type session_name: str 893 894 @return: True if the session could be successfully terminate 895 @rtype: C{bool} 896 897 """ 898 899 _ret = False 900 _session_names = [ t.get_session_name() for t in self.associated_terminals.values() ] 901 if session_name in _session_names: 902 903 self.logger('terminating associated session: %s' % session_name, loglevel=log.loglevel_DEBUG) 904 (stdin, stdout, stderr) = self._x2go_exec_command("x2goterminate-session %s" % session_name, loglevel=log.loglevel_DEBUG) 905 dummy_stdout = stdout.read() 906 dummy_stderr = stderr.read() 907 if self.associated_terminals.has_key(session_name): 908 if self.associated_terminals[session_name] is not None and destroy_terminals: 909 self.associated_terminals[session_name].__del__() 910 try: del self.associated_terminals[session_name] 911 except KeyError: pass 912 self.terminated_terminals.append(session_name) 913 _ret = True 914 915 else: 916 917 self.logger('terminating non-associated session: %s' % session_name, loglevel=log.loglevel_DEBUG) 918 (stdin, stdout, stderr) = self._x2go_exec_command("x2goterminate-session %s" % session_name, loglevel=log.loglevel_DEBUG) 919 dummy_stdout = stdout.read() 920 dummy_stderr = stderr.read() 921 _ret = True 922 923 return _ret
924