Adding start_with_condition, stop_with_condition and set_with_condition to the EC...
[nepi.git] / src / neco / resources / linux / application.py
index 1ffcdf5..de4f850 100644 (file)
-from neco.execution import tags
-from neco.execution.resource import ResourceManager
+from neco.execution.attribute import Attribute, Flags
+from neco.execution.resource import ResourceManager, clsinit, ResourceState
+from neco.resources.linux.ssh_api import SSHApiFactory
 
-import cStringIO
 import logging
 
-class Application(ResourceManager):
-    def __init__(self, box, ec):
-        super(Application, self).__init__(box, ec)
-        self.command = None
-        self.pid = None
-        self.ppid = None
-        self.stdin = None
-        self.del_app_home = True
-        self.env = None
-        
-        self.app_home = "${HOME}/app-%s" % self.box.guid
+@clsinit
+class LinuxApplication(ResourceManager):
+    _rtype = "LinuxApplication"
+
+    @classmethod
+    def _register_attributes(cls):
+        command = Attribute("command", "Command to execute", 
+                flags = Flags.ReadOnly)
+        env = Attribute("env", "Environment variables string for command execution",
+                flags = Flags.ReadOnly)
+        sudo = Attribute("sudo", "Run with root privileges", 
+                flags = Flags.ReadOnly)
+        depends = Attribute("depends", 
+                "Space-separated list of packages required to run the application",
+                flags = Flags.ReadOnly)
+        sources = Attribute("sources", 
+                "Space-separated list of regular files to be deployed in the working "
+                "path prior to building. Archives won't be expanded automatically.",
+                flags = Flags.ReadOnly)
+        build = Attribute("build", 
+                "Build commands to execute after deploying the sources. "
+                "Sources will be in the ${SOURCES} folder. "
+                "Example: tar xzf ${SOURCES}/my-app.tgz && cd my-app && ./configure && make && make clean.\n"
+                "Try to make the commands return with a nonzero exit code on error.\n"
+                "Also, do not install any programs here, use the 'install' attribute. This will "
+                "help keep the built files constrained to the build folder (which may "
+                "not be the home folder), and will result in faster deployment. Also, "
+                "make sure to clean up temporary files, to reduce bandwidth usage between "
+                "nodes when transferring built packages.",
+                flags = Flags.ReadOnly)
+        install = Attribute("install", 
+                "Commands to transfer built files to their final destinations. "
+                "Sources will be in the initial working folder, and a special "
+                "tag ${SOURCES} can be used to reference the experiment's "
+                "home folder (where the application commands will run).\n"
+                "ALL sources and targets needed for execution must be copied there, "
+                "if building has been enabled.\n"
+                "That is, 'slave' nodes will not automatically get any source files. "
+                "'slave' nodes don't get build dependencies either, so if you need "
+                "make and other tools to install, be sure to provide them as "
+                "actual dependencies instead.",
+                flags = Flags.ReadOnly)
+        stdin = Attribute("stdin", "Standard input", flags = Flags.ReadOnly)
+        stdout = Attribute("stdout", "Standard output", flags = Flags.ReadOnly)
+        stderr = Attribute("stderr", "Standard error", flags = Flags.ReadOnly)
+
+        tear_down = Attribute("tearDown", "Bash script to be executed before
+                releasing the resource", flags = Flags.ReadOnly)
+
+        cls._register_attribute(command)
+        cls._register_attribute(env)
+        cls._register_attribute(sudo)
+        cls._register_attribute(depends)
+        cls._register_attribute(sources)
+        cls._register_attribute(build)
+        cls._register_attribute(install)
+        cls._register_attribute(stdin)
+        cls._register_attribute(stdout)
+        cls._register_attribute(stderr)
+        cls._register_attribute(tear_down)
+
+    def __init__(self, ec, guid):
+        super(LinuxApplication, self).__init__(ec, guid)
+        self._pid = None
+        self._ppid = None
+        self._home = "${HOME}/app-%s" % self.box.guid
         self._node = None
-       
-        # Logging
-        loglevel = "debug"
-        self._logger = logging.getLogger("neco.resources.base.Application.%s" % self.guid)
-        self._logger.setLevel(getattr(logging, loglevel.upper()))
+
+        self._logger = logging.getLogger("neco.linux.Application.%d" % guid)
+
+    @property
+    def api(self):
+        return self.node.api
 
     @property
     def node(self):
-        if self._node:
-            return self._node
+        self._node
 
-        # XXX: What if it is connected to more than one node?
-        resources = self.find_resources(exact_tags = [tags.NODE])
-        self._node = resources[0] is len(resources) == 1 else None
-        return self._node
+    @property
+    def home(self):
+        return self._home
 
-    def make_app_home(self):
-        self.node.mkdir(self.app_home)
+    @property
+    def pid(self):
+        return self._pid
 
-        if self.stdin:
-            self.node.upload(self.stdin, os.path.join(self.app_home, 'stdin'))
+    @property
+    def ppid(self):
+        return self._ppid
 
-    def cleanup(self):
-        self.kill()
+    def provision(self, filters = None):
+        # clean home
+        # upload
+        # build  
+        # Install stuff!!
+        pass
 
-    def run(self):
-        dst = os.path.join(self.app_home, "app.sh")
+    def start(self):
+        dst = os.path.join(self.home, "app.sh")
         
         # Create shell script with the command
         # This way, complex commands and scripts can be ran seamlessly
         # sync files
         cmd = ""
-        if self.env:
+        env = self.get("env")
+        if env:
             for envkey, envvals in env.iteritems():
                 for envval in envvals:
                     cmd += 'export %s=%s\n' % (envkey, envval)
 
-        cmd += self.command
-        self.node.upload(cmd, dst)
+        cmd += self.get("command")
+        self.api.upload(cmd, dst)
 
         command = 'bash ./app.sh'
-        stdin = 'stdin' if self.stdin else None
-        self.node.run(command, self.app_home, stdin = stdin)
-        self.pid, self.ppid = self.node.checkpid(self.app_home)
+        stdin = 'stdin' if self.get("stdin") else None
+        self.api.run(command, self.home, stdin = stdin)
+        self._pid, self._ppid = self.api.checkpid(self.app_home)
+
+    def stop(self):
+        self._state = ResourceState.STOPPED
+
+    def release(self):
+        tear_down = self.get("tearDown")
+        if tear_down:
+            self.api.execute(tear_down)
+
+        return self.api.kill(self.pid, self.ppid)
 
     def status(self):
-        return self.node.status(self.pid, self.ppid)
+        return self.api.status(self.pid, self.ppid)
+
+    def make_app_home(self):
+        self.api.mkdir(self.home)
+
+        stdin = self.get("stdin")
+        if stdin:
+            self.api.upload(stdin, os.path.join(self.home, 'stdin'))
+
+    def _validate_connection(self, guid):
+        # TODO: Validate!
+        return True
+        # XXX: What if it is connected to more than one node?
+        resources = self.find_resources(exact_tags = [tags.NODE])
+        self._node = resources[0] is len(resources) == 1 else None
+        return self._node
+
 
-    def kill(self):
-        return self.node.kill(self.pid, self.ppid)