blob: 7d2345b47c8261944e3a969ffae6d780dca3d6a5 [file] [log] [blame]
mbligh96cf0512008-04-17 15:25:38 +00001import re, os, sys, types, time
2
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:
mbligh2bb6dfd2008-01-10 16:38:30 +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
17 self.host = host
18 self.database = database
19 self.user = user
20 self.password = password
21
mblighed4d6dd2008-02-27 16:49:43 +000022 # grab the global config
23 c = global_config.global_config
mbligh96cf0512008-04-17 15:25:38 +000024
mblighed4d6dd2008-02-27 16:49:43 +000025 # grab the host, database
mbligh96cf0512008-04-17 15:25:38 +000026 if not self.host:
27 self.host = c.get_config_value("TKO", "host")
28 if not self.database:
29 self.database = c.get_config_value("TKO", "database")
30
mblighed4d6dd2008-02-27 16:49:43 +000031 # grab the user and password
mbligh96cf0512008-04-17 15:25:38 +000032 if not self.user:
33 self.user = c.get_config_value("TKO", "user")
34 if not self.password:
35 self.password = c.get_config_value("TKO", "password")
36
37 self._init_db()
mblighd5c33db2006-10-08 21:34:16 +000038
mbligh8e1ab172007-09-13 17:29:56 +000039 # if not present, insert statuses
40 self.status_idx = {}
41 self.status_word = {}
mblighc82f2462007-10-02 19:19:17 +000042 status_rows = self.select('status_idx, word', 'status', None)
43 for s in status_rows:
mbligh97894452007-10-05 15:10:33 +000044 self.status_idx[s[1]] = s[0]
mblighc82f2462007-10-02 19:19:17 +000045 self.status_word[s[0]] = s[1]
mbligh048e1c92007-10-07 00:10:33 +000046
mbligh96cf0512008-04-17 15:25:38 +000047 machine_map = os.path.join(os.path.dirname(__file__),
48 'machines')
mbligh048e1c92007-10-07 00:10:33 +000049 if os.path.exists(machine_map):
50 self.machine_map = machine_map
mblighba9c54c2007-10-26 16:41:26 +000051 else:
52 self.machine_map = None
mbligh048e1c92007-10-07 00:10:33 +000053 self.machine_group = {}
54
mbligh8e1ab172007-09-13 17:29:56 +000055
mbligh96cf0512008-04-17 15:25:38 +000056 def _init_db(self):
57 # create the db connection and cursor
58 self.con = self.connect(self.host, self.database,
59 self.user, self.password)
60 self.cur = self.con.cursor()
61
62
63 def _run_with_retry(self, function, *args, **dargs):
64 """Call function(*args, **dargs) until either it passes
65 without an operational error, or a timeout is reached. This
66 is intended for internal use with database functions, not
67 for generic use."""
68 OperationalError = _get_error_class("OperationalError")
69 # TODO: make this configurable
70 TIMEOUT = 3600 # one hour
71 success = False
72 start_time = time.time()
73 while not success:
74 try:
75 result = function(*args, **dargs)
76 except OperationalError:
77 stop_time = time.time()
78 elapsed_time = stop_time - start_time
79 if elapsed_time > TIMEOUT:
80 raise
81 else:
82 try:
83 self._init_db()
84 except OperationalError:
85 pass
86 else:
87 success = True
88 return result
89
90
mbligh8e1ab172007-09-13 17:29:56 +000091 def dprint(self, value):
92 if self.debug:
mbligh83f63a02007-12-12 19:13:04 +000093 sys.stdout.write('SQL: ' + str(value) + '\n')
mbligh8e1ab172007-09-13 17:29:56 +000094
mblighd5c33db2006-10-08 21:34:16 +000095
mbligh432bad42007-10-09 19:56:07 +000096 def commit(self):
97 self.con.commit()
98
99
mblighe12b8612008-02-12 20:58:14 +0000100 def get_last_autonumber_value(self):
101 self.cur.execute('SELECT LAST_INSERT_ID()', [])
102 return self.cur.fetchall()[0][0]
103
104
mblighaea09602008-04-16 22:59:37 +0000105 def select(self, fields, table, where, wherein={},
106 distinct = False, group_by = None, max_rows = None):
mbligh608c3252007-08-31 13:53:00 +0000107 """\
mbligh12eebfa2008-01-03 02:01:53 +0000108 This selects all the fields requested from a
109 specific table with a particular where clause.
110 The where clause can either be a dictionary of
111 field=value pairs, a string, or a tuple of (string,
112 a list of values). The last option is what you
113 should use when accepting user input as it'll
114 protect you against sql injection attacks (if
115 all user data is placed in the array rather than
116 the raw SQL).
117
118 For example:
119 where = ("a = %s AND b = %s", ['val', 'val'])
120 is better than
121 where = "a = 'val' AND b = 'val'"
mbligh608c3252007-08-31 13:53:00 +0000122 """
mbligh31d29c42007-09-27 00:51:33 +0000123 cmd = ['select']
124 if distinct:
125 cmd.append('distinct')
126 cmd += [fields, 'from', table]
mbligh608c3252007-08-31 13:53:00 +0000127
mbligh31d29c42007-09-27 00:51:33 +0000128 values = []
mbligh414c69e2007-10-05 15:13:06 +0000129 if where and isinstance(where, types.DictionaryType):
mbligh12eebfa2008-01-03 02:01:53 +0000130 # key/value pairs (which should be equal)
mbligh53d14252007-09-12 16:33:14 +0000131 keys = [field + '=%s' for field in where.keys()]
132 values = [where[field] for field in where.keys()]
133
mbligh31d29c42007-09-27 00:51:33 +0000134 cmd.append(' where ' + ' and '.join(keys))
mbligh414c69e2007-10-05 15:13:06 +0000135 elif where and isinstance(where, types.StringTypes):
mbligh12eebfa2008-01-03 02:01:53 +0000136 # the exact string
mbligh414c69e2007-10-05 15:13:06 +0000137 cmd.append(' where ' + where)
mbligh12eebfa2008-01-03 02:01:53 +0000138 elif where and isinstance(where, types.TupleType):
139 # preformatted where clause + values
140 (sql, vals) = where
141 values = vals
142 cmd.append(' where (%s) ' % sql)
mbligh53d14252007-09-12 16:33:14 +0000143
mbligh12eebfa2008-01-03 02:01:53 +0000144 # TODO: this assumes there's a where clause...bad
mbligh85952b42007-12-07 16:28:33 +0000145 if wherein and isinstance(wherein, types.DictionaryType):
mbligh96cf0512008-04-17 15:25:38 +0000146 keys_in = ["%s in (%s) " % (field, ','.join(where))
147 for field, where in wherein.iteritems()]
mbligh85952b42007-12-07 16:28:33 +0000148 cmd.append(' and '+' and '.join(keys_in))
mbligh96cf0512008-04-17 15:25:38 +0000149
mbligh83f63a02007-12-12 19:13:04 +0000150 if group_by:
151 cmd.append(' GROUP BY ' + group_by)
152
153 self.dprint('%s %s' % (' '.join(cmd), values))
mbligh96cf0512008-04-17 15:25:38 +0000154
155 # create a re-runable function for executing the query
156 def exec_sql():
157 sql = ' '.join(cmd)
158 numRec = self.cur.execute(sql, values)
159 if max_rows != None and numRec > max_rows:
160 msg = 'Exceeded allowed number of records'
161 raise MySQLTooManyRows(msg)
162 return self.cur.fetchall()
163
164 # run the query, re-trying after operational errors
165 return self._run_with_retry(exec_sql)
mblighd5c33db2006-10-08 21:34:16 +0000166
mbligh056d0d32006-10-08 22:31:10 +0000167
mbligh414c69e2007-10-05 15:13:06 +0000168 def select_sql(self, fields, table, sql, values):
169 """\
170 select fields from table "sql"
171 """
172 cmd = 'select %s from %s %s' % (fields, table, sql)
173 self.dprint(cmd)
mbligh96cf0512008-04-17 15:25:38 +0000174
175 # create a -re-runable function for executing the query
176 def exec_sql():
177 self.cur.execute(cmd, values)
178 return self.cur.fetchall()
179
180 # run the query, re-trying after operational errors
181 return self._run_with_retry(exec_sql)
182
183
184 def _exec_sql_with_commit(self, sql, values, commit):
185 if self.autocommit:
186 # re-run the query until it succeeds
187 def exec_sql():
188 self.cur.execute(sql, values)
189 self.con.commit()
190 self._run_with_retry(exec_sql)
191 else:
192 # take one shot at running the query
193 self.cur.execute(sql, values)
194 if commit:
195 self.con.commit()
mbligh414c69e2007-10-05 15:13:06 +0000196
197
mbligh432bad42007-10-09 19:56:07 +0000198 def insert(self, table, data, commit = None):
mbligh608c3252007-08-31 13:53:00 +0000199 """\
200 'insert into table (keys) values (%s ... %s)', values
201
202 data:
203 dictionary of fields and data
204 """
205 fields = data.keys()
206 refs = ['%s' for field in fields]
207 values = [data[field] for field in fields]
208 cmd = 'insert into %s (%s) values (%s)' % \
209 (table, ','.join(fields), ','.join(refs))
mbligh96cf0512008-04-17 15:25:38 +0000210 self.dprint('%s %s' % (cmd, values))
mbligh53d14252007-09-12 16:33:14 +0000211
mbligh96cf0512008-04-17 15:25:38 +0000212 self._exec_sql_with_commit(cmd, values, commit)
mbligh608c3252007-08-31 13:53:00 +0000213
214
mbligh96b9a5a2007-11-24 19:32:20 +0000215 def delete(self, table, where, commit = None):
216 cmd = ['delete from', table]
217 if commit == None:
218 commit = self.autocommit
219 if where and isinstance(where, types.DictionaryType):
220 keys = [field + '=%s' for field in where.keys()]
221 values = [where[field] for field in where.keys()]
222 cmd += ['where', ' and '.join(keys)]
mbligh96cf0512008-04-17 15:25:38 +0000223 sql = ' '.join(cmd)
224 self.dprint('%s %s' % (sql, values))
225
226 self._exec_sql_with_commit(sql, values, commit)
227
mbligh96b9a5a2007-11-24 19:32:20 +0000228
mbligh432bad42007-10-09 19:56:07 +0000229 def update(self, table, data, where, commit = None):
mbligh414c69e2007-10-05 15:13:06 +0000230 """\
231 'update table set data values (%s ... %s) where ...'
232
233 data:
234 dictionary of fields and data
235 """
mbligh432bad42007-10-09 19:56:07 +0000236 if commit == None:
237 commit = self.autocommit
mbligh414c69e2007-10-05 15:13:06 +0000238 cmd = 'update %s ' % table
239 fields = data.keys()
240 data_refs = [field + '=%s' for field in fields]
241 data_values = [data[field] for field in fields]
242 cmd += ' set ' + ' and '.join(data_refs)
243
244 where_keys = [field + '=%s' for field in where.keys()]
245 where_values = [where[field] for field in where.keys()]
246 cmd += ' where ' + ' and '.join(where_keys)
247
mbligh96cf0512008-04-17 15:25:38 +0000248 values = data_values + where_values
249 print '%s %s' % (cmd, values)
250
251 self._exec_sql_with_commit(cmd, values, commit)
mbligh414c69e2007-10-05 15:13:06 +0000252
253
mbligh96b9a5a2007-11-24 19:32:20 +0000254 def delete_job(self, tag, commit = None):
255 job_idx = self.find_job(tag)
256 for test_idx in self.find_tests(job_idx):
257 where = {'test_idx' : test_idx}
258 self.delete('iteration_result', where)
259 self.delete('test_attributes', where)
260 where = {'job_idx' : job_idx}
261 self.delete('tests', where)
262 self.delete('jobs', where)
263
264
mbligh432bad42007-10-09 19:56:07 +0000265 def insert_job(self, tag, job, commit = None):
mbligh2aaeb672007-10-01 14:54:18 +0000266 job.machine_idx = self.lookup_machine(job.machine)
267 if not job.machine_idx:
mbligh7a41a862007-11-30 17:44:24 +0000268 job.machine_idx = self.insert_machine(job,
mbligh432bad42007-10-09 19:56:07 +0000269 commit=commit)
mblighb10a60f2007-10-17 00:03:32 +0000270 self.insert('jobs', {'tag':tag,
271 'label': job.label,
mbligh05067a32007-12-03 17:48:04 +0000272 'username': job.user,
mbligh26b992b2008-02-19 15:46:21 +0000273 'machine_idx': job.machine_idx,
274 'queued_time': job.queued_time,
275 'started_time': job.started_time,
276 'finished_time': job.finished_time},
mblighb10a60f2007-10-17 00:03:32 +0000277 commit=commit)
mblighe12b8612008-02-12 20:58:14 +0000278 job.index = self.get_last_autonumber_value()
mbligh608c3252007-08-31 13:53:00 +0000279 for test in job.tests:
mbligh432bad42007-10-09 19:56:07 +0000280 self.insert_test(job, test, commit=commit)
mbligh608c3252007-08-31 13:53:00 +0000281
mbligh96b9a5a2007-11-24 19:32:20 +0000282
mbligh432bad42007-10-09 19:56:07 +0000283 def insert_test(self, job, test, commit = None):
284 kver = self.insert_kernel(test.kernel, commit=commit)
mbligh608c3252007-08-31 13:53:00 +0000285 data = {'job_idx':job.index, 'test':test.testname,
mbligh2bd48872007-09-20 18:32:25 +0000286 'subdir':test.subdir, 'kernel_idx':kver,
mbligh8e1ab172007-09-13 17:29:56 +0000287 'status':self.status_idx[test.status],
mblighc2514542008-02-19 15:54:26 +0000288 'reason':test.reason, 'machine_idx':job.machine_idx,
289 'finished_time':test.finished_time}
mbligh432bad42007-10-09 19:56:07 +0000290 self.insert('tests', data, commit=commit)
mblighe12b8612008-02-12 20:58:14 +0000291 test_idx = self.get_last_autonumber_value()
mbligh2bd48872007-09-20 18:32:25 +0000292 data = { 'test_idx':test_idx }
293
294 for i in test.iterations:
295 data['iteration'] = i.index
296 for key in i.keyval:
297 data['attribute'] = key
298 data['value'] = i.keyval[key]
mbligh432bad42007-10-09 19:56:07 +0000299 self.insert('iteration_result',
300 data,
301 commit=commit)
mbligh96cf0512008-04-17 15:25:38 +0000302
303 for key, value in test.attributes.iteritems():
304 data = {'test_idx': test_idx, 'attribute': key, 'value': value}
305 self.insert('test_attributes', data, commit=commit)
mblighe9cf9d42007-08-31 08:56:00 +0000306
307
mbligh048e1c92007-10-07 00:10:33 +0000308 def read_machine_map(self):
309 self.machine_group = {}
310 for line in open(self.machine_map, 'r').readlines():
311 (machine, group) = line.split()
312 self.machine_group[machine] = group
313
314
mbligh7a41a862007-11-30 17:44:24 +0000315 def insert_machine(self, job, group = None, commit = None):
316 hostname = job.machine
mbligh048e1c92007-10-07 00:10:33 +0000317 if self.machine_map and not self.machine_group:
318 self.read_machine_map()
319
320 if not group:
321 group = self.machine_group.get(hostname, hostname)
mbligh7a41a862007-11-30 17:44:24 +0000322 if group == hostname and job.machine_owner:
323 group = job.machine_owner + '/' + hostname
324
mbligh432bad42007-10-09 19:56:07 +0000325 self.insert('machines',
326 { 'hostname' : hostname ,
mbligh7a41a862007-11-30 17:44:24 +0000327 'machine_group' : group ,
328 'owner' : job.machine_owner },
mbligh432bad42007-10-09 19:56:07 +0000329 commit=commit)
mblighe12b8612008-02-12 20:58:14 +0000330 return self.get_last_autonumber_value()
mbligh2aaeb672007-10-01 14:54:18 +0000331
332
333 def lookup_machine(self, hostname):
334 where = { 'hostname' : hostname }
335 rows = self.select('machine_idx', 'machines', where)
336 if rows:
337 return rows[0][0]
338 else:
339 return None
340
341
mbligh9bb92fe2007-09-12 15:54:23 +0000342 def lookup_kernel(self, kernel):
343 rows = self.select('kernel_idx', 'kernels',
mbligh048e1c92007-10-07 00:10:33 +0000344 {'kernel_hash':kernel.kernel_hash})
mbligh237bed32007-09-05 13:05:57 +0000345 if rows:
346 return rows[0][0]
347 else:
348 return None
mblighe9cf9d42007-08-31 08:56:00 +0000349
350
mbligh432bad42007-10-09 19:56:07 +0000351 def insert_kernel(self, kernel, commit = None):
mbligh9bb92fe2007-09-12 15:54:23 +0000352 kver = self.lookup_kernel(kernel)
mbligh237bed32007-09-05 13:05:57 +0000353 if kver:
354 return kver
apw7a7316b2008-02-21 17:42:05 +0000355
356 # If this kernel has any significant patches, append their hash
357 # as diferentiator.
358 printable = kernel.base
359 patch_count = 0
360 for patch in kernel.patches:
361 match = re.match(r'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$',
362 patch.reference)
363 if not match:
364 patch_count += 1
365
mbligh432bad42007-10-09 19:56:07 +0000366 self.insert('kernels',
367 {'base':kernel.base,
368 'kernel_hash':kernel.kernel_hash,
apw7a7316b2008-02-21 17:42:05 +0000369 'printable':printable},
mbligh432bad42007-10-09 19:56:07 +0000370 commit=commit)
mblighe12b8612008-02-12 20:58:14 +0000371 kver = self.get_last_autonumber_value()
apw7a7316b2008-02-21 17:42:05 +0000372
373 if patch_count > 0:
374 printable += ' p%d' % (kver)
375 self.update('kernels',
376 {'printable':printable},
377 {'kernel_idx':kver})
378
mbligh237bed32007-09-05 13:05:57 +0000379 for patch in kernel.patches:
mbligh432bad42007-10-09 19:56:07 +0000380 self.insert_patch(kver, patch, commit=commit)
mbligh237bed32007-09-05 13:05:57 +0000381 return kver
382
383
mbligh432bad42007-10-09 19:56:07 +0000384 def insert_patch(self, kver, patch, commit = None):
mbligh237bed32007-09-05 13:05:57 +0000385 print patch.reference
386 name = os.path.basename(patch.reference)[:80]
mbligh432bad42007-10-09 19:56:07 +0000387 self.insert('patches',
388 {'kernel_idx': kver,
389 'name':name,
390 'url':patch.reference,
391 'hash':patch.hash},
392 commit=commit)
mbligh056d0d32006-10-08 22:31:10 +0000393
mbligh048e1c92007-10-07 00:10:33 +0000394
mbligh2bd48872007-09-20 18:32:25 +0000395 def find_test(self, job_idx, subdir):
396 where = { 'job_idx':job_idx , 'subdir':subdir }
397 rows = self.select('test_idx', 'tests', where)
398 if rows:
399 return rows[0][0]
400 else:
401 return None
402
mbligh056d0d32006-10-08 22:31:10 +0000403
mbligh96b9a5a2007-11-24 19:32:20 +0000404 def find_tests(self, job_idx):
405 where = { 'job_idx':job_idx }
406 rows = self.select('test_idx', 'tests', where)
407 if rows:
408 return [row[0] for row in rows]
409 else:
mbligh2e57e7b2007-11-29 16:10:50 +0000410 return []
mbligh96b9a5a2007-11-24 19:32:20 +0000411
412
mbligh056d0d32006-10-08 22:31:10 +0000413 def find_job(self, tag):
mbligh608c3252007-08-31 13:53:00 +0000414 rows = self.select('job_idx', 'jobs', {'tag': tag})
415 if rows:
416 return rows[0][0]
417 else:
418 return None
mblighaf25f062007-12-03 17:48:35 +0000419
420
mbligh96cf0512008-04-17 15:25:38 +0000421def _get_db_type():
422 """Get the database type name to use from the global config."""
423 get_value = global_config.global_config.get_config_value
424 return "db_" + get_value("TKO", "db_type", default="mysql")
mblighaf25f062007-12-03 17:48:35 +0000425
mbligh96cf0512008-04-17 15:25:38 +0000426
427def _get_error_class(class_name):
428 """Retrieves the appropriate error class by name from the database
429 module."""
430 db_module = __import__("autotest_lib.tko." + _get_db_type(),
431 globals(), locals(), ["driver"])
432 return getattr(db_module.driver, class_name)
433
434
435def db(*args, **dargs):
436 """Creates an instance of the database class with the arguments
437 provided in args and dargs, using the database type specified by
438 the global configuration (defaulting to mysql)."""
439 db_type = _get_db_type()
440 db_module = __import__("autotest_lib.tko." + db_type, globals(),
441 locals(), [db_type])
442 db = getattr(db_module, db_type)(*args, **dargs)
mblighaf25f062007-12-03 17:48:35 +0000443 return db