def test_ptyprocess_suspend_resume () : """ Test pty_process which gets suspended/resumed """ pty = supp.PTYProcess ("cat") os.kill (pty.child, signal.SIGSTOP) os.kill (pty.child, signal.SIGCONT) time.sleep (0.1) assert (pty.alive ())
def test_ptyprocess_stderr () : """ Test pty_process printing stderr messages""" txt = "______1______2_____3_____\n" pty = supp.PTYProcess ("sh -c 'printf \"%s\" 1>&2'" % txt) out = pty.read () # print "--%s--%s--\n" % ( len(txt), txt) # print "--%s--%s--\n" % ( len(out), out) assert (str(txt) == str(out))
def test_ptyprocess_stdout () : """ Test pty_process printing stdout messages""" txt = "______1______2_____3_____\n" pty = supp.PTYProcess ("printf \"%s\"" % txt) out = pty.read (size=len(txt), timeout=1.0) pty.wait () assert (str(txt) == str(out)), "'%s' == '%s'" % \ (str(txt) , str(out))
def test_ptyprocess_kill () : """ Test pty_process which gets killed """ pty = supp.PTYProcess ("cat") os.kill (pty.child, signal.SIGKILL) time.sleep (0.1) assert (not pty.alive ()) assert (pty.exit_signal == signal.SIGKILL), "'%s' == '%s'" % \ (pty.exit_signal , signal.SIGKILL)
def test_ptyprocess_find () : """ Test pty_process selecting stdout messages""" txt = "______1_____2______3_____" pty = supp.PTYProcess ("printf \"%s\"" % txt) out = pty.find ('2', '3') # print out assert (out == (0, '______1_____2')), "'%s' == '%s'" % \ (out , (0, '______1_____2'))
def get_cp_slave(self, s_cmd, info): with self.rlock: # print 'new cp shell to %s' % s_cmd cp_slave = supp.PTYProcess(s_cmd, info['logger']) self._initialize_pty(cp_slave, info) return cp_slave
def initialize(self, url, session=None, prompt=None, logger=None): with self.rlock: # make sure we have a valid url type url = saga.Url(url) if not prompt: prompt = "^(.*[\$#%>\]])\s*$" if not logger: logger = rul.getLogger('saga', 'PTYShellFactory') # collect all information we have/need about the requested master # connection info = self._create_master_entry(url, session, prompt, logger) # we got master info - register the master, and create the instance! type_s = str(info['type']) user_s = str(info['user']) host_s = str(info['host_str']) # Now, if we don't have that master, yet, we need to instantiate it if not host_s in self.registry: self.registry[host_s] = {} if not user_s in self.registry[host_s]: self.registry[host_s][user_s] = {} if not type_s in self.registry[host_s][user_s]: # new master: create an instance, and register it m_cmd = info['scripts'][info['type']]['master'] % info logger.debug ("open master pty for [%s] [%s] %s: %s'" \ % (type_s, host_s, user_s, m_cmd)) info['pty'] = supp.PTYProcess(m_cmd, logger=logger) if not info['pty'].alive(): raise se.NoSuccess._log (logger, \ "Shell not connected to %s" % info['host_str']) # authorization, prompt setup, etc self._initialize_pty(info['pty'], info, is_shell=True) # master was created - register it self.registry[host_s][user_s][type_s] = info else: # we already have a master: make sure it is alive, and restart as # needed info = self.registry[host_s][user_s][type_s] if not info['pty'].alive(recover=True): raise se.IncorrectState._log (logger, \ "Lost shell connection to %s" % info['host_str']) return info
def test_ptyprocess_write () : """ Test pty_process reading stdin, printing stdout messages""" # cat is line buffered, thus need \n txt = "______1______2_____3_____\n" pty = supp.PTYProcess ("cat") pty.write (txt) out = pty.read (size=len(txt), timeout=1.0) # print "--%s--%s--\n" % ( len(txt), txt) # print "--%s--%s--\n" % ( len(out), out) assert (txt == out), "'%s' == '%s'" % \ (txt , out)
def get_cp_slave(self, s_cmd, info, posix=None): with self.rlock: if posix == None: posix = info.get('copy_is_posix') # print '> -- new cp shell to %s' % s_cmd cp_slave = supp.PTYProcess(s_cmd, info['logger']) self._initialize_pty(cp_slave, info, posix) return cp_slave
def test_ptyprocess_restart () : """ Test pty_process restart""" pty = supp.PTYProcess ("cat") assert (pty.alive ()) pty.finalize () assert (not pty.alive ()) pty.initialize () assert (pty.alive ()) pty.finalize () assert (not pty.alive ())
def run_shell(self, info): """ This initiates a master connection. If there is a suitable master connection in the registry, it is re-used, and no new master connection is created. If needed, the existing master connection is revived. """ # if True : with self.rlock: s_cmd = info['scripts'][info['shell_type']]['shell'] % info # at this point, we do have a valid, living master sh_slave = supp.PTYProcess(s_cmd, info['logger']) # authorization, prompt setup, etc self._initialize_pty(sh_slave, info) return sh_slave
def run_copy_to (self, src, tgt, cp_flags="") : """ This initiates a slave copy connection. Src is interpreted as local path, tgt as path on the remote host. Now, this is ugly when over sftp: sftp supports recursive copy, and wildcards, all right -- but for recursive copies, it wants the target dir to exist -- so, we have to check if the local src is a dir, and if so, we first create the target before the copy. Worse, for wildcards we have to do a local expansion, and the to do the same for each entry... """ with self.pty_shell.rlock : self._trace ("copy to : %s -> %s" % (src, tgt)) self.pty_shell.flush () info = self.pty_info repl = dict ({'src' : src, 'tgt' : tgt, 'cp_flags' : '' # cp_flags # TODO: needs to be "translated" for specific backend }.items () + info.items ()) # at this point, we do have a valid, living master s_cmd = info['scripts'][info['copy_mode']]['copy_to'] % repl s_in = info['scripts'][info['copy_mode']]['copy_to_in'] % repl posix = info['scripts'][info['copy_mode']]['copy_is_posix'] if not s_in : # this code path does not use an interactive shell for copy -- # so the above s_cmd is all we want to run, really. We get # do not use the chached cp_slave in this case, but just run the # command. We do not have a list of transferred files though, # yet -- that should be parsed from the proc output. cp_proc = supp.PTYProcess (s_cmd) out = cp_proc.wait () if cp_proc.exit_code : raise ptye.translate_exception (se.NoSuccess ("file copy failed: %s" % out)) return list() # this code path uses an interactive shell to transfer files, of # some form, such as sftp. Get the shell cp_slave from cache, and # run the actual copy command. if not self.cp_slave : self._trace ("get cp slave") self.cp_slave = self.factory.get_cp_slave (s_cmd, info, posix) self.cp_slave.flush () if 'sftp' in s_cmd : # prepare target dirs for recursive copy, if needed import glob src_list = glob.glob (src) for s in src_list : if os.path.isdir (s) : prep = "mkdir %s/%s\n" % (tgt, os.path.basename (s)) # TODO: this doesn't deal with multiple levels of creation self.cp_slave.flush() self.cp_slave.write("%s\n" % prep) self.cp_slave.find(['[\$\>\]]\s*$'], -1) # TODO: check return values if cp_flags == sfs.CREATE_PARENTS and os.path.split(tgt)[0]: # TODO: this needs to be numeric and checking the flag prep = "mkdir %s\n" % os.path.dirname(tgt) # TODO: this doesn't deal with multiple levels of creation self.cp_slave.flush() self.cp_slave.write("%s\n" % prep) self.cp_slave.find(['[\$\>\]]\s*$'], -1) # TODO: check return values self.cp_slave.flush() _ = self.cp_slave.write("%s\n" % s_in) _, out = self.cp_slave.find(['[\$\>\]]\s*$'], -1) # FIXME: we don't really get exit codes from copy # if self.cp_slave.exit_code != 0 : # raise se.NoSuccess._log (info['logger'], "file copy failed: %s" % str(out)) if 'Invalid flag' in out : raise se.NoSuccess._log (info['logger'], "sftp version not supported (%s)" % str(out)) if 'No such file or directory' in out : raise se.DoesNotExist._log (info['logger'], "file copy failed: %s" % str(out)) if 'is not a directory' in out : raise se.BadParameter._log (info['logger'], "File copy failed: %s" % str(out)) if 'sftp' in s_cmd : if 'not found' in out : raise se.BadParameter._log (info['logger'], "file copy failed: %s" % out) # we interpret the first word on the line as name of src file -- we # will return a list of those lines = out.split ('\n') files = [] for line in lines : elems = line.split (' ', 2) if elems : f = elems[0] # remove quotes if f : if f[ 0] in ["'", '"', '`'] : f = f[1: ] if f[-1] in ["'", '"', '`'] : f = f[ :-1] # ignore empty lines if f : files.append (f) info['logger'].debug ("copy done: %s" % files) return files
def run_copy_from (self, src, tgt, cp_flags="") : """ This initiates a slave copy connection. Src is interpreted as path on the remote host, tgt as local path. We have to do the same mkdir trick as for the run_copy_to, but here we need to expand wildcards on the *remote* side :/ """ with self.pty_shell.rlock : self._trace ("copy from: %s -> %s" % (src, tgt)) self.pty_shell.flush () info = self.pty_info repl = dict ({'src' : src, 'tgt' : tgt, 'cp_flags' : cp_flags}.items() + info.items ()) # at this point, we do have a valid, living master s_cmd = info['scripts'][info['copy_mode']]['copy_from'] % repl s_in = info['scripts'][info['copy_mode']]['copy_from_in'] % repl posix = info['scripts'][info['copy_mode']]['copy_is_posix'] if not s_in : # this code path does not use an interactive shell for copy -- # so the above s_cmd is all we want to run, really. We get # do not use the chached cp_slave in this case, but just run the # command. We do not have a list of transferred files though, # yet -- that should be parsed from the proc output. cp_proc = supp.PTYProcess (s_cmd) cp_proc.wait () if cp_proc.exit_code : raise ptye.translate_exception (se.NoSuccess ("file copy failed: exit code %s" % cp_proc.exit_code)) return list() if not self.cp_slave : self._trace ("get cp slave") self.cp_slave = self.factory.get_cp_slave (s_cmd, info, posix) self.cp_slave.flush () prep = "" if 'sftp' in s_cmd : # prepare target dirs for recursive copy, if needed self.cp_slave.write (" ls %s\n" % src) _, out = self.cp_slave.find (["^sftp> "], -1) src_list = out[1].split('\n') for s in src_list : if os.path.isdir (s) : prep += "lmkdir %s/%s\n" % (tgt, os.path.basename (s)) self.cp_slave.flush () _ = self.cp_slave.write ("%s%s\n" % (prep, s_in)) _, out = self.cp_slave.find (['[\$\>\]] *$'], -1) # FIXME: we don't really get exit codes from copy # if self.cp_slave.exit_code != 0 : # raise se.NoSuccess._log (info['logger'], "file copy failed: %s" % out) if 'Invalid flag' in out : raise se.NoSuccess._log (info['logger'], "sftp version not supported (%s)" % out) if 'No such file or directory' in out : raise se.DoesNotExist._log (info['logger'], "file copy failed: %s" % out) if 'is not a directory' in out : raise se.BadParameter._log (info['logger'], "file copy failed: %s" % out) if 'sftp' in s_cmd : if 'not found' in out : raise se.BadParameter._log (info['logger'], "file copy failed: %s" % out) # we run copy with -v, so get a list of files which have been copied # -- we parse that list and return it. we interpret the *second* # word on the line as name of src file. lines = out.split ('\n') files = [] for line in lines : elems = line.split (' ', 3) if elems and len(elems) > 1 and elems[0] == 'Fetching' : f = elems[1] # remove quotes if f : if f[ 0] in ["'", '"', '`'] : f = f[1: ] if f[-1] in ["'", '"', '`'] : f = f[ :-1] # ignore empty lines if f : files.append (f) info['logger'].debug ("copy done: %s" % files) return files
def test_ptyprocess_nok () : """ Test pty_process which finishes unsuccessfully """ pty = supp.PTYProcess ("false") pty.wait () assert pty.exit_code != 0
def test_ptyprocess_ok () : """ Test pty_process which finishes successfully """ pty = supp.PTYProcess ("true") pty.wait () assert pty.exit_code == 0