blob: 99f9621d6fd507fe29038097bdebcc73457595b3 [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 .
Shuqian Zhao31425d52016-12-07 09:35:03 -0800450
451 @return The dict of data inserted into the tko_jobs table.
Michael Tang5f74ffd2016-10-31 10:34:53 -0700452 """
jadmanski0afbb632008-06-06 21:10:57 +0000453 job.machine_idx = self.lookup_machine(job.machine)
454 if not job.machine_idx:
showard71b94312009-08-20 23:40:02 +0000455 job.machine_idx = self.insert_machine(job, commit=commit)
Dan Shid77fc1b2015-10-13 17:34:50 -0700456 elif job.machine:
457 # Only try to update tko_machines record if machine is set. This
458 # prevents unnecessary db writes for suite jobs.
showard71b94312009-08-20 23:40:02 +0000459 self.update_machine_information(job, commit=commit)
460
jamesrena12b8a02010-06-16 23:28:23 +0000461 afe_job_id = utils.get_afe_job_id(tag)
showardc1c1caf2009-09-08 16:26:50 +0000462
showard0fec8a02009-12-04 01:19:54 +0000463 data = {'tag':tag,
464 'label': job.label,
465 'username': job.user,
466 'machine_idx': job.machine_idx,
467 'queued_time': job.queued_time,
468 'started_time': job.started_time,
469 'finished_time': job.finished_time,
Dan Shi70647ca2015-07-16 22:52:35 -0700470 'afe_job_id': afe_job_id,
471 'afe_parent_job_id': parent_job_id}
Michael Tang5f74ffd2016-10-31 10:34:53 -0700472 job.afe_job_id = afe_job_id
473 if parent_job_id:
474 job.afe_parent_job_id = str(parent_job_id)
Dan Shi70647ca2015-07-16 22:52:35 -0700475 if job.label:
476 label_info = site_utils.parse_job_name(job.label)
477 if label_info:
478 data['build'] = label_info.get('build', None)
Michael Tang5f74ffd2016-10-31 10:34:53 -0700479 job.build_version = data['build_version'] = label_info.get(
480 'build_version', None)
481 job.board = data['board'] = label_info.get('board', None)
482 job.suite = data['suite'] = label_info.get('suite', None)
483
484 # TODO(ntang): check job.index directly.
showard0fec8a02009-12-04 01:19:54 +0000485 is_update = hasattr(job, 'index')
486 if is_update:
showardeab66ce2009-12-23 00:03:56 +0000487 self.update('tko_jobs', data, {'job_idx': job.index}, commit=commit)
showard0fec8a02009-12-04 01:19:54 +0000488 else:
showardeab66ce2009-12-23 00:03:56 +0000489 self.insert('tko_jobs', data, commit=commit)
showard0fec8a02009-12-04 01:19:54 +0000490 job.index = self.get_last_autonumber_value()
showardc1a98d12010-01-15 00:22:22 +0000491 self.update_job_keyvals(job, commit=commit)
jadmanski0afbb632008-06-06 21:10:57 +0000492 for test in job.tests:
493 self.insert_test(job, test, commit=commit)
apw7a7316b2008-02-21 17:42:05 +0000494
Shuqian Zhao31425d52016-12-07 09:35:03 -0800495 return data
496
mbligh237bed32007-09-05 13:05:57 +0000497
showardc1a98d12010-01-15 00:22:22 +0000498 def update_job_keyvals(self, job, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700499 """Updates the job key values.
500
501 @param job: The job object.
502 @param commit: If commit the transaction .
503 """
showardc1a98d12010-01-15 00:22:22 +0000504 for key, value in job.keyval_dict.iteritems():
505 where = {'job_id': job.index, 'key': key}
506 data = dict(where, value=value)
507 exists = self.select('id', 'tko_job_keyvals', where=where)
508
509 if exists:
510 self.update('tko_job_keyvals', data, where=where, commit=commit)
511 else:
512 self.insert('tko_job_keyvals', data, commit=commit)
513
514
jadmanski0afbb632008-06-06 21:10:57 +0000515 def insert_test(self, job, test, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700516 """Inserts a job test.
517
518 @param job: The job object.
519 @param test: The test object.
520 @param commit: If commit the transaction .
521 """
jadmanski0afbb632008-06-06 21:10:57 +0000522 kver = self.insert_kernel(test.kernel, commit=commit)
523 data = {'job_idx':job.index, 'test':test.testname,
524 'subdir':test.subdir, 'kernel_idx':kver,
525 'status':self.status_idx[test.status],
526 'reason':test.reason, 'machine_idx':job.machine_idx,
527 'started_time': test.started_time,
528 'finished_time':test.finished_time}
jadmanski9b6babf2009-04-21 17:57:40 +0000529 is_update = hasattr(test, "test_idx")
530 if is_update:
jadmanski74eebf32008-07-15 20:04:42 +0000531 test_idx = test.test_idx
showardeab66ce2009-12-23 00:03:56 +0000532 self.update('tko_tests', data,
533 {'test_idx': test_idx}, commit=commit)
jadmanskib591fba2008-09-10 16:19:22 +0000534 where = {'test_idx': test_idx}
showardeab66ce2009-12-23 00:03:56 +0000535 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700536 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000537 self.delete('tko_iteration_attributes', where)
showard0fec8a02009-12-04 01:19:54 +0000538 where['user_created'] = 0
showardeab66ce2009-12-23 00:03:56 +0000539 self.delete('tko_test_attributes', where)
jadmanski74eebf32008-07-15 20:04:42 +0000540 else:
showardeab66ce2009-12-23 00:03:56 +0000541 self.insert('tko_tests', data, commit=commit)
jadmanski74eebf32008-07-15 20:04:42 +0000542 test_idx = test.test_idx = self.get_last_autonumber_value()
543 data = {'test_idx': test_idx}
mbligh237bed32007-09-05 13:05:57 +0000544
jadmanski0afbb632008-06-06 21:10:57 +0000545 for i in test.iterations:
546 data['iteration'] = i.index
547 for key, value in i.attr_keyval.iteritems():
548 data['attribute'] = key
549 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000550 self.insert('tko_iteration_attributes', data,
jadmanski0afbb632008-06-06 21:10:57 +0000551 commit=commit)
552 for key, value in i.perf_keyval.iteritems():
553 data['attribute'] = key
Allen Li64692a32016-11-04 13:35:19 -0700554 if math.isnan(value) or math.isinf(value):
555 data['value'] = None
556 else:
557 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000558 self.insert('tko_iteration_result', data,
mbligh432bad42007-10-09 19:56:07 +0000559 commit=commit)
mbligh056d0d32006-10-08 22:31:10 +0000560
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700561 data = {'test_idx': test_idx}
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700562
jadmanski0afbb632008-06-06 21:10:57 +0000563 for key, value in test.attributes.iteritems():
564 data = {'test_idx': test_idx, 'attribute': key,
565 'value': value}
showardeab66ce2009-12-23 00:03:56 +0000566 self.insert('tko_test_attributes', data, commit=commit)
mbligh2bd48872007-09-20 18:32:25 +0000567
jadmanski9b6babf2009-04-21 17:57:40 +0000568 if not is_update:
569 for label_index in test.labels:
570 data = {'test_id': test_idx, 'testlabel_id': label_index}
showardeab66ce2009-12-23 00:03:56 +0000571 self.insert('tko_test_labels_tests', data, commit=commit)
jadmanski9b6babf2009-04-21 17:57:40 +0000572
mbligh056d0d32006-10-08 22:31:10 +0000573
jadmanski0afbb632008-06-06 21:10:57 +0000574 def read_machine_map(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700575 """Reads the machine map."""
showard71b94312009-08-20 23:40:02 +0000576 if self.machine_group or not self.machine_map:
577 return
jadmanski0afbb632008-06-06 21:10:57 +0000578 for line in open(self.machine_map, 'r').readlines():
579 (machine, group) = line.split()
580 self.machine_group[machine] = group
mbligh96b9a5a2007-11-24 19:32:20 +0000581
582
showard71b94312009-08-20 23:40:02 +0000583 def machine_info_dict(self, job):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700584 """Reads the machine information of a job.
585
586 @param job: The job object.
587
588 @return: The machine info dictionary.
589 """
jadmanski0afbb632008-06-06 21:10:57 +0000590 hostname = job.machine
showard71b94312009-08-20 23:40:02 +0000591 group = job.machine_group
592 owner = job.machine_owner
jadmanski0afbb632008-06-06 21:10:57 +0000593
594 if not group:
showard71b94312009-08-20 23:40:02 +0000595 self.read_machine_map()
jadmanski0afbb632008-06-06 21:10:57 +0000596 group = self.machine_group.get(hostname, hostname)
showard71b94312009-08-20 23:40:02 +0000597 if group == hostname and owner:
598 group = owner + '/' + hostname
jadmanski0afbb632008-06-06 21:10:57 +0000599
showard71b94312009-08-20 23:40:02 +0000600 return {'hostname': hostname, 'machine_group': group, 'owner': owner}
601
602
603 def insert_machine(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700604 """Inserts the job machine.
605
606 @param job: The job object.
607 @param commit: If commit the transaction .
608 """
showard71b94312009-08-20 23:40:02 +0000609 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000610 self.insert('tko_machines', machine_info, commit=commit)
jadmanski0afbb632008-06-06 21:10:57 +0000611 return self.get_last_autonumber_value()
612
613
showard71b94312009-08-20 23:40:02 +0000614 def update_machine_information(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700615 """Updates the job machine information.
616
617 @param job: The job object.
618 @param commit: If commit the transaction .
619 """
showard71b94312009-08-20 23:40:02 +0000620 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000621 self.update('tko_machines', machine_info,
showard71b94312009-08-20 23:40:02 +0000622 where={'hostname': machine_info['hostname']},
623 commit=commit)
624
625
jadmanski0afbb632008-06-06 21:10:57 +0000626 def lookup_machine(self, hostname):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700627 """Look up the machine information.
628
629 @param hostname: The hostname as string.
630 """
jadmanski0afbb632008-06-06 21:10:57 +0000631 where = { 'hostname' : hostname }
showardeab66ce2009-12-23 00:03:56 +0000632 rows = self.select('machine_idx', 'tko_machines', where)
jadmanski0afbb632008-06-06 21:10:57 +0000633 if rows:
634 return rows[0][0]
635 else:
636 return None
637
638
639 def lookup_kernel(self, kernel):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700640 """Look up the kernel.
641
642 @param kernel: The kernel object.
643 """
showardeab66ce2009-12-23 00:03:56 +0000644 rows = self.select('kernel_idx', 'tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000645 {'kernel_hash':kernel.kernel_hash})
646 if rows:
647 return rows[0][0]
648 else:
649 return None
650
651
652 def insert_kernel(self, kernel, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700653 """Insert a kernel.
654
655 @param kernel: The kernel object.
656 @param commit: If commit the transaction .
657 """
jadmanski0afbb632008-06-06 21:10:57 +0000658 kver = self.lookup_kernel(kernel)
659 if kver:
660 return kver
661
662 # If this kernel has any significant patches, append their hash
663 # as diferentiator.
664 printable = kernel.base
665 patch_count = 0
666 for patch in kernel.patches:
667 match = re.match(r'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$',
668 patch.reference)
669 if not match:
670 patch_count += 1
671
showardeab66ce2009-12-23 00:03:56 +0000672 self.insert('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000673 {'base':kernel.base,
674 'kernel_hash':kernel.kernel_hash,
675 'printable':printable},
676 commit=commit)
677 kver = self.get_last_autonumber_value()
678
679 if patch_count > 0:
680 printable += ' p%d' % (kver)
showardeab66ce2009-12-23 00:03:56 +0000681 self.update('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000682 {'printable':printable},
683 {'kernel_idx':kver})
684
685 for patch in kernel.patches:
686 self.insert_patch(kver, patch, commit=commit)
687 return kver
688
689
690 def insert_patch(self, kver, patch, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700691 """Insert a kernel patch.
692
693 @param kver: The kernel version.
694 @param patch: The kernel patch object.
695 @param commit: If commit the transaction .
696 """
jadmanski0afbb632008-06-06 21:10:57 +0000697 print patch.reference
698 name = os.path.basename(patch.reference)[:80]
showardeab66ce2009-12-23 00:03:56 +0000699 self.insert('tko_patches',
jadmanski0afbb632008-06-06 21:10:57 +0000700 {'kernel_idx': kver,
701 'name':name,
702 'url':patch.reference,
703 'hash':patch.hash},
704 commit=commit)
705
706
jadmanski74eebf32008-07-15 20:04:42 +0000707 def find_test(self, job_idx, testname, subdir):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700708 """Find a test by name.
709
710 @param job_idx: The job index.
711 @param testname: The test name.
712 @param subdir: The test sub directory under the job directory.
713 """
jadmanski74eebf32008-07-15 20:04:42 +0000714 where = {'job_idx': job_idx , 'test': testname, 'subdir': subdir}
showardeab66ce2009-12-23 00:03:56 +0000715 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000716 if rows:
717 return rows[0][0]
718 else:
719 return None
720
721
722 def find_tests(self, job_idx):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700723 """Find all tests by job index.
724
725 @param job_idx: The job index.
726 @return: A list of tests.
727 """
jadmanski0afbb632008-06-06 21:10:57 +0000728 where = { 'job_idx':job_idx }
showardeab66ce2009-12-23 00:03:56 +0000729 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000730 if rows:
731 return [row[0] for row in rows]
732 else:
733 return []
734
735
736 def find_job(self, tag):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700737 """Find a job by tag.
738
739 @param tag: The job tag name.
740 @return: The job object or None.
741 """
showardeab66ce2009-12-23 00:03:56 +0000742 rows = self.select('job_idx', 'tko_jobs', {'tag': tag})
jadmanski0afbb632008-06-06 21:10:57 +0000743 if rows:
744 return rows[0][0]
745 else:
746 return None
mblighaf25f062007-12-03 17:48:35 +0000747
748
mbligh96cf0512008-04-17 15:25:38 +0000749def _get_db_type():
jadmanski0afbb632008-06-06 21:10:57 +0000750 """Get the database type name to use from the global config."""
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700751 get_value = global_config.global_config.get_config_value_with_fallback
752 return "db_" + get_value("AUTOTEST_WEB", "global_db_type", "db_type",
753 default="mysql")
mblighaf25f062007-12-03 17:48:35 +0000754
mbligh96cf0512008-04-17 15:25:38 +0000755
756def _get_error_class(class_name):
jadmanski0afbb632008-06-06 21:10:57 +0000757 """Retrieves the appropriate error class by name from the database
758 module."""
759 db_module = __import__("autotest_lib.tko." + _get_db_type(),
760 globals(), locals(), ["driver"])
761 return getattr(db_module.driver, class_name)
mbligh96cf0512008-04-17 15:25:38 +0000762
763
764def db(*args, **dargs):
jadmanski0afbb632008-06-06 21:10:57 +0000765 """Creates an instance of the database class with the arguments
766 provided in args and dargs, using the database type specified by
Michael Tang5f74ffd2016-10-31 10:34:53 -0700767 the global configuration (defaulting to mysql).
768
769 @param args: The db_type arguments.
770 @param dargs: The db_type named arguments.
771
772 @return: An db object.
773 """
jadmanski0afbb632008-06-06 21:10:57 +0000774 db_type = _get_db_type()
775 db_module = __import__("autotest_lib.tko." + db_type, globals(),
776 locals(), [db_type])
777 db = getattr(db_module, db_type)(*args, **dargs)
778 return db