befb22d22b861ea17ab6048253099bed916298d1
[nepi.git] / src / neco / resources / linux / application.py
1 from neco.execution.attribute import Attribute, Flags
2 from neco.execution.resource import ResourceManager, clsinit, ResourceState
3 from neco.resources.linux.ssh_api import SSHApiFactory
4
5 import logging
6
7 @clsinit
8 class LinuxApplication(ResourceManager):
9     _rtype = "LinuxApplication"
10
11     @classmethod
12     def _register_attributes(cls):
13         command = Attribute("command", "Command to execute", 
14                 flags = Flags.ReadOnly)
15         forward_x11 = Attribute("forwardX11", " Enables X11 forwarding for SSH connections", 
16                 flags = Flags.ReadOnly)
17         env = Attribute("env", "Environment variables string for command execution",
18                 flags = Flags.ReadOnly)
19         sudo = Attribute("sudo", "Run with root privileges", 
20                 flags = Flags.ReadOnly)
21         depends = Attribute("depends", 
22                 "Space-separated list of packages required to run the application",
23                 flags = Flags.ReadOnly)
24         sources = Attribute("sources", 
25                 "Space-separated list of regular files to be deployed in the working "
26                 "path prior to building. Archives won't be expanded automatically.",
27                 flags = Flags.ReadOnly)
28         build = Attribute("build", 
29                 "Build commands to execute after deploying the sources. "
30                 "Sources will be in the ${SOURCES} folder. "
31                 "Example: tar xzf ${SOURCES}/my-app.tgz && cd my-app && ./configure && make && make clean.\n"
32                 "Try to make the commands return with a nonzero exit code on error.\n"
33                 "Also, do not install any programs here, use the 'install' attribute. This will "
34                 "help keep the built files constrained to the build folder (which may "
35                 "not be the home folder), and will result in faster deployment. Also, "
36                 "make sure to clean up temporary files, to reduce bandwidth usage between "
37                 "nodes when transferring built packages.",
38                 flags = Flags.ReadOnly)
39         install = Attribute("install", 
40                 "Commands to transfer built files to their final destinations. "
41                 "Sources will be in the initial working folder, and a special "
42                 "tag ${SOURCES} can be used to reference the experiment's "
43                 "home folder (where the application commands will run).\n"
44                 "ALL sources and targets needed for execution must be copied there, "
45                 "if building has been enabled.\n"
46                 "That is, 'slave' nodes will not automatically get any source files. "
47                 "'slave' nodes don't get build dependencies either, so if you need "
48                 "make and other tools to install, be sure to provide them as "
49                 "actual dependencies instead.",
50                 flags = Flags.ReadOnly)
51         stdin = Attribute("stdin", "Standard input", flags = Flags.ReadOnly)
52         stdout = Attribute("stdout", "Standard output", flags = Flags.ReadOnly)
53         stderr = Attribute("stderr", "Standard error", flags = Flags.ReadOnly)
54
55         tear_down = Attribute("tearDown", "Bash script to be executed before
56                 releasing the resource", flags = Flags.ReadOnly)
57
58         cls._register_attribute(command)
59         cls._register_attribute(forward_x11)
60         cls._register_attribute(env)
61         cls._register_attribute(sudo)
62         cls._register_attribute(depends)
63         cls._register_attribute(sources)
64         cls._register_attribute(build)
65         cls._register_attribute(install)
66         cls._register_attribute(stdin)
67         cls._register_attribute(stdout)
68         cls._register_attribute(stderr)
69         cls._register_attribute(tear_down)
70
71     def __init__(self, ec, guid):
72         super(LinuxApplication, self).__init__(ec, guid)
73         self._pid = None
74         self._ppid = None
75         self._home = "app-%s" % self.box.guid
76         self._node = None
77
78         self._logger = logging.getLogger("neco.linux.Application.%d" % guid)
79
80     @property
81     def node(self):
82         self._node
83
84     @property
85     def home(self):
86         return self._home # + node home
87
88     @property
89     def pid(self):
90         return self._pid
91
92     @property
93     def ppid(self):
94         return self._ppid
95
96     def provision(self, filters = None):
97         # verify home hash or clean home
98         # upload sources
99         # build  
100         # Install stuff!!
101         # upload app command
102         pass
103
104     def deploy(self):
105         # Wait until node is associated and deployed
106         self.provision()
107         pass
108
109     def start(self):
110         dst = os.path.join(self.home, "app.sh")
111         
112         # Create shell script with the command
113         # This way, complex commands and scripts can be ran seamlessly
114         # sync files
115         cmd = ""
116         env = self.get("env")
117         if env:
118             for envkey, envvals in env.iteritems():
119                 for envval in envvals:
120                     cmd += 'export %s=%s\n' % (envkey, envval)
121
122         cmd += self.get("command")
123         self.api.upload(cmd, dst)
124
125         command = 'bash ./app.sh'
126         stdin = 'stdin' if self.get("stdin") else None
127         self.api.run(command, self.home, stdin = stdin)
128         self._pid, self._ppid = self.api.checkpid(self.app_home)
129
130     def stop(self):
131         # Kill
132         self._state = ResourceState.STOPPED
133
134     def release(self):
135         tear_down = self.get("tearDown")
136         if tear_down:
137             self.api.execute(tear_down)
138
139         return self.api.kill(self.pid, self.ppid)
140
141     def status(self):
142         return self.api.status(self.pid, self.ppid)
143
144     def make_app_home(self):
145         self.api.mkdir(self.home)
146
147         stdin = self.get("stdin")
148         if stdin:
149             self.api.upload(stdin, os.path.join(self.home, 'stdin'))
150
151     def _validate_connection(self, guid):
152         # TODO: Validate!
153         return True
154         # XXX: What if it is connected to more than one node?
155         resources = self.find_resources(exact_tags = [tags.NODE])
156         self._node = resources[0] if len(resources) == 1 else None
157         return self._node
158