blob: 1e2385668aaa0966ea604dd77701fcab14075502 [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
jamesrena12b8a02010-06-16 23:28:23 +000016from autotest_lib.tko import utils
mbligh96cf0512008-04-17 15:25:38 +000017
mblighed4d6dd2008-02-27 16:49:43 +000018
Allen Lie0bb7602016-11-29 13:45:53 -080019def _log_error(msg):
20 """Log an error message.
21
22 @param msg: Message string
23 """
24 print >> sys.stderr, msg
25 sys.stderr.flush() # we want these msgs to show up immediately
26
27
28def _format_operational_error(e):
29 """Format OperationalError.
30
31 @param e: OperationalError instance.
32 """
33 return ("%s: An operational error occurred during a database "
34 "operation: %s" % (time.strftime("%X %x"), str(e)))
35
36
mblighaea09602008-04-16 22:59:37 +000037class MySQLTooManyRows(Exception):
Michael Tang5f74ffd2016-10-31 10:34:53 -070038 """Too many records."""
jadmanski0afbb632008-06-06 21:10:57 +000039 pass
mblighaea09602008-04-16 22:59:37 +000040
mblighd5c33db2006-10-08 21:34:16 +000041
mbligh7636b3a2008-06-11 15:44:01 +000042class db_sql(object):
Michael Tang5f74ffd2016-10-31 10:34:53 -070043 """Data access."""
44
jadmanski0afbb632008-06-06 21:10:57 +000045 def __init__(self, debug=False, autocommit=True, host=None,
46 database=None, user=None, password=None):
47 self.debug = debug
48 self.autocommit = autocommit
49 self._load_config(host, database, user, password)
mbligh96cf0512008-04-17 15:25:38 +000050
jadmanski0afbb632008-06-06 21:10:57 +000051 self.con = None
52 self._init_db()
mblighd5c33db2006-10-08 21:34:16 +000053
jadmanski0afbb632008-06-06 21:10:57 +000054 # if not present, insert statuses
55 self.status_idx = {}
56 self.status_word = {}
showardeab66ce2009-12-23 00:03:56 +000057 status_rows = self.select('status_idx, word', 'tko_status', None)
jadmanski0afbb632008-06-06 21:10:57 +000058 for s in status_rows:
59 self.status_idx[s[1]] = s[0]
60 self.status_word[s[0]] = s[1]
mbligh048e1c92007-10-07 00:10:33 +000061
jadmanski0afbb632008-06-06 21:10:57 +000062 machine_map = os.path.join(os.path.dirname(__file__),
63 'machines')
64 if os.path.exists(machine_map):
65 self.machine_map = machine_map
66 else:
67 self.machine_map = None
68 self.machine_group = {}
mbligh048e1c92007-10-07 00:10:33 +000069
mbligh8e1ab172007-09-13 17:29:56 +000070
jadmanski0afbb632008-06-06 21:10:57 +000071 def _load_config(self, host, database, user, password):
Jakob Juelich934f0dc2014-10-14 18:21:13 -070072 """Loads configuration settings required to connect to the database.
73
74 This will try to connect to use the settings prefixed with global_db_.
75 If they do not exist, they un-prefixed settings will be used.
76
77 If parameters are supplied, these will be taken instead of the values
78 in global_config.
79
80 @param host: If set, this host will be used, if not, the host will be
81 retrieved from global_config.
82 @param database: If set, this database will be used, if not, the
83 database will be retrieved from global_config.
84 @param user: If set, this user will be used, if not, the
85 user will be retrieved from global_config.
86 @param password: If set, this password will be used, if not, the
87 password will be retrieved from global_config.
88 """
89 database_settings = database_settings_helper.get_global_db_config()
mbligh65acae52008-04-24 20:21:55 +000090
jadmanski0afbb632008-06-06 21:10:57 +000091 # grab the host, database
Jakob Juelich934f0dc2014-10-14 18:21:13 -070092 self.host = host or database_settings['HOST']
93 self.database = database or database_settings['NAME']
mbligh65acae52008-04-24 20:21:55 +000094
jadmanski0afbb632008-06-06 21:10:57 +000095 # grab the user and password
Jakob Juelich934f0dc2014-10-14 18:21:13 -070096 self.user = user or database_settings['USER']
97 self.password = password or database_settings['PASSWORD']
mbligh65acae52008-04-24 20:21:55 +000098
Michael Spang7a273472014-10-08 12:08:13 -040099 # grab the timeout configuration
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700100 self.query_timeout =(
101 database_settings.get('OPTIONS', {}).get('timeout', 3600))
102
103 # Using fallback to non-global in order to work without configuration
104 # overhead on non-shard instances.
105 get_value = global_config.global_config.get_config_value_with_fallback
Jakob Juelich475b82b2014-09-30 11:17:07 -0700106 self.min_delay = get_value("AUTOTEST_WEB", "global_db_min_retry_delay",
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700107 "min_retry_delay", type=int, default=20)
Jakob Juelich475b82b2014-09-30 11:17:07 -0700108 self.max_delay = get_value("AUTOTEST_WEB", "global_db_max_retry_delay",
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700109 "max_retry_delay", type=int, default=60)
mbligh65acae52008-04-24 20:21:55 +0000110
Richard Barnette2468fbd2014-11-07 01:12:46 +0000111 # TODO(beeps): Move this to django settings once we have routers.
112 # On test instances mysql connects through a different port. No point
113 # piping this through our entire infrastructure when it is only really
114 # used for testing; Ideally we would specify this through django
115 # settings and default it to the empty string so django will figure out
116 # the default based on the database backend (eg: mysql, 3306), but until
117 # we have database routers in place any django settings will apply to
118 # both tko and afe.
119 # The intended use of this port is to allow a testing shard vm to
120 # update the master vm's database with test results. Specifying
121 # and empty string will fallback to not even specifying the port
122 # to the backend in tko/db.py. Unfortunately this means retries
123 # won't work on the test cluster till we've migrated to routers.
124 self.port = global_config.global_config.get_config_value(
125 "AUTOTEST_WEB", "global_db_port", type=str, default='')
126
mbligh65acae52008-04-24 20:21:55 +0000127
jadmanski0afbb632008-06-06 21:10:57 +0000128 def _init_db(self):
129 # make sure we clean up any existing connection
130 if self.con:
131 self.con.close()
132 self.con = None
mbligh65acae52008-04-24 20:21:55 +0000133
Dan Shi8684b922015-10-06 13:29:18 -0700134 try:
135 # create the db connection and cursor
136 self.con = self.connect(self.host, self.database,
137 self.user, self.password, self.port)
138 except:
139 autotest_stats.Counter('tko_db_con_error').increment()
140 raise
jadmanski0afbb632008-06-06 21:10:57 +0000141 self.cur = self.con.cursor()
mbligh96cf0512008-04-17 15:25:38 +0000142
143
jadmanski0afbb632008-06-06 21:10:57 +0000144 def _random_delay(self):
145 delay = random.randint(self.min_delay, self.max_delay)
146 time.sleep(delay)
mbligh65acae52008-04-24 20:21:55 +0000147
148
jadmanski0afbb632008-06-06 21:10:57 +0000149 def run_with_retry(self, function, *args, **dargs):
150 """Call function(*args, **dargs) until either it passes
151 without an operational error, or a timeout is reached.
152 This will re-connect to the database, so it is NOT safe
153 to use this inside of a database transaction.
jadmanskie7a69092008-05-29 21:03:13 +0000154
jadmanski0afbb632008-06-06 21:10:57 +0000155 It can be safely used with transactions, but the
156 transaction start & end must be completely contained
Michael Tang5f74ffd2016-10-31 10:34:53 -0700157 within the call to 'function'.
158
159 @param function: The function to run with retry.
160 @param args: The arguments
161 @param dargs: The named arguments.
162 """
jadmanski0afbb632008-06-06 21:10:57 +0000163 OperationalError = _get_error_class("OperationalError")
mbligh65acae52008-04-24 20:21:55 +0000164
jadmanski0afbb632008-06-06 21:10:57 +0000165 success = False
166 start_time = time.time()
167 while not success:
168 try:
169 result = function(*args, **dargs)
170 except OperationalError, e:
Allen Lie0bb7602016-11-29 13:45:53 -0800171 _log_error("%s; retrying, don't panic yet"
172 % _format_operational_error(e))
jadmanski0afbb632008-06-06 21:10:57 +0000173 stop_time = time.time()
174 elapsed_time = stop_time - start_time
175 if elapsed_time > self.query_timeout:
176 raise
177 else:
178 try:
179 self._random_delay()
180 self._init_db()
Dan Shie8e0c052015-09-01 00:27:27 -0700181 autotest_stats.Counter('tko_db_error').increment()
jadmanski0afbb632008-06-06 21:10:57 +0000182 except OperationalError, e:
Allen Lie0bb7602016-11-29 13:45:53 -0800183 _log_error('%s; panic now'
184 % _format_operational_error(e))
jadmanski0afbb632008-06-06 21:10:57 +0000185 else:
186 success = True
187 return result
mbligh96cf0512008-04-17 15:25:38 +0000188
189
jadmanski0afbb632008-06-06 21:10:57 +0000190 def dprint(self, value):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700191 """Print out debug value.
192
193 @param value: The value to print out.
194 """
jadmanski0afbb632008-06-06 21:10:57 +0000195 if self.debug:
196 sys.stdout.write('SQL: ' + str(value) + '\n')
mbligh8e1ab172007-09-13 17:29:56 +0000197
mblighd5c33db2006-10-08 21:34:16 +0000198
Dan Shie8e0c052015-09-01 00:27:27 -0700199 def _commit(self):
200 """Private method for function commit to call for retry.
201 """
202 return self.con.commit()
203
204
jadmanski0afbb632008-06-06 21:10:57 +0000205 def commit(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700206 """Commit the sql transaction."""
Dan Shie8e0c052015-09-01 00:27:27 -0700207 if self.autocommit:
208 return self.run_with_retry(self._commit)
209 else:
210 return self._commit()
mbligh432bad42007-10-09 19:56:07 +0000211
212
Simran Basie129a962012-08-31 13:03:53 -0700213 def rollback(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700214 """Rollback the sql transaction."""
Simran Basie129a962012-08-31 13:03:53 -0700215 self.con.rollback()
216
217
jadmanski0afbb632008-06-06 21:10:57 +0000218 def get_last_autonumber_value(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700219 """Gets the last auto number.
220
221 @return: The last auto number.
222 """
jadmanski0afbb632008-06-06 21:10:57 +0000223 self.cur.execute('SELECT LAST_INSERT_ID()', [])
224 return self.cur.fetchall()[0][0]
mblighe12b8612008-02-12 20:58:14 +0000225
226
showardc1a98d12010-01-15 00:22:22 +0000227 def _quote(self, field):
228 return '`%s`' % field
229
230
231 def _where_clause(self, where):
232 if not where:
233 return '', []
234
235 if isinstance(where, dict):
236 # key/value pairs (which should be equal, or None for null)
237 keys, values = [], []
238 for field, value in where.iteritems():
239 quoted_field = self._quote(field)
240 if value is None:
241 keys.append(quoted_field + ' is null')
242 else:
243 keys.append(quoted_field + '=%s')
244 values.append(value)
245 where_clause = ' and '.join(keys)
246 elif isinstance(where, basestring):
247 # the exact string
248 where_clause = where
249 values = []
250 elif isinstance(where, tuple):
251 # preformatted where clause + values
252 where_clause, values = where
253 assert where_clause
254 else:
255 raise ValueError('Invalid "where" value: %r' % where)
256
257 return ' WHERE ' + where_clause, values
258
259
260
261 def select(self, fields, table, where, distinct=False, group_by=None,
262 max_rows=None):
jadmanski0afbb632008-06-06 21:10:57 +0000263 """\
264 This selects all the fields requested from a
265 specific table with a particular where clause.
266 The where clause can either be a dictionary of
267 field=value pairs, a string, or a tuple of (string,
268 a list of values). The last option is what you
269 should use when accepting user input as it'll
270 protect you against sql injection attacks (if
271 all user data is placed in the array rather than
272 the raw SQL).
mbligh12eebfa2008-01-03 02:01:53 +0000273
jadmanski0afbb632008-06-06 21:10:57 +0000274 For example:
275 where = ("a = %s AND b = %s", ['val', 'val'])
276 is better than
277 where = "a = 'val' AND b = 'val'"
Michael Tang5f74ffd2016-10-31 10:34:53 -0700278
279 @param fields: The list of selected fields string.
280 @param table: The name of the database table.
281 @param where: The where clause string.
282 @param distinct: If select distinct values.
283 @param group_by: Group by clause.
284 @param max_rows: unused.
jadmanski0afbb632008-06-06 21:10:57 +0000285 """
286 cmd = ['select']
287 if distinct:
288 cmd.append('distinct')
289 cmd += [fields, 'from', table]
mbligh608c3252007-08-31 13:53:00 +0000290
showardc1a98d12010-01-15 00:22:22 +0000291 where_clause, values = self._where_clause(where)
292 cmd.append(where_clause)
mbligh96cf0512008-04-17 15:25:38 +0000293
jadmanski0afbb632008-06-06 21:10:57 +0000294 if group_by:
295 cmd.append(' GROUP BY ' + group_by)
mbligh83f63a02007-12-12 19:13:04 +0000296
jadmanski0afbb632008-06-06 21:10:57 +0000297 self.dprint('%s %s' % (' '.join(cmd), values))
mbligh96cf0512008-04-17 15:25:38 +0000298
jadmanski0afbb632008-06-06 21:10:57 +0000299 # create a re-runable function for executing the query
300 def exec_sql():
Michael Tang5f74ffd2016-10-31 10:34:53 -0700301 """Exeuctes an the sql command."""
jadmanski0afbb632008-06-06 21:10:57 +0000302 sql = ' '.join(cmd)
303 numRec = self.cur.execute(sql, values)
mblighd876f452008-12-03 15:09:17 +0000304 if max_rows is not None and numRec > max_rows:
jadmanski0afbb632008-06-06 21:10:57 +0000305 msg = 'Exceeded allowed number of records'
306 raise MySQLTooManyRows(msg)
307 return self.cur.fetchall()
mbligh96cf0512008-04-17 15:25:38 +0000308
jadmanski0afbb632008-06-06 21:10:57 +0000309 # run the query, re-trying after operational errors
310 if self.autocommit:
311 return self.run_with_retry(exec_sql)
312 else:
313 return exec_sql()
mblighd5c33db2006-10-08 21:34:16 +0000314
mbligh056d0d32006-10-08 22:31:10 +0000315
jadmanski0afbb632008-06-06 21:10:57 +0000316 def select_sql(self, fields, table, sql, values):
317 """\
318 select fields from table "sql"
Michael Tang5f74ffd2016-10-31 10:34:53 -0700319
320 @param fields: The list of selected fields string.
321 @param table: The name of the database table.
322 @param sql: The sql string.
323 @param values: The sql string parameter values.
jadmanski0afbb632008-06-06 21:10:57 +0000324 """
325 cmd = 'select %s from %s %s' % (fields, table, sql)
326 self.dprint(cmd)
mbligh414c69e2007-10-05 15:13:06 +0000327
jadmanski0afbb632008-06-06 21:10:57 +0000328 # create a -re-runable function for executing the query
Michael Tang5f74ffd2016-10-31 10:34:53 -0700329 def _exec_sql():
jadmanski0afbb632008-06-06 21:10:57 +0000330 self.cur.execute(cmd, values)
331 return self.cur.fetchall()
mbligh96b9a5a2007-11-24 19:32:20 +0000332
jadmanski0afbb632008-06-06 21:10:57 +0000333 # run the query, re-trying after operational errors
334 if self.autocommit:
Michael Tang5f74ffd2016-10-31 10:34:53 -0700335 return self.run_with_retry(_exec_sql)
jadmanski0afbb632008-06-06 21:10:57 +0000336 else:
Michael Tang5f74ffd2016-10-31 10:34:53 -0700337 return _exec_sql()
mbligh96b9a5a2007-11-24 19:32:20 +0000338
mbligh608c3252007-08-31 13:53:00 +0000339
jadmanski0afbb632008-06-06 21:10:57 +0000340 def _exec_sql_with_commit(self, sql, values, commit):
341 if self.autocommit:
342 # re-run the query until it succeeds
Michael Tang5f74ffd2016-10-31 10:34:53 -0700343 def _exec_sql():
jadmanski0afbb632008-06-06 21:10:57 +0000344 self.cur.execute(sql, values)
345 self.con.commit()
Michael Tang5f74ffd2016-10-31 10:34:53 -0700346 self.run_with_retry(_exec_sql)
jadmanski0afbb632008-06-06 21:10:57 +0000347 else:
348 # take one shot at running the query
349 self.cur.execute(sql, values)
350 if commit:
351 self.con.commit()
mbligh96b9a5a2007-11-24 19:32:20 +0000352
mbligh2bd48872007-09-20 18:32:25 +0000353
jadmanskib591fba2008-09-10 16:19:22 +0000354 def insert(self, table, data, commit=None):
jadmanski0afbb632008-06-06 21:10:57 +0000355 """\
356 'insert into table (keys) values (%s ... %s)', values
mbligh96cf0512008-04-17 15:25:38 +0000357
jadmanski0afbb632008-06-06 21:10:57 +0000358 data:
359 dictionary of fields and data
Michael Tang5f74ffd2016-10-31 10:34:53 -0700360
361 @param table: The name of the table.
362 @param data: The insert data.
363 @param commit: If commit the transaction .
jadmanski0afbb632008-06-06 21:10:57 +0000364 """
365 fields = data.keys()
366 refs = ['%s' for field in fields]
367 values = [data[field] for field in fields]
showardc1a98d12010-01-15 00:22:22 +0000368 cmd = ('insert into %s (%s) values (%s)' %
369 (table, ','.join(self._quote(field) for field in fields),
370 ','.join(refs)))
jadmanski0afbb632008-06-06 21:10:57 +0000371 self.dprint('%s %s' % (cmd, values))
mblighe9cf9d42007-08-31 08:56:00 +0000372
jadmanski0afbb632008-06-06 21:10:57 +0000373 self._exec_sql_with_commit(cmd, values, commit)
mblighe9cf9d42007-08-31 08:56:00 +0000374
mbligh048e1c92007-10-07 00:10:33 +0000375
jadmanski0afbb632008-06-06 21:10:57 +0000376 def delete(self, table, where, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700377 """Delete entries.
378
379 @param table: The name of the table.
380 @param where: The where clause.
381 @param commit: If commit the transaction .
382 """
jadmanski0afbb632008-06-06 21:10:57 +0000383 cmd = ['delete from', table]
mblighd876f452008-12-03 15:09:17 +0000384 if commit is None:
jadmanski0afbb632008-06-06 21:10:57 +0000385 commit = self.autocommit
showardc1a98d12010-01-15 00:22:22 +0000386 where_clause, values = self._where_clause(where)
387 cmd.append(where_clause)
jadmanski0afbb632008-06-06 21:10:57 +0000388 sql = ' '.join(cmd)
389 self.dprint('%s %s' % (sql, values))
mbligh048e1c92007-10-07 00:10:33 +0000390
jadmanski0afbb632008-06-06 21:10:57 +0000391 self._exec_sql_with_commit(sql, values, commit)
mbligh048e1c92007-10-07 00:10:33 +0000392
mbligh7a41a862007-11-30 17:44:24 +0000393
jadmanski0afbb632008-06-06 21:10:57 +0000394 def update(self, table, data, where, commit = None):
395 """\
396 'update table set data values (%s ... %s) where ...'
mbligh2aaeb672007-10-01 14:54:18 +0000397
jadmanski0afbb632008-06-06 21:10:57 +0000398 data:
399 dictionary of fields and data
Michael Tang5f74ffd2016-10-31 10:34:53 -0700400
401 @param table: The name of the table.
402 @param data: The sql parameter values.
403 @param where: The where clause.
404 @param commit: If commit the transaction .
jadmanski0afbb632008-06-06 21:10:57 +0000405 """
mblighd876f452008-12-03 15:09:17 +0000406 if commit is None:
jadmanski0afbb632008-06-06 21:10:57 +0000407 commit = self.autocommit
408 cmd = 'update %s ' % table
409 fields = data.keys()
showardc1a98d12010-01-15 00:22:22 +0000410 data_refs = [self._quote(field) + '=%s' for field in fields]
jadmanski0afbb632008-06-06 21:10:57 +0000411 data_values = [data[field] for field in fields]
jadmanski74eebf32008-07-15 20:04:42 +0000412 cmd += ' set ' + ', '.join(data_refs)
mbligh2aaeb672007-10-01 14:54:18 +0000413
showardc1a98d12010-01-15 00:22:22 +0000414 where_clause, where_values = self._where_clause(where)
415 cmd += where_clause
mbligh2aaeb672007-10-01 14:54:18 +0000416
jadmanski0afbb632008-06-06 21:10:57 +0000417 values = data_values + where_values
jadmanski74eebf32008-07-15 20:04:42 +0000418 self.dprint('%s %s' % (cmd, values))
mbligh2aaeb672007-10-01 14:54:18 +0000419
jadmanski0afbb632008-06-06 21:10:57 +0000420 self._exec_sql_with_commit(cmd, values, commit)
mblighe9cf9d42007-08-31 08:56:00 +0000421
422
jadmanski0afbb632008-06-06 21:10:57 +0000423 def delete_job(self, tag, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700424 """Delete a tko job.
425
426 @param tag: The job tag.
427 @param commit: If commit the transaction .
428 """
jadmanski0afbb632008-06-06 21:10:57 +0000429 job_idx = self.find_job(tag)
430 for test_idx in self.find_tests(job_idx):
431 where = {'test_idx' : test_idx}
showardeab66ce2009-12-23 00:03:56 +0000432 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700433 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000434 self.delete('tko_iteration_attributes', where)
435 self.delete('tko_test_attributes', where)
436 self.delete('tko_test_labels_tests', {'test_id': test_idx})
jadmanski0afbb632008-06-06 21:10:57 +0000437 where = {'job_idx' : job_idx}
showardeab66ce2009-12-23 00:03:56 +0000438 self.delete('tko_tests', where)
439 self.delete('tko_jobs', where)
apw7a7316b2008-02-21 17:42:05 +0000440
apw7a7316b2008-02-21 17:42:05 +0000441
Dan Shi70647ca2015-07-16 22:52:35 -0700442 def insert_job(self, tag, job, parent_job_id=None, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700443 """Insert a tko job.
444
445 @param tag: The job tag.
446 @param job: The job object.
447 @param parent_job_id: The parent job id.
448 @param commit: If commit the transaction .
Shuqian Zhao31425d52016-12-07 09:35:03 -0800449
450 @return The dict of data inserted into the tko_jobs table.
Michael Tang5f74ffd2016-10-31 10:34:53 -0700451 """
jadmanski0afbb632008-06-06 21:10:57 +0000452 job.machine_idx = self.lookup_machine(job.machine)
453 if not job.machine_idx:
showard71b94312009-08-20 23:40:02 +0000454 job.machine_idx = self.insert_machine(job, commit=commit)
Dan Shid77fc1b2015-10-13 17:34:50 -0700455 elif job.machine:
456 # Only try to update tko_machines record if machine is set. This
457 # prevents unnecessary db writes for suite jobs.
showard71b94312009-08-20 23:40:02 +0000458 self.update_machine_information(job, commit=commit)
459
jamesrena12b8a02010-06-16 23:28:23 +0000460 afe_job_id = utils.get_afe_job_id(tag)
showardc1c1caf2009-09-08 16:26:50 +0000461
showard0fec8a02009-12-04 01:19:54 +0000462 data = {'tag':tag,
463 'label': job.label,
464 'username': job.user,
465 'machine_idx': job.machine_idx,
466 'queued_time': job.queued_time,
467 'started_time': job.started_time,
468 'finished_time': job.finished_time,
Dan Shi70647ca2015-07-16 22:52:35 -0700469 'afe_job_id': afe_job_id,
Benny Peakefeb775c2017-02-08 15:14:14 -0800470 'afe_parent_job_id': parent_job_id,
471 'build': job.build,
472 'build_version': job.build_version,
473 'board': job.board,
474 'suite': job.suite}
Michael Tang5f74ffd2016-10-31 10:34:53 -0700475 job.afe_job_id = afe_job_id
476 if parent_job_id:
477 job.afe_parent_job_id = str(parent_job_id)
Michael Tang5f74ffd2016-10-31 10:34:53 -0700478
479 # TODO(ntang): check job.index directly.
showard0fec8a02009-12-04 01:19:54 +0000480 is_update = hasattr(job, 'index')
481 if is_update:
showardeab66ce2009-12-23 00:03:56 +0000482 self.update('tko_jobs', data, {'job_idx': job.index}, commit=commit)
showard0fec8a02009-12-04 01:19:54 +0000483 else:
showardeab66ce2009-12-23 00:03:56 +0000484 self.insert('tko_jobs', data, commit=commit)
showard0fec8a02009-12-04 01:19:54 +0000485 job.index = self.get_last_autonumber_value()
showardc1a98d12010-01-15 00:22:22 +0000486 self.update_job_keyvals(job, commit=commit)
jadmanski0afbb632008-06-06 21:10:57 +0000487 for test in job.tests:
488 self.insert_test(job, test, commit=commit)
apw7a7316b2008-02-21 17:42:05 +0000489
Shuqian Zhao31425d52016-12-07 09:35:03 -0800490 return data
491
mbligh237bed32007-09-05 13:05:57 +0000492
showardc1a98d12010-01-15 00:22:22 +0000493 def update_job_keyvals(self, job, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700494 """Updates the job key values.
495
496 @param job: The job object.
497 @param commit: If commit the transaction .
498 """
showardc1a98d12010-01-15 00:22:22 +0000499 for key, value in job.keyval_dict.iteritems():
500 where = {'job_id': job.index, 'key': key}
501 data = dict(where, value=value)
502 exists = self.select('id', 'tko_job_keyvals', where=where)
503
504 if exists:
505 self.update('tko_job_keyvals', data, where=where, commit=commit)
506 else:
507 self.insert('tko_job_keyvals', data, commit=commit)
508
509
jadmanski0afbb632008-06-06 21:10:57 +0000510 def insert_test(self, job, test, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700511 """Inserts a job test.
512
513 @param job: The job object.
514 @param test: The test object.
515 @param commit: If commit the transaction .
516 """
jadmanski0afbb632008-06-06 21:10:57 +0000517 kver = self.insert_kernel(test.kernel, commit=commit)
518 data = {'job_idx':job.index, 'test':test.testname,
519 'subdir':test.subdir, 'kernel_idx':kver,
520 'status':self.status_idx[test.status],
521 'reason':test.reason, 'machine_idx':job.machine_idx,
522 'started_time': test.started_time,
523 'finished_time':test.finished_time}
jadmanski9b6babf2009-04-21 17:57:40 +0000524 is_update = hasattr(test, "test_idx")
525 if is_update:
jadmanski74eebf32008-07-15 20:04:42 +0000526 test_idx = test.test_idx
showardeab66ce2009-12-23 00:03:56 +0000527 self.update('tko_tests', data,
528 {'test_idx': test_idx}, commit=commit)
jadmanskib591fba2008-09-10 16:19:22 +0000529 where = {'test_idx': test_idx}
showardeab66ce2009-12-23 00:03:56 +0000530 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700531 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000532 self.delete('tko_iteration_attributes', where)
showard0fec8a02009-12-04 01:19:54 +0000533 where['user_created'] = 0
showardeab66ce2009-12-23 00:03:56 +0000534 self.delete('tko_test_attributes', where)
jadmanski74eebf32008-07-15 20:04:42 +0000535 else:
showardeab66ce2009-12-23 00:03:56 +0000536 self.insert('tko_tests', data, commit=commit)
jadmanski74eebf32008-07-15 20:04:42 +0000537 test_idx = test.test_idx = self.get_last_autonumber_value()
538 data = {'test_idx': test_idx}
mbligh237bed32007-09-05 13:05:57 +0000539
jadmanski0afbb632008-06-06 21:10:57 +0000540 for i in test.iterations:
541 data['iteration'] = i.index
542 for key, value in i.attr_keyval.iteritems():
543 data['attribute'] = key
544 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000545 self.insert('tko_iteration_attributes', data,
jadmanski0afbb632008-06-06 21:10:57 +0000546 commit=commit)
547 for key, value in i.perf_keyval.iteritems():
548 data['attribute'] = key
Allen Li64692a32016-11-04 13:35:19 -0700549 if math.isnan(value) or math.isinf(value):
550 data['value'] = None
551 else:
552 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000553 self.insert('tko_iteration_result', data,
mbligh432bad42007-10-09 19:56:07 +0000554 commit=commit)
mbligh056d0d32006-10-08 22:31:10 +0000555
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700556 data = {'test_idx': test_idx}
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700557
jadmanski0afbb632008-06-06 21:10:57 +0000558 for key, value in test.attributes.iteritems():
559 data = {'test_idx': test_idx, 'attribute': key,
560 'value': value}
showardeab66ce2009-12-23 00:03:56 +0000561 self.insert('tko_test_attributes', data, commit=commit)
mbligh2bd48872007-09-20 18:32:25 +0000562
jadmanski9b6babf2009-04-21 17:57:40 +0000563 if not is_update:
564 for label_index in test.labels:
565 data = {'test_id': test_idx, 'testlabel_id': label_index}
showardeab66ce2009-12-23 00:03:56 +0000566 self.insert('tko_test_labels_tests', data, commit=commit)
jadmanski9b6babf2009-04-21 17:57:40 +0000567
mbligh056d0d32006-10-08 22:31:10 +0000568
jadmanski0afbb632008-06-06 21:10:57 +0000569 def read_machine_map(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700570 """Reads the machine map."""
showard71b94312009-08-20 23:40:02 +0000571 if self.machine_group or not self.machine_map:
572 return
jadmanski0afbb632008-06-06 21:10:57 +0000573 for line in open(self.machine_map, 'r').readlines():
574 (machine, group) = line.split()
575 self.machine_group[machine] = group
mbligh96b9a5a2007-11-24 19:32:20 +0000576
577
showard71b94312009-08-20 23:40:02 +0000578 def machine_info_dict(self, job):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700579 """Reads the machine information of a job.
580
581 @param job: The job object.
582
583 @return: The machine info dictionary.
584 """
jadmanski0afbb632008-06-06 21:10:57 +0000585 hostname = job.machine
showard71b94312009-08-20 23:40:02 +0000586 group = job.machine_group
587 owner = job.machine_owner
jadmanski0afbb632008-06-06 21:10:57 +0000588
589 if not group:
showard71b94312009-08-20 23:40:02 +0000590 self.read_machine_map()
jadmanski0afbb632008-06-06 21:10:57 +0000591 group = self.machine_group.get(hostname, hostname)
showard71b94312009-08-20 23:40:02 +0000592 if group == hostname and owner:
593 group = owner + '/' + hostname
jadmanski0afbb632008-06-06 21:10:57 +0000594
showard71b94312009-08-20 23:40:02 +0000595 return {'hostname': hostname, 'machine_group': group, 'owner': owner}
596
597
598 def insert_machine(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700599 """Inserts the job machine.
600
601 @param job: The job object.
602 @param commit: If commit the transaction .
603 """
showard71b94312009-08-20 23:40:02 +0000604 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000605 self.insert('tko_machines', machine_info, commit=commit)
jadmanski0afbb632008-06-06 21:10:57 +0000606 return self.get_last_autonumber_value()
607
608
showard71b94312009-08-20 23:40:02 +0000609 def update_machine_information(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700610 """Updates the job machine information.
611
612 @param job: The job object.
613 @param commit: If commit the transaction .
614 """
showard71b94312009-08-20 23:40:02 +0000615 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000616 self.update('tko_machines', machine_info,
showard71b94312009-08-20 23:40:02 +0000617 where={'hostname': machine_info['hostname']},
618 commit=commit)
619
620
jadmanski0afbb632008-06-06 21:10:57 +0000621 def lookup_machine(self, hostname):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700622 """Look up the machine information.
623
624 @param hostname: The hostname as string.
625 """
jadmanski0afbb632008-06-06 21:10:57 +0000626 where = { 'hostname' : hostname }
showardeab66ce2009-12-23 00:03:56 +0000627 rows = self.select('machine_idx', 'tko_machines', where)
jadmanski0afbb632008-06-06 21:10:57 +0000628 if rows:
629 return rows[0][0]
630 else:
631 return None
632
633
634 def lookup_kernel(self, kernel):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700635 """Look up the kernel.
636
637 @param kernel: The kernel object.
638 """
showardeab66ce2009-12-23 00:03:56 +0000639 rows = self.select('kernel_idx', 'tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000640 {'kernel_hash':kernel.kernel_hash})
641 if rows:
642 return rows[0][0]
643 else:
644 return None
645
646
647 def insert_kernel(self, kernel, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700648 """Insert a kernel.
649
650 @param kernel: The kernel object.
651 @param commit: If commit the transaction .
652 """
jadmanski0afbb632008-06-06 21:10:57 +0000653 kver = self.lookup_kernel(kernel)
654 if kver:
655 return kver
656
657 # If this kernel has any significant patches, append their hash
658 # as diferentiator.
659 printable = kernel.base
660 patch_count = 0
661 for patch in kernel.patches:
662 match = re.match(r'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$',
663 patch.reference)
664 if not match:
665 patch_count += 1
666
showardeab66ce2009-12-23 00:03:56 +0000667 self.insert('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000668 {'base':kernel.base,
669 'kernel_hash':kernel.kernel_hash,
670 'printable':printable},
671 commit=commit)
672 kver = self.get_last_autonumber_value()
673
674 if patch_count > 0:
675 printable += ' p%d' % (kver)
showardeab66ce2009-12-23 00:03:56 +0000676 self.update('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000677 {'printable':printable},
678 {'kernel_idx':kver})
679
680 for patch in kernel.patches:
681 self.insert_patch(kver, patch, commit=commit)
682 return kver
683
684
685 def insert_patch(self, kver, patch, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700686 """Insert a kernel patch.
687
688 @param kver: The kernel version.
689 @param patch: The kernel patch object.
690 @param commit: If commit the transaction .
691 """
jadmanski0afbb632008-06-06 21:10:57 +0000692 print patch.reference
693 name = os.path.basename(patch.reference)[:80]
showardeab66ce2009-12-23 00:03:56 +0000694 self.insert('tko_patches',
jadmanski0afbb632008-06-06 21:10:57 +0000695 {'kernel_idx': kver,
696 'name':name,
697 'url':patch.reference,
698 'hash':patch.hash},
699 commit=commit)
700
701
jadmanski74eebf32008-07-15 20:04:42 +0000702 def find_test(self, job_idx, testname, subdir):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700703 """Find a test by name.
704
705 @param job_idx: The job index.
706 @param testname: The test name.
707 @param subdir: The test sub directory under the job directory.
708 """
jadmanski74eebf32008-07-15 20:04:42 +0000709 where = {'job_idx': job_idx , 'test': testname, 'subdir': subdir}
showardeab66ce2009-12-23 00:03:56 +0000710 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000711 if rows:
712 return rows[0][0]
713 else:
714 return None
715
716
717 def find_tests(self, job_idx):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700718 """Find all tests by job index.
719
720 @param job_idx: The job index.
721 @return: A list of tests.
722 """
jadmanski0afbb632008-06-06 21:10:57 +0000723 where = { 'job_idx':job_idx }
showardeab66ce2009-12-23 00:03:56 +0000724 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000725 if rows:
726 return [row[0] for row in rows]
727 else:
728 return []
729
730
731 def find_job(self, tag):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700732 """Find a job by tag.
733
734 @param tag: The job tag name.
735 @return: The job object or None.
736 """
showardeab66ce2009-12-23 00:03:56 +0000737 rows = self.select('job_idx', 'tko_jobs', {'tag': tag})
jadmanski0afbb632008-06-06 21:10:57 +0000738 if rows:
739 return rows[0][0]
740 else:
741 return None
mblighaf25f062007-12-03 17:48:35 +0000742
743
mbligh96cf0512008-04-17 15:25:38 +0000744def _get_db_type():
jadmanski0afbb632008-06-06 21:10:57 +0000745 """Get the database type name to use from the global config."""
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700746 get_value = global_config.global_config.get_config_value_with_fallback
747 return "db_" + get_value("AUTOTEST_WEB", "global_db_type", "db_type",
748 default="mysql")
mblighaf25f062007-12-03 17:48:35 +0000749
mbligh96cf0512008-04-17 15:25:38 +0000750
751def _get_error_class(class_name):
jadmanski0afbb632008-06-06 21:10:57 +0000752 """Retrieves the appropriate error class by name from the database
753 module."""
754 db_module = __import__("autotest_lib.tko." + _get_db_type(),
755 globals(), locals(), ["driver"])
756 return getattr(db_module.driver, class_name)
mbligh96cf0512008-04-17 15:25:38 +0000757
758
759def db(*args, **dargs):
jadmanski0afbb632008-06-06 21:10:57 +0000760 """Creates an instance of the database class with the arguments
761 provided in args and dargs, using the database type specified by
Michael Tang5f74ffd2016-10-31 10:34:53 -0700762 the global configuration (defaulting to mysql).
763
764 @param args: The db_type arguments.
765 @param dargs: The db_type named arguments.
766
767 @return: An db object.
768 """
jadmanski0afbb632008-06-06 21:10:57 +0000769 db_type = _get_db_type()
770 db_module = __import__("autotest_lib.tko." + db_type, globals(),
771 locals(), [db_type])
772 db = getattr(db_module, db_type)(*args, **dargs)
773 return db