blob: 9da76e74f260c8724de6f2337bf29ca9e2b8d24f [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
Allen Lie0bb7602016-11-29 13:45:53 -080020def _log_error(msg):
21 """Log an error message.
22
23 @param msg: Message string
24 """
25 print >> sys.stderr, msg
26 sys.stderr.flush() # we want these msgs to show up immediately
27
28
29def _format_operational_error(e):
30 """Format OperationalError.
31
32 @param e: OperationalError instance.
33 """
34 return ("%s: An operational error occurred during a database "
35 "operation: %s" % (time.strftime("%X %x"), str(e)))
36
37
mblighaea09602008-04-16 22:59:37 +000038class MySQLTooManyRows(Exception):
Michael Tang5f74ffd2016-10-31 10:34:53 -070039 """Too many records."""
jadmanski0afbb632008-06-06 21:10:57 +000040 pass
mblighaea09602008-04-16 22:59:37 +000041
mblighd5c33db2006-10-08 21:34:16 +000042
mbligh7636b3a2008-06-11 15:44:01 +000043class db_sql(object):
Michael Tang5f74ffd2016-10-31 10:34:53 -070044 """Data access."""
45
jadmanski0afbb632008-06-06 21:10:57 +000046 def __init__(self, debug=False, autocommit=True, host=None,
47 database=None, user=None, password=None):
48 self.debug = debug
49 self.autocommit = autocommit
50 self._load_config(host, database, user, password)
mbligh96cf0512008-04-17 15:25:38 +000051
jadmanski0afbb632008-06-06 21:10:57 +000052 self.con = None
53 self._init_db()
mblighd5c33db2006-10-08 21:34:16 +000054
jadmanski0afbb632008-06-06 21:10:57 +000055 # if not present, insert statuses
56 self.status_idx = {}
57 self.status_word = {}
showardeab66ce2009-12-23 00:03:56 +000058 status_rows = self.select('status_idx, word', 'tko_status', None)
jadmanski0afbb632008-06-06 21:10:57 +000059 for s in status_rows:
60 self.status_idx[s[1]] = s[0]
61 self.status_word[s[0]] = s[1]
mbligh048e1c92007-10-07 00:10:33 +000062
jadmanski0afbb632008-06-06 21:10:57 +000063 machine_map = os.path.join(os.path.dirname(__file__),
64 'machines')
65 if os.path.exists(machine_map):
66 self.machine_map = machine_map
67 else:
68 self.machine_map = None
69 self.machine_group = {}
mbligh048e1c92007-10-07 00:10:33 +000070
mbligh8e1ab172007-09-13 17:29:56 +000071
jadmanski0afbb632008-06-06 21:10:57 +000072 def _load_config(self, host, database, user, password):
Jakob Juelich934f0dc2014-10-14 18:21:13 -070073 """Loads configuration settings required to connect to the database.
74
75 This will try to connect to use the settings prefixed with global_db_.
76 If they do not exist, they un-prefixed settings will be used.
77
78 If parameters are supplied, these will be taken instead of the values
79 in global_config.
80
81 @param host: If set, this host will be used, if not, the host will be
82 retrieved from global_config.
83 @param database: If set, this database will be used, if not, the
84 database will be retrieved from global_config.
85 @param user: If set, this user will be used, if not, the
86 user will be retrieved from global_config.
87 @param password: If set, this password will be used, if not, the
88 password will be retrieved from global_config.
89 """
90 database_settings = database_settings_helper.get_global_db_config()
mbligh65acae52008-04-24 20:21:55 +000091
jadmanski0afbb632008-06-06 21:10:57 +000092 # grab the host, database
Jakob Juelich934f0dc2014-10-14 18:21:13 -070093 self.host = host or database_settings['HOST']
94 self.database = database or database_settings['NAME']
mbligh65acae52008-04-24 20:21:55 +000095
jadmanski0afbb632008-06-06 21:10:57 +000096 # grab the user and password
Jakob Juelich934f0dc2014-10-14 18:21:13 -070097 self.user = user or database_settings['USER']
98 self.password = password or database_settings['PASSWORD']
mbligh65acae52008-04-24 20:21:55 +000099
Michael Spang7a273472014-10-08 12:08:13 -0400100 # grab the timeout configuration
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700101 self.query_timeout =(
102 database_settings.get('OPTIONS', {}).get('timeout', 3600))
103
104 # Using fallback to non-global in order to work without configuration
105 # overhead on non-shard instances.
106 get_value = global_config.global_config.get_config_value_with_fallback
Jakob Juelich475b82b2014-09-30 11:17:07 -0700107 self.min_delay = get_value("AUTOTEST_WEB", "global_db_min_retry_delay",
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700108 "min_retry_delay", type=int, default=20)
Jakob Juelich475b82b2014-09-30 11:17:07 -0700109 self.max_delay = get_value("AUTOTEST_WEB", "global_db_max_retry_delay",
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700110 "max_retry_delay", type=int, default=60)
mbligh65acae52008-04-24 20:21:55 +0000111
Richard Barnette2468fbd2014-11-07 01:12:46 +0000112 # TODO(beeps): Move this to django settings once we have routers.
113 # On test instances mysql connects through a different port. No point
114 # piping this through our entire infrastructure when it is only really
115 # used for testing; Ideally we would specify this through django
116 # settings and default it to the empty string so django will figure out
117 # the default based on the database backend (eg: mysql, 3306), but until
118 # we have database routers in place any django settings will apply to
119 # both tko and afe.
120 # The intended use of this port is to allow a testing shard vm to
121 # update the master vm's database with test results. Specifying
122 # and empty string will fallback to not even specifying the port
123 # to the backend in tko/db.py. Unfortunately this means retries
124 # won't work on the test cluster till we've migrated to routers.
125 self.port = global_config.global_config.get_config_value(
126 "AUTOTEST_WEB", "global_db_port", type=str, default='')
127
mbligh65acae52008-04-24 20:21:55 +0000128
jadmanski0afbb632008-06-06 21:10:57 +0000129 def _init_db(self):
130 # make sure we clean up any existing connection
131 if self.con:
132 self.con.close()
133 self.con = None
mbligh65acae52008-04-24 20:21:55 +0000134
Dan Shi8684b922015-10-06 13:29:18 -0700135 try:
136 # create the db connection and cursor
137 self.con = self.connect(self.host, self.database,
138 self.user, self.password, self.port)
139 except:
140 autotest_stats.Counter('tko_db_con_error').increment()
141 raise
jadmanski0afbb632008-06-06 21:10:57 +0000142 self.cur = self.con.cursor()
mbligh96cf0512008-04-17 15:25:38 +0000143
144
jadmanski0afbb632008-06-06 21:10:57 +0000145 def _random_delay(self):
146 delay = random.randint(self.min_delay, self.max_delay)
147 time.sleep(delay)
mbligh65acae52008-04-24 20:21:55 +0000148
149
jadmanski0afbb632008-06-06 21:10:57 +0000150 def run_with_retry(self, function, *args, **dargs):
151 """Call function(*args, **dargs) until either it passes
152 without an operational error, or a timeout is reached.
153 This will re-connect to the database, so it is NOT safe
154 to use this inside of a database transaction.
jadmanskie7a69092008-05-29 21:03:13 +0000155
jadmanski0afbb632008-06-06 21:10:57 +0000156 It can be safely used with transactions, but the
157 transaction start & end must be completely contained
Michael Tang5f74ffd2016-10-31 10:34:53 -0700158 within the call to 'function'.
159
160 @param function: The function to run with retry.
161 @param args: The arguments
162 @param dargs: The named arguments.
163 """
jadmanski0afbb632008-06-06 21:10:57 +0000164 OperationalError = _get_error_class("OperationalError")
mbligh65acae52008-04-24 20:21:55 +0000165
jadmanski0afbb632008-06-06 21:10:57 +0000166 success = False
167 start_time = time.time()
168 while not success:
169 try:
170 result = function(*args, **dargs)
171 except OperationalError, e:
Allen Lie0bb7602016-11-29 13:45:53 -0800172 _log_error("%s; retrying, don't panic yet"
173 % _format_operational_error(e))
jadmanski0afbb632008-06-06 21:10:57 +0000174 stop_time = time.time()
175 elapsed_time = stop_time - start_time
176 if elapsed_time > self.query_timeout:
177 raise
178 else:
179 try:
180 self._random_delay()
181 self._init_db()
Dan Shie8e0c052015-09-01 00:27:27 -0700182 autotest_stats.Counter('tko_db_error').increment()
jadmanski0afbb632008-06-06 21:10:57 +0000183 except OperationalError, e:
Allen Lie0bb7602016-11-29 13:45:53 -0800184 _log_error('%s; panic now'
185 % _format_operational_error(e))
jadmanski0afbb632008-06-06 21:10:57 +0000186 else:
187 success = True
188 return result
mbligh96cf0512008-04-17 15:25:38 +0000189
190
jadmanski0afbb632008-06-06 21:10:57 +0000191 def dprint(self, value):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700192 """Print out debug value.
193
194 @param value: The value to print out.
195 """
jadmanski0afbb632008-06-06 21:10:57 +0000196 if self.debug:
197 sys.stdout.write('SQL: ' + str(value) + '\n')
mbligh8e1ab172007-09-13 17:29:56 +0000198
mblighd5c33db2006-10-08 21:34:16 +0000199
Dan Shie8e0c052015-09-01 00:27:27 -0700200 def _commit(self):
201 """Private method for function commit to call for retry.
202 """
203 return self.con.commit()
204
205
jadmanski0afbb632008-06-06 21:10:57 +0000206 def commit(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700207 """Commit the sql transaction."""
Dan Shie8e0c052015-09-01 00:27:27 -0700208 if self.autocommit:
209 return self.run_with_retry(self._commit)
210 else:
211 return self._commit()
mbligh432bad42007-10-09 19:56:07 +0000212
213
Simran Basie129a962012-08-31 13:03:53 -0700214 def rollback(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700215 """Rollback the sql transaction."""
Simran Basie129a962012-08-31 13:03:53 -0700216 self.con.rollback()
217
218
jadmanski0afbb632008-06-06 21:10:57 +0000219 def get_last_autonumber_value(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700220 """Gets the last auto number.
221
222 @return: The last auto number.
223 """
jadmanski0afbb632008-06-06 21:10:57 +0000224 self.cur.execute('SELECT LAST_INSERT_ID()', [])
225 return self.cur.fetchall()[0][0]
mblighe12b8612008-02-12 20:58:14 +0000226
227
showardc1a98d12010-01-15 00:22:22 +0000228 def _quote(self, field):
229 return '`%s`' % field
230
231
232 def _where_clause(self, where):
233 if not where:
234 return '', []
235
236 if isinstance(where, dict):
237 # key/value pairs (which should be equal, or None for null)
238 keys, values = [], []
239 for field, value in where.iteritems():
240 quoted_field = self._quote(field)
241 if value is None:
242 keys.append(quoted_field + ' is null')
243 else:
244 keys.append(quoted_field + '=%s')
245 values.append(value)
246 where_clause = ' and '.join(keys)
247 elif isinstance(where, basestring):
248 # the exact string
249 where_clause = where
250 values = []
251 elif isinstance(where, tuple):
252 # preformatted where clause + values
253 where_clause, values = where
254 assert where_clause
255 else:
256 raise ValueError('Invalid "where" value: %r' % where)
257
258 return ' WHERE ' + where_clause, values
259
260
261
262 def select(self, fields, table, where, distinct=False, group_by=None,
263 max_rows=None):
jadmanski0afbb632008-06-06 21:10:57 +0000264 """\
265 This selects all the fields requested from a
266 specific table with a particular where clause.
267 The where clause can either be a dictionary of
268 field=value pairs, a string, or a tuple of (string,
269 a list of values). The last option is what you
270 should use when accepting user input as it'll
271 protect you against sql injection attacks (if
272 all user data is placed in the array rather than
273 the raw SQL).
mbligh12eebfa2008-01-03 02:01:53 +0000274
jadmanski0afbb632008-06-06 21:10:57 +0000275 For example:
276 where = ("a = %s AND b = %s", ['val', 'val'])
277 is better than
278 where = "a = 'val' AND b = 'val'"
Michael Tang5f74ffd2016-10-31 10:34:53 -0700279
280 @param fields: The list of selected fields string.
281 @param table: The name of the database table.
282 @param where: The where clause string.
283 @param distinct: If select distinct values.
284 @param group_by: Group by clause.
285 @param max_rows: unused.
jadmanski0afbb632008-06-06 21:10:57 +0000286 """
287 cmd = ['select']
288 if distinct:
289 cmd.append('distinct')
290 cmd += [fields, 'from', table]
mbligh608c3252007-08-31 13:53:00 +0000291
showardc1a98d12010-01-15 00:22:22 +0000292 where_clause, values = self._where_clause(where)
293 cmd.append(where_clause)
mbligh96cf0512008-04-17 15:25:38 +0000294
jadmanski0afbb632008-06-06 21:10:57 +0000295 if group_by:
296 cmd.append(' GROUP BY ' + group_by)
mbligh83f63a02007-12-12 19:13:04 +0000297
jadmanski0afbb632008-06-06 21:10:57 +0000298 self.dprint('%s %s' % (' '.join(cmd), values))
mbligh96cf0512008-04-17 15:25:38 +0000299
jadmanski0afbb632008-06-06 21:10:57 +0000300 # create a re-runable function for executing the query
301 def exec_sql():
Michael Tang5f74ffd2016-10-31 10:34:53 -0700302 """Exeuctes an the sql command."""
jadmanski0afbb632008-06-06 21:10:57 +0000303 sql = ' '.join(cmd)
304 numRec = self.cur.execute(sql, values)
mblighd876f452008-12-03 15:09:17 +0000305 if max_rows is not None and numRec > max_rows:
jadmanski0afbb632008-06-06 21:10:57 +0000306 msg = 'Exceeded allowed number of records'
307 raise MySQLTooManyRows(msg)
308 return self.cur.fetchall()
mbligh96cf0512008-04-17 15:25:38 +0000309
jadmanski0afbb632008-06-06 21:10:57 +0000310 # run the query, re-trying after operational errors
311 if self.autocommit:
312 return self.run_with_retry(exec_sql)
313 else:
314 return exec_sql()
mblighd5c33db2006-10-08 21:34:16 +0000315
mbligh056d0d32006-10-08 22:31:10 +0000316
jadmanski0afbb632008-06-06 21:10:57 +0000317 def select_sql(self, fields, table, sql, values):
318 """\
319 select fields from table "sql"
Michael Tang5f74ffd2016-10-31 10:34:53 -0700320
321 @param fields: The list of selected fields string.
322 @param table: The name of the database table.
323 @param sql: The sql string.
324 @param values: The sql string parameter values.
jadmanski0afbb632008-06-06 21:10:57 +0000325 """
326 cmd = 'select %s from %s %s' % (fields, table, sql)
327 self.dprint(cmd)
mbligh414c69e2007-10-05 15:13:06 +0000328
jadmanski0afbb632008-06-06 21:10:57 +0000329 # create a -re-runable function for executing the query
Michael Tang5f74ffd2016-10-31 10:34:53 -0700330 def _exec_sql():
jadmanski0afbb632008-06-06 21:10:57 +0000331 self.cur.execute(cmd, values)
332 return self.cur.fetchall()
mbligh96b9a5a2007-11-24 19:32:20 +0000333
jadmanski0afbb632008-06-06 21:10:57 +0000334 # run the query, re-trying after operational errors
335 if self.autocommit:
Michael Tang5f74ffd2016-10-31 10:34:53 -0700336 return self.run_with_retry(_exec_sql)
jadmanski0afbb632008-06-06 21:10:57 +0000337 else:
Michael Tang5f74ffd2016-10-31 10:34:53 -0700338 return _exec_sql()
mbligh96b9a5a2007-11-24 19:32:20 +0000339
mbligh608c3252007-08-31 13:53:00 +0000340
jadmanski0afbb632008-06-06 21:10:57 +0000341 def _exec_sql_with_commit(self, sql, values, commit):
342 if self.autocommit:
343 # re-run the query until it succeeds
Michael Tang5f74ffd2016-10-31 10:34:53 -0700344 def _exec_sql():
jadmanski0afbb632008-06-06 21:10:57 +0000345 self.cur.execute(sql, values)
346 self.con.commit()
Michael Tang5f74ffd2016-10-31 10:34:53 -0700347 self.run_with_retry(_exec_sql)
jadmanski0afbb632008-06-06 21:10:57 +0000348 else:
349 # take one shot at running the query
350 self.cur.execute(sql, values)
351 if commit:
352 self.con.commit()
mbligh96b9a5a2007-11-24 19:32:20 +0000353
mbligh2bd48872007-09-20 18:32:25 +0000354
jadmanskib591fba2008-09-10 16:19:22 +0000355 def insert(self, table, data, commit=None):
jadmanski0afbb632008-06-06 21:10:57 +0000356 """\
357 'insert into table (keys) values (%s ... %s)', values
mbligh96cf0512008-04-17 15:25:38 +0000358
jadmanski0afbb632008-06-06 21:10:57 +0000359 data:
360 dictionary of fields and data
Michael Tang5f74ffd2016-10-31 10:34:53 -0700361
362 @param table: The name of the table.
363 @param data: The insert data.
364 @param commit: If commit the transaction .
jadmanski0afbb632008-06-06 21:10:57 +0000365 """
366 fields = data.keys()
367 refs = ['%s' for field in fields]
368 values = [data[field] for field in fields]
showardc1a98d12010-01-15 00:22:22 +0000369 cmd = ('insert into %s (%s) values (%s)' %
370 (table, ','.join(self._quote(field) for field in fields),
371 ','.join(refs)))
jadmanski0afbb632008-06-06 21:10:57 +0000372 self.dprint('%s %s' % (cmd, values))
mblighe9cf9d42007-08-31 08:56:00 +0000373
jadmanski0afbb632008-06-06 21:10:57 +0000374 self._exec_sql_with_commit(cmd, values, commit)
mblighe9cf9d42007-08-31 08:56:00 +0000375
mbligh048e1c92007-10-07 00:10:33 +0000376
jadmanski0afbb632008-06-06 21:10:57 +0000377 def delete(self, table, where, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700378 """Delete entries.
379
380 @param table: The name of the table.
381 @param where: The where clause.
382 @param commit: If commit the transaction .
383 """
jadmanski0afbb632008-06-06 21:10:57 +0000384 cmd = ['delete from', table]
mblighd876f452008-12-03 15:09:17 +0000385 if commit is None:
jadmanski0afbb632008-06-06 21:10:57 +0000386 commit = self.autocommit
showardc1a98d12010-01-15 00:22:22 +0000387 where_clause, values = self._where_clause(where)
388 cmd.append(where_clause)
jadmanski0afbb632008-06-06 21:10:57 +0000389 sql = ' '.join(cmd)
390 self.dprint('%s %s' % (sql, values))
mbligh048e1c92007-10-07 00:10:33 +0000391
jadmanski0afbb632008-06-06 21:10:57 +0000392 self._exec_sql_with_commit(sql, values, commit)
mbligh048e1c92007-10-07 00:10:33 +0000393
mbligh7a41a862007-11-30 17:44:24 +0000394
jadmanski0afbb632008-06-06 21:10:57 +0000395 def update(self, table, data, where, commit = None):
396 """\
397 'update table set data values (%s ... %s) where ...'
mbligh2aaeb672007-10-01 14:54:18 +0000398
jadmanski0afbb632008-06-06 21:10:57 +0000399 data:
400 dictionary of fields and data
Michael Tang5f74ffd2016-10-31 10:34:53 -0700401
402 @param table: The name of the table.
403 @param data: The sql parameter values.
404 @param where: The where clause.
405 @param commit: If commit the transaction .
jadmanski0afbb632008-06-06 21:10:57 +0000406 """
mblighd876f452008-12-03 15:09:17 +0000407 if commit is None:
jadmanski0afbb632008-06-06 21:10:57 +0000408 commit = self.autocommit
409 cmd = 'update %s ' % table
410 fields = data.keys()
showardc1a98d12010-01-15 00:22:22 +0000411 data_refs = [self._quote(field) + '=%s' for field in fields]
jadmanski0afbb632008-06-06 21:10:57 +0000412 data_values = [data[field] for field in fields]
jadmanski74eebf32008-07-15 20:04:42 +0000413 cmd += ' set ' + ', '.join(data_refs)
mbligh2aaeb672007-10-01 14:54:18 +0000414
showardc1a98d12010-01-15 00:22:22 +0000415 where_clause, where_values = self._where_clause(where)
416 cmd += where_clause
mbligh2aaeb672007-10-01 14:54:18 +0000417
jadmanski0afbb632008-06-06 21:10:57 +0000418 values = data_values + where_values
jadmanski74eebf32008-07-15 20:04:42 +0000419 self.dprint('%s %s' % (cmd, values))
mbligh2aaeb672007-10-01 14:54:18 +0000420
jadmanski0afbb632008-06-06 21:10:57 +0000421 self._exec_sql_with_commit(cmd, values, commit)
mblighe9cf9d42007-08-31 08:56:00 +0000422
423
jadmanski0afbb632008-06-06 21:10:57 +0000424 def delete_job(self, tag, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700425 """Delete a tko job.
426
427 @param tag: The job tag.
428 @param commit: If commit the transaction .
429 """
jadmanski0afbb632008-06-06 21:10:57 +0000430 job_idx = self.find_job(tag)
431 for test_idx in self.find_tests(job_idx):
432 where = {'test_idx' : test_idx}
showardeab66ce2009-12-23 00:03:56 +0000433 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700434 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000435 self.delete('tko_iteration_attributes', where)
436 self.delete('tko_test_attributes', where)
437 self.delete('tko_test_labels_tests', {'test_id': test_idx})
jadmanski0afbb632008-06-06 21:10:57 +0000438 where = {'job_idx' : job_idx}
showardeab66ce2009-12-23 00:03:56 +0000439 self.delete('tko_tests', where)
440 self.delete('tko_jobs', where)
apw7a7316b2008-02-21 17:42:05 +0000441
apw7a7316b2008-02-21 17:42:05 +0000442
Dan Shi70647ca2015-07-16 22:52:35 -0700443 def insert_job(self, tag, job, parent_job_id=None, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700444 """Insert a tko job.
445
446 @param tag: The job tag.
447 @param job: The job object.
448 @param parent_job_id: The parent job id.
449 @param commit: If commit the transaction .
450 """
jadmanski0afbb632008-06-06 21:10:57 +0000451 job.machine_idx = self.lookup_machine(job.machine)
452 if not job.machine_idx:
showard71b94312009-08-20 23:40:02 +0000453 job.machine_idx = self.insert_machine(job, commit=commit)
Dan Shid77fc1b2015-10-13 17:34:50 -0700454 elif job.machine:
455 # Only try to update tko_machines record if machine is set. This
456 # prevents unnecessary db writes for suite jobs.
showard71b94312009-08-20 23:40:02 +0000457 self.update_machine_information(job, commit=commit)
458
jamesrena12b8a02010-06-16 23:28:23 +0000459 afe_job_id = utils.get_afe_job_id(tag)
showardc1c1caf2009-09-08 16:26:50 +0000460
showard0fec8a02009-12-04 01:19:54 +0000461 data = {'tag':tag,
462 'label': job.label,
463 'username': job.user,
464 'machine_idx': job.machine_idx,
465 'queued_time': job.queued_time,
466 'started_time': job.started_time,
467 'finished_time': job.finished_time,
Dan Shi70647ca2015-07-16 22:52:35 -0700468 'afe_job_id': afe_job_id,
469 'afe_parent_job_id': parent_job_id}
Michael Tang5f74ffd2016-10-31 10:34:53 -0700470 job.afe_job_id = afe_job_id
471 if parent_job_id:
472 job.afe_parent_job_id = str(parent_job_id)
Dan Shi70647ca2015-07-16 22:52:35 -0700473 if job.label:
474 label_info = site_utils.parse_job_name(job.label)
475 if label_info:
476 data['build'] = label_info.get('build', None)
Michael Tang5f74ffd2016-10-31 10:34:53 -0700477 job.build_version = data['build_version'] = label_info.get(
478 'build_version', None)
479 job.board = data['board'] = label_info.get('board', None)
480 job.suite = data['suite'] = label_info.get('suite', None)
481
482 # TODO(ntang): check job.index directly.
showard0fec8a02009-12-04 01:19:54 +0000483 is_update = hasattr(job, 'index')
484 if is_update:
showardeab66ce2009-12-23 00:03:56 +0000485 self.update('tko_jobs', data, {'job_idx': job.index}, commit=commit)
showard0fec8a02009-12-04 01:19:54 +0000486 else:
showardeab66ce2009-12-23 00:03:56 +0000487 self.insert('tko_jobs', data, commit=commit)
showard0fec8a02009-12-04 01:19:54 +0000488 job.index = self.get_last_autonumber_value()
showardc1a98d12010-01-15 00:22:22 +0000489 self.update_job_keyvals(job, commit=commit)
jadmanski0afbb632008-06-06 21:10:57 +0000490 for test in job.tests:
491 self.insert_test(job, test, commit=commit)
apw7a7316b2008-02-21 17:42:05 +0000492
mbligh237bed32007-09-05 13:05:57 +0000493
showardc1a98d12010-01-15 00:22:22 +0000494 def update_job_keyvals(self, job, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700495 """Updates the job key values.
496
497 @param job: The job object.
498 @param commit: If commit the transaction .
499 """
showardc1a98d12010-01-15 00:22:22 +0000500 for key, value in job.keyval_dict.iteritems():
501 where = {'job_id': job.index, 'key': key}
502 data = dict(where, value=value)
503 exists = self.select('id', 'tko_job_keyvals', where=where)
504
505 if exists:
506 self.update('tko_job_keyvals', data, where=where, commit=commit)
507 else:
508 self.insert('tko_job_keyvals', data, commit=commit)
509
510
jadmanski0afbb632008-06-06 21:10:57 +0000511 def insert_test(self, job, test, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700512 """Inserts a job test.
513
514 @param job: The job object.
515 @param test: The test object.
516 @param commit: If commit the transaction .
517 """
jadmanski0afbb632008-06-06 21:10:57 +0000518 kver = self.insert_kernel(test.kernel, commit=commit)
519 data = {'job_idx':job.index, 'test':test.testname,
520 'subdir':test.subdir, 'kernel_idx':kver,
521 'status':self.status_idx[test.status],
522 'reason':test.reason, 'machine_idx':job.machine_idx,
523 'started_time': test.started_time,
524 'finished_time':test.finished_time}
jadmanski9b6babf2009-04-21 17:57:40 +0000525 is_update = hasattr(test, "test_idx")
526 if is_update:
jadmanski74eebf32008-07-15 20:04:42 +0000527 test_idx = test.test_idx
showardeab66ce2009-12-23 00:03:56 +0000528 self.update('tko_tests', data,
529 {'test_idx': test_idx}, commit=commit)
jadmanskib591fba2008-09-10 16:19:22 +0000530 where = {'test_idx': test_idx}
showardeab66ce2009-12-23 00:03:56 +0000531 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700532 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000533 self.delete('tko_iteration_attributes', where)
showard0fec8a02009-12-04 01:19:54 +0000534 where['user_created'] = 0
showardeab66ce2009-12-23 00:03:56 +0000535 self.delete('tko_test_attributes', where)
jadmanski74eebf32008-07-15 20:04:42 +0000536 else:
showardeab66ce2009-12-23 00:03:56 +0000537 self.insert('tko_tests', data, commit=commit)
jadmanski74eebf32008-07-15 20:04:42 +0000538 test_idx = test.test_idx = self.get_last_autonumber_value()
539 data = {'test_idx': test_idx}
mbligh237bed32007-09-05 13:05:57 +0000540
jadmanski0afbb632008-06-06 21:10:57 +0000541 for i in test.iterations:
542 data['iteration'] = i.index
543 for key, value in i.attr_keyval.iteritems():
544 data['attribute'] = key
545 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000546 self.insert('tko_iteration_attributes', data,
jadmanski0afbb632008-06-06 21:10:57 +0000547 commit=commit)
548 for key, value in i.perf_keyval.iteritems():
549 data['attribute'] = key
Allen Li64692a32016-11-04 13:35:19 -0700550 if math.isnan(value) or math.isinf(value):
551 data['value'] = None
552 else:
553 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000554 self.insert('tko_iteration_result', data,
mbligh432bad42007-10-09 19:56:07 +0000555 commit=commit)
mbligh056d0d32006-10-08 22:31:10 +0000556
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700557 data = {'test_idx': test_idx}
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700558
jadmanski0afbb632008-06-06 21:10:57 +0000559 for key, value in test.attributes.iteritems():
560 data = {'test_idx': test_idx, 'attribute': key,
561 'value': value}
showardeab66ce2009-12-23 00:03:56 +0000562 self.insert('tko_test_attributes', data, commit=commit)
mbligh2bd48872007-09-20 18:32:25 +0000563
jadmanski9b6babf2009-04-21 17:57:40 +0000564 if not is_update:
565 for label_index in test.labels:
566 data = {'test_id': test_idx, 'testlabel_id': label_index}
showardeab66ce2009-12-23 00:03:56 +0000567 self.insert('tko_test_labels_tests', data, commit=commit)
jadmanski9b6babf2009-04-21 17:57:40 +0000568
mbligh056d0d32006-10-08 22:31:10 +0000569
jadmanski0afbb632008-06-06 21:10:57 +0000570 def read_machine_map(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700571 """Reads the machine map."""
showard71b94312009-08-20 23:40:02 +0000572 if self.machine_group or not self.machine_map:
573 return
jadmanski0afbb632008-06-06 21:10:57 +0000574 for line in open(self.machine_map, 'r').readlines():
575 (machine, group) = line.split()
576 self.machine_group[machine] = group
mbligh96b9a5a2007-11-24 19:32:20 +0000577
578
showard71b94312009-08-20 23:40:02 +0000579 def machine_info_dict(self, job):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700580 """Reads the machine information of a job.
581
582 @param job: The job object.
583
584 @return: The machine info dictionary.
585 """
jadmanski0afbb632008-06-06 21:10:57 +0000586 hostname = job.machine
showard71b94312009-08-20 23:40:02 +0000587 group = job.machine_group
588 owner = job.machine_owner
jadmanski0afbb632008-06-06 21:10:57 +0000589
590 if not group:
showard71b94312009-08-20 23:40:02 +0000591 self.read_machine_map()
jadmanski0afbb632008-06-06 21:10:57 +0000592 group = self.machine_group.get(hostname, hostname)
showard71b94312009-08-20 23:40:02 +0000593 if group == hostname and owner:
594 group = owner + '/' + hostname
jadmanski0afbb632008-06-06 21:10:57 +0000595
showard71b94312009-08-20 23:40:02 +0000596 return {'hostname': hostname, 'machine_group': group, 'owner': owner}
597
598
599 def insert_machine(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700600 """Inserts the job machine.
601
602 @param job: The job object.
603 @param commit: If commit the transaction .
604 """
showard71b94312009-08-20 23:40:02 +0000605 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000606 self.insert('tko_machines', machine_info, commit=commit)
jadmanski0afbb632008-06-06 21:10:57 +0000607 return self.get_last_autonumber_value()
608
609
showard71b94312009-08-20 23:40:02 +0000610 def update_machine_information(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700611 """Updates the job machine information.
612
613 @param job: The job object.
614 @param commit: If commit the transaction .
615 """
showard71b94312009-08-20 23:40:02 +0000616 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000617 self.update('tko_machines', machine_info,
showard71b94312009-08-20 23:40:02 +0000618 where={'hostname': machine_info['hostname']},
619 commit=commit)
620
621
jadmanski0afbb632008-06-06 21:10:57 +0000622 def lookup_machine(self, hostname):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700623 """Look up the machine information.
624
625 @param hostname: The hostname as string.
626 """
jadmanski0afbb632008-06-06 21:10:57 +0000627 where = { 'hostname' : hostname }
showardeab66ce2009-12-23 00:03:56 +0000628 rows = self.select('machine_idx', 'tko_machines', where)
jadmanski0afbb632008-06-06 21:10:57 +0000629 if rows:
630 return rows[0][0]
631 else:
632 return None
633
634
635 def lookup_kernel(self, kernel):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700636 """Look up the kernel.
637
638 @param kernel: The kernel object.
639 """
showardeab66ce2009-12-23 00:03:56 +0000640 rows = self.select('kernel_idx', 'tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000641 {'kernel_hash':kernel.kernel_hash})
642 if rows:
643 return rows[0][0]
644 else:
645 return None
646
647
648 def insert_kernel(self, kernel, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700649 """Insert a kernel.
650
651 @param kernel: The kernel object.
652 @param commit: If commit the transaction .
653 """
jadmanski0afbb632008-06-06 21:10:57 +0000654 kver = self.lookup_kernel(kernel)
655 if kver:
656 return kver
657
658 # If this kernel has any significant patches, append their hash
659 # as diferentiator.
660 printable = kernel.base
661 patch_count = 0
662 for patch in kernel.patches:
663 match = re.match(r'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$',
664 patch.reference)
665 if not match:
666 patch_count += 1
667
showardeab66ce2009-12-23 00:03:56 +0000668 self.insert('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000669 {'base':kernel.base,
670 'kernel_hash':kernel.kernel_hash,
671 'printable':printable},
672 commit=commit)
673 kver = self.get_last_autonumber_value()
674
675 if patch_count > 0:
676 printable += ' p%d' % (kver)
showardeab66ce2009-12-23 00:03:56 +0000677 self.update('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000678 {'printable':printable},
679 {'kernel_idx':kver})
680
681 for patch in kernel.patches:
682 self.insert_patch(kver, patch, commit=commit)
683 return kver
684
685
686 def insert_patch(self, kver, patch, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700687 """Insert a kernel patch.
688
689 @param kver: The kernel version.
690 @param patch: The kernel patch object.
691 @param commit: If commit the transaction .
692 """
jadmanski0afbb632008-06-06 21:10:57 +0000693 print patch.reference
694 name = os.path.basename(patch.reference)[:80]
showardeab66ce2009-12-23 00:03:56 +0000695 self.insert('tko_patches',
jadmanski0afbb632008-06-06 21:10:57 +0000696 {'kernel_idx': kver,
697 'name':name,
698 'url':patch.reference,
699 'hash':patch.hash},
700 commit=commit)
701
702
jadmanski74eebf32008-07-15 20:04:42 +0000703 def find_test(self, job_idx, testname, subdir):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700704 """Find a test by name.
705
706 @param job_idx: The job index.
707 @param testname: The test name.
708 @param subdir: The test sub directory under the job directory.
709 """
jadmanski74eebf32008-07-15 20:04:42 +0000710 where = {'job_idx': job_idx , 'test': testname, 'subdir': subdir}
showardeab66ce2009-12-23 00:03:56 +0000711 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000712 if rows:
713 return rows[0][0]
714 else:
715 return None
716
717
718 def find_tests(self, job_idx):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700719 """Find all tests by job index.
720
721 @param job_idx: The job index.
722 @return: A list of tests.
723 """
jadmanski0afbb632008-06-06 21:10:57 +0000724 where = { 'job_idx':job_idx }
showardeab66ce2009-12-23 00:03:56 +0000725 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000726 if rows:
727 return [row[0] for row in rows]
728 else:
729 return []
730
731
732 def find_job(self, tag):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700733 """Find a job by tag.
734
735 @param tag: The job tag name.
736 @return: The job object or None.
737 """
showardeab66ce2009-12-23 00:03:56 +0000738 rows = self.select('job_idx', 'tko_jobs', {'tag': tag})
jadmanski0afbb632008-06-06 21:10:57 +0000739 if rows:
740 return rows[0][0]
741 else:
742 return None
mblighaf25f062007-12-03 17:48:35 +0000743
744
mbligh96cf0512008-04-17 15:25:38 +0000745def _get_db_type():
jadmanski0afbb632008-06-06 21:10:57 +0000746 """Get the database type name to use from the global config."""
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700747 get_value = global_config.global_config.get_config_value_with_fallback
748 return "db_" + get_value("AUTOTEST_WEB", "global_db_type", "db_type",
749 default="mysql")
mblighaf25f062007-12-03 17:48:35 +0000750
mbligh96cf0512008-04-17 15:25:38 +0000751
752def _get_error_class(class_name):
jadmanski0afbb632008-06-06 21:10:57 +0000753 """Retrieves the appropriate error class by name from the database
754 module."""
755 db_module = __import__("autotest_lib.tko." + _get_db_type(),
756 globals(), locals(), ["driver"])
757 return getattr(db_module.driver, class_name)
mbligh96cf0512008-04-17 15:25:38 +0000758
759
760def db(*args, **dargs):
jadmanski0afbb632008-06-06 21:10:57 +0000761 """Creates an instance of the database class with the arguments
762 provided in args and dargs, using the database type specified by
Michael Tang5f74ffd2016-10-31 10:34:53 -0700763 the global configuration (defaulting to mysql).
764
765 @param args: The db_type arguments.
766 @param dargs: The db_type named arguments.
767
768 @return: An db object.
769 """
jadmanski0afbb632008-06-06 21:10:57 +0000770 db_type = _get_db_type()
771 db_module = __import__("autotest_lib.tko." + db_type, globals(),
772 locals(), [db_type])
773 db = getattr(db_module, db_type)(*args, **dargs)
774 return db