blob: 5f4925d8392cdae9bf74590e842a41664ae7bd86 [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,
mbligh9cae1502008-04-18 20:40:45 +0000289 'started_time': test.started_time,
mblighc2514542008-02-19 15:54:26 +0000290 'finished_time':test.finished_time}
mbligh432bad42007-10-09 19:56:07 +0000291 self.insert('tests', data, commit=commit)
mblighe12b8612008-02-12 20:58:14 +0000292 test_idx = self.get_last_autonumber_value()
mbligh2bd48872007-09-20 18:32:25 +0000293 data = { 'test_idx':test_idx }
294
295 for i in test.iterations:
296 data['iteration'] = i.index
297 for key in i.keyval:
298 data['attribute'] = key
299 data['value'] = i.keyval[key]
mbligh432bad42007-10-09 19:56:07 +0000300 self.insert('iteration_result',
301 data,
302 commit=commit)
mbligh96cf0512008-04-17 15:25:38 +0000303
304 for key, value in test.attributes.iteritems():
305 data = {'test_idx': test_idx, 'attribute': key, 'value': value}
306 self.insert('test_attributes', data, commit=commit)
mblighe9cf9d42007-08-31 08:56:00 +0000307
308
mbligh048e1c92007-10-07 00:10:33 +0000309 def read_machine_map(self):
310 self.machine_group = {}
311 for line in open(self.machine_map, 'r').readlines():
312 (machine, group) = line.split()
313 self.machine_group[machine] = group
314
315
mbligh7a41a862007-11-30 17:44:24 +0000316 def insert_machine(self, job, group = None, commit = None):
317 hostname = job.machine
mbligh048e1c92007-10-07 00:10:33 +0000318 if self.machine_map and not self.machine_group:
319 self.read_machine_map()
320
321 if not group:
322 group = self.machine_group.get(hostname, hostname)
mbligh7a41a862007-11-30 17:44:24 +0000323 if group == hostname and job.machine_owner:
324 group = job.machine_owner + '/' + hostname
325
mbligh432bad42007-10-09 19:56:07 +0000326 self.insert('machines',
327 { 'hostname' : hostname ,
mbligh7a41a862007-11-30 17:44:24 +0000328 'machine_group' : group ,
329 'owner' : job.machine_owner },
mbligh432bad42007-10-09 19:56:07 +0000330 commit=commit)
mblighe12b8612008-02-12 20:58:14 +0000331 return self.get_last_autonumber_value()
mbligh2aaeb672007-10-01 14:54:18 +0000332
333
334 def lookup_machine(self, hostname):
335 where = { 'hostname' : hostname }
336 rows = self.select('machine_idx', 'machines', where)
337 if rows:
338 return rows[0][0]
339 else:
340 return None
341
342
mbligh9bb92fe2007-09-12 15:54:23 +0000343 def lookup_kernel(self, kernel):
344 rows = self.select('kernel_idx', 'kernels',
mbligh048e1c92007-10-07 00:10:33 +0000345 {'kernel_hash':kernel.kernel_hash})
mbligh237bed32007-09-05 13:05:57 +0000346 if rows:
347 return rows[0][0]
348 else:
349 return None
mblighe9cf9d42007-08-31 08:56:00 +0000350
351
mbligh432bad42007-10-09 19:56:07 +0000352 def insert_kernel(self, kernel, commit = None):
mbligh9bb92fe2007-09-12 15:54:23 +0000353 kver = self.lookup_kernel(kernel)
mbligh237bed32007-09-05 13:05:57 +0000354 if kver:
355 return kver
apw7a7316b2008-02-21 17:42:05 +0000356
357 # If this kernel has any significant patches, append their hash
358 # as diferentiator.
359 printable = kernel.base
360 patch_count = 0
361 for patch in kernel.patches:
362 match = re.match(r'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$',
363 patch.reference)
364 if not match:
365 patch_count += 1
366
mbligh432bad42007-10-09 19:56:07 +0000367 self.insert('kernels',
368 {'base':kernel.base,
369 'kernel_hash':kernel.kernel_hash,
apw7a7316b2008-02-21 17:42:05 +0000370 'printable':printable},
mbligh432bad42007-10-09 19:56:07 +0000371 commit=commit)
mblighe12b8612008-02-12 20:58:14 +0000372 kver = self.get_last_autonumber_value()
apw7a7316b2008-02-21 17:42:05 +0000373
374 if patch_count > 0:
375 printable += ' p%d' % (kver)
376 self.update('kernels',
377 {'printable':printable},
378 {'kernel_idx':kver})
379
mbligh237bed32007-09-05 13:05:57 +0000380 for patch in kernel.patches:
mbligh432bad42007-10-09 19:56:07 +0000381 self.insert_patch(kver, patch, commit=commit)
mbligh237bed32007-09-05 13:05:57 +0000382 return kver
383
384
mbligh432bad42007-10-09 19:56:07 +0000385 def insert_patch(self, kver, patch, commit = None):
mbligh237bed32007-09-05 13:05:57 +0000386 print patch.reference
387 name = os.path.basename(patch.reference)[:80]
mbligh432bad42007-10-09 19:56:07 +0000388 self.insert('patches',
389 {'kernel_idx': kver,
390 'name':name,
391 'url':patch.reference,
392 'hash':patch.hash},
393 commit=commit)
mbligh056d0d32006-10-08 22:31:10 +0000394
mbligh048e1c92007-10-07 00:10:33 +0000395
mbligh2bd48872007-09-20 18:32:25 +0000396 def find_test(self, job_idx, subdir):
397 where = { 'job_idx':job_idx , 'subdir':subdir }
398 rows = self.select('test_idx', 'tests', where)
399 if rows:
400 return rows[0][0]
401 else:
402 return None
403
mbligh056d0d32006-10-08 22:31:10 +0000404
mbligh96b9a5a2007-11-24 19:32:20 +0000405 def find_tests(self, job_idx):
406 where = { 'job_idx':job_idx }
407 rows = self.select('test_idx', 'tests', where)
408 if rows:
409 return [row[0] for row in rows]
410 else:
mbligh2e57e7b2007-11-29 16:10:50 +0000411 return []
mbligh96b9a5a2007-11-24 19:32:20 +0000412
413
mbligh056d0d32006-10-08 22:31:10 +0000414 def find_job(self, tag):
mbligh608c3252007-08-31 13:53:00 +0000415 rows = self.select('job_idx', 'jobs', {'tag': tag})
416 if rows:
417 return rows[0][0]
418 else:
419 return None
mblighaf25f062007-12-03 17:48:35 +0000420
421
mbligh96cf0512008-04-17 15:25:38 +0000422def _get_db_type():
423 """Get the database type name to use from the global config."""
424 get_value = global_config.global_config.get_config_value
425 return "db_" + get_value("TKO", "db_type", default="mysql")
mblighaf25f062007-12-03 17:48:35 +0000426
mbligh96cf0512008-04-17 15:25:38 +0000427
428def _get_error_class(class_name):
429 """Retrieves the appropriate error class by name from the database
430 module."""
431 db_module = __import__("autotest_lib.tko." + _get_db_type(),
432 globals(), locals(), ["driver"])
433 return getattr(db_module.driver, class_name)
434
435
436def db(*args, **dargs):
437 """Creates an instance of the database class with the arguments
438 provided in args and dargs, using the database type specified by
439 the global configuration (defaulting to mysql)."""
440 db_type = _get_db_type()
441 db_module = __import__("autotest_lib.tko." + db_type, globals(),
442 locals(), [db_type])
443 db = getattr(db_module, db_type)(*args, **dargs)
mblighaf25f062007-12-03 17:48:35 +0000444 return db