Adding comments to Linux CCN examples
[nepi.git] / src / nepi / resources / linux / application.py
index d4ddf92..ea4b0a4 100644 (file)
@@ -25,8 +25,10 @@ from nepi.util.sshfuncs import ProcStatus
 from nepi.util.timefuncs import strfnow, strfdiff
 
 import os
+import subprocess
 
 # TODO: Resolve wildcards in commands!!
+# TODO: compare_hash for all files that are uploaded!
 
 
 @clsinit
@@ -37,7 +39,7 @@ class LinuxApplication(ResourceManager):
     def _register_attributes(cls):
         command = Attribute("command", "Command to execute", 
                 flags = Flags.ExecReadOnly)
-        forward_x11 = Attribute("forwardX11", " Enables X11 forwarding for SSH connections", 
+        forward_x11 = Attribute("forwardX11", "Enables X11 forwarding for SSH connections", 
                 flags = Flags.ExecReadOnly)
         env = Attribute("env", "Environment variables string for command execution",
                 flags = Flags.ExecReadOnly)
@@ -111,6 +113,7 @@ class LinuxApplication(ResourceManager):
         self._pid = None
         self._ppid = None
         self._home = "app-%s" % self.guid
+        self._in_foreground = False
 
         # keep a reference to the running process handler when 
         # the command is not executed as remote daemon in background
@@ -155,12 +158,12 @@ class LinuxApplication(ResourceManager):
         This means that command will be executed using 'execute' instead of
         'run' ('run' executes a command in background and detached from the 
         terminal)
-
+        
         When using X11 forwarding option, the command can not run in background
         and detached from a terminal, since we need to keep the terminal attached 
         to interact with it.
         """
-        return self.get("forwardX11") or False
+        return self.get("forwardX11") or self._in_foreground
 
     def trace(self, name, attr = TraceAttr.ALL, block = 512, offset = 0):
         self.info("Retrieving '%s' trace %s " % (name, attr))
@@ -312,8 +315,14 @@ class LinuxApplication(ResourceManager):
         if stdin:
             # create dir for sources
             self.info(" Uploading stdin ")
-
+            
             dst = os.path.join(self.app_home, "stdin")
+
+            # If what we are uploading is a file, check whether
+            # the same file already exists (using md5sum)
+            if self.compare_hash(stdin, dst):
+                return
+
             self.node.upload(stdin, dst, text = True)
 
     def install_dependencies(self):
@@ -391,6 +400,7 @@ class LinuxApplication(ResourceManager):
             # installation), then the application is directly marked as FINISHED
             self._state = ResourceState.FINISHED
         else:
+
             if self.in_foreground:
                 self._start_in_foreground()
             else:
@@ -400,7 +410,6 @@ class LinuxApplication(ResourceManager):
 
     def _start_in_foreground(self):
         command = self.get("command")
-        env = self.get("env")
         stdin = "stdin" if self.get("stdin") else None
         sudo = self.get("sudo") or False
         x11 = self.get("forwardX11")
@@ -409,12 +418,11 @@ class LinuxApplication(ResourceManager):
         # terminal using the node 'execute' in non blocking mode.
 
         # Export environment
+        env = self.get("env")
         environ = self.node.format_environment(env, inline = True)
         command = environ + command
         command = self.replace_paths(command)
 
-        self.info("Starting command IN FOREGROUND '%s'" % command)
-        
         # We save the reference to the process in self._proc 
         # to be able to kill the process from the stop method.
         # We also set blocking = False, since we don't want the
@@ -465,7 +473,8 @@ class LinuxApplication(ResourceManager):
         # If the process is not running, check for error information
         # on the remote machine
         if not self.pid or not self.ppid:
-            (out, err), proc = self.check_errors(home, ecodefile, stderr)
+            (out, err), proc = self.node.check_errors(self.app_home,
+                    stderr = stderr) 
 
             # Out is what was written in the stderr file
             if err:
@@ -492,14 +501,17 @@ class LinuxApplication(ResourceManager):
             if self._proc:
                 self._proc.kill()
             else:
-                (out, err), proc = self.node.kill(self.pid, self.ppid)
-
-                if out or err:
-                    # check if execution errors occurred
-                    msg = " Failed to STOP command '%s' " % self.get("command")
-                    self.error(msg, out, err)
-                    self._state = ResourceState.FAILED
-                    stopped = False
+                # Only try to kill the process if the pid and ppid
+                # were retrieved
+                if self.pid and self.ppid:
+                    (out, err), proc = self.node.kill(self.pid, self.ppid)
+
+                    if out or err:
+                        # check if execution errors occurred
+                        msg = " Failed to STOP command '%s' " % self.get("command")
+                        self.error(msg, out, err)
+                        self._state = ResourceState.FAILED
+                        stopped = False
 
             if stopped:
                 super(LinuxApplication, self).stop()
@@ -525,7 +537,7 @@ class LinuxApplication(ResourceManager):
                 # Check if the process we used to execute the command
                 # is still running ...
                 retcode = self._proc.poll()
-                
+
                 # retcode == None -> running
                 # retcode > 0 -> error
                 # retcode == 0 -> finished
@@ -579,7 +591,39 @@ class LinuxApplication(ResourceManager):
             .replace("${NODE_HOME}", absolute_dir(self.node.node_home))
             .replace("${EXP_HOME}", absolute_dir(self.node.exp_home) )
             )
-        
+
+    def compare_hash(self, local, remote):
+        # getting md5sum from remote file
+        (out, err), proc = self.node.execute("md5sum %s " % remote)
+
+        if proc.poll() == 0: #OK
+            if not os.path.isfile(local):
+                # store to a tmp file
+                f = tempfile.NamedTemporaryFile()
+                f.write(local)
+                f.flush()
+                local = f.name
+
+            lproc = subprocess.Popen(["md5sum", local],
+                stdout = subprocess.PIPE,
+                stderr = subprocess.PIPE) 
+
+            # getting md5sum from local file
+            (lout, lerr) = lproc.communicate()
+
+            # files are the same, no need to upload
+            lchk = lout.strip().split(" ")[0]
+            rchk = out.strip().split(" ")[0]
+
+            msg = " Comparing files: LOCAL %s md5sum %s - REMOTE %s md5sum %s" % (
+                    local, lchk, remote, rchk)
+            self.debug(msg)
+
+            if lchk == rchk:
+                return True
+
+        return False
+
     def valid_connection(self, guid):
         # TODO: Validate!
         return True