blob: 0dda40d53dc227b592d9acab4fc43df6814457a4 [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
Jakob Juelich934f0dc2014-10-14 18:21:13 -070014from autotest_lib.frontend import database_settings_helper
Laurence Goodby0cb61952018-07-04 16:52:06 +000015from autotest_lib.tko import utils
mbligh96cf0512008-04-17 15:25:38 +000016
mblighed4d6dd2008-02-27 16:49:43 +000017
Allen Lie0bb7602016-11-29 13:45:53 -080018def _log_error(msg):
19 """Log an error message.
20
21 @param msg: Message string
22 """
23 print >> sys.stderr, msg
24 sys.stderr.flush() # we want these msgs to show up immediately
25
26
27def _format_operational_error(e):
Laurence Goodby0cb61952018-07-04 16:52:06 +000028 """Format OperationalError.
Allen Lie0bb7602016-11-29 13:45:53 -080029
Laurence Goodby0cb61952018-07-04 16:52:06 +000030 @param e: OperationalError instance.
Allen Lie0bb7602016-11-29 13:45:53 -080031 """
32 return ("%s: An operational error occurred during a database "
33 "operation: %s" % (time.strftime("%X %x"), str(e)))
34
35
mblighaea09602008-04-16 22:59:37 +000036class MySQLTooManyRows(Exception):
Michael Tang5f74ffd2016-10-31 10:34:53 -070037 """Too many records."""
jadmanski0afbb632008-06-06 21:10:57 +000038 pass
mblighaea09602008-04-16 22:59:37 +000039
mblighd5c33db2006-10-08 21:34:16 +000040
Laurence Goodby0cb61952018-07-04 16:52:06 +000041class db_sql(object):
Michael Tang5f74ffd2016-10-31 10:34:53 -070042 """Data access."""
43
jadmanski0afbb632008-06-06 21:10:57 +000044 def __init__(self, debug=False, autocommit=True, host=None,
45 database=None, user=None, password=None):
46 self.debug = debug
47 self.autocommit = autocommit
48 self._load_config(host, database, user, password)
mbligh96cf0512008-04-17 15:25:38 +000049
jadmanski0afbb632008-06-06 21:10:57 +000050 self.con = None
51 self._init_db()
mblighd5c33db2006-10-08 21:34:16 +000052
jadmanski0afbb632008-06-06 21:10:57 +000053 # if not present, insert statuses
54 self.status_idx = {}
55 self.status_word = {}
showardeab66ce2009-12-23 00:03:56 +000056 status_rows = self.select('status_idx, word', 'tko_status', None)
jadmanski0afbb632008-06-06 21:10:57 +000057 for s in status_rows:
58 self.status_idx[s[1]] = s[0]
59 self.status_word[s[0]] = s[1]
mbligh048e1c92007-10-07 00:10:33 +000060
jadmanski0afbb632008-06-06 21:10:57 +000061 machine_map = os.path.join(os.path.dirname(__file__),
62 'machines')
63 if os.path.exists(machine_map):
64 self.machine_map = machine_map
65 else:
66 self.machine_map = None
67 self.machine_group = {}
mbligh048e1c92007-10-07 00:10:33 +000068
mbligh8e1ab172007-09-13 17:29:56 +000069
jadmanski0afbb632008-06-06 21:10:57 +000070 def _load_config(self, host, database, user, password):
Jakob Juelich934f0dc2014-10-14 18:21:13 -070071 """Loads configuration settings required to connect to the database.
72
73 This will try to connect to use the settings prefixed with global_db_.
74 If they do not exist, they un-prefixed settings will be used.
75
76 If parameters are supplied, these will be taken instead of the values
77 in global_config.
78
79 @param host: If set, this host will be used, if not, the host will be
80 retrieved from global_config.
81 @param database: If set, this database will be used, if not, the
82 database will be retrieved from global_config.
83 @param user: If set, this user will be used, if not, the
84 user will be retrieved from global_config.
85 @param password: If set, this password will be used, if not, the
86 password will be retrieved from global_config.
87 """
88 database_settings = database_settings_helper.get_global_db_config()
mbligh65acae52008-04-24 20:21:55 +000089
jadmanski0afbb632008-06-06 21:10:57 +000090 # grab the host, database
Jakob Juelich934f0dc2014-10-14 18:21:13 -070091 self.host = host or database_settings['HOST']
92 self.database = database or database_settings['NAME']
mbligh65acae52008-04-24 20:21:55 +000093
jadmanski0afbb632008-06-06 21:10:57 +000094 # grab the user and password
Jakob Juelich934f0dc2014-10-14 18:21:13 -070095 self.user = user or database_settings['USER']
96 self.password = password or database_settings['PASSWORD']
mbligh65acae52008-04-24 20:21:55 +000097
Michael Spang7a273472014-10-08 12:08:13 -040098 # grab the timeout configuration
Jakob Juelich934f0dc2014-10-14 18:21:13 -070099 self.query_timeout =(
100 database_settings.get('OPTIONS', {}).get('timeout', 3600))
101
102 # Using fallback to non-global in order to work without configuration
103 # overhead on non-shard instances.
104 get_value = global_config.global_config.get_config_value_with_fallback
Jakob Juelich475b82b2014-09-30 11:17:07 -0700105 self.min_delay = get_value("AUTOTEST_WEB", "global_db_min_retry_delay",
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700106 "min_retry_delay", type=int, default=20)
Jakob Juelich475b82b2014-09-30 11:17:07 -0700107 self.max_delay = get_value("AUTOTEST_WEB", "global_db_max_retry_delay",
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700108 "max_retry_delay", type=int, default=60)
mbligh65acae52008-04-24 20:21:55 +0000109
Richard Barnette2468fbd2014-11-07 01:12:46 +0000110 # TODO(beeps): Move this to django settings once we have routers.
111 # On test instances mysql connects through a different port. No point
112 # piping this through our entire infrastructure when it is only really
113 # used for testing; Ideally we would specify this through django
114 # settings and default it to the empty string so django will figure out
115 # the default based on the database backend (eg: mysql, 3306), but until
116 # we have database routers in place any django settings will apply to
117 # both tko and afe.
118 # The intended use of this port is to allow a testing shard vm to
119 # update the master vm's database with test results. Specifying
120 # and empty string will fallback to not even specifying the port
121 # to the backend in tko/db.py. Unfortunately this means retries
122 # won't work on the test cluster till we've migrated to routers.
123 self.port = global_config.global_config.get_config_value(
124 "AUTOTEST_WEB", "global_db_port", type=str, default='')
125
mbligh65acae52008-04-24 20:21:55 +0000126
jadmanski0afbb632008-06-06 21:10:57 +0000127 def _init_db(self):
128 # make sure we clean up any existing connection
129 if self.con:
130 self.con.close()
131 self.con = None
mbligh65acae52008-04-24 20:21:55 +0000132
Prathmesh Prabhuba8adaf2017-07-05 13:07:05 -0700133 # create the db connection and cursor
Laurence Goodby0cb61952018-07-04 16:52:06 +0000134 self.con = self.connect(self.host, self.database,
Prathmesh Prabhuba8adaf2017-07-05 13:07:05 -0700135 self.user, self.password, self.port)
jadmanski0afbb632008-06-06 21:10:57 +0000136 self.cur = self.con.cursor()
mbligh96cf0512008-04-17 15:25:38 +0000137
138
jadmanski0afbb632008-06-06 21:10:57 +0000139 def _random_delay(self):
140 delay = random.randint(self.min_delay, self.max_delay)
141 time.sleep(delay)
mbligh65acae52008-04-24 20:21:55 +0000142
143
jadmanski0afbb632008-06-06 21:10:57 +0000144 def run_with_retry(self, function, *args, **dargs):
145 """Call function(*args, **dargs) until either it passes
146 without an operational error, or a timeout is reached.
147 This will re-connect to the database, so it is NOT safe
148 to use this inside of a database transaction.
jadmanskie7a69092008-05-29 21:03:13 +0000149
jadmanski0afbb632008-06-06 21:10:57 +0000150 It can be safely used with transactions, but the
151 transaction start & end must be completely contained
Michael Tang5f74ffd2016-10-31 10:34:53 -0700152 within the call to 'function'.
153
154 @param function: The function to run with retry.
155 @param args: The arguments
156 @param dargs: The named arguments.
157 """
Laurence Goodby0cb61952018-07-04 16:52:06 +0000158 OperationalError = _get_error_class("OperationalError")
159
jadmanski0afbb632008-06-06 21:10:57 +0000160 success = False
161 start_time = time.time()
162 while not success:
163 try:
164 result = function(*args, **dargs)
Laurence Goodby0cb61952018-07-04 16:52:06 +0000165 except OperationalError, e:
Allen Lie0bb7602016-11-29 13:45:53 -0800166 _log_error("%s; retrying, don't panic yet"
167 % _format_operational_error(e))
jadmanski0afbb632008-06-06 21:10:57 +0000168 stop_time = time.time()
169 elapsed_time = stop_time - start_time
170 if elapsed_time > self.query_timeout:
171 raise
172 else:
173 try:
174 self._random_delay()
175 self._init_db()
Laurence Goodby0cb61952018-07-04 16:52:06 +0000176 except OperationalError, e:
Allen Lie0bb7602016-11-29 13:45:53 -0800177 _log_error('%s; panic now'
178 % _format_operational_error(e))
jadmanski0afbb632008-06-06 21:10:57 +0000179 else:
180 success = True
181 return result
mbligh96cf0512008-04-17 15:25:38 +0000182
183
jadmanski0afbb632008-06-06 21:10:57 +0000184 def dprint(self, value):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700185 """Print out debug value.
186
187 @param value: The value to print out.
188 """
jadmanski0afbb632008-06-06 21:10:57 +0000189 if self.debug:
190 sys.stdout.write('SQL: ' + str(value) + '\n')
mbligh8e1ab172007-09-13 17:29:56 +0000191
mblighd5c33db2006-10-08 21:34:16 +0000192
Dan Shie8e0c052015-09-01 00:27:27 -0700193 def _commit(self):
194 """Private method for function commit to call for retry.
195 """
196 return self.con.commit()
197
198
jadmanski0afbb632008-06-06 21:10:57 +0000199 def commit(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700200 """Commit the sql transaction."""
Dan Shie8e0c052015-09-01 00:27:27 -0700201 if self.autocommit:
202 return self.run_with_retry(self._commit)
203 else:
204 return self._commit()
mbligh432bad42007-10-09 19:56:07 +0000205
206
Simran Basie129a962012-08-31 13:03:53 -0700207 def rollback(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700208 """Rollback the sql transaction."""
Simran Basie129a962012-08-31 13:03:53 -0700209 self.con.rollback()
210
211
jadmanski0afbb632008-06-06 21:10:57 +0000212 def get_last_autonumber_value(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700213 """Gets the last auto number.
214
215 @return: The last auto number.
216 """
jadmanski0afbb632008-06-06 21:10:57 +0000217 self.cur.execute('SELECT LAST_INSERT_ID()', [])
218 return self.cur.fetchall()[0][0]
mblighe12b8612008-02-12 20:58:14 +0000219
220
showardc1a98d12010-01-15 00:22:22 +0000221 def _quote(self, field):
222 return '`%s`' % field
223
224
225 def _where_clause(self, where):
226 if not where:
227 return '', []
228
229 if isinstance(where, dict):
230 # key/value pairs (which should be equal, or None for null)
231 keys, values = [], []
232 for field, value in where.iteritems():
233 quoted_field = self._quote(field)
234 if value is None:
235 keys.append(quoted_field + ' is null')
236 else:
237 keys.append(quoted_field + '=%s')
238 values.append(value)
239 where_clause = ' and '.join(keys)
240 elif isinstance(where, basestring):
241 # the exact string
242 where_clause = where
243 values = []
244 elif isinstance(where, tuple):
245 # preformatted where clause + values
246 where_clause, values = where
247 assert where_clause
248 else:
249 raise ValueError('Invalid "where" value: %r' % where)
250
251 return ' WHERE ' + where_clause, values
252
253
254
255 def select(self, fields, table, where, distinct=False, group_by=None,
256 max_rows=None):
jadmanski0afbb632008-06-06 21:10:57 +0000257 """\
258 This selects all the fields requested from a
259 specific table with a particular where clause.
260 The where clause can either be a dictionary of
261 field=value pairs, a string, or a tuple of (string,
262 a list of values). The last option is what you
263 should use when accepting user input as it'll
264 protect you against sql injection attacks (if
265 all user data is placed in the array rather than
266 the raw SQL).
mbligh12eebfa2008-01-03 02:01:53 +0000267
jadmanski0afbb632008-06-06 21:10:57 +0000268 For example:
269 where = ("a = %s AND b = %s", ['val', 'val'])
270 is better than
271 where = "a = 'val' AND b = 'val'"
Michael Tang5f74ffd2016-10-31 10:34:53 -0700272
273 @param fields: The list of selected fields string.
274 @param table: The name of the database table.
275 @param where: The where clause string.
276 @param distinct: If select distinct values.
277 @param group_by: Group by clause.
278 @param max_rows: unused.
jadmanski0afbb632008-06-06 21:10:57 +0000279 """
280 cmd = ['select']
281 if distinct:
282 cmd.append('distinct')
283 cmd += [fields, 'from', table]
mbligh608c3252007-08-31 13:53:00 +0000284
showardc1a98d12010-01-15 00:22:22 +0000285 where_clause, values = self._where_clause(where)
286 cmd.append(where_clause)
mbligh96cf0512008-04-17 15:25:38 +0000287
jadmanski0afbb632008-06-06 21:10:57 +0000288 if group_by:
289 cmd.append(' GROUP BY ' + group_by)
mbligh83f63a02007-12-12 19:13:04 +0000290
jadmanski0afbb632008-06-06 21:10:57 +0000291 self.dprint('%s %s' % (' '.join(cmd), values))
mbligh96cf0512008-04-17 15:25:38 +0000292
jadmanski0afbb632008-06-06 21:10:57 +0000293 # create a re-runable function for executing the query
294 def exec_sql():
Michael Tang5f74ffd2016-10-31 10:34:53 -0700295 """Exeuctes an the sql command."""
jadmanski0afbb632008-06-06 21:10:57 +0000296 sql = ' '.join(cmd)
297 numRec = self.cur.execute(sql, values)
mblighd876f452008-12-03 15:09:17 +0000298 if max_rows is not None and numRec > max_rows:
jadmanski0afbb632008-06-06 21:10:57 +0000299 msg = 'Exceeded allowed number of records'
300 raise MySQLTooManyRows(msg)
301 return self.cur.fetchall()
mbligh96cf0512008-04-17 15:25:38 +0000302
jadmanski0afbb632008-06-06 21:10:57 +0000303 # run the query, re-trying after operational errors
304 if self.autocommit:
305 return self.run_with_retry(exec_sql)
306 else:
307 return exec_sql()
mblighd5c33db2006-10-08 21:34:16 +0000308
mbligh056d0d32006-10-08 22:31:10 +0000309
jadmanski0afbb632008-06-06 21:10:57 +0000310 def select_sql(self, fields, table, sql, values):
311 """\
312 select fields from table "sql"
Michael Tang5f74ffd2016-10-31 10:34:53 -0700313
314 @param fields: The list of selected fields string.
315 @param table: The name of the database table.
316 @param sql: The sql string.
317 @param values: The sql string parameter values.
jadmanski0afbb632008-06-06 21:10:57 +0000318 """
319 cmd = 'select %s from %s %s' % (fields, table, sql)
320 self.dprint(cmd)
mbligh414c69e2007-10-05 15:13:06 +0000321
jadmanski0afbb632008-06-06 21:10:57 +0000322 # create a -re-runable function for executing the query
Michael Tang5f74ffd2016-10-31 10:34:53 -0700323 def _exec_sql():
jadmanski0afbb632008-06-06 21:10:57 +0000324 self.cur.execute(cmd, values)
325 return self.cur.fetchall()
mbligh96b9a5a2007-11-24 19:32:20 +0000326
jadmanski0afbb632008-06-06 21:10:57 +0000327 # run the query, re-trying after operational errors
328 if self.autocommit:
Michael Tang5f74ffd2016-10-31 10:34:53 -0700329 return self.run_with_retry(_exec_sql)
jadmanski0afbb632008-06-06 21:10:57 +0000330 else:
Michael Tang5f74ffd2016-10-31 10:34:53 -0700331 return _exec_sql()
mbligh96b9a5a2007-11-24 19:32:20 +0000332
mbligh608c3252007-08-31 13:53:00 +0000333
jadmanski0afbb632008-06-06 21:10:57 +0000334 def _exec_sql_with_commit(self, sql, values, commit):
335 if self.autocommit:
336 # re-run the query until it succeeds
Michael Tang5f74ffd2016-10-31 10:34:53 -0700337 def _exec_sql():
jadmanski0afbb632008-06-06 21:10:57 +0000338 self.cur.execute(sql, values)
339 self.con.commit()
Michael Tang5f74ffd2016-10-31 10:34:53 -0700340 self.run_with_retry(_exec_sql)
jadmanski0afbb632008-06-06 21:10:57 +0000341 else:
342 # take one shot at running the query
343 self.cur.execute(sql, values)
344 if commit:
345 self.con.commit()
mbligh96b9a5a2007-11-24 19:32:20 +0000346
mbligh2bd48872007-09-20 18:32:25 +0000347
jadmanskib591fba2008-09-10 16:19:22 +0000348 def insert(self, table, data, commit=None):
jadmanski0afbb632008-06-06 21:10:57 +0000349 """\
350 'insert into table (keys) values (%s ... %s)', values
mbligh96cf0512008-04-17 15:25:38 +0000351
jadmanski0afbb632008-06-06 21:10:57 +0000352 data:
353 dictionary of fields and data
Michael Tang5f74ffd2016-10-31 10:34:53 -0700354
355 @param table: The name of the table.
356 @param data: The insert data.
357 @param commit: If commit the transaction .
jadmanski0afbb632008-06-06 21:10:57 +0000358 """
359 fields = data.keys()
360 refs = ['%s' for field in fields]
361 values = [data[field] for field in fields]
showardc1a98d12010-01-15 00:22:22 +0000362 cmd = ('insert into %s (%s) values (%s)' %
363 (table, ','.join(self._quote(field) for field in fields),
364 ','.join(refs)))
jadmanski0afbb632008-06-06 21:10:57 +0000365 self.dprint('%s %s' % (cmd, values))
mblighe9cf9d42007-08-31 08:56:00 +0000366
jadmanski0afbb632008-06-06 21:10:57 +0000367 self._exec_sql_with_commit(cmd, values, commit)
mblighe9cf9d42007-08-31 08:56:00 +0000368
mbligh048e1c92007-10-07 00:10:33 +0000369
jadmanski0afbb632008-06-06 21:10:57 +0000370 def delete(self, table, where, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700371 """Delete entries.
372
373 @param table: The name of the table.
374 @param where: The where clause.
375 @param commit: If commit the transaction .
376 """
jadmanski0afbb632008-06-06 21:10:57 +0000377 cmd = ['delete from', table]
mblighd876f452008-12-03 15:09:17 +0000378 if commit is None:
jadmanski0afbb632008-06-06 21:10:57 +0000379 commit = self.autocommit
showardc1a98d12010-01-15 00:22:22 +0000380 where_clause, values = self._where_clause(where)
381 cmd.append(where_clause)
jadmanski0afbb632008-06-06 21:10:57 +0000382 sql = ' '.join(cmd)
383 self.dprint('%s %s' % (sql, values))
mbligh048e1c92007-10-07 00:10:33 +0000384
jadmanski0afbb632008-06-06 21:10:57 +0000385 self._exec_sql_with_commit(sql, values, commit)
mbligh048e1c92007-10-07 00:10:33 +0000386
mbligh7a41a862007-11-30 17:44:24 +0000387
jadmanski0afbb632008-06-06 21:10:57 +0000388 def update(self, table, data, where, commit = None):
389 """\
390 'update table set data values (%s ... %s) where ...'
mbligh2aaeb672007-10-01 14:54:18 +0000391
jadmanski0afbb632008-06-06 21:10:57 +0000392 data:
393 dictionary of fields and data
Michael Tang5f74ffd2016-10-31 10:34:53 -0700394
395 @param table: The name of the table.
396 @param data: The sql parameter values.
397 @param where: The where clause.
398 @param commit: If commit the transaction .
jadmanski0afbb632008-06-06 21:10:57 +0000399 """
mblighd876f452008-12-03 15:09:17 +0000400 if commit is None:
jadmanski0afbb632008-06-06 21:10:57 +0000401 commit = self.autocommit
402 cmd = 'update %s ' % table
403 fields = data.keys()
showardc1a98d12010-01-15 00:22:22 +0000404 data_refs = [self._quote(field) + '=%s' for field in fields]
jadmanski0afbb632008-06-06 21:10:57 +0000405 data_values = [data[field] for field in fields]
jadmanski74eebf32008-07-15 20:04:42 +0000406 cmd += ' set ' + ', '.join(data_refs)
mbligh2aaeb672007-10-01 14:54:18 +0000407
showardc1a98d12010-01-15 00:22:22 +0000408 where_clause, where_values = self._where_clause(where)
409 cmd += where_clause
mbligh2aaeb672007-10-01 14:54:18 +0000410
jadmanski0afbb632008-06-06 21:10:57 +0000411 values = data_values + where_values
jadmanski74eebf32008-07-15 20:04:42 +0000412 self.dprint('%s %s' % (cmd, values))
mbligh2aaeb672007-10-01 14:54:18 +0000413
jadmanski0afbb632008-06-06 21:10:57 +0000414 self._exec_sql_with_commit(cmd, values, commit)
mblighe9cf9d42007-08-31 08:56:00 +0000415
416
jadmanski0afbb632008-06-06 21:10:57 +0000417 def delete_job(self, tag, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700418 """Delete a tko job.
419
420 @param tag: The job tag.
421 @param commit: If commit the transaction .
422 """
jadmanski0afbb632008-06-06 21:10:57 +0000423 job_idx = self.find_job(tag)
424 for test_idx in self.find_tests(job_idx):
425 where = {'test_idx' : test_idx}
showardeab66ce2009-12-23 00:03:56 +0000426 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700427 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000428 self.delete('tko_iteration_attributes', where)
429 self.delete('tko_test_attributes', where)
430 self.delete('tko_test_labels_tests', {'test_id': test_idx})
jadmanski0afbb632008-06-06 21:10:57 +0000431 where = {'job_idx' : job_idx}
showardeab66ce2009-12-23 00:03:56 +0000432 self.delete('tko_tests', where)
433 self.delete('tko_jobs', where)
apw7a7316b2008-02-21 17:42:05 +0000434
apw7a7316b2008-02-21 17:42:05 +0000435
Prathmesh Prabhu30dee862018-04-18 20:24:20 -0700436 def insert_job(self, tag, job, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700437 """Insert a tko job.
438
439 @param tag: The job tag.
440 @param job: The job object.
Michael Tang5f74ffd2016-10-31 10:34:53 -0700441 @param commit: If commit the transaction .
442 """
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700443 data = self._get_common_job_data(tag, job)
444 data.update({
Prathmesh Prabhu30dee862018-04-18 20:24:20 -0700445 'afe_job_id': job.afe_job_id,
446 'afe_parent_job_id': job.afe_parent_job_id,
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700447 })
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700448 if job.job_idx is not None:
449 self.update(
450 'tko_jobs', data, {'job_idx': job.job_idx}, commit=commit)
showard0fec8a02009-12-04 01:19:54 +0000451 else:
showardeab66ce2009-12-23 00:03:56 +0000452 self.insert('tko_jobs', data, commit=commit)
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700453 job.job_idx = self.get_last_autonumber_value()
Shuqian Zhao31425d52016-12-07 09:35:03 -0800454
mbligh237bed32007-09-05 13:05:57 +0000455
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700456 def _get_common_job_data(self, tag, job):
457 """Construct a dictionary with the common data to insert in job/task."""
458 return {
459 'tag':tag,
460 'label': job.label,
461 'username': job.user,
462 'machine_idx': job.machine_idx,
463 'queued_time': job.queued_time,
464 'started_time': job.started_time,
465 'finished_time': job.finished_time,
466 'build': job.build,
467 'build_version': job.build_version,
468 'board': job.board,
469 'suite': job.suite,
470 }
471
472
473 def insert_or_update_task_reference(self, job, reference_type, commit=None):
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700474 """Insert an entry in the tko_task_references table.
475
476 The job should already have been inserted in tko_jobs.
477 @param job: tko.models.job object.
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700478 @param reference_type: The type of reference to insert.
479 One of: {'afe', 'skylab'}
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700480 @param commit: Whether to commit this transaction.
481 """
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700482 assert reference_type in {'afe', 'skylab'}
483 if reference_type == 'afe':
484 task_id = job.afe_job_id
485 parent_task_id = job.afe_parent_job_id
486 else:
487 task_id = job.skylab_task_id
488 parent_task_id = job.skylab_parent_task_id
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700489 data = {
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700490 'reference_type': reference_type,
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700491 'tko_job_idx': job.job_idx,
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700492 'task_id': task_id,
493 'parent_task_id': parent_task_id,
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700494 }
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700495
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700496 task_reference_id = self._lookup_task_reference(job)
497 if task_reference_id is not None:
498 self.update('tko_task_references',
499 data,
500 {'id': task_reference_id},
501 commit=commit)
502 job.task_reference_id = task_reference_id
503 else:
504 self.insert('tko_task_references', data, commit=commit)
505 job.task_reference_id = self.get_last_autonumber_value()
506
507
showardc1a98d12010-01-15 00:22:22 +0000508 def update_job_keyvals(self, job, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700509 """Updates the job key values.
510
511 @param job: The job object.
512 @param commit: If commit the transaction .
513 """
showardc1a98d12010-01-15 00:22:22 +0000514 for key, value in job.keyval_dict.iteritems():
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700515 where = {'job_id': job.job_idx, 'key': key}
showardc1a98d12010-01-15 00:22:22 +0000516 data = dict(where, value=value)
517 exists = self.select('id', 'tko_job_keyvals', where=where)
518
519 if exists:
520 self.update('tko_job_keyvals', data, where=where, commit=commit)
521 else:
522 self.insert('tko_job_keyvals', data, commit=commit)
523
524
jadmanski0afbb632008-06-06 21:10:57 +0000525 def insert_test(self, job, test, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700526 """Inserts a job test.
527
528 @param job: The job object.
529 @param test: The test object.
530 @param commit: If commit the transaction .
531 """
jadmanski0afbb632008-06-06 21:10:57 +0000532 kver = self.insert_kernel(test.kernel, commit=commit)
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700533 data = {'job_idx':job.job_idx, 'test':test.testname,
jadmanski0afbb632008-06-06 21:10:57 +0000534 'subdir':test.subdir, 'kernel_idx':kver,
535 'status':self.status_idx[test.status],
536 'reason':test.reason, 'machine_idx':job.machine_idx,
537 'started_time': test.started_time,
538 'finished_time':test.finished_time}
jadmanski9b6babf2009-04-21 17:57:40 +0000539 is_update = hasattr(test, "test_idx")
540 if is_update:
jadmanski74eebf32008-07-15 20:04:42 +0000541 test_idx = test.test_idx
showardeab66ce2009-12-23 00:03:56 +0000542 self.update('tko_tests', data,
543 {'test_idx': test_idx}, commit=commit)
jadmanskib591fba2008-09-10 16:19:22 +0000544 where = {'test_idx': test_idx}
showardeab66ce2009-12-23 00:03:56 +0000545 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700546 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000547 self.delete('tko_iteration_attributes', where)
showard0fec8a02009-12-04 01:19:54 +0000548 where['user_created'] = 0
showardeab66ce2009-12-23 00:03:56 +0000549 self.delete('tko_test_attributes', where)
jadmanski74eebf32008-07-15 20:04:42 +0000550 else:
showardeab66ce2009-12-23 00:03:56 +0000551 self.insert('tko_tests', data, commit=commit)
jadmanski74eebf32008-07-15 20:04:42 +0000552 test_idx = test.test_idx = self.get_last_autonumber_value()
553 data = {'test_idx': test_idx}
mbligh237bed32007-09-05 13:05:57 +0000554
jadmanski0afbb632008-06-06 21:10:57 +0000555 for i in test.iterations:
556 data['iteration'] = i.index
557 for key, value in i.attr_keyval.iteritems():
558 data['attribute'] = key
559 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000560 self.insert('tko_iteration_attributes', data,
jadmanski0afbb632008-06-06 21:10:57 +0000561 commit=commit)
562 for key, value in i.perf_keyval.iteritems():
563 data['attribute'] = key
Allen Li64692a32016-11-04 13:35:19 -0700564 if math.isnan(value) or math.isinf(value):
565 data['value'] = None
566 else:
567 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000568 self.insert('tko_iteration_result', data,
mbligh432bad42007-10-09 19:56:07 +0000569 commit=commit)
mbligh056d0d32006-10-08 22:31:10 +0000570
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700571 data = {'test_idx': test_idx}
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700572
jadmanski0afbb632008-06-06 21:10:57 +0000573 for key, value in test.attributes.iteritems():
574 data = {'test_idx': test_idx, 'attribute': key,
575 'value': value}
showardeab66ce2009-12-23 00:03:56 +0000576 self.insert('tko_test_attributes', data, commit=commit)
mbligh2bd48872007-09-20 18:32:25 +0000577
jadmanski9b6babf2009-04-21 17:57:40 +0000578 if not is_update:
579 for label_index in test.labels:
580 data = {'test_id': test_idx, 'testlabel_id': label_index}
showardeab66ce2009-12-23 00:03:56 +0000581 self.insert('tko_test_labels_tests', data, commit=commit)
jadmanski9b6babf2009-04-21 17:57:40 +0000582
mbligh056d0d32006-10-08 22:31:10 +0000583
jadmanski0afbb632008-06-06 21:10:57 +0000584 def read_machine_map(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700585 """Reads the machine map."""
showard71b94312009-08-20 23:40:02 +0000586 if self.machine_group or not self.machine_map:
587 return
jadmanski0afbb632008-06-06 21:10:57 +0000588 for line in open(self.machine_map, 'r').readlines():
589 (machine, group) = line.split()
590 self.machine_group[machine] = group
mbligh96b9a5a2007-11-24 19:32:20 +0000591
592
showard71b94312009-08-20 23:40:02 +0000593 def machine_info_dict(self, job):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700594 """Reads the machine information of a job.
595
596 @param job: The job object.
597
598 @return: The machine info dictionary.
599 """
jadmanski0afbb632008-06-06 21:10:57 +0000600 hostname = job.machine
showard71b94312009-08-20 23:40:02 +0000601 group = job.machine_group
602 owner = job.machine_owner
jadmanski0afbb632008-06-06 21:10:57 +0000603
604 if not group:
showard71b94312009-08-20 23:40:02 +0000605 self.read_machine_map()
jadmanski0afbb632008-06-06 21:10:57 +0000606 group = self.machine_group.get(hostname, hostname)
showard71b94312009-08-20 23:40:02 +0000607 if group == hostname and owner:
608 group = owner + '/' + hostname
jadmanski0afbb632008-06-06 21:10:57 +0000609
showard71b94312009-08-20 23:40:02 +0000610 return {'hostname': hostname, 'machine_group': group, 'owner': owner}
611
612
Prathmesh Prabhuec96d6e2018-04-18 18:13:50 -0700613 def insert_or_update_machine(self, job, commit=None):
614 """Insert or updates machine information for the given job.
615
616 Also updates the job object with new machine index, if any.
617
618 @param job: tko.models.job object.
619 @param commit: Whether to commit the database transaction.
620 """
621 job.machine_idx = self._lookup_machine(job.machine)
622 if not job.machine_idx:
623 job.machine_idx = self._insert_machine(job, commit=commit)
624 elif job.machine:
625 # Only try to update tko_machines record if machine is set. This
626 # prevents unnecessary db writes for suite jobs.
627 self._update_machine_information(job, commit=commit)
628
629
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700630 def _lookup_task_reference(self, job):
631 """Find the task_reference_id for a given job. Return None if not found.
632
633 @param job: tko.models.job object.
634 """
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700635 if job.job_idx is None:
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700636 return None
637 rows = self.select(
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700638 'id', 'tko_task_references', {'tko_job_idx': job.job_idx})
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700639 if not rows:
640 return None
641 if len(rows) > 1:
642 raise MySQLTooManyRows('Got %d tko_task_references for tko_job %d'
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700643 % (len(rows), job.job_idx))
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700644 return rows[0][0]
645
646
Prathmesh Prabhu432648c2018-04-18 18:10:50 -0700647 def _insert_machine(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700648 """Inserts the job machine.
649
650 @param job: The job object.
651 @param commit: If commit the transaction .
652 """
showard71b94312009-08-20 23:40:02 +0000653 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000654 self.insert('tko_machines', machine_info, commit=commit)
jadmanski0afbb632008-06-06 21:10:57 +0000655 return self.get_last_autonumber_value()
656
657
Prathmesh Prabhu432648c2018-04-18 18:10:50 -0700658 def _update_machine_information(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700659 """Updates the job machine information.
660
661 @param job: The job object.
662 @param commit: If commit the transaction .
663 """
showard71b94312009-08-20 23:40:02 +0000664 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000665 self.update('tko_machines', machine_info,
showard71b94312009-08-20 23:40:02 +0000666 where={'hostname': machine_info['hostname']},
667 commit=commit)
668
669
Prathmesh Prabhu432648c2018-04-18 18:10:50 -0700670 def _lookup_machine(self, hostname):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700671 """Look up the machine information.
672
673 @param hostname: The hostname as string.
674 """
jadmanski0afbb632008-06-06 21:10:57 +0000675 where = { 'hostname' : hostname }
showardeab66ce2009-12-23 00:03:56 +0000676 rows = self.select('machine_idx', 'tko_machines', where)
jadmanski0afbb632008-06-06 21:10:57 +0000677 if rows:
678 return rows[0][0]
679 else:
680 return None
681
682
683 def lookup_kernel(self, kernel):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700684 """Look up the kernel.
685
686 @param kernel: The kernel object.
687 """
showardeab66ce2009-12-23 00:03:56 +0000688 rows = self.select('kernel_idx', 'tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000689 {'kernel_hash':kernel.kernel_hash})
690 if rows:
691 return rows[0][0]
692 else:
693 return None
694
695
696 def insert_kernel(self, kernel, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700697 """Insert a kernel.
698
699 @param kernel: The kernel object.
700 @param commit: If commit the transaction .
701 """
jadmanski0afbb632008-06-06 21:10:57 +0000702 kver = self.lookup_kernel(kernel)
703 if kver:
704 return kver
705
706 # If this kernel has any significant patches, append their hash
707 # as diferentiator.
708 printable = kernel.base
709 patch_count = 0
710 for patch in kernel.patches:
711 match = re.match(r'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$',
712 patch.reference)
713 if not match:
714 patch_count += 1
715
showardeab66ce2009-12-23 00:03:56 +0000716 self.insert('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000717 {'base':kernel.base,
718 'kernel_hash':kernel.kernel_hash,
719 'printable':printable},
720 commit=commit)
721 kver = self.get_last_autonumber_value()
722
723 if patch_count > 0:
724 printable += ' p%d' % (kver)
showardeab66ce2009-12-23 00:03:56 +0000725 self.update('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000726 {'printable':printable},
727 {'kernel_idx':kver})
728
729 for patch in kernel.patches:
730 self.insert_patch(kver, patch, commit=commit)
731 return kver
732
733
734 def insert_patch(self, kver, patch, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700735 """Insert a kernel patch.
736
737 @param kver: The kernel version.
738 @param patch: The kernel patch object.
739 @param commit: If commit the transaction .
740 """
jadmanski0afbb632008-06-06 21:10:57 +0000741 print patch.reference
742 name = os.path.basename(patch.reference)[:80]
showardeab66ce2009-12-23 00:03:56 +0000743 self.insert('tko_patches',
jadmanski0afbb632008-06-06 21:10:57 +0000744 {'kernel_idx': kver,
745 'name':name,
746 'url':patch.reference,
747 'hash':patch.hash},
748 commit=commit)
749
750
jadmanski74eebf32008-07-15 20:04:42 +0000751 def find_test(self, job_idx, testname, subdir):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700752 """Find a test by name.
753
754 @param job_idx: The job index.
755 @param testname: The test name.
756 @param subdir: The test sub directory under the job directory.
757 """
jadmanski74eebf32008-07-15 20:04:42 +0000758 where = {'job_idx': job_idx , 'test': testname, 'subdir': subdir}
showardeab66ce2009-12-23 00:03:56 +0000759 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000760 if rows:
761 return rows[0][0]
762 else:
763 return None
764
765
766 def find_tests(self, job_idx):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700767 """Find all tests by job index.
768
769 @param job_idx: The job index.
770 @return: A list of tests.
771 """
jadmanski0afbb632008-06-06 21:10:57 +0000772 where = { 'job_idx':job_idx }
showardeab66ce2009-12-23 00:03:56 +0000773 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000774 if rows:
775 return [row[0] for row in rows]
776 else:
777 return []
778
779
780 def find_job(self, tag):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700781 """Find a job by tag.
782
783 @param tag: The job tag name.
784 @return: The job object or None.
785 """
showardeab66ce2009-12-23 00:03:56 +0000786 rows = self.select('job_idx', 'tko_jobs', {'tag': tag})
jadmanski0afbb632008-06-06 21:10:57 +0000787 if rows:
788 return rows[0][0]
789 else:
790 return None
mblighaf25f062007-12-03 17:48:35 +0000791
792
Laurence Goodby0cb61952018-07-04 16:52:06 +0000793def _get_db_type():
794 """Get the database type name to use from the global config."""
795 get_value = global_config.global_config.get_config_value_with_fallback
796 return "db_" + get_value("AUTOTEST_WEB", "global_db_type", "db_type",
797 default="mysql")
798
799
800def _get_error_class(class_name):
801 """Retrieves the appropriate error class by name from the database
802 module."""
803 db_module = __import__("autotest_lib.tko." + _get_db_type(),
804 globals(), locals(), ["driver"])
805 return getattr(db_module.driver, class_name)
806
807
mbligh96cf0512008-04-17 15:25:38 +0000808def db(*args, **dargs):
Laurence Goodby0cb61952018-07-04 16:52:06 +0000809 """Creates an instance of the database class with the arguments
810 provided in args and dargs, using the database type specified by
811 the global configuration (defaulting to mysql).
812
813 @param args: The db_type arguments.
814 @param dargs: The db_type named arguments.
815
816 @return: An db object.
817 """
818 db_type = _get_db_type()
819 db_module = __import__("autotest_lib.tko." + db_type, globals(),
820 locals(), [db_type])
821 db = getattr(db_module, db_type)(*args, **dargs)
822 return db