blob: 656f26757ae335b231c014e25102bc193e467066 [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)
93 except OperationalError:
94 stop_time = time.time()
95 elapsed_time = stop_time - start_time
mbligh65acae52008-04-24 20:21:55 +000096 if elapsed_time > self.query_timeout:
mbligh96cf0512008-04-17 15:25:38 +000097 raise
98 else:
99 try:
mbligh65acae52008-04-24 20:21:55 +0000100 self._random_delay()
mbligh96cf0512008-04-17 15:25:38 +0000101 self._init_db()
102 except OperationalError:
103 pass
104 else:
105 success = True
106 return result
107
108
mbligh8e1ab172007-09-13 17:29:56 +0000109 def dprint(self, value):
110 if self.debug:
mbligh83f63a02007-12-12 19:13:04 +0000111 sys.stdout.write('SQL: ' + str(value) + '\n')
mbligh8e1ab172007-09-13 17:29:56 +0000112
mblighd5c33db2006-10-08 21:34:16 +0000113
mbligh432bad42007-10-09 19:56:07 +0000114 def commit(self):
115 self.con.commit()
116
117
mblighe12b8612008-02-12 20:58:14 +0000118 def get_last_autonumber_value(self):
119 self.cur.execute('SELECT LAST_INSERT_ID()', [])
120 return self.cur.fetchall()[0][0]
121
122
mblighaea09602008-04-16 22:59:37 +0000123 def select(self, fields, table, where, wherein={},
124 distinct = False, group_by = None, max_rows = None):
mbligh608c3252007-08-31 13:53:00 +0000125 """\
mbligh12eebfa2008-01-03 02:01:53 +0000126 This selects all the fields requested from a
127 specific table with a particular where clause.
128 The where clause can either be a dictionary of
129 field=value pairs, a string, or a tuple of (string,
130 a list of values). The last option is what you
131 should use when accepting user input as it'll
132 protect you against sql injection attacks (if
133 all user data is placed in the array rather than
134 the raw SQL).
135
136 For example:
137 where = ("a = %s AND b = %s", ['val', 'val'])
138 is better than
139 where = "a = 'val' AND b = 'val'"
mbligh608c3252007-08-31 13:53:00 +0000140 """
mbligh31d29c42007-09-27 00:51:33 +0000141 cmd = ['select']
142 if distinct:
143 cmd.append('distinct')
144 cmd += [fields, 'from', table]
mbligh608c3252007-08-31 13:53:00 +0000145
mbligh31d29c42007-09-27 00:51:33 +0000146 values = []
mbligh414c69e2007-10-05 15:13:06 +0000147 if where and isinstance(where, types.DictionaryType):
mbligh12eebfa2008-01-03 02:01:53 +0000148 # key/value pairs (which should be equal)
mbligh53d14252007-09-12 16:33:14 +0000149 keys = [field + '=%s' for field in where.keys()]
150 values = [where[field] for field in where.keys()]
151
mbligh31d29c42007-09-27 00:51:33 +0000152 cmd.append(' where ' + ' and '.join(keys))
mbligh414c69e2007-10-05 15:13:06 +0000153 elif where and isinstance(where, types.StringTypes):
mbligh12eebfa2008-01-03 02:01:53 +0000154 # the exact string
mbligh414c69e2007-10-05 15:13:06 +0000155 cmd.append(' where ' + where)
mbligh12eebfa2008-01-03 02:01:53 +0000156 elif where and isinstance(where, types.TupleType):
157 # preformatted where clause + values
158 (sql, vals) = where
159 values = vals
160 cmd.append(' where (%s) ' % sql)
mbligh53d14252007-09-12 16:33:14 +0000161
mbligh12eebfa2008-01-03 02:01:53 +0000162 # TODO: this assumes there's a where clause...bad
mbligh85952b42007-12-07 16:28:33 +0000163 if wherein and isinstance(wherein, types.DictionaryType):
mbligh96cf0512008-04-17 15:25:38 +0000164 keys_in = ["%s in (%s) " % (field, ','.join(where))
165 for field, where in wherein.iteritems()]
mbligh85952b42007-12-07 16:28:33 +0000166 cmd.append(' and '+' and '.join(keys_in))
mbligh96cf0512008-04-17 15:25:38 +0000167
mbligh83f63a02007-12-12 19:13:04 +0000168 if group_by:
169 cmd.append(' GROUP BY ' + group_by)
170
171 self.dprint('%s %s' % (' '.join(cmd), values))
mbligh96cf0512008-04-17 15:25:38 +0000172
173 # create a re-runable function for executing the query
174 def exec_sql():
175 sql = ' '.join(cmd)
176 numRec = self.cur.execute(sql, values)
177 if max_rows != None and numRec > max_rows:
178 msg = 'Exceeded allowed number of records'
179 raise MySQLTooManyRows(msg)
180 return self.cur.fetchall()
181
182 # run the query, re-trying after operational errors
183 return self._run_with_retry(exec_sql)
mblighd5c33db2006-10-08 21:34:16 +0000184
mbligh056d0d32006-10-08 22:31:10 +0000185
mbligh414c69e2007-10-05 15:13:06 +0000186 def select_sql(self, fields, table, sql, values):
187 """\
188 select fields from table "sql"
189 """
190 cmd = 'select %s from %s %s' % (fields, table, sql)
191 self.dprint(cmd)
mbligh96cf0512008-04-17 15:25:38 +0000192
193 # create a -re-runable function for executing the query
194 def exec_sql():
195 self.cur.execute(cmd, values)
196 return self.cur.fetchall()
197
198 # run the query, re-trying after operational errors
199 return self._run_with_retry(exec_sql)
200
201
202 def _exec_sql_with_commit(self, sql, values, commit):
203 if self.autocommit:
204 # re-run the query until it succeeds
205 def exec_sql():
206 self.cur.execute(sql, values)
207 self.con.commit()
208 self._run_with_retry(exec_sql)
209 else:
210 # take one shot at running the query
211 self.cur.execute(sql, values)
212 if commit:
213 self.con.commit()
mbligh414c69e2007-10-05 15:13:06 +0000214
215
mbligh432bad42007-10-09 19:56:07 +0000216 def insert(self, table, data, commit = None):
mbligh608c3252007-08-31 13:53:00 +0000217 """\
218 'insert into table (keys) values (%s ... %s)', values
219
220 data:
221 dictionary of fields and data
222 """
223 fields = data.keys()
224 refs = ['%s' for field in fields]
225 values = [data[field] for field in fields]
226 cmd = 'insert into %s (%s) values (%s)' % \
227 (table, ','.join(fields), ','.join(refs))
mbligh96cf0512008-04-17 15:25:38 +0000228 self.dprint('%s %s' % (cmd, values))
mbligh53d14252007-09-12 16:33:14 +0000229
mbligh96cf0512008-04-17 15:25:38 +0000230 self._exec_sql_with_commit(cmd, values, commit)
mbligh608c3252007-08-31 13:53:00 +0000231
232
mbligh96b9a5a2007-11-24 19:32:20 +0000233 def delete(self, table, where, commit = None):
234 cmd = ['delete from', table]
235 if commit == None:
236 commit = self.autocommit
237 if where and isinstance(where, types.DictionaryType):
238 keys = [field + '=%s' for field in where.keys()]
239 values = [where[field] for field in where.keys()]
240 cmd += ['where', ' and '.join(keys)]
mbligh96cf0512008-04-17 15:25:38 +0000241 sql = ' '.join(cmd)
242 self.dprint('%s %s' % (sql, values))
243
244 self._exec_sql_with_commit(sql, values, commit)
245
mbligh96b9a5a2007-11-24 19:32:20 +0000246
mbligh432bad42007-10-09 19:56:07 +0000247 def update(self, table, data, where, commit = None):
mbligh414c69e2007-10-05 15:13:06 +0000248 """\
249 'update table set data values (%s ... %s) where ...'
250
251 data:
252 dictionary of fields and data
253 """
mbligh432bad42007-10-09 19:56:07 +0000254 if commit == None:
255 commit = self.autocommit
mbligh414c69e2007-10-05 15:13:06 +0000256 cmd = 'update %s ' % table
257 fields = data.keys()
258 data_refs = [field + '=%s' for field in fields]
259 data_values = [data[field] for field in fields]
260 cmd += ' set ' + ' and '.join(data_refs)
261
262 where_keys = [field + '=%s' for field in where.keys()]
263 where_values = [where[field] for field in where.keys()]
264 cmd += ' where ' + ' and '.join(where_keys)
265
mbligh96cf0512008-04-17 15:25:38 +0000266 values = data_values + where_values
267 print '%s %s' % (cmd, values)
268
269 self._exec_sql_with_commit(cmd, values, commit)
mbligh414c69e2007-10-05 15:13:06 +0000270
271
mbligh96b9a5a2007-11-24 19:32:20 +0000272 def delete_job(self, tag, commit = None):
273 job_idx = self.find_job(tag)
274 for test_idx in self.find_tests(job_idx):
275 where = {'test_idx' : test_idx}
276 self.delete('iteration_result', where)
277 self.delete('test_attributes', where)
278 where = {'job_idx' : job_idx}
279 self.delete('tests', where)
280 self.delete('jobs', where)
281
282
mbligh432bad42007-10-09 19:56:07 +0000283 def insert_job(self, tag, job, commit = None):
mbligh2aaeb672007-10-01 14:54:18 +0000284 job.machine_idx = self.lookup_machine(job.machine)
285 if not job.machine_idx:
mbligh7a41a862007-11-30 17:44:24 +0000286 job.machine_idx = self.insert_machine(job,
mbligh432bad42007-10-09 19:56:07 +0000287 commit=commit)
mblighb10a60f2007-10-17 00:03:32 +0000288 self.insert('jobs', {'tag':tag,
289 'label': job.label,
mbligh05067a32007-12-03 17:48:04 +0000290 'username': job.user,
mbligh26b992b2008-02-19 15:46:21 +0000291 'machine_idx': job.machine_idx,
292 'queued_time': job.queued_time,
293 'started_time': job.started_time,
294 'finished_time': job.finished_time},
mblighb10a60f2007-10-17 00:03:32 +0000295 commit=commit)
mblighe12b8612008-02-12 20:58:14 +0000296 job.index = self.get_last_autonumber_value()
mbligh608c3252007-08-31 13:53:00 +0000297 for test in job.tests:
mbligh432bad42007-10-09 19:56:07 +0000298 self.insert_test(job, test, commit=commit)
mbligh608c3252007-08-31 13:53:00 +0000299
mbligh96b9a5a2007-11-24 19:32:20 +0000300
mbligh432bad42007-10-09 19:56:07 +0000301 def insert_test(self, job, test, commit = None):
302 kver = self.insert_kernel(test.kernel, commit=commit)
mbligh608c3252007-08-31 13:53:00 +0000303 data = {'job_idx':job.index, 'test':test.testname,
mbligh2bd48872007-09-20 18:32:25 +0000304 'subdir':test.subdir, 'kernel_idx':kver,
mbligh8e1ab172007-09-13 17:29:56 +0000305 'status':self.status_idx[test.status],
mblighc2514542008-02-19 15:54:26 +0000306 'reason':test.reason, 'machine_idx':job.machine_idx,
mbligh9cae1502008-04-18 20:40:45 +0000307 'started_time': test.started_time,
mblighc2514542008-02-19 15:54:26 +0000308 'finished_time':test.finished_time}
mbligh432bad42007-10-09 19:56:07 +0000309 self.insert('tests', data, commit=commit)
mblighe12b8612008-02-12 20:58:14 +0000310 test_idx = self.get_last_autonumber_value()
mbligh2bd48872007-09-20 18:32:25 +0000311 data = { 'test_idx':test_idx }
312
313 for i in test.iterations:
314 data['iteration'] = i.index
315 for key in i.keyval:
316 data['attribute'] = key
317 data['value'] = i.keyval[key]
mbligh432bad42007-10-09 19:56:07 +0000318 self.insert('iteration_result',
319 data,
320 commit=commit)
mbligh96cf0512008-04-17 15:25:38 +0000321
322 for key, value in test.attributes.iteritems():
323 data = {'test_idx': test_idx, 'attribute': key, 'value': value}
324 self.insert('test_attributes', data, commit=commit)
mblighe9cf9d42007-08-31 08:56:00 +0000325
326
mbligh048e1c92007-10-07 00:10:33 +0000327 def read_machine_map(self):
328 self.machine_group = {}
329 for line in open(self.machine_map, 'r').readlines():
330 (machine, group) = line.split()
331 self.machine_group[machine] = group
332
333
mbligh7a41a862007-11-30 17:44:24 +0000334 def insert_machine(self, job, group = None, commit = None):
335 hostname = job.machine
mbligh048e1c92007-10-07 00:10:33 +0000336 if self.machine_map and not self.machine_group:
337 self.read_machine_map()
338
339 if not group:
340 group = self.machine_group.get(hostname, hostname)
mbligh7a41a862007-11-30 17:44:24 +0000341 if group == hostname and job.machine_owner:
342 group = job.machine_owner + '/' + hostname
343
mbligh432bad42007-10-09 19:56:07 +0000344 self.insert('machines',
345 { 'hostname' : hostname ,
mbligh7a41a862007-11-30 17:44:24 +0000346 'machine_group' : group ,
347 'owner' : job.machine_owner },
mbligh432bad42007-10-09 19:56:07 +0000348 commit=commit)
mblighe12b8612008-02-12 20:58:14 +0000349 return self.get_last_autonumber_value()
mbligh2aaeb672007-10-01 14:54:18 +0000350
351
352 def lookup_machine(self, hostname):
353 where = { 'hostname' : hostname }
354 rows = self.select('machine_idx', 'machines', where)
355 if rows:
356 return rows[0][0]
357 else:
358 return None
359
360
mbligh9bb92fe2007-09-12 15:54:23 +0000361 def lookup_kernel(self, kernel):
362 rows = self.select('kernel_idx', 'kernels',
mbligh048e1c92007-10-07 00:10:33 +0000363 {'kernel_hash':kernel.kernel_hash})
mbligh237bed32007-09-05 13:05:57 +0000364 if rows:
365 return rows[0][0]
366 else:
367 return None
mblighe9cf9d42007-08-31 08:56:00 +0000368
369
mbligh432bad42007-10-09 19:56:07 +0000370 def insert_kernel(self, kernel, commit = None):
mbligh9bb92fe2007-09-12 15:54:23 +0000371 kver = self.lookup_kernel(kernel)
mbligh237bed32007-09-05 13:05:57 +0000372 if kver:
373 return kver
apw7a7316b2008-02-21 17:42:05 +0000374
375 # If this kernel has any significant patches, append their hash
376 # as diferentiator.
377 printable = kernel.base
378 patch_count = 0
379 for patch in kernel.patches:
380 match = re.match(r'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$',
381 patch.reference)
382 if not match:
383 patch_count += 1
384
mbligh432bad42007-10-09 19:56:07 +0000385 self.insert('kernels',
386 {'base':kernel.base,
387 'kernel_hash':kernel.kernel_hash,
apw7a7316b2008-02-21 17:42:05 +0000388 'printable':printable},
mbligh432bad42007-10-09 19:56:07 +0000389 commit=commit)
mblighe12b8612008-02-12 20:58:14 +0000390 kver = self.get_last_autonumber_value()
apw7a7316b2008-02-21 17:42:05 +0000391
392 if patch_count > 0:
393 printable += ' p%d' % (kver)
394 self.update('kernels',
395 {'printable':printable},
396 {'kernel_idx':kver})
397
mbligh237bed32007-09-05 13:05:57 +0000398 for patch in kernel.patches:
mbligh432bad42007-10-09 19:56:07 +0000399 self.insert_patch(kver, patch, commit=commit)
mbligh237bed32007-09-05 13:05:57 +0000400 return kver
401
402
mbligh432bad42007-10-09 19:56:07 +0000403 def insert_patch(self, kver, patch, commit = None):
mbligh237bed32007-09-05 13:05:57 +0000404 print patch.reference
405 name = os.path.basename(patch.reference)[:80]
mbligh432bad42007-10-09 19:56:07 +0000406 self.insert('patches',
407 {'kernel_idx': kver,
408 'name':name,
409 'url':patch.reference,
410 'hash':patch.hash},
411 commit=commit)
mbligh056d0d32006-10-08 22:31:10 +0000412
mbligh048e1c92007-10-07 00:10:33 +0000413
mbligh2bd48872007-09-20 18:32:25 +0000414 def find_test(self, job_idx, subdir):
415 where = { 'job_idx':job_idx , 'subdir':subdir }
416 rows = self.select('test_idx', 'tests', where)
417 if rows:
418 return rows[0][0]
419 else:
420 return None
421
mbligh056d0d32006-10-08 22:31:10 +0000422
mbligh96b9a5a2007-11-24 19:32:20 +0000423 def find_tests(self, job_idx):
424 where = { 'job_idx':job_idx }
425 rows = self.select('test_idx', 'tests', where)
426 if rows:
427 return [row[0] for row in rows]
428 else:
mbligh2e57e7b2007-11-29 16:10:50 +0000429 return []
mbligh96b9a5a2007-11-24 19:32:20 +0000430
431
mbligh056d0d32006-10-08 22:31:10 +0000432 def find_job(self, tag):
mbligh608c3252007-08-31 13:53:00 +0000433 rows = self.select('job_idx', 'jobs', {'tag': tag})
434 if rows:
435 return rows[0][0]
436 else:
437 return None
mblighaf25f062007-12-03 17:48:35 +0000438
439
mbligh96cf0512008-04-17 15:25:38 +0000440def _get_db_type():
441 """Get the database type name to use from the global config."""
442 get_value = global_config.global_config.get_config_value
443 return "db_" + get_value("TKO", "db_type", default="mysql")
mblighaf25f062007-12-03 17:48:35 +0000444
mbligh96cf0512008-04-17 15:25:38 +0000445
446def _get_error_class(class_name):
447 """Retrieves the appropriate error class by name from the database
448 module."""
449 db_module = __import__("autotest_lib.tko." + _get_db_type(),
450 globals(), locals(), ["driver"])
451 return getattr(db_module.driver, class_name)
452
453
454def db(*args, **dargs):
455 """Creates an instance of the database class with the arguments
456 provided in args and dargs, using the database type specified by
457 the global configuration (defaulting to mysql)."""
458 db_type = _get_db_type()
459 db_module = __import__("autotest_lib.tko." + db_type, globals(),
460 locals(), [db_type])
461 db = getattr(db_module, db_type)(*args, **dargs)
mblighaf25f062007-12-03 17:48:35 +0000462 return db