blob: c197ba55015efbd70b94149f3ee3331278835dfa [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
jadmanskicc549172008-05-21 18:11:51 +0000323 for key, value in i.attr_keyval.iteritems():
mbligh2bd48872007-09-20 18:32:25 +0000324 data['attribute'] = key
jadmanskicc549172008-05-21 18:11:51 +0000325 data['value'] = value
326 self.insert('iteration_attributes', data,
327 commit=commit)
328 for key, value in i.perf_keyval.iteritems():
329 data['attribute'] = key
330 data['value'] = value
331 self.insert('iteration_result', data,
332 commit=commit)
mbligh96cf0512008-04-17 15:25:38 +0000333
334 for key, value in test.attributes.iteritems():
jadmanskicc549172008-05-21 18:11:51 +0000335 data = {'test_idx': test_idx, 'attribute': key,
336 'value': value}
mbligh96cf0512008-04-17 15:25:38 +0000337 self.insert('test_attributes', data, commit=commit)
mblighe9cf9d42007-08-31 08:56:00 +0000338
339
mbligh048e1c92007-10-07 00:10:33 +0000340 def read_machine_map(self):
341 self.machine_group = {}
342 for line in open(self.machine_map, 'r').readlines():
343 (machine, group) = line.split()
344 self.machine_group[machine] = group
345
346
mbligh7a41a862007-11-30 17:44:24 +0000347 def insert_machine(self, job, group = None, commit = None):
348 hostname = job.machine
mbligh048e1c92007-10-07 00:10:33 +0000349 if self.machine_map and not self.machine_group:
350 self.read_machine_map()
351
352 if not group:
353 group = self.machine_group.get(hostname, hostname)
mbligh7a41a862007-11-30 17:44:24 +0000354 if group == hostname and job.machine_owner:
355 group = job.machine_owner + '/' + hostname
356
mbligh432bad42007-10-09 19:56:07 +0000357 self.insert('machines',
358 { 'hostname' : hostname ,
mbligh7a41a862007-11-30 17:44:24 +0000359 'machine_group' : group ,
360 'owner' : job.machine_owner },
mbligh432bad42007-10-09 19:56:07 +0000361 commit=commit)
mblighe12b8612008-02-12 20:58:14 +0000362 return self.get_last_autonumber_value()
mbligh2aaeb672007-10-01 14:54:18 +0000363
364
365 def lookup_machine(self, hostname):
366 where = { 'hostname' : hostname }
367 rows = self.select('machine_idx', 'machines', where)
368 if rows:
369 return rows[0][0]
370 else:
371 return None
372
373
mbligh9bb92fe2007-09-12 15:54:23 +0000374 def lookup_kernel(self, kernel):
375 rows = self.select('kernel_idx', 'kernels',
mbligh048e1c92007-10-07 00:10:33 +0000376 {'kernel_hash':kernel.kernel_hash})
mbligh237bed32007-09-05 13:05:57 +0000377 if rows:
378 return rows[0][0]
379 else:
380 return None
mblighe9cf9d42007-08-31 08:56:00 +0000381
382
mbligh432bad42007-10-09 19:56:07 +0000383 def insert_kernel(self, kernel, commit = None):
mbligh9bb92fe2007-09-12 15:54:23 +0000384 kver = self.lookup_kernel(kernel)
mbligh237bed32007-09-05 13:05:57 +0000385 if kver:
386 return kver
apw7a7316b2008-02-21 17:42:05 +0000387
388 # If this kernel has any significant patches, append their hash
389 # as diferentiator.
390 printable = kernel.base
391 patch_count = 0
392 for patch in kernel.patches:
393 match = re.match(r'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$',
394 patch.reference)
395 if not match:
396 patch_count += 1
397
mbligh432bad42007-10-09 19:56:07 +0000398 self.insert('kernels',
399 {'base':kernel.base,
400 'kernel_hash':kernel.kernel_hash,
apw7a7316b2008-02-21 17:42:05 +0000401 'printable':printable},
mbligh432bad42007-10-09 19:56:07 +0000402 commit=commit)
mblighe12b8612008-02-12 20:58:14 +0000403 kver = self.get_last_autonumber_value()
apw7a7316b2008-02-21 17:42:05 +0000404
405 if patch_count > 0:
406 printable += ' p%d' % (kver)
407 self.update('kernels',
408 {'printable':printable},
409 {'kernel_idx':kver})
410
mbligh237bed32007-09-05 13:05:57 +0000411 for patch in kernel.patches:
mbligh432bad42007-10-09 19:56:07 +0000412 self.insert_patch(kver, patch, commit=commit)
mbligh237bed32007-09-05 13:05:57 +0000413 return kver
414
415
mbligh432bad42007-10-09 19:56:07 +0000416 def insert_patch(self, kver, patch, commit = None):
mbligh237bed32007-09-05 13:05:57 +0000417 print patch.reference
418 name = os.path.basename(patch.reference)[:80]
mbligh432bad42007-10-09 19:56:07 +0000419 self.insert('patches',
420 {'kernel_idx': kver,
421 'name':name,
422 'url':patch.reference,
423 'hash':patch.hash},
424 commit=commit)
mbligh056d0d32006-10-08 22:31:10 +0000425
mbligh048e1c92007-10-07 00:10:33 +0000426
mbligh2bd48872007-09-20 18:32:25 +0000427 def find_test(self, job_idx, subdir):
428 where = { 'job_idx':job_idx , 'subdir':subdir }
429 rows = self.select('test_idx', 'tests', where)
430 if rows:
431 return rows[0][0]
432 else:
433 return None
434
mbligh056d0d32006-10-08 22:31:10 +0000435
mbligh96b9a5a2007-11-24 19:32:20 +0000436 def find_tests(self, job_idx):
437 where = { 'job_idx':job_idx }
438 rows = self.select('test_idx', 'tests', where)
439 if rows:
440 return [row[0] for row in rows]
441 else:
mbligh2e57e7b2007-11-29 16:10:50 +0000442 return []
mbligh96b9a5a2007-11-24 19:32:20 +0000443
444
mbligh056d0d32006-10-08 22:31:10 +0000445 def find_job(self, tag):
mbligh608c3252007-08-31 13:53:00 +0000446 rows = self.select('job_idx', 'jobs', {'tag': tag})
447 if rows:
448 return rows[0][0]
449 else:
450 return None
mblighaf25f062007-12-03 17:48:35 +0000451
452
mbligh96cf0512008-04-17 15:25:38 +0000453def _get_db_type():
454 """Get the database type name to use from the global config."""
455 get_value = global_config.global_config.get_config_value
456 return "db_" + get_value("TKO", "db_type", default="mysql")
mblighaf25f062007-12-03 17:48:35 +0000457
mbligh96cf0512008-04-17 15:25:38 +0000458
459def _get_error_class(class_name):
460 """Retrieves the appropriate error class by name from the database
461 module."""
462 db_module = __import__("autotest_lib.tko." + _get_db_type(),
463 globals(), locals(), ["driver"])
464 return getattr(db_module.driver, class_name)
465
466
467def db(*args, **dargs):
468 """Creates an instance of the database class with the arguments
469 provided in args and dargs, using the database type specified by
470 the global configuration (defaulting to mysql)."""
471 db_type = _get_db_type()
472 db_module = __import__("autotest_lib.tko." + db_type, globals(),
473 locals(), [db_type])
474 db = getattr(db_module, db_type)(*args, **dargs)
mblighaf25f062007-12-03 17:48:35 +0000475 return db