blob: ac4b8663eec7c0aca655afdee582341542432804 [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
jadmanskie7a69092008-05-29 21:03:13 +000081 def run_with_retry(self, function, *args, **dargs):
mbligh96cf0512008-04-17 15:25:38 +000082 """Call function(*args, **dargs) until either it passes
jadmanskie7a69092008-05-29 21:03:13 +000083 without an operational error, or a timeout is reached.
84 This will re-connect to the database, so it is NOT safe
85 to use this inside of a database transaction.
86
87 It can be safely used with transactions, but the
88 transaction start & end must be completely contained
89 within the call to 'function'."""
mbligh96cf0512008-04-17 15:25:38 +000090 OperationalError = _get_error_class("OperationalError")
mbligh65acae52008-04-24 20:21:55 +000091
mbligh96cf0512008-04-17 15:25:38 +000092 success = False
93 start_time = time.time()
94 while not success:
95 try:
96 result = function(*args, **dargs)
jadmanski60d4fa62008-05-06 22:49:41 +000097 except OperationalError, e:
98 self._log_operational_error(e)
mbligh96cf0512008-04-17 15:25:38 +000099 stop_time = time.time()
100 elapsed_time = stop_time - start_time
mbligh65acae52008-04-24 20:21:55 +0000101 if elapsed_time > self.query_timeout:
mbligh96cf0512008-04-17 15:25:38 +0000102 raise
103 else:
104 try:
mbligh65acae52008-04-24 20:21:55 +0000105 self._random_delay()
mbligh96cf0512008-04-17 15:25:38 +0000106 self._init_db()
jadmanski60d4fa62008-05-06 22:49:41 +0000107 except OperationalError, e:
108 self._log_operational_error(e)
mbligh96cf0512008-04-17 15:25:38 +0000109 else:
110 success = True
111 return result
112
113
jadmanski60d4fa62008-05-06 22:49:41 +0000114 def _log_operational_error(self, e):
115 msg = ("An operational error occured during a database "
116 "operation: %s" % str(e))
117 print >> sys.stderr, msg
118 sys.stderr.flush() # we want these msgs to show up immediately
119
120
mbligh8e1ab172007-09-13 17:29:56 +0000121 def dprint(self, value):
122 if self.debug:
mbligh83f63a02007-12-12 19:13:04 +0000123 sys.stdout.write('SQL: ' + str(value) + '\n')
mbligh8e1ab172007-09-13 17:29:56 +0000124
mblighd5c33db2006-10-08 21:34:16 +0000125
mbligh432bad42007-10-09 19:56:07 +0000126 def commit(self):
127 self.con.commit()
128
129
mblighe12b8612008-02-12 20:58:14 +0000130 def get_last_autonumber_value(self):
131 self.cur.execute('SELECT LAST_INSERT_ID()', [])
132 return self.cur.fetchall()[0][0]
133
134
mblighaea09602008-04-16 22:59:37 +0000135 def select(self, fields, table, where, wherein={},
136 distinct = False, group_by = None, max_rows = None):
mbligh608c3252007-08-31 13:53:00 +0000137 """\
mbligh12eebfa2008-01-03 02:01:53 +0000138 This selects all the fields requested from a
139 specific table with a particular where clause.
140 The where clause can either be a dictionary of
141 field=value pairs, a string, or a tuple of (string,
142 a list of values). The last option is what you
143 should use when accepting user input as it'll
144 protect you against sql injection attacks (if
145 all user data is placed in the array rather than
146 the raw SQL).
147
148 For example:
149 where = ("a = %s AND b = %s", ['val', 'val'])
150 is better than
151 where = "a = 'val' AND b = 'val'"
mbligh608c3252007-08-31 13:53:00 +0000152 """
mbligh31d29c42007-09-27 00:51:33 +0000153 cmd = ['select']
154 if distinct:
155 cmd.append('distinct')
156 cmd += [fields, 'from', table]
mbligh608c3252007-08-31 13:53:00 +0000157
mbligh31d29c42007-09-27 00:51:33 +0000158 values = []
mbligh414c69e2007-10-05 15:13:06 +0000159 if where and isinstance(where, types.DictionaryType):
mbligh12eebfa2008-01-03 02:01:53 +0000160 # key/value pairs (which should be equal)
mbligh53d14252007-09-12 16:33:14 +0000161 keys = [field + '=%s' for field in where.keys()]
162 values = [where[field] for field in where.keys()]
163
mbligh31d29c42007-09-27 00:51:33 +0000164 cmd.append(' where ' + ' and '.join(keys))
mbligh414c69e2007-10-05 15:13:06 +0000165 elif where and isinstance(where, types.StringTypes):
mbligh12eebfa2008-01-03 02:01:53 +0000166 # the exact string
mbligh414c69e2007-10-05 15:13:06 +0000167 cmd.append(' where ' + where)
mbligh12eebfa2008-01-03 02:01:53 +0000168 elif where and isinstance(where, types.TupleType):
169 # preformatted where clause + values
170 (sql, vals) = where
171 values = vals
172 cmd.append(' where (%s) ' % sql)
mbligh53d14252007-09-12 16:33:14 +0000173
mbligh12eebfa2008-01-03 02:01:53 +0000174 # TODO: this assumes there's a where clause...bad
mbligh85952b42007-12-07 16:28:33 +0000175 if wherein and isinstance(wherein, types.DictionaryType):
mbligh96cf0512008-04-17 15:25:38 +0000176 keys_in = ["%s in (%s) " % (field, ','.join(where))
177 for field, where in wherein.iteritems()]
mbligh85952b42007-12-07 16:28:33 +0000178 cmd.append(' and '+' and '.join(keys_in))
mbligh96cf0512008-04-17 15:25:38 +0000179
mbligh83f63a02007-12-12 19:13:04 +0000180 if group_by:
181 cmd.append(' GROUP BY ' + group_by)
182
183 self.dprint('%s %s' % (' '.join(cmd), values))
mbligh96cf0512008-04-17 15:25:38 +0000184
185 # create a re-runable function for executing the query
186 def exec_sql():
187 sql = ' '.join(cmd)
188 numRec = self.cur.execute(sql, values)
189 if max_rows != None and numRec > max_rows:
190 msg = 'Exceeded allowed number of records'
191 raise MySQLTooManyRows(msg)
192 return self.cur.fetchall()
193
194 # run the query, re-trying after operational errors
jadmanskie7a69092008-05-29 21:03:13 +0000195 if self.autocommit:
196 return self.run_with_retry(exec_sql)
197 else:
198 return exec_sql()
mblighd5c33db2006-10-08 21:34:16 +0000199
mbligh056d0d32006-10-08 22:31:10 +0000200
mbligh414c69e2007-10-05 15:13:06 +0000201 def select_sql(self, fields, table, sql, values):
202 """\
203 select fields from table "sql"
204 """
205 cmd = 'select %s from %s %s' % (fields, table, sql)
206 self.dprint(cmd)
mbligh96cf0512008-04-17 15:25:38 +0000207
208 # create a -re-runable function for executing the query
209 def exec_sql():
210 self.cur.execute(cmd, values)
211 return self.cur.fetchall()
212
213 # run the query, re-trying after operational errors
jadmanskie7a69092008-05-29 21:03:13 +0000214 if self.autocommit:
215 return self.run_with_retry(exec_sql)
216 else:
217 return exec_sql()
mbligh96cf0512008-04-17 15:25:38 +0000218
219
220 def _exec_sql_with_commit(self, sql, values, commit):
221 if self.autocommit:
222 # re-run the query until it succeeds
223 def exec_sql():
224 self.cur.execute(sql, values)
225 self.con.commit()
jadmanskie7a69092008-05-29 21:03:13 +0000226 self.run_with_retry(exec_sql)
mbligh96cf0512008-04-17 15:25:38 +0000227 else:
228 # take one shot at running the query
229 self.cur.execute(sql, values)
230 if commit:
231 self.con.commit()
mbligh414c69e2007-10-05 15:13:06 +0000232
233
mbligh432bad42007-10-09 19:56:07 +0000234 def insert(self, table, data, commit = None):
mbligh608c3252007-08-31 13:53:00 +0000235 """\
236 'insert into table (keys) values (%s ... %s)', values
237
238 data:
239 dictionary of fields and data
240 """
241 fields = data.keys()
242 refs = ['%s' for field in fields]
243 values = [data[field] for field in fields]
244 cmd = 'insert into %s (%s) values (%s)' % \
245 (table, ','.join(fields), ','.join(refs))
mbligh96cf0512008-04-17 15:25:38 +0000246 self.dprint('%s %s' % (cmd, values))
mbligh53d14252007-09-12 16:33:14 +0000247
mbligh96cf0512008-04-17 15:25:38 +0000248 self._exec_sql_with_commit(cmd, values, commit)
mbligh608c3252007-08-31 13:53:00 +0000249
250
mbligh96b9a5a2007-11-24 19:32:20 +0000251 def delete(self, table, where, commit = None):
252 cmd = ['delete from', table]
253 if commit == None:
254 commit = self.autocommit
255 if where and isinstance(where, types.DictionaryType):
256 keys = [field + '=%s' for field in where.keys()]
257 values = [where[field] for field in where.keys()]
258 cmd += ['where', ' and '.join(keys)]
mbligh96cf0512008-04-17 15:25:38 +0000259 sql = ' '.join(cmd)
260 self.dprint('%s %s' % (sql, values))
261
262 self._exec_sql_with_commit(sql, values, commit)
263
mbligh96b9a5a2007-11-24 19:32:20 +0000264
mbligh432bad42007-10-09 19:56:07 +0000265 def update(self, table, data, where, commit = None):
mbligh414c69e2007-10-05 15:13:06 +0000266 """\
267 'update table set data values (%s ... %s) where ...'
268
269 data:
270 dictionary of fields and data
271 """
mbligh432bad42007-10-09 19:56:07 +0000272 if commit == None:
273 commit = self.autocommit
mbligh414c69e2007-10-05 15:13:06 +0000274 cmd = 'update %s ' % table
275 fields = data.keys()
276 data_refs = [field + '=%s' for field in fields]
277 data_values = [data[field] for field in fields]
278 cmd += ' set ' + ' and '.join(data_refs)
279
280 where_keys = [field + '=%s' for field in where.keys()]
281 where_values = [where[field] for field in where.keys()]
282 cmd += ' where ' + ' and '.join(where_keys)
283
mbligh96cf0512008-04-17 15:25:38 +0000284 values = data_values + where_values
285 print '%s %s' % (cmd, values)
286
287 self._exec_sql_with_commit(cmd, values, commit)
mbligh414c69e2007-10-05 15:13:06 +0000288
289
mbligh96b9a5a2007-11-24 19:32:20 +0000290 def delete_job(self, tag, commit = None):
291 job_idx = self.find_job(tag)
292 for test_idx in self.find_tests(job_idx):
293 where = {'test_idx' : test_idx}
294 self.delete('iteration_result', where)
295 self.delete('test_attributes', where)
296 where = {'job_idx' : job_idx}
297 self.delete('tests', where)
298 self.delete('jobs', where)
299
300
mbligh432bad42007-10-09 19:56:07 +0000301 def insert_job(self, tag, job, commit = None):
mbligh2aaeb672007-10-01 14:54:18 +0000302 job.machine_idx = self.lookup_machine(job.machine)
303 if not job.machine_idx:
mbligh7a41a862007-11-30 17:44:24 +0000304 job.machine_idx = self.insert_machine(job,
mbligh432bad42007-10-09 19:56:07 +0000305 commit=commit)
mblighb10a60f2007-10-17 00:03:32 +0000306 self.insert('jobs', {'tag':tag,
307 'label': job.label,
mbligh05067a32007-12-03 17:48:04 +0000308 'username': job.user,
mbligh26b992b2008-02-19 15:46:21 +0000309 'machine_idx': job.machine_idx,
310 'queued_time': job.queued_time,
311 'started_time': job.started_time,
312 'finished_time': job.finished_time},
mblighb10a60f2007-10-17 00:03:32 +0000313 commit=commit)
mblighe12b8612008-02-12 20:58:14 +0000314 job.index = self.get_last_autonumber_value()
mbligh608c3252007-08-31 13:53:00 +0000315 for test in job.tests:
mbligh432bad42007-10-09 19:56:07 +0000316 self.insert_test(job, test, commit=commit)
mbligh608c3252007-08-31 13:53:00 +0000317
mbligh96b9a5a2007-11-24 19:32:20 +0000318
mbligh432bad42007-10-09 19:56:07 +0000319 def insert_test(self, job, test, commit = None):
320 kver = self.insert_kernel(test.kernel, commit=commit)
mbligh608c3252007-08-31 13:53:00 +0000321 data = {'job_idx':job.index, 'test':test.testname,
mbligh2bd48872007-09-20 18:32:25 +0000322 'subdir':test.subdir, 'kernel_idx':kver,
mbligh8e1ab172007-09-13 17:29:56 +0000323 'status':self.status_idx[test.status],
mblighc2514542008-02-19 15:54:26 +0000324 'reason':test.reason, 'machine_idx':job.machine_idx,
mbligh9cae1502008-04-18 20:40:45 +0000325 'started_time': test.started_time,
mblighc2514542008-02-19 15:54:26 +0000326 'finished_time':test.finished_time}
mbligh432bad42007-10-09 19:56:07 +0000327 self.insert('tests', data, commit=commit)
mblighe12b8612008-02-12 20:58:14 +0000328 test_idx = self.get_last_autonumber_value()
mbligh2bd48872007-09-20 18:32:25 +0000329 data = { 'test_idx':test_idx }
330
331 for i in test.iterations:
332 data['iteration'] = i.index
jadmanskicc549172008-05-21 18:11:51 +0000333 for key, value in i.attr_keyval.iteritems():
mbligh2bd48872007-09-20 18:32:25 +0000334 data['attribute'] = key
jadmanskicc549172008-05-21 18:11:51 +0000335 data['value'] = value
336 self.insert('iteration_attributes', data,
337 commit=commit)
338 for key, value in i.perf_keyval.iteritems():
339 data['attribute'] = key
340 data['value'] = value
341 self.insert('iteration_result', data,
342 commit=commit)
mbligh96cf0512008-04-17 15:25:38 +0000343
344 for key, value in test.attributes.iteritems():
jadmanskicc549172008-05-21 18:11:51 +0000345 data = {'test_idx': test_idx, 'attribute': key,
346 'value': value}
mbligh96cf0512008-04-17 15:25:38 +0000347 self.insert('test_attributes', data, commit=commit)
mblighe9cf9d42007-08-31 08:56:00 +0000348
349
mbligh048e1c92007-10-07 00:10:33 +0000350 def read_machine_map(self):
351 self.machine_group = {}
352 for line in open(self.machine_map, 'r').readlines():
353 (machine, group) = line.split()
354 self.machine_group[machine] = group
355
356
mbligh7a41a862007-11-30 17:44:24 +0000357 def insert_machine(self, job, group = None, commit = None):
358 hostname = job.machine
mbligh048e1c92007-10-07 00:10:33 +0000359 if self.machine_map and not self.machine_group:
360 self.read_machine_map()
361
362 if not group:
363 group = self.machine_group.get(hostname, hostname)
mbligh7a41a862007-11-30 17:44:24 +0000364 if group == hostname and job.machine_owner:
365 group = job.machine_owner + '/' + hostname
366
mbligh432bad42007-10-09 19:56:07 +0000367 self.insert('machines',
368 { 'hostname' : hostname ,
mbligh7a41a862007-11-30 17:44:24 +0000369 'machine_group' : group ,
370 'owner' : job.machine_owner },
mbligh432bad42007-10-09 19:56:07 +0000371 commit=commit)
mblighe12b8612008-02-12 20:58:14 +0000372 return self.get_last_autonumber_value()
mbligh2aaeb672007-10-01 14:54:18 +0000373
374
375 def lookup_machine(self, hostname):
376 where = { 'hostname' : hostname }
377 rows = self.select('machine_idx', 'machines', where)
378 if rows:
379 return rows[0][0]
380 else:
381 return None
382
383
mbligh9bb92fe2007-09-12 15:54:23 +0000384 def lookup_kernel(self, kernel):
385 rows = self.select('kernel_idx', 'kernels',
mbligh048e1c92007-10-07 00:10:33 +0000386 {'kernel_hash':kernel.kernel_hash})
mbligh237bed32007-09-05 13:05:57 +0000387 if rows:
388 return rows[0][0]
389 else:
390 return None
mblighe9cf9d42007-08-31 08:56:00 +0000391
392
mbligh432bad42007-10-09 19:56:07 +0000393 def insert_kernel(self, kernel, commit = None):
mbligh9bb92fe2007-09-12 15:54:23 +0000394 kver = self.lookup_kernel(kernel)
mbligh237bed32007-09-05 13:05:57 +0000395 if kver:
396 return kver
apw7a7316b2008-02-21 17:42:05 +0000397
398 # If this kernel has any significant patches, append their hash
399 # as diferentiator.
400 printable = kernel.base
401 patch_count = 0
402 for patch in kernel.patches:
403 match = re.match(r'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$',
404 patch.reference)
405 if not match:
406 patch_count += 1
407
mbligh432bad42007-10-09 19:56:07 +0000408 self.insert('kernels',
409 {'base':kernel.base,
410 'kernel_hash':kernel.kernel_hash,
apw7a7316b2008-02-21 17:42:05 +0000411 'printable':printable},
mbligh432bad42007-10-09 19:56:07 +0000412 commit=commit)
mblighe12b8612008-02-12 20:58:14 +0000413 kver = self.get_last_autonumber_value()
apw7a7316b2008-02-21 17:42:05 +0000414
415 if patch_count > 0:
416 printable += ' p%d' % (kver)
417 self.update('kernels',
418 {'printable':printable},
419 {'kernel_idx':kver})
420
mbligh237bed32007-09-05 13:05:57 +0000421 for patch in kernel.patches:
mbligh432bad42007-10-09 19:56:07 +0000422 self.insert_patch(kver, patch, commit=commit)
mbligh237bed32007-09-05 13:05:57 +0000423 return kver
424
425
mbligh432bad42007-10-09 19:56:07 +0000426 def insert_patch(self, kver, patch, commit = None):
mbligh237bed32007-09-05 13:05:57 +0000427 print patch.reference
428 name = os.path.basename(patch.reference)[:80]
mbligh432bad42007-10-09 19:56:07 +0000429 self.insert('patches',
430 {'kernel_idx': kver,
431 'name':name,
432 'url':patch.reference,
433 'hash':patch.hash},
434 commit=commit)
mbligh056d0d32006-10-08 22:31:10 +0000435
mbligh048e1c92007-10-07 00:10:33 +0000436
mbligh2bd48872007-09-20 18:32:25 +0000437 def find_test(self, job_idx, subdir):
438 where = { 'job_idx':job_idx , 'subdir':subdir }
439 rows = self.select('test_idx', 'tests', where)
440 if rows:
441 return rows[0][0]
442 else:
443 return None
444
mbligh056d0d32006-10-08 22:31:10 +0000445
mbligh96b9a5a2007-11-24 19:32:20 +0000446 def find_tests(self, job_idx):
447 where = { 'job_idx':job_idx }
448 rows = self.select('test_idx', 'tests', where)
449 if rows:
450 return [row[0] for row in rows]
451 else:
mbligh2e57e7b2007-11-29 16:10:50 +0000452 return []
mbligh96b9a5a2007-11-24 19:32:20 +0000453
454
mbligh056d0d32006-10-08 22:31:10 +0000455 def find_job(self, tag):
mbligh608c3252007-08-31 13:53:00 +0000456 rows = self.select('job_idx', 'jobs', {'tag': tag})
457 if rows:
458 return rows[0][0]
459 else:
460 return None
mblighaf25f062007-12-03 17:48:35 +0000461
462
mbligh96cf0512008-04-17 15:25:38 +0000463def _get_db_type():
464 """Get the database type name to use from the global config."""
465 get_value = global_config.global_config.get_config_value
466 return "db_" + get_value("TKO", "db_type", default="mysql")
mblighaf25f062007-12-03 17:48:35 +0000467
mbligh96cf0512008-04-17 15:25:38 +0000468
469def _get_error_class(class_name):
470 """Retrieves the appropriate error class by name from the database
471 module."""
472 db_module = __import__("autotest_lib.tko." + _get_db_type(),
473 globals(), locals(), ["driver"])
474 return getattr(db_module.driver, class_name)
475
476
477def db(*args, **dargs):
478 """Creates an instance of the database class with the arguments
479 provided in args and dargs, using the database type specified by
480 the global configuration (defaulting to mysql)."""
481 db_type = _get_db_type()
482 db_module = __import__("autotest_lib.tko." + db_type, globals(),
483 locals(), [db_type])
484 db = getattr(db_module, db_type)(*args, **dargs)
mblighaf25f062007-12-03 17:48:35 +0000485 return db