efe2cf8ee3c27fe7f3b8ff7998173187d2ec7db9
[nepi.git] / src / nepi / testbeds / planetlab / application.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 from constants import TESTBED_ID
5 import plcapi
6 import operator
7 import os
8 import os.path
9 import nepi.util.server as server
10 import cStringIO
11 import subprocess
12 import rspawn
13
14 from nepi.util.constants import STATUS_NOT_STARTED, STATUS_RUNNING, \
15         STATUS_FINISHED
16
17 class Application(object):
18     def __init__(self, api=None):
19         if not api:
20             api = plcapi.PLCAPI()
21         self._api = api
22         
23         # Attributes
24         self.command = None
25         self.sudo = False
26         
27         self.build = None
28         self.depends = None
29         self.buildDepends = None
30         self.sources = None
31         
32         self.stdin = None
33         self.stdout = None
34         self.stderr = None
35         self.buildlog = None
36         
37         # Those are filled when the app is configured
38         self.home_path = None
39         self.ident_path = None
40         self.slicename = None
41         
42         # Those are filled when an actual node is connected
43         self.node = None
44         
45         # Those are filled when the app is started
46         #   Having both pid and ppid makes it harder
47         #   for pid rollover to induce tracking mistakes
48         self._started = False
49         self._pid = None
50         self._ppid = None
51     
52     def __str__(self):
53         return "%s<command:%s%s>" % (
54             self.__class__.__name__,
55             "sudo " if self.sudo else "",
56             self.command,
57         )
58     
59     def validate(self):
60         if self.home_path is None:
61             raise AssertionError, "Misconfigured application: missing home path"
62         if self.ident_path is None or not os.access(self.ident_path, os.R_OK):
63             raise AssertionError, "Misconfigured application: missing slice SSH key"
64         if self.node is None:
65             raise AssertionError, "Misconfigured application: unconnected node"
66         if self.node.hostname is None:
67             raise AssertionError, "Misconfigured application: misconfigured node"
68         if self.slicename is None:
69             raise AssertionError, "Misconfigured application: unspecified slice"
70
71     def start(self):
72         # Start process in a "daemonized" way, using nohup and heavy
73         # stdin/out redirection to avoid connection issues
74         (out,err),proc = rspawn.remote_spawn(
75             self.command,
76             
77             pidfile = './pid',
78             home = self.home_path,
79             stdin = 'stdin' if self.stdin is not None else '/dev/null',
80             stdout = 'stdout' if self.stdout else '/dev/null',
81             stderr = 'stderr' if self.stderr else '/dev/null',
82             sudo = self.sudo,
83             
84             host = self.node.hostname,
85             port = None,
86             user = self.slicename,
87             agent = None,
88             ident_key = self.ident_path
89             )
90         
91         if proc.wait():
92             raise RuntimeError, "Failed to set up application: %s %s" % (out,err,)
93
94         self._started = True
95
96     def checkpid(self):            
97         # Get PID/PPID
98         # NOTE: wait a bit for the pidfile to be created
99         if self._started and not self._pid or not self._ppid:
100             pidtuple = rspawn.remote_check_pid(
101                 os.path.join(self.home_path,'pid'),
102                 host = self.node.hostname,
103                 port = None,
104                 user = self.slicename,
105                 agent = None,
106                 ident_key = self.ident_path
107                 )
108             
109             if pidtuple:
110                 self._pid, self._ppid = pidtuple
111     
112     def status(self):
113         self.checkpid()
114         if not self._started:
115             return STATUS_NOT_STARTED
116         elif not self._pid or not self._ppid:
117             return STATUS_NOT_STARTED
118         else:
119             status = rspawn.remote_status(
120                 self._pid, self._ppid,
121                 host = self.node.hostname,
122                 port = None,
123                 user = self.slicename,
124                 agent = None,
125                 ident_key = self.ident_path
126                 )
127             
128             if status is rspawn.NOT_STARTED:
129                 return STATUS_NOT_STARTED
130             elif status is rspawn.RUNNING:
131                 return STATUS_RUNNING
132             elif status is rspawn.FINISHED:
133                 return STATUS_FINISHED
134             else:
135                 # WTF?
136                 return STATUS_NOT_STARTED
137     
138     def kill(self):
139         status = self.status()
140         if status == STATUS_RUNNING:
141             # kill by ppid+pid - SIGTERM first, then try SIGKILL
142             rspawn.remote_kill(
143                 self._pid, self._ppid,
144                 host = self.node.hostname,
145                 port = None,
146                 user = self.slicename,
147                 agent = None,
148                 ident_key = self.ident_path
149                 )
150     
151     def remote_trace_path(self, whichtrace):
152         if whichtrace in ('stdout','stderr'):
153             tracefile = os.path.join(self.home_path, whichtrace)
154         else:
155             tracefile = None
156         
157         return tracefile
158     
159     def sync_trace(self, local_dir, whichtrace):
160         tracefile = self.remote_trace_path(whichtrace)
161         if not tracefile:
162             return None
163         
164         local_path = os.path.join(local_dir, tracefile)
165         
166         # create parent local folders
167         proc = subprocess.Popen(
168             ["mkdir", "-p", os.path.dirname(local_path)],
169             stdout = open("/dev/null","w"),
170             stdin = open("/dev/null","r"))
171
172         if proc.wait():
173             raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
174         
175         # sync files
176         (out,err),proc = server.popen_scp(
177             '%s@%s:%s' % (self.slicename, self.node.hostname, 
178                 tracefile),
179             local_path,
180             port = None,
181             agent = None,
182             ident_key = self.ident_path
183             )
184         
185         if proc.wait():
186             raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
187         
188         return local_path
189     
190
191     def setup(self):
192         # Make sure all the paths are created where 
193         # they have to be created for deployment
194         (out,err),proc = server.popen_ssh_command(
195             "mkdir -p %s" % (server.shell_escape(self.home_path),),
196             host = self.node.hostname,
197             port = None,
198             user = self.slicename,
199             agent = None,
200             ident_key = self.ident_path
201             )
202         
203         if proc.wait():
204             raise RuntimeError, "Failed to set up application: %s %s" % (out,err,)
205         
206         
207         if self.stdin:
208             # Write program input
209             (out,err),proc = server.popen_scp(
210                 cStringIO.StringIO(self.stdin),
211                 '%s@%s:%s' % (self.slicename, self.node.hostname, 
212                     os.path.join(self.home_path, 'stdin') ),
213                 port = None,
214                 agent = None,
215                 ident_key = self.ident_path
216                 )
217             
218             if proc.wait():
219                 raise RuntimeError, "Failed to set up application: %s %s" % (out,err,)
220
221