-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)