blob: 450c40a1d394040759fe5964fb507632a88ef383 [file] [log] [blame]
Jakob Juelich934f0dc2014-10-14 18:21:13 -07001# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Allen Li64692a32016-11-04 13:35:19 -07005import math
6import os
7import random
8import re
9import sys
10import time
mbligh96cf0512008-04-17 15:25:38 +000011
12import common
13from autotest_lib.client.common_lib import global_config
Dan Shie8e0c052015-09-01 00:27:27 -070014from autotest_lib.client.common_lib.cros.graphite import autotest_stats
Jakob Juelich934f0dc2014-10-14 18:21:13 -070015from autotest_lib.frontend import database_settings_helper
Dan Shi70647ca2015-07-16 22:52:35 -070016from autotest_lib.server import site_utils
jamesrena12b8a02010-06-16 23:28:23 +000017from autotest_lib.tko import utils
mbligh96cf0512008-04-17 15:25:38 +000018
mblighed4d6dd2008-02-27 16:49:43 +000019
mblighaea09602008-04-16 22:59:37 +000020class MySQLTooManyRows(Exception):
Michael Tang5f74ffd2016-10-31 10:34:53 -070021 """Too many records."""
jadmanski0afbb632008-06-06 21:10:57 +000022 pass
mblighaea09602008-04-16 22:59:37 +000023
mblighd5c33db2006-10-08 21:34:16 +000024
mbligh7636b3a2008-06-11 15:44:01 +000025class db_sql(object):
Michael Tang5f74ffd2016-10-31 10:34:53 -070026 """Data access."""
27
jadmanski0afbb632008-06-06 21:10:57 +000028 def __init__(self, debug=False, autocommit=True, host=None,
29 database=None, user=None, password=None):
30 self.debug = debug
31 self.autocommit = autocommit
32 self._load_config(host, database, user, password)
mbligh96cf0512008-04-17 15:25:38 +000033
jadmanski0afbb632008-06-06 21:10:57 +000034 self.con = None
35 self._init_db()
mblighd5c33db2006-10-08 21:34:16 +000036
jadmanski0afbb632008-06-06 21:10:57 +000037 # if not present, insert statuses
38 self.status_idx = {}
39 self.status_word = {}
showardeab66ce2009-12-23 00:03:56 +000040 status_rows = self.select('status_idx, word', 'tko_status', None)
jadmanski0afbb632008-06-06 21:10:57 +000041 for s in status_rows:
42 self.status_idx[s[1]] = s[0]
43 self.status_word[s[0]] = s[1]
mbligh048e1c92007-10-07 00:10:33 +000044
jadmanski0afbb632008-06-06 21:10:57 +000045 machine_map = os.path.join(os.path.dirname(__file__),
46 'machines')
47 if os.path.exists(machine_map):
48 self.machine_map = machine_map
49 else:
50 self.machine_map = None
51 self.machine_group = {}
mbligh048e1c92007-10-07 00:10:33 +000052
mbligh8e1ab172007-09-13 17:29:56 +000053
jadmanski0afbb632008-06-06 21:10:57 +000054 def _load_config(self, host, database, user, password):
Jakob Juelich934f0dc2014-10-14 18:21:13 -070055 """Loads configuration settings required to connect to the database.
56
57 This will try to connect to use the settings prefixed with global_db_.
58 If they do not exist, they un-prefixed settings will be used.
59
60 If parameters are supplied, these will be taken instead of the values
61 in global_config.
62
63 @param host: If set, this host will be used, if not, the host will be
64 retrieved from global_config.
65 @param database: If set, this database will be used, if not, the
66 database will be retrieved from global_config.
67 @param user: If set, this user will be used, if not, the
68 user will be retrieved from global_config.
69 @param password: If set, this password will be used, if not, the
70 password will be retrieved from global_config.
71 """
72 database_settings = database_settings_helper.get_global_db_config()
mbligh65acae52008-04-24 20:21:55 +000073
jadmanski0afbb632008-06-06 21:10:57 +000074 # grab the host, database
Jakob Juelich934f0dc2014-10-14 18:21:13 -070075 self.host = host or database_settings['HOST']
76 self.database = database or database_settings['NAME']
mbligh65acae52008-04-24 20:21:55 +000077
jadmanski0afbb632008-06-06 21:10:57 +000078 # grab the user and password
Jakob Juelich934f0dc2014-10-14 18:21:13 -070079 self.user = user or database_settings['USER']
80 self.password = password or database_settings['PASSWORD']
mbligh65acae52008-04-24 20:21:55 +000081
Michael Spang7a273472014-10-08 12:08:13 -040082 # grab the timeout configuration
Jakob Juelich934f0dc2014-10-14 18:21:13 -070083 self.query_timeout =(
84 database_settings.get('OPTIONS', {}).get('timeout', 3600))
85
86 # Using fallback to non-global in order to work without configuration
87 # overhead on non-shard instances.
88 get_value = global_config.global_config.get_config_value_with_fallback
Jakob Juelich475b82b2014-09-30 11:17:07 -070089 self.min_delay = get_value("AUTOTEST_WEB", "global_db_min_retry_delay",
Jakob Juelich934f0dc2014-10-14 18:21:13 -070090 "min_retry_delay", type=int, default=20)
Jakob Juelich475b82b2014-09-30 11:17:07 -070091 self.max_delay = get_value("AUTOTEST_WEB", "global_db_max_retry_delay",
Jakob Juelich934f0dc2014-10-14 18:21:13 -070092 "max_retry_delay", type=int, default=60)
mbligh65acae52008-04-24 20:21:55 +000093
Richard Barnette2468fbd2014-11-07 01:12:46 +000094 # TODO(beeps): Move this to django settings once we have routers.
95 # On test instances mysql connects through a different port. No point
96 # piping this through our entire infrastructure when it is only really
97 # used for testing; Ideally we would specify this through django
98 # settings and default it to the empty string so django will figure out
99 # the default based on the database backend (eg: mysql, 3306), but until
100 # we have database routers in place any django settings will apply to
101 # both tko and afe.
102 # The intended use of this port is to allow a testing shard vm to
103 # update the master vm's database with test results. Specifying
104 # and empty string will fallback to not even specifying the port
105 # to the backend in tko/db.py. Unfortunately this means retries
106 # won't work on the test cluster till we've migrated to routers.
107 self.port = global_config.global_config.get_config_value(
108 "AUTOTEST_WEB", "global_db_port", type=str, default='')
109
mbligh65acae52008-04-24 20:21:55 +0000110
jadmanski0afbb632008-06-06 21:10:57 +0000111 def _init_db(self):
112 # make sure we clean up any existing connection
113 if self.con:
114 self.con.close()
115 self.con = None
mbligh65acae52008-04-24 20:21:55 +0000116
Dan Shi8684b922015-10-06 13:29:18 -0700117 try:
118 # create the db connection and cursor
119 self.con = self.connect(self.host, self.database,
120 self.user, self.password, self.port)
121 except:
122 autotest_stats.Counter('tko_db_con_error').increment()
123 raise
jadmanski0afbb632008-06-06 21:10:57 +0000124 self.cur = self.con.cursor()
mbligh96cf0512008-04-17 15:25:38 +0000125
126
jadmanski0afbb632008-06-06 21:10:57 +0000127 def _random_delay(self):
128 delay = random.randint(self.min_delay, self.max_delay)
129 time.sleep(delay)
mbligh65acae52008-04-24 20:21:55 +0000130
131
jadmanski0afbb632008-06-06 21:10:57 +0000132 def run_with_retry(self, function, *args, **dargs):
133 """Call function(*args, **dargs) until either it passes
134 without an operational error, or a timeout is reached.
135 This will re-connect to the database, so it is NOT safe
136 to use this inside of a database transaction.
jadmanskie7a69092008-05-29 21:03:13 +0000137
jadmanski0afbb632008-06-06 21:10:57 +0000138 It can be safely used with transactions, but the
139 transaction start & end must be completely contained
Michael Tang5f74ffd2016-10-31 10:34:53 -0700140 within the call to 'function'.
141
142 @param function: The function to run with retry.
143 @param args: The arguments
144 @param dargs: The named arguments.
145 """
jadmanski0afbb632008-06-06 21:10:57 +0000146 OperationalError = _get_error_class("OperationalError")
mbligh65acae52008-04-24 20:21:55 +0000147
jadmanski0afbb632008-06-06 21:10:57 +0000148 success = False
149 start_time = time.time()
150 while not success:
151 try:
152 result = function(*args, **dargs)
153 except OperationalError, e:
154 self._log_operational_error(e)
155 stop_time = time.time()
156 elapsed_time = stop_time - start_time
157 if elapsed_time > self.query_timeout:
158 raise
159 else:
160 try:
161 self._random_delay()
162 self._init_db()
Dan Shie8e0c052015-09-01 00:27:27 -0700163 autotest_stats.Counter('tko_db_error').increment()
jadmanski0afbb632008-06-06 21:10:57 +0000164 except OperationalError, e:
165 self._log_operational_error(e)
166 else:
167 success = True
168 return result
mbligh96cf0512008-04-17 15:25:38 +0000169
170
jadmanski0afbb632008-06-06 21:10:57 +0000171 def _log_operational_error(self, e):
mbligh097407d2009-02-17 15:49:37 +0000172 msg = ("%s: An operational error occured during a database "
jadmanski5d4c27e2009-03-02 16:45:42 +0000173 "operation: %s" % (time.strftime("%X %x"), str(e)))
jadmanski0afbb632008-06-06 21:10:57 +0000174 print >> sys.stderr, msg
175 sys.stderr.flush() # we want these msgs to show up immediately
jadmanski60d4fa62008-05-06 22:49:41 +0000176
177
jadmanski0afbb632008-06-06 21:10:57 +0000178 def dprint(self, value):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700179 """Print out debug value.
180
181 @param value: The value to print out.
182 """
jadmanski0afbb632008-06-06 21:10:57 +0000183 if self.debug:
184 sys.stdout.write('SQL: ' + str(value) + '\n')
mbligh8e1ab172007-09-13 17:29:56 +0000185
mblighd5c33db2006-10-08 21:34:16 +0000186
Dan Shie8e0c052015-09-01 00:27:27 -0700187 def _commit(self):
188 """Private method for function commit to call for retry.
189 """
190 return self.con.commit()
191
192
jadmanski0afbb632008-06-06 21:10:57 +0000193 def commit(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700194 """Commit the sql transaction."""
Dan Shie8e0c052015-09-01 00:27:27 -0700195 if self.autocommit:
196 return self.run_with_retry(self._commit)
197 else:
198 return self._commit()
mbligh432bad42007-10-09 19:56:07 +0000199
200
Simran Basie129a962012-08-31 13:03:53 -0700201 def rollback(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700202 """Rollback the sql transaction."""
Simran Basie129a962012-08-31 13:03:53 -0700203 self.con.rollback()
204
205
jadmanski0afbb632008-06-06 21:10:57 +0000206 def get_last_autonumber_value(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700207 """Gets the last auto number.
208
209 @return: The last auto number.
210 """
jadmanski0afbb632008-06-06 21:10:57 +0000211 self.cur.execute('SELECT LAST_INSERT_ID()', [])
212 return self.cur.fetchall()[0][0]
mblighe12b8612008-02-12 20:58:14 +0000213
214
showardc1a98d12010-01-15 00:22:22 +0000215 def _quote(self, field):
216 return '`%s`' % field
217
218
219 def _where_clause(self, where):
220 if not where:
221 return '', []
222
223 if isinstance(where, dict):
224 # key/value pairs (which should be equal, or None for null)
225 keys, values = [], []
226 for field, value in where.iteritems():
227 quoted_field = self._quote(field)
228 if value is None:
229 keys.append(quoted_field + ' is null')
230 else:
231 keys.append(quoted_field + '=%s')
232 values.append(value)
233 where_clause = ' and '.join(keys)
234 elif isinstance(where, basestring):
235 # the exact string
236 where_clause = where
237 values = []
238 elif isinstance(where, tuple):
239 # preformatted where clause + values
240 where_clause, values = where
241 assert where_clause
242 else:
243 raise ValueError('Invalid "where" value: %r' % where)
244
245 return ' WHERE ' + where_clause, values
246
247
248
249 def select(self, fields, table, where, distinct=False, group_by=None,
250 max_rows=None):
jadmanski0afbb632008-06-06 21:10:57 +0000251 """\
252 This selects all the fields requested from a
253 specific table with a particular where clause.
254 The where clause can either be a dictionary of
255 field=value pairs, a string, or a tuple of (string,
256 a list of values). The last option is what you
257 should use when accepting user input as it'll
258 protect you against sql injection attacks (if
259 all user data is placed in the array rather than
260 the raw SQL).
mbligh12eebfa2008-01-03 02:01:53 +0000261
jadmanski0afbb632008-06-06 21:10:57 +0000262 For example:
263 where = ("a = %s AND b = %s", ['val', 'val'])
264 is better than
265 where = "a = 'val' AND b = 'val'"
Michael Tang5f74ffd2016-10-31 10:34:53 -0700266
267 @param fields: The list of selected fields string.
268 @param table: The name of the database table.
269 @param where: The where clause string.
270 @param distinct: If select distinct values.
271 @param group_by: Group by clause.
272 @param max_rows: unused.
jadmanski0afbb632008-06-06 21:10:57 +0000273 """
274 cmd = ['select']
275 if distinct:
276 cmd.append('distinct')
277 cmd += [fields, 'from', table]
mbligh608c3252007-08-31 13:53:00 +0000278
showardc1a98d12010-01-15 00:22:22 +0000279 where_clause, values = self._where_clause(where)
280 cmd.append(where_clause)
mbligh96cf0512008-04-17 15:25:38 +0000281
jadmanski0afbb632008-06-06 21:10:57 +0000282 if group_by:
283 cmd.append(' GROUP BY ' + group_by)
mbligh83f63a02007-12-12 19:13:04 +0000284
jadmanski0afbb632008-06-06 21:10:57 +0000285 self.dprint('%s %s' % (' '.join(cmd), values))
mbligh96cf0512008-04-17 15:25:38 +0000286
jadmanski0afbb632008-06-06 21:10:57 +0000287 # create a re-runable function for executing the query
288 def exec_sql():
Michael Tang5f74ffd2016-10-31 10:34:53 -0700289 """Exeuctes an the sql command."""
jadmanski0afbb632008-06-06 21:10:57 +0000290 sql = ' '.join(cmd)
291 numRec = self.cur.execute(sql, values)
mblighd876f452008-12-03 15:09:17 +0000292 if max_rows is not None and numRec > max_rows:
jadmanski0afbb632008-06-06 21:10:57 +0000293 msg = 'Exceeded allowed number of records'
294 raise MySQLTooManyRows(msg)
295 return self.cur.fetchall()
mbligh96cf0512008-04-17 15:25:38 +0000296
jadmanski0afbb632008-06-06 21:10:57 +0000297 # run the query, re-trying after operational errors
298 if self.autocommit:
299 return self.run_with_retry(exec_sql)
300 else:
301 return exec_sql()
mblighd5c33db2006-10-08 21:34:16 +0000302
mbligh056d0d32006-10-08 22:31:10 +0000303
jadmanski0afbb632008-06-06 21:10:57 +0000304 def select_sql(self, fields, table, sql, values):
305 """\
306 select fields from table "sql"
Michael Tang5f74ffd2016-10-31 10:34:53 -0700307
308 @param fields: The list of selected fields string.
309 @param table: The name of the database table.
310 @param sql: The sql string.
311 @param values: The sql string parameter values.
jadmanski0afbb632008-06-06 21:10:57 +0000312 """
313 cmd = 'select %s from %s %s' % (fields, table, sql)
314 self.dprint(cmd)
mbligh414c69e2007-10-05 15:13:06 +0000315
jadmanski0afbb632008-06-06 21:10:57 +0000316 # create a -re-runable function for executing the query
Michael Tang5f74ffd2016-10-31 10:34:53 -0700317 def _exec_sql():
jadmanski0afbb632008-06-06 21:10:57 +0000318 self.cur.execute(cmd, values)
319 return self.cur.fetchall()
mbligh96b9a5a2007-11-24 19:32:20 +0000320
jadmanski0afbb632008-06-06 21:10:57 +0000321 # run the query, re-trying after operational errors
322 if self.autocommit:
Michael Tang5f74ffd2016-10-31 10:34:53 -0700323 return self.run_with_retry(_exec_sql)
jadmanski0afbb632008-06-06 21:10:57 +0000324 else:
Michael Tang5f74ffd2016-10-31 10:34:53 -0700325 return _exec_sql()
mbligh96b9a5a2007-11-24 19:32:20 +0000326
mbligh608c3252007-08-31 13:53:00 +0000327
jadmanski0afbb632008-06-06 21:10:57 +0000328 def _exec_sql_with_commit(self, sql, values, commit):
329 if self.autocommit:
330 # re-run the query until it succeeds
Michael Tang5f74ffd2016-10-31 10:34:53 -0700331 def _exec_sql():
jadmanski0afbb632008-06-06 21:10:57 +0000332 self.cur.execute(sql, values)
333 self.con.commit()
Michael Tang5f74ffd2016-10-31 10:34:53 -0700334 self.run_with_retry(_exec_sql)
jadmanski0afbb632008-06-06 21:10:57 +0000335 else:
336 # take one shot at running the query
337 self.cur.execute(sql, values)
338 if commit:
339 self.con.commit()
mbligh96b9a5a2007-11-24 19:32:20 +0000340
mbligh2bd48872007-09-20 18:32:25 +0000341
jadmanskib591fba2008-09-10 16:19:22 +0000342 def insert(self, table, data, commit=None):
jadmanski0afbb632008-06-06 21:10:57 +0000343 """\
344 'insert into table (keys) values (%s ... %s)', values
mbligh96cf0512008-04-17 15:25:38 +0000345
jadmanski0afbb632008-06-06 21:10:57 +0000346 data:
347 dictionary of fields and data
Michael Tang5f74ffd2016-10-31 10:34:53 -0700348
349 @param table: The name of the table.
350 @param data: The insert data.
351 @param commit: If commit the transaction .
jadmanski0afbb632008-06-06 21:10:57 +0000352 """
353 fields = data.keys()
354 refs = ['%s' for field in fields]
355 values = [data[field] for field in fields]
showardc1a98d12010-01-15 00:22:22 +0000356 cmd = ('insert into %s (%s) values (%s)' %
357 (table, ','.join(self._quote(field) for field in fields),
358 ','.join(refs)))
jadmanski0afbb632008-06-06 21:10:57 +0000359 self.dprint('%s %s' % (cmd, values))
mblighe9cf9d42007-08-31 08:56:00 +0000360
jadmanski0afbb632008-06-06 21:10:57 +0000361 self._exec_sql_with_commit(cmd, values, commit)
mblighe9cf9d42007-08-31 08:56:00 +0000362
mbligh048e1c92007-10-07 00:10:33 +0000363
jadmanski0afbb632008-06-06 21:10:57 +0000364 def delete(self, table, where, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700365 """Delete entries.
366
367 @param table: The name of the table.
368 @param where: The where clause.
369 @param commit: If commit the transaction .
370 """
jadmanski0afbb632008-06-06 21:10:57 +0000371 cmd = ['delete from', table]
mblighd876f452008-12-03 15:09:17 +0000372 if commit is None:
jadmanski0afbb632008-06-06 21:10:57 +0000373 commit = self.autocommit
showardc1a98d12010-01-15 00:22:22 +0000374 where_clause, values = self._where_clause(where)
375 cmd.append(where_clause)
jadmanski0afbb632008-06-06 21:10:57 +0000376 sql = ' '.join(cmd)
377 self.dprint('%s %s' % (sql, values))
mbligh048e1c92007-10-07 00:10:33 +0000378
jadmanski0afbb632008-06-06 21:10:57 +0000379 self._exec_sql_with_commit(sql, values, commit)
mbligh048e1c92007-10-07 00:10:33 +0000380
mbligh7a41a862007-11-30 17:44:24 +0000381
jadmanski0afbb632008-06-06 21:10:57 +0000382 def update(self, table, data, where, commit = None):
383 """\
384 'update table set data values (%s ... %s) where ...'
mbligh2aaeb672007-10-01 14:54:18 +0000385
jadmanski0afbb632008-06-06 21:10:57 +0000386 data:
387 dictionary of fields and data
Michael Tang5f74ffd2016-10-31 10:34:53 -0700388
389 @param table: The name of the table.
390 @param data: The sql parameter values.
391 @param where: The where clause.
392 @param commit: If commit the transaction .
jadmanski0afbb632008-06-06 21:10:57 +0000393 """
mblighd876f452008-12-03 15:09:17 +0000394 if commit is None:
jadmanski0afbb632008-06-06 21:10:57 +0000395 commit = self.autocommit
396 cmd = 'update %s ' % table
397 fields = data.keys()
showardc1a98d12010-01-15 00:22:22 +0000398 data_refs = [self._quote(field) + '=%s' for field in fields]
jadmanski0afbb632008-06-06 21:10:57 +0000399 data_values = [data[field] for field in fields]
jadmanski74eebf32008-07-15 20:04:42 +0000400 cmd += ' set ' + ', '.join(data_refs)
mbligh2aaeb672007-10-01 14:54:18 +0000401
showardc1a98d12010-01-15 00:22:22 +0000402 where_clause, where_values = self._where_clause(where)
403 cmd += where_clause
mbligh2aaeb672007-10-01 14:54:18 +0000404
jadmanski0afbb632008-06-06 21:10:57 +0000405 values = data_values + where_values
jadmanski74eebf32008-07-15 20:04:42 +0000406 self.dprint('%s %s' % (cmd, values))
mbligh2aaeb672007-10-01 14:54:18 +0000407
jadmanski0afbb632008-06-06 21:10:57 +0000408 self._exec_sql_with_commit(cmd, values, commit)
mblighe9cf9d42007-08-31 08:56:00 +0000409
410
jadmanski0afbb632008-06-06 21:10:57 +0000411 def delete_job(self, tag, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700412 """Delete a tko job.
413
414 @param tag: The job tag.
415 @param commit: If commit the transaction .
416 """
jadmanski0afbb632008-06-06 21:10:57 +0000417 job_idx = self.find_job(tag)
418 for test_idx in self.find_tests(job_idx):
419 where = {'test_idx' : test_idx}
showardeab66ce2009-12-23 00:03:56 +0000420 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700421 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000422 self.delete('tko_iteration_attributes', where)
423 self.delete('tko_test_attributes', where)
424 self.delete('tko_test_labels_tests', {'test_id': test_idx})
jadmanski0afbb632008-06-06 21:10:57 +0000425 where = {'job_idx' : job_idx}
showardeab66ce2009-12-23 00:03:56 +0000426 self.delete('tko_tests', where)
427 self.delete('tko_jobs', where)
apw7a7316b2008-02-21 17:42:05 +0000428
apw7a7316b2008-02-21 17:42:05 +0000429
Dan Shi70647ca2015-07-16 22:52:35 -0700430 def insert_job(self, tag, job, parent_job_id=None, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700431 """Insert a tko job.
432
433 @param tag: The job tag.
434 @param job: The job object.
435 @param parent_job_id: The parent job id.
436 @param commit: If commit the transaction .
437 """
jadmanski0afbb632008-06-06 21:10:57 +0000438 job.machine_idx = self.lookup_machine(job.machine)
439 if not job.machine_idx:
showard71b94312009-08-20 23:40:02 +0000440 job.machine_idx = self.insert_machine(job, commit=commit)
Dan Shid77fc1b2015-10-13 17:34:50 -0700441 elif job.machine:
442 # Only try to update tko_machines record if machine is set. This
443 # prevents unnecessary db writes for suite jobs.
showard71b94312009-08-20 23:40:02 +0000444 self.update_machine_information(job, commit=commit)
445
jamesrena12b8a02010-06-16 23:28:23 +0000446 afe_job_id = utils.get_afe_job_id(tag)
showardc1c1caf2009-09-08 16:26:50 +0000447
showard0fec8a02009-12-04 01:19:54 +0000448 data = {'tag':tag,
449 'label': job.label,
450 'username': job.user,
451 'machine_idx': job.machine_idx,
452 'queued_time': job.queued_time,
453 'started_time': job.started_time,
454 'finished_time': job.finished_time,
Dan Shi70647ca2015-07-16 22:52:35 -0700455 'afe_job_id': afe_job_id,
456 'afe_parent_job_id': parent_job_id}
Michael Tang5f74ffd2016-10-31 10:34:53 -0700457 job.afe_job_id = afe_job_id
458 if parent_job_id:
459 job.afe_parent_job_id = str(parent_job_id)
Dan Shi70647ca2015-07-16 22:52:35 -0700460 if job.label:
461 label_info = site_utils.parse_job_name(job.label)
462 if label_info:
463 data['build'] = label_info.get('build', None)
Michael Tang5f74ffd2016-10-31 10:34:53 -0700464 job.build_version = data['build_version'] = label_info.get(
465 'build_version', None)
466 job.board = data['board'] = label_info.get('board', None)
467 job.suite = data['suite'] = label_info.get('suite', None)
468
469 # TODO(ntang): check job.index directly.
showard0fec8a02009-12-04 01:19:54 +0000470 is_update = hasattr(job, 'index')
471 if is_update:
showardeab66ce2009-12-23 00:03:56 +0000472 self.update('tko_jobs', data, {'job_idx': job.index}, commit=commit)
showard0fec8a02009-12-04 01:19:54 +0000473 else:
showardeab66ce2009-12-23 00:03:56 +0000474 self.insert('tko_jobs', data, commit=commit)
showard0fec8a02009-12-04 01:19:54 +0000475 job.index = self.get_last_autonumber_value()
showardc1a98d12010-01-15 00:22:22 +0000476 self.update_job_keyvals(job, commit=commit)
jadmanski0afbb632008-06-06 21:10:57 +0000477 for test in job.tests:
478 self.insert_test(job, test, commit=commit)
apw7a7316b2008-02-21 17:42:05 +0000479
mbligh237bed32007-09-05 13:05:57 +0000480
showardc1a98d12010-01-15 00:22:22 +0000481 def update_job_keyvals(self, job, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700482 """Updates the job key values.
483
484 @param job: The job object.
485 @param commit: If commit the transaction .
486 """
showardc1a98d12010-01-15 00:22:22 +0000487 for key, value in job.keyval_dict.iteritems():
488 where = {'job_id': job.index, 'key': key}
489 data = dict(where, value=value)
490 exists = self.select('id', 'tko_job_keyvals', where=where)
491
492 if exists:
493 self.update('tko_job_keyvals', data, where=where, commit=commit)
494 else:
495 self.insert('tko_job_keyvals', data, commit=commit)
496
497
jadmanski0afbb632008-06-06 21:10:57 +0000498 def insert_test(self, job, test, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700499 """Inserts a job test.
500
501 @param job: The job object.
502 @param test: The test object.
503 @param commit: If commit the transaction .
504 """
jadmanski0afbb632008-06-06 21:10:57 +0000505 kver = self.insert_kernel(test.kernel, commit=commit)
506 data = {'job_idx':job.index, 'test':test.testname,
507 'subdir':test.subdir, 'kernel_idx':kver,
508 'status':self.status_idx[test.status],
509 'reason':test.reason, 'machine_idx':job.machine_idx,
510 'started_time': test.started_time,
511 'finished_time':test.finished_time}
jadmanski9b6babf2009-04-21 17:57:40 +0000512 is_update = hasattr(test, "test_idx")
513 if is_update:
jadmanski74eebf32008-07-15 20:04:42 +0000514 test_idx = test.test_idx
showardeab66ce2009-12-23 00:03:56 +0000515 self.update('tko_tests', data,
516 {'test_idx': test_idx}, commit=commit)
jadmanskib591fba2008-09-10 16:19:22 +0000517 where = {'test_idx': test_idx}
showardeab66ce2009-12-23 00:03:56 +0000518 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700519 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000520 self.delete('tko_iteration_attributes', where)
showard0fec8a02009-12-04 01:19:54 +0000521 where['user_created'] = 0
showardeab66ce2009-12-23 00:03:56 +0000522 self.delete('tko_test_attributes', where)
jadmanski74eebf32008-07-15 20:04:42 +0000523 else:
showardeab66ce2009-12-23 00:03:56 +0000524 self.insert('tko_tests', data, commit=commit)
jadmanski74eebf32008-07-15 20:04:42 +0000525 test_idx = test.test_idx = self.get_last_autonumber_value()
526 data = {'test_idx': test_idx}
mbligh237bed32007-09-05 13:05:57 +0000527
jadmanski0afbb632008-06-06 21:10:57 +0000528 for i in test.iterations:
529 data['iteration'] = i.index
530 for key, value in i.attr_keyval.iteritems():
531 data['attribute'] = key
532 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000533 self.insert('tko_iteration_attributes', data,
jadmanski0afbb632008-06-06 21:10:57 +0000534 commit=commit)
535 for key, value in i.perf_keyval.iteritems():
536 data['attribute'] = key
Allen Li64692a32016-11-04 13:35:19 -0700537 if math.isnan(value) or math.isinf(value):
538 data['value'] = None
539 else:
540 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000541 self.insert('tko_iteration_result', data,
mbligh432bad42007-10-09 19:56:07 +0000542 commit=commit)
mbligh056d0d32006-10-08 22:31:10 +0000543
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700544 data = {'test_idx': test_idx}
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700545
jadmanski0afbb632008-06-06 21:10:57 +0000546 for key, value in test.attributes.iteritems():
547 data = {'test_idx': test_idx, 'attribute': key,
548 'value': value}
showardeab66ce2009-12-23 00:03:56 +0000549 self.insert('tko_test_attributes', data, commit=commit)
mbligh2bd48872007-09-20 18:32:25 +0000550
jadmanski9b6babf2009-04-21 17:57:40 +0000551 if not is_update:
552 for label_index in test.labels:
553 data = {'test_id': test_idx, 'testlabel_id': label_index}
showardeab66ce2009-12-23 00:03:56 +0000554 self.insert('tko_test_labels_tests', data, commit=commit)
jadmanski9b6babf2009-04-21 17:57:40 +0000555
mbligh056d0d32006-10-08 22:31:10 +0000556
jadmanski0afbb632008-06-06 21:10:57 +0000557 def read_machine_map(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700558 """Reads the machine map."""
showard71b94312009-08-20 23:40:02 +0000559 if self.machine_group or not self.machine_map:
560 return
jadmanski0afbb632008-06-06 21:10:57 +0000561 for line in open(self.machine_map, 'r').readlines():
562 (machine, group) = line.split()
563 self.machine_group[machine] = group
mbligh96b9a5a2007-11-24 19:32:20 +0000564
565
showard71b94312009-08-20 23:40:02 +0000566 def machine_info_dict(self, job):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700567 """Reads the machine information of a job.
568
569 @param job: The job object.
570
571 @return: The machine info dictionary.
572 """
jadmanski0afbb632008-06-06 21:10:57 +0000573 hostname = job.machine
showard71b94312009-08-20 23:40:02 +0000574 group = job.machine_group
575 owner = job.machine_owner
jadmanski0afbb632008-06-06 21:10:57 +0000576
577 if not group:
showard71b94312009-08-20 23:40:02 +0000578 self.read_machine_map()
jadmanski0afbb632008-06-06 21:10:57 +0000579 group = self.machine_group.get(hostname, hostname)
showard71b94312009-08-20 23:40:02 +0000580 if group == hostname and owner:
581 group = owner + '/' + hostname
jadmanski0afbb632008-06-06 21:10:57 +0000582
showard71b94312009-08-20 23:40:02 +0000583 return {'hostname': hostname, 'machine_group': group, 'owner': owner}
584
585
586 def insert_machine(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700587 """Inserts the job machine.
588
589 @param job: The job object.
590 @param commit: If commit the transaction .
591 """
showard71b94312009-08-20 23:40:02 +0000592 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000593 self.insert('tko_machines', machine_info, commit=commit)
jadmanski0afbb632008-06-06 21:10:57 +0000594 return self.get_last_autonumber_value()
595
596
showard71b94312009-08-20 23:40:02 +0000597 def update_machine_information(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700598 """Updates the job machine information.
599
600 @param job: The job object.
601 @param commit: If commit the transaction .
602 """
showard71b94312009-08-20 23:40:02 +0000603 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000604 self.update('tko_machines', machine_info,
showard71b94312009-08-20 23:40:02 +0000605 where={'hostname': machine_info['hostname']},
606 commit=commit)
607
608
jadmanski0afbb632008-06-06 21:10:57 +0000609 def lookup_machine(self, hostname):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700610 """Look up the machine information.
611
612 @param hostname: The hostname as string.
613 """
jadmanski0afbb632008-06-06 21:10:57 +0000614 where = { 'hostname' : hostname }
showardeab66ce2009-12-23 00:03:56 +0000615 rows = self.select('machine_idx', 'tko_machines', where)
jadmanski0afbb632008-06-06 21:10:57 +0000616 if rows:
617 return rows[0][0]
618 else:
619 return None
620
621
622 def lookup_kernel(self, kernel):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700623 """Look up the kernel.
624
625 @param kernel: The kernel object.
626 """
showardeab66ce2009-12-23 00:03:56 +0000627 rows = self.select('kernel_idx', 'tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000628 {'kernel_hash':kernel.kernel_hash})
629 if rows:
630 return rows[0][0]
631 else:
632 return None
633
634
635 def insert_kernel(self, kernel, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700636 """Insert a kernel.
637
638 @param kernel: The kernel object.
639 @param commit: If commit the transaction .
640 """
jadmanski0afbb632008-06-06 21:10:57 +0000641 kver = self.lookup_kernel(kernel)
642 if kver:
643 return kver
644
645 # If this kernel has any significant patches, append their hash
646 # as diferentiator.
647 printable = kernel.base
648 patch_count = 0
649 for patch in kernel.patches:
650 match = re.match(r'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$',
651 patch.reference)
652 if not match:
653 patch_count += 1
654
showardeab66ce2009-12-23 00:03:56 +0000655 self.insert('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000656 {'base':kernel.base,
657 'kernel_hash':kernel.kernel_hash,
658 'printable':printable},
659 commit=commit)
660 kver = self.get_last_autonumber_value()
661
662 if patch_count > 0:
663 printable += ' p%d' % (kver)
showardeab66ce2009-12-23 00:03:56 +0000664 self.update('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000665 {'printable':printable},
666 {'kernel_idx':kver})
667
668 for patch in kernel.patches:
669 self.insert_patch(kver, patch, commit=commit)
670 return kver
671
672
673 def insert_patch(self, kver, patch, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700674 """Insert a kernel patch.
675
676 @param kver: The kernel version.
677 @param patch: The kernel patch object.
678 @param commit: If commit the transaction .
679 """
jadmanski0afbb632008-06-06 21:10:57 +0000680 print patch.reference
681 name = os.path.basename(patch.reference)[:80]
showardeab66ce2009-12-23 00:03:56 +0000682 self.insert('tko_patches',
jadmanski0afbb632008-06-06 21:10:57 +0000683 {'kernel_idx': kver,
684 'name':name,
685 'url':patch.reference,
686 'hash':patch.hash},
687 commit=commit)
688
689
jadmanski74eebf32008-07-15 20:04:42 +0000690 def find_test(self, job_idx, testname, subdir):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700691 """Find a test by name.
692
693 @param job_idx: The job index.
694 @param testname: The test name.
695 @param subdir: The test sub directory under the job directory.
696 """
jadmanski74eebf32008-07-15 20:04:42 +0000697 where = {'job_idx': job_idx , 'test': testname, 'subdir': subdir}
showardeab66ce2009-12-23 00:03:56 +0000698 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000699 if rows:
700 return rows[0][0]
701 else:
702 return None
703
704
705 def find_tests(self, job_idx):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700706 """Find all tests by job index.
707
708 @param job_idx: The job index.
709 @return: A list of tests.
710 """
jadmanski0afbb632008-06-06 21:10:57 +0000711 where = { 'job_idx':job_idx }
showardeab66ce2009-12-23 00:03:56 +0000712 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000713 if rows:
714 return [row[0] for row in rows]
715 else:
716 return []
717
718
719 def find_job(self, tag):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700720 """Find a job by tag.
721
722 @param tag: The job tag name.
723 @return: The job object or None.
724 """
showardeab66ce2009-12-23 00:03:56 +0000725 rows = self.select('job_idx', 'tko_jobs', {'tag': tag})
jadmanski0afbb632008-06-06 21:10:57 +0000726 if rows:
727 return rows[0][0]
728 else:
729 return None
mblighaf25f062007-12-03 17:48:35 +0000730
731
mbligh96cf0512008-04-17 15:25:38 +0000732def _get_db_type():
jadmanski0afbb632008-06-06 21:10:57 +0000733 """Get the database type name to use from the global config."""
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700734 get_value = global_config.global_config.get_config_value_with_fallback
735 return "db_" + get_value("AUTOTEST_WEB", "global_db_type", "db_type",
736 default="mysql")
mblighaf25f062007-12-03 17:48:35 +0000737
mbligh96cf0512008-04-17 15:25:38 +0000738
739def _get_error_class(class_name):
jadmanski0afbb632008-06-06 21:10:57 +0000740 """Retrieves the appropriate error class by name from the database
741 module."""
742 db_module = __import__("autotest_lib.tko." + _get_db_type(),
743 globals(), locals(), ["driver"])
744 return getattr(db_module.driver, class_name)
mbligh96cf0512008-04-17 15:25:38 +0000745
746
747def db(*args, **dargs):
jadmanski0afbb632008-06-06 21:10:57 +0000748 """Creates an instance of the database class with the arguments
749 provided in args and dargs, using the database type specified by
Michael Tang5f74ffd2016-10-31 10:34:53 -0700750 the global configuration (defaulting to mysql).
751
752 @param args: The db_type arguments.
753 @param dargs: The db_type named arguments.
754
755 @return: An db object.
756 """
jadmanski0afbb632008-06-06 21:10:57 +0000757 db_type = _get_db_type()
758 db_module = __import__("autotest_lib.tko." + db_type, globals(),
759 locals(), [db_type])
760 db = getattr(db_module, db_type)(*args, **dargs)
761 return db