blob: 2d992ac6ca45d024eaadf6e6d17f05e32ad7e3ea [file] [log] [blame]
mbligh65acae52008-04-24 20:21:55 +00001import re, os, sys, types, time, random
mbligh96cf0512008-04-17 15:25:38 +00002
3import common
4from autotest_lib.client.common_lib import global_config
5
mblighed4d6dd2008-02-27 16:49:43 +00006
mblighaea09602008-04-16 22:59:37 +00007class MySQLTooManyRows(Exception):
8 pass
9
mblighd5c33db2006-10-08 21:34:16 +000010
mblighaf25f062007-12-03 17:48:35 +000011class db_sql:
mbligh65acae52008-04-24 20:21:55 +000012 def __init__(self, debug=False, autocommit=True, host=None,
13 database=None, user=None, password=None):
mbligh8e1ab172007-09-13 17:29:56 +000014 self.debug = debug
mbligh432bad42007-10-09 19:56:07 +000015 self.autocommit = autocommit
mbligh96cf0512008-04-17 15:25:38 +000016
mbligh65acae52008-04-24 20:21:55 +000017 self._load_config(host, database, user, password)
mbligh96cf0512008-04-17 15:25:38 +000018
mbligh65acae52008-04-24 20:21:55 +000019 self.con = None
mbligh96cf0512008-04-17 15:25:38 +000020 self._init_db()
mblighd5c33db2006-10-08 21:34:16 +000021
mbligh8e1ab172007-09-13 17:29:56 +000022 # if not present, insert statuses
23 self.status_idx = {}
24 self.status_word = {}
mblighc82f2462007-10-02 19:19:17 +000025 status_rows = self.select('status_idx, word', 'status', None)
26 for s in status_rows:
mbligh97894452007-10-05 15:10:33 +000027 self.status_idx[s[1]] = s[0]
mblighc82f2462007-10-02 19:19:17 +000028 self.status_word[s[0]] = s[1]
mbligh048e1c92007-10-07 00:10:33 +000029
mbligh96cf0512008-04-17 15:25:38 +000030 machine_map = os.path.join(os.path.dirname(__file__),
31 'machines')
mbligh048e1c92007-10-07 00:10:33 +000032 if os.path.exists(machine_map):
33 self.machine_map = machine_map
mblighba9c54c2007-10-26 16:41:26 +000034 else:
35 self.machine_map = None
mbligh048e1c92007-10-07 00:10:33 +000036 self.machine_group = {}
37
mbligh8e1ab172007-09-13 17:29:56 +000038
mbligh65acae52008-04-24 20:21:55 +000039 def _load_config(self, host, database, user, password):
40 # grab the global config
41 get_value = global_config.global_config.get_config_value
42
43 # grab the host, database
44 if not host:
45 self.host = get_value("TKO", "host")
46 if not database:
47 self.database = get_value("TKO", "database")
48
49 # grab the user and password
50 if not user:
51 self.user = get_value("TKO", "user")
52 if not password:
53 self.password = get_value("TKO", "password")
54
55 # grab the timeout configuration
56 self.query_timeout = get_value("TKO", "query_timeout",
57 type=int, default=3600)
58 self.min_delay = get_value("TKO", "min_retry_delay", type=int,
59 default=20)
60 self.max_delay = get_value("TKO", "max_retry_delay", type=int,
61 default=60)
62
63
mbligh96cf0512008-04-17 15:25:38 +000064 def _init_db(self):
mbligh65acae52008-04-24 20:21:55 +000065 # make sure we clean up any existing connection
66 if self.con:
67 self.con.close()
68 self.con = None
69
mbligh96cf0512008-04-17 15:25:38 +000070 # create the db connection and cursor
71 self.con = self.connect(self.host, self.database,
72 self.user, self.password)
73 self.cur = self.con.cursor()
74
75
mbligh65acae52008-04-24 20:21:55 +000076 def _random_delay(self):
77 delay = random.randint(self.min_delay, self.max_delay)
78 time.sleep(delay)
79
80
mbligh96cf0512008-04-17 15:25:38 +000081 def _run_with_retry(self, function, *args, **dargs):
82 """Call function(*args, **dargs) until either it passes
83 without an operational error, or a timeout is reached. This
84 is intended for internal use with database functions, not
85 for generic use."""
86 OperationalError = _get_error_class("OperationalError")
mbligh65acae52008-04-24 20:21:55 +000087
mbligh96cf0512008-04-17 15:25:38 +000088 success = False
89 start_time = time.time()
90 while not success:
91 try:
92 result = function(*args, **dargs)
jadmanski60d4fa62008-05-06 22:49:41 +000093 except OperationalError, e:
94 self._log_operational_error(e)
mbligh96cf0512008-04-17 15:25:38 +000095 stop_time = time.time()
96 elapsed_time = stop_time - start_time
mbligh65acae52008-04-24 20:21:55 +000097 if elapsed_time > self.query_timeout:
mbligh96cf0512008-04-17 15:25:38 +000098 raise
99 else:
100 try:
mbligh65acae52008-04-24 20:21:55 +0000101 self._random_delay()
mbligh96cf0512008-04-17 15:25:38 +0000102 self._init_db()
jadmanski60d4fa62008-05-06 22:49:41 +0000103 except OperationalError, e:
104 self._log_operational_error(e)
mbligh96cf0512008-04-17 15:25:38 +0000105 else:
106 success = True
107 return result
108
109
jadmanski60d4fa62008-05-06 22:49:41 +0000110 def _log_operational_error(self, e):
111 msg = ("An operational error occured during a database "
112 "operation: %s" % str(e))
113 print >> sys.stderr, msg
114 sys.stderr.flush() # we want these msgs to show up immediately
115
116
mbligh8e1ab172007-09-13 17:29:56 +0000117 def dprint(self, value):
118 if self.debug:
mbligh83f63a02007-12-12 19:13:04 +0000119 sys.stdout.write('SQL: ' + str(value) + '\n')
mbligh8e1ab172007-09-13 17:29:56 +0000120
mblighd5c33db2006-10-08 21:34:16 +0000121
mbligh432bad42007-10-09 19:56:07 +0000122 def commit(self):
123 self.con.commit()
124
125
mblighe12b8612008-02-12 20:58:14 +0000126 def get_last_autonumber_value(self):
127 self.cur.execute('SELECT LAST_INSERT_ID()', [])
128 return self.cur.fetchall()[0][0]
129
130
mblighaea09602008-04-16 22:59:37 +0000131 def select(self, fields, table, where, wherein={},
132 distinct = False, group_by = None, max_rows = None):
mbligh608c3252007-08-31 13:53:00 +0000133 """\
mbligh12eebfa2008-01-03 02:01:53 +0000134 This selects all the fields requested from a
135 specific table with a particular where clause.
136 The where clause can either be a dictionary of
137 field=value pairs, a string, or a tuple of (string,
138 a list of values). The last option is what you
139 should use when accepting user input as it'll
140 protect you against sql injection attacks (if
141 all user data is placed in the array rather than
142 the raw SQL).
143
144 For example:
145 where = ("a = %s AND b = %s", ['val', 'val'])
146 is better than
147 where = "a = 'val' AND b = 'val'"
mbligh608c3252007-08-31 13:53:00 +0000148 """
mbligh31d29c42007-09-27 00:51:33 +0000149 cmd = ['select']
150 if distinct:
151 cmd.append('distinct')
152 cmd += [fields, 'from', table]
mbligh608c3252007-08-31 13:53:00 +0000153
mbligh31d29c42007-09-27 00:51:33 +0000154 values = []
mbligh414c69e2007-10-05 15:13:06 +0000155 if where and isinstance(where, types.DictionaryType):
mbligh12eebfa2008-01-03 02:01:53 +0000156 # key/value pairs (which should be equal)
mbligh53d14252007-09-12 16:33:14 +0000157 keys = [field + '=%s' for field in where.keys()]
158 values = [where[field] for field in where.keys()]
159
mbligh31d29c42007-09-27 00:51:33 +0000160 cmd.append(' where ' + ' and '.join(keys))
mbligh414c69e2007-10-05 15:13:06 +0000161 elif where and isinstance(where, types.StringTypes):
mbligh12eebfa2008-01-03 02:01:53 +0000162 # the exact string
mbligh414c69e2007-10-05 15:13:06 +0000163 cmd.append(' where ' + where)
mbligh12eebfa2008-01-03 02:01:53 +0000164 elif where and isinstance(where, types.TupleType):
165 # preformatted where clause + values
166 (sql, vals) = where
167 values = vals
168 cmd.append(' where (%s) ' % sql)
mbligh53d14252007-09-12 16:33:14 +0000169
mbligh12eebfa2008-01-03 02:01:53 +0000170 # TODO: this assumes there's a where clause...bad
mbligh85952b42007-12-07 16:28:33 +0000171 if wherein and isinstance(wherein, types.DictionaryType):
mbligh96cf0512008-04-17 15:25:38 +0000172 keys_in = ["%s in (%s) " % (field, ','.join(where))
173 for field, where in wherein.iteritems()]
mbligh85952b42007-12-07 16:28:33 +0000174 cmd.append(' and '+' and '.join(keys_in))
mbligh96cf0512008-04-17 15:25:38 +0000175
mbligh83f63a02007-12-12 19:13:04 +0000176 if group_by:
177 cmd.append(' GROUP BY ' + group_by)
178
179 self.dprint('%s %s' % (' '.join(cmd), values))
mbligh96cf0512008-04-17 15:25:38 +0000180
181 # create a re-runable function for executing the query
182 def exec_sql():
183 sql = ' '.join(cmd)
184 numRec = self.cur.execute(sql, values)
185 if max_rows != None and numRec > max_rows:
186 msg = 'Exceeded allowed number of records'
187 raise MySQLTooManyRows(msg)
188 return self.cur.fetchall()
189
190 # run the query, re-trying after operational errors
191 return self._run_with_retry(exec_sql)
mblighd5c33db2006-10-08 21:34:16 +0000192
mbligh056d0d32006-10-08 22:31:10 +0000193
mbligh414c69e2007-10-05 15:13:06 +0000194 def select_sql(self, fields, table, sql, values):
195 """\
196 select fields from table "sql"
197 """
198 cmd = 'select %s from %s %s' % (fields, table, sql)
199 self.dprint(cmd)
mbligh96cf0512008-04-17 15:25:38 +0000200
201 # create a -re-runable function for executing the query
202 def exec_sql():
203 self.cur.execute(cmd, values)
204 return self.cur.fetchall()
205
206 # run the query, re-trying after operational errors
207 return self._run_with_retry(exec_sql)
208
209
210 def _exec_sql_with_commit(self, sql, values, commit):
211 if self.autocommit:
212 # re-run the query until it succeeds
213 def exec_sql():
214 self.cur.execute(sql, values)
215 self.con.commit()
216 self._run_with_retry(exec_sql)
217 else:
218 # take one shot at running the query
219 self.cur.execute(sql, values)
220 if commit:
221 self.con.commit()
mbligh414c69e2007-10-05 15:13:06 +0000222
223
mbligh432bad42007-10-09 19:56:07 +0000224 def insert(self, table, data, commit = None):
mbligh608c3252007-08-31 13:53:00 +0000225 """\
226 'insert into table (keys) values (%s ... %s)', values
227
228 data:
229 dictionary of fields and data
230 """
231 fields = data.keys()
232 refs = ['%s' for field in fields]
233 values = [data[field] for field in fields]
234 cmd = 'insert into %s (%s) values (%s)' % \
235 (table, ','.join(fields), ','.join(refs))
mbligh96cf0512008-04-17 15:25:38 +0000236 self.dprint('%s %s' % (cmd, values))
mbligh53d14252007-09-12 16:33:14 +0000237
mbligh96cf0512008-04-17 15:25:38 +0000238 self._exec_sql_with_commit(cmd, values, commit)
mbligh608c3252007-08-31 13:53:00 +0000239
240
mbligh96b9a5a2007-11-24 19:32:20 +0000241 def delete(self, table, where, commit = None):
242 cmd = ['delete from', table]
243 if commit == None:
244 commit = self.autocommit
245 if where and isinstance(where, types.DictionaryType):
246 keys = [field + '=%s' for field in where.keys()]
247 values = [where[field] for field in where.keys()]
248 cmd += ['where', ' and '.join(keys)]
mbligh96cf0512008-04-17 15:25:38 +0000249 sql = ' '.join(cmd)
250 self.dprint('%s %s' % (sql, values))
251
252 self._exec_sql_with_commit(sql, values, commit)
253
mbligh96b9a5a2007-11-24 19:32:20 +0000254
mbligh432bad42007-10-09 19:56:07 +0000255 def update(self, table, data, where, commit = None):
mbligh414c69e2007-10-05 15:13:06 +0000256 """\
257 'update table set data values (%s ... %s) where ...'
258
259 data:
260 dictionary of fields and data
261 """
mbligh432bad42007-10-09 19:56:07 +0000262 if commit == None:
263 commit = self.autocommit
mbligh414c69e2007-10-05 15:13:06 +0000264 cmd = 'update %s ' % table
265 fields = data.keys()
266 data_refs = [field + '=%s' for field in fields]
267 data_values = [data[field] for field in fields]
268 cmd += ' set ' + ' and '.join(data_refs)
269
270 where_keys = [field + '=%s' for field in where.keys()]
271 where_values = [where[field] for field in where.keys()]
272 cmd += ' where ' + ' and '.join(where_keys)
273
mbligh96cf0512008-04-17 15:25:38 +0000274 values = data_values + where_values
275 print '%s %s' % (cmd, values)
276
277 self._exec_sql_with_commit(cmd, values, commit)
mbligh414c69e2007-10-05 15:13:06 +0000278
279
mbligh96b9a5a2007-11-24 19:32:20 +0000280 def delete_job(self, tag, commit = None):
281 job_idx = self.find_job(tag)
282 for test_idx in self.find_tests(job_idx):
283 where = {'test_idx' : test_idx}
284 self.delete('iteration_result', where)
285 self.delete('test_attributes', where)
286 where = {'job_idx' : job_idx}
287 self.delete('tests', where)
288 self.delete('jobs', where)
289
290
mbligh432bad42007-10-09 19:56:07 +0000291 def insert_job(self, tag, job, commit = None):
mbligh2aaeb672007-10-01 14:54:18 +0000292 job.machine_idx = self.lookup_machine(job.machine)
293 if not job.machine_idx:
mbligh7a41a862007-11-30 17:44:24 +0000294 job.machine_idx = self.insert_machine(job,
mbligh432bad42007-10-09 19:56:07 +0000295 commit=commit)
mblighb10a60f2007-10-17 00:03:32 +0000296 self.insert('jobs', {'tag':tag,
297 'label': job.label,
mbligh05067a32007-12-03 17:48:04 +0000298 'username': job.user,
mbligh26b992b2008-02-19 15:46:21 +0000299 'machine_idx': job.machine_idx,
300 'queued_time': job.queued_time,
301 'started_time': job.started_time,
302 'finished_time': job.finished_time},
mblighb10a60f2007-10-17 00:03:32 +0000303 commit=commit)
mblighe12b8612008-02-12 20:58:14 +0000304 job.index = self.get_last_autonumber_value()
mbligh608c3252007-08-31 13:53:00 +0000305 for test in job.tests:
mbligh432bad42007-10-09 19:56:07 +0000306 self.insert_test(job, test, commit=commit)
mbligh608c3252007-08-31 13:53:00 +0000307
mbligh96b9a5a2007-11-24 19:32:20 +0000308
mbligh432bad42007-10-09 19:56:07 +0000309 def insert_test(self, job, test, commit = None):
310 kver = self.insert_kernel(test.kernel, commit=commit)
mbligh608c3252007-08-31 13:53:00 +0000311 data = {'job_idx':job.index, 'test':test.testname,
mbligh2bd48872007-09-20 18:32:25 +0000312 'subdir':test.subdir, 'kernel_idx':kver,
mbligh8e1ab172007-09-13 17:29:56 +0000313 'status':self.status_idx[test.status],
mblighc2514542008-02-19 15:54:26 +0000314 'reason':test.reason, 'machine_idx':job.machine_idx,
mbligh9cae1502008-04-18 20:40:45 +0000315 'started_time': test.started_time,
mblighc2514542008-02-19 15:54:26 +0000316 'finished_time':test.finished_time}
mbligh432bad42007-10-09 19:56:07 +0000317 self.insert('tests', data, commit=commit)
mblighe12b8612008-02-12 20:58:14 +0000318 test_idx = self.get_last_autonumber_value()
mbligh2bd48872007-09-20 18:32:25 +0000319 data = { 'test_idx':test_idx }
320
321 for i in test.iterations:
322 data['iteration'] = i.index
323 for key in i.keyval:
324 data['attribute'] = key
325 data['value'] = i.keyval[key]
mbligh432bad42007-10-09 19:56:07 +0000326 self.insert('iteration_result',
327 data,
328 commit=commit)
mbligh96cf0512008-04-17 15:25:38 +0000329
330 for key, value in test.attributes.iteritems():
331 data = {'test_idx': test_idx, 'attribute': key, 'value': value}
332 self.insert('test_attributes', data, commit=commit)
mblighe9cf9d42007-08-31 08:56:00 +0000333
334
mbligh048e1c92007-10-07 00:10:33 +0000335 def read_machine_map(self):
336 self.machine_group = {}
337 for line in open(self.machine_map, 'r').readlines():
338 (machine, group) = line.split()
339 self.machine_group[machine] = group
340
341
mbligh7a41a862007-11-30 17:44:24 +0000342 def insert_machine(self, job, group = None, commit = None):
343 hostname = job.machine
mbligh048e1c92007-10-07 00:10:33 +0000344 if self.machine_map and not self.machine_group:
345 self.read_machine_map()
346
347 if not group:
348 group = self.machine_group.get(hostname, hostname)
mbligh7a41a862007-11-30 17:44:24 +0000349 if group == hostname and job.machine_owner:
350 group = job.machine_owner + '/' + hostname
351
mbligh432bad42007-10-09 19:56:07 +0000352 self.insert('machines',
353 { 'hostname' : hostname ,
mbligh7a41a862007-11-30 17:44:24 +0000354 'machine_group' : group ,
355 'owner' : job.machine_owner },
mbligh432bad42007-10-09 19:56:07 +0000356 commit=commit)
mblighe12b8612008-02-12 20:58:14 +0000357 return self.get_last_autonumber_value()
mbligh2aaeb672007-10-01 14:54:18 +0000358
359
360 def lookup_machine(self, hostname):
361 where = { 'hostname' : hostname }
362 rows = self.select('machine_idx', 'machines', where)
363 if rows:
364 return rows[0][0]
365 else:
366 return None
367
368
mbligh9bb92fe2007-09-12 15:54:23 +0000369 def lookup_kernel(self, kernel):
370 rows = self.select('kernel_idx', 'kernels',
mbligh048e1c92007-10-07 00:10:33 +0000371 {'kernel_hash':kernel.kernel_hash})
mbligh237bed32007-09-05 13:05:57 +0000372 if rows:
373 return rows[0][0]
374 else:
375 return None
mblighe9cf9d42007-08-31 08:56:00 +0000376
377
mbligh432bad42007-10-09 19:56:07 +0000378 def insert_kernel(self, kernel, commit = None):
mbligh9bb92fe2007-09-12 15:54:23 +0000379 kver = self.lookup_kernel(kernel)
mbligh237bed32007-09-05 13:05:57 +0000380 if kver:
381 return kver
apw7a7316b2008-02-21 17:42:05 +0000382
383 # If this kernel has any significant patches, append their hash
384 # as diferentiator.
385 printable = kernel.base
386 patch_count = 0
387 for patch in kernel.patches:
388 match = re.match(r'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$',
389 patch.reference)
390 if not match:
391 patch_count += 1
392
mbligh432bad42007-10-09 19:56:07 +0000393 self.insert('kernels',
394 {'base':kernel.base,
395 'kernel_hash':kernel.kernel_hash,
apw7a7316b2008-02-21 17:42:05 +0000396 'printable':printable},
mbligh432bad42007-10-09 19:56:07 +0000397 commit=commit)
mblighe12b8612008-02-12 20:58:14 +0000398 kver = self.get_last_autonumber_value()
apw7a7316b2008-02-21 17:42:05 +0000399
400 if patch_count > 0:
401 printable += ' p%d' % (kver)
402 self.update('kernels',
403 {'printable':printable},
404 {'kernel_idx':kver})
405
mbligh237bed32007-09-05 13:05:57 +0000406 for patch in kernel.patches:
mbligh432bad42007-10-09 19:56:07 +0000407 self.insert_patch(kver, patch, commit=commit)
mbligh237bed32007-09-05 13:05:57 +0000408 return kver
409
410
mbligh432bad42007-10-09 19:56:07 +0000411 def insert_patch(self, kver, patch, commit = None):
mbligh237bed32007-09-05 13:05:57 +0000412 print patch.reference
413 name = os.path.basename(patch.reference)[:80]
mbligh432bad42007-10-09 19:56:07 +0000414 self.insert('patches',
415 {'kernel_idx': kver,
416 'name':name,
417 'url':patch.reference,
418 'hash':patch.hash},
419 commit=commit)
mbligh056d0d32006-10-08 22:31:10 +0000420
mbligh048e1c92007-10-07 00:10:33 +0000421
mbligh2bd48872007-09-20 18:32:25 +0000422 def find_test(self, job_idx, subdir):
423 where = { 'job_idx':job_idx , 'subdir':subdir }
424 rows = self.select('test_idx', 'tests', where)
425 if rows:
426 return rows[0][0]
427 else:
428 return None
429
mbligh056d0d32006-10-08 22:31:10 +0000430
mbligh96b9a5a2007-11-24 19:32:20 +0000431 def find_tests(self, job_idx):
432 where = { 'job_idx':job_idx }
433 rows = self.select('test_idx', 'tests', where)
434 if rows:
435 return [row[0] for row in rows]
436 else:
mbligh2e57e7b2007-11-29 16:10:50 +0000437 return []
mbligh96b9a5a2007-11-24 19:32:20 +0000438
439
mbligh056d0d32006-10-08 22:31:10 +0000440 def find_job(self, tag):
mbligh608c3252007-08-31 13:53:00 +0000441 rows = self.select('job_idx', 'jobs', {'tag': tag})
442 if rows:
443 return rows[0][0]
444 else:
445 return None
mblighaf25f062007-12-03 17:48:35 +0000446
447
mbligh96cf0512008-04-17 15:25:38 +0000448def _get_db_type():
449 """Get the database type name to use from the global config."""
450 get_value = global_config.global_config.get_config_value
451 return "db_" + get_value("TKO", "db_type", default="mysql")
mblighaf25f062007-12-03 17:48:35 +0000452
mbligh96cf0512008-04-17 15:25:38 +0000453
454def _get_error_class(class_name):
455 """Retrieves the appropriate error class by name from the database
456 module."""
457 db_module = __import__("autotest_lib.tko." + _get_db_type(),
458 globals(), locals(), ["driver"])
459 return getattr(db_module.driver, class_name)
460
461
462def db(*args, **dargs):
463 """Creates an instance of the database class with the arguments
464 provided in args and dargs, using the database type specified by
465 the global configuration (defaulting to mysql)."""
466 db_type = _get_db_type()
467 db_module = __import__("autotest_lib.tko." + db_type, globals(),
468 locals(), [db_type])
469 db = getattr(db_module, db_type)(*args, **dargs)
mblighaf25f062007-12-03 17:48:35 +0000470 return db