blob: 495b970995759b524976585ba9c82e189359911e [file] [log] [blame]
mblighf1c52842007-10-16 15:21:38 +00001"""
2The main job wrapper for the server side.
3
4This is the core infrastructure. Derived from the client side job.py
5
6Copyright Martin J. Bligh, Andy Whitcroft 2007
7"""
8
9__author__ = """
10Martin J. Bligh <mbligh@google.com>
11Andy Whitcroft <apw@shadowen.org>
12"""
13
14import os, sys, re
15from utils import *
16
17preamble = """\
18import os, sys
19
20import errors, hosts, autotest, kvm
21import source_kernel, rpm_kernel, deb_kernel
22from subcommand import *
23from utils import run, get_tmp_dir, sh_escape
24
25"""
26
27client_wrapper = """
28at = autotest.Autotest()
29
30def run_client(machine):
31 host = hosts.SSHHost(machine)
32 at.run(control, host=host)
33
34if len(machines) > 1:
35 parallel_simple(run_client, machines)
36else:
37 run_client(machines[0])
38"""
39
40cleanup="""\
41def cleanup(machine):
42 host = hosts.SSHHost(machine, initialize=False)
43 host.reboot()
44
45parallel_simple(cleanup, machines)
46"""
47
48class server_job:
49 """The actual job against which we do everything.
50
51 Properties:
52 autodir
53 The top level autotest directory (/usr/local/autotest).
54 serverdir
55 <autodir>/server/
56 clientdir
57 <autodir>/client/
58 conmuxdir
59 <autodir>/conmux/
60 testdir
61 <autodir>/server/tests/
62 control
63 the control file for this job
64 """
65
66 def __init__(self, control, args, resultdir, tag, user, client=False):
67 """
68 control
69 The control file (pathname of)
70 args
71 args to pass to the control file
72 resultdir
73 where to throw the results
74 tag
75 tag for the job
76 user
77 Username for the job (email address)
78 client
79 True if a client-side control file
80 """
81 path = sys.modules['server_job'].__file__
82 self.autodir = os.path.abspath(os.path.join(path, '..'))
83 self.serverdir = os.path.join(self.autodir, 'server')
84 self.testdir = os.path.join(self.autodir, 'tests')
85 self.conmuxdir = os.path.join(self.autodir, 'conmux')
86 self.clientdir = os.path.join(self.autodir, 'client')
87 self.control = re.sub('\r\n', '\n', open(control, 'r').read())
88 self.resultdir = resultdir
89 if not os.path.exists(resultdir):
90 os.mkdir(resultdir)
91 self.tag = tag
92 self.user = user
93 self.args = args
94 self.client = client
95 self.record_prefix = ''
96
97 job_data = { 'tag' : tag, 'user' : user}
98 write_keyval(self.resultdir, job_data)
99
100
101 def run(self, machines, reboot = False, namespace = {}):
102 namespace['machines'] = machines
103 namespace['args'] = self.args
104 namespace['job'] = self
105
106 os.chdir(self.resultdir)
107 try:
108 if self.client:
109 namespace['control'] = self.control
110 open('control', 'w').write(self.control)
111 open('control.srv', 'w').write(client_wrapper)
112 server_control = client_wrapper
113 else:
114 open('control.srv', 'w').write(self.control)
115 server_control = self.control
116 print preamble
117 print server_control
118 exec(preamble + server_control, namespace, namespace)
119
120 finally:
121 if reboot and machines:
122 exec(preamble + cleanup, namespace, namespace)
123
124
125 def run_test(self, url, *args, **dargs):
126 """Summon a test object and run it.
127
128 tag
129 tag to add to testname
130 url
131 url of the test to run
132 """
133
134 if not url:
135 raise "Test name is invalid. Switched arguments?"
136 (group, testname) = test.testname(url)
137 tag = None
138 subdir = testname
139 if dargs.has_key('tag'):
140 tag = dargs['tag']
141 del dargs['tag']
142 if tag:
143 subdir += '.' + tag
144 try:
145 try:
146 self.__runtest(url, tag, args, dargs)
147 except Exception, detail:
148 self.record('FAIL', subdir, testname, \
149 detail.__str__())
150
151 raise
152 else:
153 self.record('GOOD', subdir, testname, \
154 'completed successfully')
155 except TestError:
156 return 0
157 except:
158 raise
159 else:
160 return 1
161
162
163 def run_group(self, function, *args, **dargs):
164 """\
165 function:
166 subroutine to run
167 *args:
168 arguments for the function
169 """
170
171 result = None
172 name = function.__name__
173
174 # Allow the tag for the group to be specified.
175 if dargs.has_key('tag'):
176 tag = dargs['tag']
177 del dargs['tag']
178 if tag:
179 name = tag
180
181 # if tag:
182 # name += '.' + tag
183 old_record_prefix = self.record_prefix
184 try:
185 try:
186 self.record('START', None, name)
187 self.record_prefix += '\t'
188 result = function(*args, **dargs)
189 self.record_prefix = old_record_prefix
190 self.record('END GOOD', None, name)
191 except:
192 self.record_prefix = old_record_prefix
193 self.record('END FAIL', None, name, format_error())
194 # We don't want to raise up an error higher if it's just
195 # a TestError - we want to carry on to other tests. Hence
196 # this outer try/except block.
197 except TestError:
198 pass
199 except:
200 raise TestError(name + ' failed\n' + format_error())
201
202 return result
203
204
205 def record(self, status_code, subdir, operation, status = ''):
206 """
207 Record job-level status
208
209 The intent is to make this file both machine parseable and
210 human readable. That involves a little more complexity, but
211 really isn't all that bad ;-)
212
213 Format is <status code>\t<subdir>\t<operation>\t<status>
214
215 status code: (GOOD|WARN|FAIL|ABORT)
216 or START
217 or END (GOOD|WARN|FAIL|ABORT)
218
219 subdir: MUST be a relevant subdirectory in the results,
220 or None, which will be represented as '----'
221
222 operation: description of what you ran (e.g. "dbench", or
223 "mkfs -t foobar /dev/sda9")
224
225 status: error message or "completed sucessfully"
226
227 ------------------------------------------------------------
228
229 Initial tabs indicate indent levels for grouping, and is
230 governed by self.record_prefix
231
232 multiline messages have secondary lines prefaced by a double
233 space (' ')
234 """
235
236 if subdir:
237 if re.match(r'[\n\t]', subdir):
238 raise "Invalid character in subdir string"
239 substr = subdir
240 else:
241 substr = '----'
242
243 if not re.match(r'(START|(END )?(GOOD|WARN|FAIL|ABORT))$', \
244 status_code):
245 raise "Invalid status code supplied: %s" % status_code
246 if re.match(r'[\n\t]', operation):
247 raise "Invalid character in operation string"
248 operation = operation.rstrip()
249 status = status.rstrip()
250 status = re.sub(r"\t", " ", status)
251 # Ensure any continuation lines are marked so we can
252 # detect them in the status file to ensure it is parsable.
253 status = re.sub(r"\n", "\n" + self.record_prefix + " ", status)
254
255 msg = '%s\t%s\t%s\t%s' %(status_code, substr, operation, status)
256
257 status_file = os.path.join(self.resultdir, 'status')
258 print status_file
259 print msg
260 open(status_file, "a").write(self.record_prefix + msg + "\n")
261 if subdir:
262 status_file = os.path.join(self.resultdir, subdir, 'status')
263 open(status_file, "a").write(msg + "\n")
264