X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=src%2Fnepi%2Fresources%2Flinux%2Fnode.py;h=44bd29df751164346767576fb02464de5e0a33fb;hb=245dc3d34b17d5ccbdb4e438c998bd547a7af076;hp=b7e86684199a807c20b101179a4449319155cd92;hpb=75a9c13d51a01652643281acd06fa78b93d68281;p=nepi.git diff --git a/src/nepi/resources/linux/node.py b/src/nepi/resources/linux/node.py index b7e86684..44bd29df 100644 --- a/src/nepi/resources/linux/node.py +++ b/src/nepi/resources/linux/node.py @@ -60,6 +60,88 @@ class OSType: @clsinit class LinuxNode(ResourceManager): + """ + .. class:: Class Args : + + :param ec: The Experiment controller + :type ec: ExperimentController + :param guid: guid of the RM + :type guid: int + + .. note:: + + There are different ways in which commands can be executed using the + LinuxNode interface (i.e. 'execute' - blocking and non blocking, 'run', + 'run_and_wait'). + + Brief explanation: + + * 'execute' (blocking mode) : + + HOW IT WORKS: 'execute', forks a process and run the + command, synchronously, attached to the terminal, in + foreground. + The execute method will block until the command returns + the result on 'out', 'err' (so until it finishes executing). + + USAGE: short-lived commands that must be executed attached + to a terminal and in foreground, for which it IS necessary + to block until the command has finished (e.g. if you want + to run 'ls' or 'cat'). + + * 'execute' (NON blocking mode - blocking = False) : + + HOW IT WORKS: Same as before, except that execute method + will return immediately (even if command still running). + + USAGE: long-lived commands that must be executed attached + to a terminal and in foreground, but for which it is not + necessary to block until the command has finished. (e.g. + start an application using X11 forwarding) + + * 'run' : + + HOW IT WORKS: Connects to the host ( using SSH if remote) + and launches the command in background, detached from any + terminal (daemonized), and returns. The command continues to + run remotely, but since it is detached from the terminal, + its pipes (stdin, stdout, stderr) can't be redirected to the + console (as normal non detached processes would), and so they + are explicitly redirected to files. The pidfile is created as + part of the process of launching the command. The pidfile + holds the pid and ppid of the process forked in background, + so later on it is possible to check whether the command is still + running. + + USAGE: long-lived commands that can run detached in background, + for which it is NOT necessary to block (wait) until the command + has finished. (e.g. start an application that is not using X11 + forwarding. It can run detached and remotely in background) + + * 'run_and_wait' : + + HOW IT WORKS: Similar to 'run' except that it 'blocks' until + the command has finished execution. It also checks whether + errors occurred during runtime by reading the exitcode file, + which contains the exit code of the command that was run + (checking stderr only is not always reliable since many + commands throw debugging info to stderr and the only way to + automatically know whether an error really happened is to + check the process exit code). + + Another difference with respect to 'run', is that instead + of directly executing the command as a bash command line, + it uploads the command to a bash script and runs the script. + This allows to use the bash script to debug errors, since + it remains at the remote host and can be run manually to + reproduce the error. + + USAGE: medium-lived commands that can run detached in + background, for which it IS necessary to block (wait) until + the command has finished. (e.g. Package installation, + source compilation, file download, etc) + + """ _rtype = "LinuxNode" @classmethod @@ -269,7 +351,6 @@ class LinuxNode(ResourceManager): if not self.localhost: # Build destination as @: dst = "%s@%s:%s" % (self.get("username"), self.get("hostname"), dst) - result = self.copy(src, dst) # clean up temp file @@ -371,7 +452,7 @@ class LinuxNode(ResourceManager): tty = tty) # check no errors occurred - if proc.poll() and err: + if proc.poll(): msg = " Failed to run command '%s' " % command self.error(msg, out, err) if raise_on_error: @@ -386,10 +467,13 @@ class LinuxNode(ResourceManager): # wait until command finishes to execute self.wait_run(pid, ppid) - (out, err), proc = self.check_errors(home, ecodefile, stderr) + (out, err), proc = self.check_errors(home, + ecodefile = ecodefile, + stdout = stdout, + stderr= stderr) # Out is what was written in the stderr file - if out or err: + if err: msg = " Failed to run command '%s' " % command self.error(msg, out, err) @@ -427,21 +511,18 @@ class LinuxNode(ResourceManager): """ Saves the command as a bash script file in the remote host, and forces to save the exit code of the command execution to the ecodefile """ - - # Prepare command to be executed as a bash script file - # Make sure command ends in ';' so the curly brackets syntax is correct - if not command.strip()[-1] == ';': - command += " ; " + if not (command.strip().endswith(";") or command.strip().endswith("&")): + command += ";" + # The exit code of the command will be stored in ecodefile - command = " { { %(command)s } ; echo $? > %(ecodefile)s ; } " % { + command = " { %(command)s } ; echo $? > %(ecodefile)s ;" % { 'command': command, 'ecodefile': ecodefile, } # Export environment - environ = "\n".join(map(lambda e: "export %s" % e, env.split(" "))) \ - if env else "" + environ = self.format_environment(env) # Add environ to command command = environ + command @@ -449,18 +530,34 @@ class LinuxNode(ResourceManager): dst = os.path.join(home, shfile) return self.upload(command, dst, text = True) + def format_environment(self, env, inline = False): + """Format environmental variables for command to be executed either + as an inline command + (i.e. export PYTHONPATH=src/..; export LALAL= ..;python script.py) or + as a bash script (i.e. export PYTHONPATH=src/.. \n export LALA=.. \n) + """ + if not env: return "" + env = env.strip() + + sep = ";" if inline else "\n" + return sep.join(map(lambda e: " export %s" % e, env.split(" "))) + sep + def check_errors(self, home, ecodefile = "exitcode", + stdout = "stdout", stderr = "stderr"): """ Checks whether errors occurred while running a command. It first checks the exit code for the command, and only if the exit code is an error one it returns the error output. + """ - out = err = "" proc = None + err = "" + # retrive standard output from the file + (out, oerr), oproc = self.check_output(home, stdout) - # get Exit code + # get exit code saved in the 'exitcode' file ecode = self.exitcode(home, ecodefile) if ecode in [ ExitCode.CORRUPTFILE, ExitCode.ERROR ]: @@ -468,14 +565,14 @@ class LinuxNode(ResourceManager): elif ecode > 0 or ecode == ExitCode.FILENOTFOUND: # The process returned an error code or didn't exist. # Check standard error. - (out, err), proc = self.check_output(home, stderr) - - # If the stderr file was not found, assume nothing happened. - # We just ignore the error. + (err, eerr), proc = self.check_output(home, stderr) + + # If the stderr file was not found, assume nothing bad happened, + # and just ignore the error. # (cat returns 1 for error "No such file or directory") if ecode == ExitCode.FILENOTFOUND and proc.poll() == 1: - out = err = "" - + err = "" + return (out, err), proc def wait_pid(self, home, pidfile = "pidfile", raise_on_error = False):