blob: 34c07eb219819e2a531685d881dfc1524d810bbe [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
Aviv Keshet7f4071c2018-07-12 14:15:11 -07005try:
6 import MySQLdb as driver
7except ImportError:
8 # This module (tko) is unconditionally imported by autoserv,
9 # even in environments where MyQSLdb is unavailable. Thus, we
10 # need to cope with import failure here.
11 # See https://bugs.chromium.org/p/chromium/issues/detail?id=860166#c17 for
12 # context.
13 class UtterlyFakeDb(object):
14 """Lame fake of MySQLdb for import time needs of this file."""
15 OperationalError = object()
16
17 driver = UtterlyFakeDb
18
Allen Li64692a32016-11-04 13:35:19 -070019import math
20import os
21import random
22import re
23import sys
24import time
mbligh96cf0512008-04-17 15:25:38 +000025
26import common
27from autotest_lib.client.common_lib import global_config
Aviv Keshetc53e0052018-07-12 18:37:44 -070028from autotest_lib.client.common_lib import utils
Aviv Keshet7f4071c2018-07-12 14:15:11 -070029from autotest_lib.client.common_lib.cros import retry
Jakob Juelich934f0dc2014-10-14 18:21:13 -070030from autotest_lib.frontend import database_settings_helper
mblighed4d6dd2008-02-27 16:49:43 +000031
Aviv Keshetc53e0052018-07-12 18:37:44 -070032try:
33 from chromite.lib import metrics
34except ImportError:
35 metrics = utils.metrics_mock
36
37
Allen Lie0bb7602016-11-29 13:45:53 -080038def _log_error(msg):
39 """Log an error message.
40
41 @param msg: Message string
42 """
43 print >> sys.stderr, msg
44 sys.stderr.flush() # we want these msgs to show up immediately
45
46
47def _format_operational_error(e):
Laurence Goodby0cb61952018-07-04 16:52:06 +000048 """Format OperationalError.
Allen Lie0bb7602016-11-29 13:45:53 -080049
Laurence Goodby0cb61952018-07-04 16:52:06 +000050 @param e: OperationalError instance.
Allen Lie0bb7602016-11-29 13:45:53 -080051 """
52 return ("%s: An operational error occurred during a database "
53 "operation: %s" % (time.strftime("%X %x"), str(e)))
54
55
mblighaea09602008-04-16 22:59:37 +000056class MySQLTooManyRows(Exception):
Michael Tang5f74ffd2016-10-31 10:34:53 -070057 """Too many records."""
jadmanski0afbb632008-06-06 21:10:57 +000058 pass
mblighaea09602008-04-16 22:59:37 +000059
mblighd5c33db2006-10-08 21:34:16 +000060
Aviv Keshetc53e0052018-07-12 18:37:44 -070061def _connection_retry_callback():
62 """Callback method used to increment a retry metric."""
63 metrics.Counter('chromeos/autotest/tko/connection_retries').increment()
64
65
Laurence Goodby0cb61952018-07-04 16:52:06 +000066class db_sql(object):
Michael Tang5f74ffd2016-10-31 10:34:53 -070067 """Data access."""
68
jadmanski0afbb632008-06-06 21:10:57 +000069 def __init__(self, debug=False, autocommit=True, host=None,
70 database=None, user=None, password=None):
71 self.debug = debug
72 self.autocommit = autocommit
73 self._load_config(host, database, user, password)
mbligh96cf0512008-04-17 15:25:38 +000074
jadmanski0afbb632008-06-06 21:10:57 +000075 self.con = None
76 self._init_db()
mblighd5c33db2006-10-08 21:34:16 +000077
jadmanski0afbb632008-06-06 21:10:57 +000078 # if not present, insert statuses
79 self.status_idx = {}
80 self.status_word = {}
showardeab66ce2009-12-23 00:03:56 +000081 status_rows = self.select('status_idx, word', 'tko_status', None)
jadmanski0afbb632008-06-06 21:10:57 +000082 for s in status_rows:
83 self.status_idx[s[1]] = s[0]
84 self.status_word[s[0]] = s[1]
mbligh048e1c92007-10-07 00:10:33 +000085
jadmanski0afbb632008-06-06 21:10:57 +000086 machine_map = os.path.join(os.path.dirname(__file__),
87 'machines')
88 if os.path.exists(machine_map):
89 self.machine_map = machine_map
90 else:
91 self.machine_map = None
92 self.machine_group = {}
mbligh048e1c92007-10-07 00:10:33 +000093
mbligh8e1ab172007-09-13 17:29:56 +000094
jadmanski0afbb632008-06-06 21:10:57 +000095 def _load_config(self, host, database, user, password):
Jakob Juelich934f0dc2014-10-14 18:21:13 -070096 """Loads configuration settings required to connect to the database.
97
98 This will try to connect to use the settings prefixed with global_db_.
99 If they do not exist, they un-prefixed settings will be used.
100
101 If parameters are supplied, these will be taken instead of the values
102 in global_config.
103
104 @param host: If set, this host will be used, if not, the host will be
105 retrieved from global_config.
106 @param database: If set, this database will be used, if not, the
107 database will be retrieved from global_config.
108 @param user: If set, this user will be used, if not, the
109 user will be retrieved from global_config.
110 @param password: If set, this password will be used, if not, the
111 password will be retrieved from global_config.
112 """
113 database_settings = database_settings_helper.get_global_db_config()
mbligh65acae52008-04-24 20:21:55 +0000114
jadmanski0afbb632008-06-06 21:10:57 +0000115 # grab the host, database
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700116 self.host = host or database_settings['HOST']
117 self.database = database or database_settings['NAME']
mbligh65acae52008-04-24 20:21:55 +0000118
jadmanski0afbb632008-06-06 21:10:57 +0000119 # grab the user and password
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700120 self.user = user or database_settings['USER']
121 self.password = password or database_settings['PASSWORD']
mbligh65acae52008-04-24 20:21:55 +0000122
Michael Spang7a273472014-10-08 12:08:13 -0400123 # grab the timeout configuration
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700124 self.query_timeout =(
125 database_settings.get('OPTIONS', {}).get('timeout', 3600))
126
127 # Using fallback to non-global in order to work without configuration
128 # overhead on non-shard instances.
129 get_value = global_config.global_config.get_config_value_with_fallback
Jakob Juelich475b82b2014-09-30 11:17:07 -0700130 self.min_delay = get_value("AUTOTEST_WEB", "global_db_min_retry_delay",
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700131 "min_retry_delay", type=int, default=20)
Jakob Juelich475b82b2014-09-30 11:17:07 -0700132 self.max_delay = get_value("AUTOTEST_WEB", "global_db_max_retry_delay",
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700133 "max_retry_delay", type=int, default=60)
mbligh65acae52008-04-24 20:21:55 +0000134
Richard Barnette2468fbd2014-11-07 01:12:46 +0000135 # TODO(beeps): Move this to django settings once we have routers.
136 # On test instances mysql connects through a different port. No point
137 # piping this through our entire infrastructure when it is only really
138 # used for testing; Ideally we would specify this through django
139 # settings and default it to the empty string so django will figure out
140 # the default based on the database backend (eg: mysql, 3306), but until
141 # we have database routers in place any django settings will apply to
142 # both tko and afe.
143 # The intended use of this port is to allow a testing shard vm to
144 # update the master vm's database with test results. Specifying
145 # and empty string will fallback to not even specifying the port
146 # to the backend in tko/db.py. Unfortunately this means retries
147 # won't work on the test cluster till we've migrated to routers.
148 self.port = global_config.global_config.get_config_value(
149 "AUTOTEST_WEB", "global_db_port", type=str, default='')
150
mbligh65acae52008-04-24 20:21:55 +0000151
jadmanski0afbb632008-06-06 21:10:57 +0000152 def _init_db(self):
153 # make sure we clean up any existing connection
154 if self.con:
155 self.con.close()
156 self.con = None
mbligh65acae52008-04-24 20:21:55 +0000157
Prathmesh Prabhuba8adaf2017-07-05 13:07:05 -0700158 # create the db connection and cursor
Laurence Goodby0cb61952018-07-04 16:52:06 +0000159 self.con = self.connect(self.host, self.database,
Prathmesh Prabhuba8adaf2017-07-05 13:07:05 -0700160 self.user, self.password, self.port)
jadmanski0afbb632008-06-06 21:10:57 +0000161 self.cur = self.con.cursor()
mbligh96cf0512008-04-17 15:25:38 +0000162
163
jadmanski0afbb632008-06-06 21:10:57 +0000164 def _random_delay(self):
165 delay = random.randint(self.min_delay, self.max_delay)
166 time.sleep(delay)
mbligh65acae52008-04-24 20:21:55 +0000167
168
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700169 @retry.retry(driver.OperationalError, timeout_min=10,
Aviv Keshetc53e0052018-07-12 18:37:44 -0700170 delay_sec=5, callback=_connection_retry_callback)
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700171 def connect(self, host, database, user, password, port):
172 """Open and return a connection to mysql database."""
173 connection_args = {
174 'host': host,
175 'user': user,
176 'db': database,
177 'passwd': password,
178 'connect_timeout': 20,
179 }
180 if port:
181 connection_args['port'] = int(port)
182 return driver.connect(**connection_args)
183
184
jadmanski0afbb632008-06-06 21:10:57 +0000185 def run_with_retry(self, function, *args, **dargs):
186 """Call function(*args, **dargs) until either it passes
187 without an operational error, or a timeout is reached.
188 This will re-connect to the database, so it is NOT safe
189 to use this inside of a database transaction.
jadmanskie7a69092008-05-29 21:03:13 +0000190
jadmanski0afbb632008-06-06 21:10:57 +0000191 It can be safely used with transactions, but the
192 transaction start & end must be completely contained
Michael Tang5f74ffd2016-10-31 10:34:53 -0700193 within the call to 'function'.
194
195 @param function: The function to run with retry.
196 @param args: The arguments
197 @param dargs: The named arguments.
198 """
jadmanski0afbb632008-06-06 21:10:57 +0000199 success = False
200 start_time = time.time()
201 while not success:
202 try:
203 result = function(*args, **dargs)
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700204 except driver.OperationalError, e:
Allen Lie0bb7602016-11-29 13:45:53 -0800205 _log_error("%s; retrying, don't panic yet"
206 % _format_operational_error(e))
jadmanski0afbb632008-06-06 21:10:57 +0000207 stop_time = time.time()
208 elapsed_time = stop_time - start_time
209 if elapsed_time > self.query_timeout:
210 raise
211 else:
212 try:
213 self._random_delay()
214 self._init_db()
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700215 except driver.OperationalError, e:
Allen Lie0bb7602016-11-29 13:45:53 -0800216 _log_error('%s; panic now'
217 % _format_operational_error(e))
jadmanski0afbb632008-06-06 21:10:57 +0000218 else:
219 success = True
220 return result
mbligh96cf0512008-04-17 15:25:38 +0000221
222
jadmanski0afbb632008-06-06 21:10:57 +0000223 def dprint(self, value):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700224 """Print out debug value.
225
226 @param value: The value to print out.
227 """
jadmanski0afbb632008-06-06 21:10:57 +0000228 if self.debug:
229 sys.stdout.write('SQL: ' + str(value) + '\n')
mbligh8e1ab172007-09-13 17:29:56 +0000230
mblighd5c33db2006-10-08 21:34:16 +0000231
Dan Shie8e0c052015-09-01 00:27:27 -0700232 def _commit(self):
233 """Private method for function commit to call for retry.
234 """
235 return self.con.commit()
236
237
jadmanski0afbb632008-06-06 21:10:57 +0000238 def commit(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700239 """Commit the sql transaction."""
Dan Shie8e0c052015-09-01 00:27:27 -0700240 if self.autocommit:
241 return self.run_with_retry(self._commit)
242 else:
243 return self._commit()
mbligh432bad42007-10-09 19:56:07 +0000244
245
Simran Basie129a962012-08-31 13:03:53 -0700246 def rollback(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700247 """Rollback the sql transaction."""
Simran Basie129a962012-08-31 13:03:53 -0700248 self.con.rollback()
249
250
jadmanski0afbb632008-06-06 21:10:57 +0000251 def get_last_autonumber_value(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700252 """Gets the last auto number.
253
254 @return: The last auto number.
255 """
jadmanski0afbb632008-06-06 21:10:57 +0000256 self.cur.execute('SELECT LAST_INSERT_ID()', [])
257 return self.cur.fetchall()[0][0]
mblighe12b8612008-02-12 20:58:14 +0000258
259
showardc1a98d12010-01-15 00:22:22 +0000260 def _quote(self, field):
261 return '`%s`' % field
262
263
264 def _where_clause(self, where):
265 if not where:
266 return '', []
267
268 if isinstance(where, dict):
269 # key/value pairs (which should be equal, or None for null)
270 keys, values = [], []
271 for field, value in where.iteritems():
272 quoted_field = self._quote(field)
273 if value is None:
274 keys.append(quoted_field + ' is null')
275 else:
276 keys.append(quoted_field + '=%s')
277 values.append(value)
278 where_clause = ' and '.join(keys)
279 elif isinstance(where, basestring):
280 # the exact string
281 where_clause = where
282 values = []
283 elif isinstance(where, tuple):
284 # preformatted where clause + values
285 where_clause, values = where
286 assert where_clause
287 else:
288 raise ValueError('Invalid "where" value: %r' % where)
289
290 return ' WHERE ' + where_clause, values
291
292
293
294 def select(self, fields, table, where, distinct=False, group_by=None,
295 max_rows=None):
jadmanski0afbb632008-06-06 21:10:57 +0000296 """\
297 This selects all the fields requested from a
298 specific table with a particular where clause.
299 The where clause can either be a dictionary of
300 field=value pairs, a string, or a tuple of (string,
301 a list of values). The last option is what you
302 should use when accepting user input as it'll
303 protect you against sql injection attacks (if
304 all user data is placed in the array rather than
305 the raw SQL).
mbligh12eebfa2008-01-03 02:01:53 +0000306
jadmanski0afbb632008-06-06 21:10:57 +0000307 For example:
308 where = ("a = %s AND b = %s", ['val', 'val'])
309 is better than
310 where = "a = 'val' AND b = 'val'"
Michael Tang5f74ffd2016-10-31 10:34:53 -0700311
312 @param fields: The list of selected fields string.
313 @param table: The name of the database table.
314 @param where: The where clause string.
315 @param distinct: If select distinct values.
316 @param group_by: Group by clause.
317 @param max_rows: unused.
jadmanski0afbb632008-06-06 21:10:57 +0000318 """
319 cmd = ['select']
320 if distinct:
321 cmd.append('distinct')
322 cmd += [fields, 'from', table]
mbligh608c3252007-08-31 13:53:00 +0000323
showardc1a98d12010-01-15 00:22:22 +0000324 where_clause, values = self._where_clause(where)
325 cmd.append(where_clause)
mbligh96cf0512008-04-17 15:25:38 +0000326
jadmanski0afbb632008-06-06 21:10:57 +0000327 if group_by:
328 cmd.append(' GROUP BY ' + group_by)
mbligh83f63a02007-12-12 19:13:04 +0000329
jadmanski0afbb632008-06-06 21:10:57 +0000330 self.dprint('%s %s' % (' '.join(cmd), values))
mbligh96cf0512008-04-17 15:25:38 +0000331
jadmanski0afbb632008-06-06 21:10:57 +0000332 # create a re-runable function for executing the query
333 def exec_sql():
Michael Tang5f74ffd2016-10-31 10:34:53 -0700334 """Exeuctes an the sql command."""
jadmanski0afbb632008-06-06 21:10:57 +0000335 sql = ' '.join(cmd)
336 numRec = self.cur.execute(sql, values)
mblighd876f452008-12-03 15:09:17 +0000337 if max_rows is not None and numRec > max_rows:
jadmanski0afbb632008-06-06 21:10:57 +0000338 msg = 'Exceeded allowed number of records'
339 raise MySQLTooManyRows(msg)
340 return self.cur.fetchall()
mbligh96cf0512008-04-17 15:25:38 +0000341
jadmanski0afbb632008-06-06 21:10:57 +0000342 # run the query, re-trying after operational errors
343 if self.autocommit:
344 return self.run_with_retry(exec_sql)
345 else:
346 return exec_sql()
mblighd5c33db2006-10-08 21:34:16 +0000347
mbligh056d0d32006-10-08 22:31:10 +0000348
jadmanski0afbb632008-06-06 21:10:57 +0000349 def select_sql(self, fields, table, sql, values):
350 """\
351 select fields from table "sql"
Michael Tang5f74ffd2016-10-31 10:34:53 -0700352
353 @param fields: The list of selected fields string.
354 @param table: The name of the database table.
355 @param sql: The sql string.
356 @param values: The sql string parameter values.
jadmanski0afbb632008-06-06 21:10:57 +0000357 """
358 cmd = 'select %s from %s %s' % (fields, table, sql)
359 self.dprint(cmd)
mbligh414c69e2007-10-05 15:13:06 +0000360
jadmanski0afbb632008-06-06 21:10:57 +0000361 # create a -re-runable function for executing the query
Michael Tang5f74ffd2016-10-31 10:34:53 -0700362 def _exec_sql():
jadmanski0afbb632008-06-06 21:10:57 +0000363 self.cur.execute(cmd, values)
364 return self.cur.fetchall()
mbligh96b9a5a2007-11-24 19:32:20 +0000365
jadmanski0afbb632008-06-06 21:10:57 +0000366 # run the query, re-trying after operational errors
367 if self.autocommit:
Michael Tang5f74ffd2016-10-31 10:34:53 -0700368 return self.run_with_retry(_exec_sql)
jadmanski0afbb632008-06-06 21:10:57 +0000369 else:
Michael Tang5f74ffd2016-10-31 10:34:53 -0700370 return _exec_sql()
mbligh96b9a5a2007-11-24 19:32:20 +0000371
mbligh608c3252007-08-31 13:53:00 +0000372
jadmanski0afbb632008-06-06 21:10:57 +0000373 def _exec_sql_with_commit(self, sql, values, commit):
374 if self.autocommit:
375 # re-run the query until it succeeds
Michael Tang5f74ffd2016-10-31 10:34:53 -0700376 def _exec_sql():
jadmanski0afbb632008-06-06 21:10:57 +0000377 self.cur.execute(sql, values)
378 self.con.commit()
Michael Tang5f74ffd2016-10-31 10:34:53 -0700379 self.run_with_retry(_exec_sql)
jadmanski0afbb632008-06-06 21:10:57 +0000380 else:
381 # take one shot at running the query
382 self.cur.execute(sql, values)
383 if commit:
384 self.con.commit()
mbligh96b9a5a2007-11-24 19:32:20 +0000385
mbligh2bd48872007-09-20 18:32:25 +0000386
jadmanskib591fba2008-09-10 16:19:22 +0000387 def insert(self, table, data, commit=None):
jadmanski0afbb632008-06-06 21:10:57 +0000388 """\
389 'insert into table (keys) values (%s ... %s)', values
mbligh96cf0512008-04-17 15:25:38 +0000390
jadmanski0afbb632008-06-06 21:10:57 +0000391 data:
392 dictionary of fields and data
Michael Tang5f74ffd2016-10-31 10:34:53 -0700393
394 @param table: The name of the table.
395 @param data: The insert data.
396 @param commit: If commit the transaction .
jadmanski0afbb632008-06-06 21:10:57 +0000397 """
398 fields = data.keys()
399 refs = ['%s' for field in fields]
400 values = [data[field] for field in fields]
showardc1a98d12010-01-15 00:22:22 +0000401 cmd = ('insert into %s (%s) values (%s)' %
402 (table, ','.join(self._quote(field) for field in fields),
403 ','.join(refs)))
jadmanski0afbb632008-06-06 21:10:57 +0000404 self.dprint('%s %s' % (cmd, values))
mblighe9cf9d42007-08-31 08:56:00 +0000405
jadmanski0afbb632008-06-06 21:10:57 +0000406 self._exec_sql_with_commit(cmd, values, commit)
mblighe9cf9d42007-08-31 08:56:00 +0000407
mbligh048e1c92007-10-07 00:10:33 +0000408
jadmanski0afbb632008-06-06 21:10:57 +0000409 def delete(self, table, where, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700410 """Delete entries.
411
412 @param table: The name of the table.
413 @param where: The where clause.
414 @param commit: If commit the transaction .
415 """
jadmanski0afbb632008-06-06 21:10:57 +0000416 cmd = ['delete from', table]
mblighd876f452008-12-03 15:09:17 +0000417 if commit is None:
jadmanski0afbb632008-06-06 21:10:57 +0000418 commit = self.autocommit
showardc1a98d12010-01-15 00:22:22 +0000419 where_clause, values = self._where_clause(where)
420 cmd.append(where_clause)
jadmanski0afbb632008-06-06 21:10:57 +0000421 sql = ' '.join(cmd)
422 self.dprint('%s %s' % (sql, values))
mbligh048e1c92007-10-07 00:10:33 +0000423
jadmanski0afbb632008-06-06 21:10:57 +0000424 self._exec_sql_with_commit(sql, values, commit)
mbligh048e1c92007-10-07 00:10:33 +0000425
mbligh7a41a862007-11-30 17:44:24 +0000426
jadmanski0afbb632008-06-06 21:10:57 +0000427 def update(self, table, data, where, commit = None):
428 """\
429 'update table set data values (%s ... %s) where ...'
mbligh2aaeb672007-10-01 14:54:18 +0000430
jadmanski0afbb632008-06-06 21:10:57 +0000431 data:
432 dictionary of fields and data
Michael Tang5f74ffd2016-10-31 10:34:53 -0700433
434 @param table: The name of the table.
435 @param data: The sql parameter values.
436 @param where: The where clause.
437 @param commit: If commit the transaction .
jadmanski0afbb632008-06-06 21:10:57 +0000438 """
mblighd876f452008-12-03 15:09:17 +0000439 if commit is None:
jadmanski0afbb632008-06-06 21:10:57 +0000440 commit = self.autocommit
441 cmd = 'update %s ' % table
442 fields = data.keys()
showardc1a98d12010-01-15 00:22:22 +0000443 data_refs = [self._quote(field) + '=%s' for field in fields]
jadmanski0afbb632008-06-06 21:10:57 +0000444 data_values = [data[field] for field in fields]
jadmanski74eebf32008-07-15 20:04:42 +0000445 cmd += ' set ' + ', '.join(data_refs)
mbligh2aaeb672007-10-01 14:54:18 +0000446
showardc1a98d12010-01-15 00:22:22 +0000447 where_clause, where_values = self._where_clause(where)
448 cmd += where_clause
mbligh2aaeb672007-10-01 14:54:18 +0000449
jadmanski0afbb632008-06-06 21:10:57 +0000450 values = data_values + where_values
jadmanski74eebf32008-07-15 20:04:42 +0000451 self.dprint('%s %s' % (cmd, values))
mbligh2aaeb672007-10-01 14:54:18 +0000452
jadmanski0afbb632008-06-06 21:10:57 +0000453 self._exec_sql_with_commit(cmd, values, commit)
mblighe9cf9d42007-08-31 08:56:00 +0000454
455
jadmanski0afbb632008-06-06 21:10:57 +0000456 def delete_job(self, tag, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700457 """Delete a tko job.
458
459 @param tag: The job tag.
460 @param commit: If commit the transaction .
461 """
jadmanski0afbb632008-06-06 21:10:57 +0000462 job_idx = self.find_job(tag)
463 for test_idx in self.find_tests(job_idx):
464 where = {'test_idx' : test_idx}
showardeab66ce2009-12-23 00:03:56 +0000465 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700466 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000467 self.delete('tko_iteration_attributes', where)
468 self.delete('tko_test_attributes', where)
469 self.delete('tko_test_labels_tests', {'test_id': test_idx})
jadmanski0afbb632008-06-06 21:10:57 +0000470 where = {'job_idx' : job_idx}
showardeab66ce2009-12-23 00:03:56 +0000471 self.delete('tko_tests', where)
472 self.delete('tko_jobs', where)
apw7a7316b2008-02-21 17:42:05 +0000473
apw7a7316b2008-02-21 17:42:05 +0000474
Prathmesh Prabhu30dee862018-04-18 20:24:20 -0700475 def insert_job(self, tag, job, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700476 """Insert a tko job.
477
478 @param tag: The job tag.
479 @param job: The job object.
Michael Tang5f74ffd2016-10-31 10:34:53 -0700480 @param commit: If commit the transaction .
481 """
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700482 data = self._get_common_job_data(tag, job)
483 data.update({
Prathmesh Prabhu30dee862018-04-18 20:24:20 -0700484 'afe_job_id': job.afe_job_id,
485 'afe_parent_job_id': job.afe_parent_job_id,
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700486 })
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700487 if job.job_idx is not None:
488 self.update(
489 'tko_jobs', data, {'job_idx': job.job_idx}, commit=commit)
showard0fec8a02009-12-04 01:19:54 +0000490 else:
showardeab66ce2009-12-23 00:03:56 +0000491 self.insert('tko_jobs', data, commit=commit)
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700492 job.job_idx = self.get_last_autonumber_value()
Shuqian Zhao31425d52016-12-07 09:35:03 -0800493
mbligh237bed32007-09-05 13:05:57 +0000494
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700495 def _get_common_job_data(self, tag, job):
496 """Construct a dictionary with the common data to insert in job/task."""
497 return {
498 'tag':tag,
499 'label': job.label,
500 'username': job.user,
501 'machine_idx': job.machine_idx,
502 'queued_time': job.queued_time,
503 'started_time': job.started_time,
504 'finished_time': job.finished_time,
505 'build': job.build,
506 'build_version': job.build_version,
507 'board': job.board,
508 'suite': job.suite,
509 }
510
511
512 def insert_or_update_task_reference(self, job, reference_type, commit=None):
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700513 """Insert an entry in the tko_task_references table.
514
515 The job should already have been inserted in tko_jobs.
516 @param job: tko.models.job object.
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700517 @param reference_type: The type of reference to insert.
518 One of: {'afe', 'skylab'}
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700519 @param commit: Whether to commit this transaction.
520 """
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700521 assert reference_type in {'afe', 'skylab'}
522 if reference_type == 'afe':
523 task_id = job.afe_job_id
524 parent_task_id = job.afe_parent_job_id
525 else:
526 task_id = job.skylab_task_id
527 parent_task_id = job.skylab_parent_task_id
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700528 data = {
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700529 'reference_type': reference_type,
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700530 'tko_job_idx': job.job_idx,
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700531 'task_id': task_id,
532 'parent_task_id': parent_task_id,
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700533 }
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700534
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700535 task_reference_id = self._lookup_task_reference(job)
536 if task_reference_id is not None:
537 self.update('tko_task_references',
538 data,
539 {'id': task_reference_id},
540 commit=commit)
541 job.task_reference_id = task_reference_id
542 else:
543 self.insert('tko_task_references', data, commit=commit)
544 job.task_reference_id = self.get_last_autonumber_value()
545
546
showardc1a98d12010-01-15 00:22:22 +0000547 def update_job_keyvals(self, job, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700548 """Updates the job key values.
549
550 @param job: The job object.
551 @param commit: If commit the transaction .
552 """
showardc1a98d12010-01-15 00:22:22 +0000553 for key, value in job.keyval_dict.iteritems():
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700554 where = {'job_id': job.job_idx, 'key': key}
showardc1a98d12010-01-15 00:22:22 +0000555 data = dict(where, value=value)
556 exists = self.select('id', 'tko_job_keyvals', where=where)
557
558 if exists:
559 self.update('tko_job_keyvals', data, where=where, commit=commit)
560 else:
561 self.insert('tko_job_keyvals', data, commit=commit)
562
563
jadmanski0afbb632008-06-06 21:10:57 +0000564 def insert_test(self, job, test, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700565 """Inserts a job test.
566
567 @param job: The job object.
568 @param test: The test object.
569 @param commit: If commit the transaction .
570 """
jadmanski0afbb632008-06-06 21:10:57 +0000571 kver = self.insert_kernel(test.kernel, commit=commit)
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700572 data = {'job_idx':job.job_idx, 'test':test.testname,
jadmanski0afbb632008-06-06 21:10:57 +0000573 'subdir':test.subdir, 'kernel_idx':kver,
574 'status':self.status_idx[test.status],
575 'reason':test.reason, 'machine_idx':job.machine_idx,
576 'started_time': test.started_time,
577 'finished_time':test.finished_time}
jadmanski9b6babf2009-04-21 17:57:40 +0000578 is_update = hasattr(test, "test_idx")
579 if is_update:
jadmanski74eebf32008-07-15 20:04:42 +0000580 test_idx = test.test_idx
showardeab66ce2009-12-23 00:03:56 +0000581 self.update('tko_tests', data,
582 {'test_idx': test_idx}, commit=commit)
jadmanskib591fba2008-09-10 16:19:22 +0000583 where = {'test_idx': test_idx}
showardeab66ce2009-12-23 00:03:56 +0000584 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700585 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000586 self.delete('tko_iteration_attributes', where)
showard0fec8a02009-12-04 01:19:54 +0000587 where['user_created'] = 0
showardeab66ce2009-12-23 00:03:56 +0000588 self.delete('tko_test_attributes', where)
jadmanski74eebf32008-07-15 20:04:42 +0000589 else:
showardeab66ce2009-12-23 00:03:56 +0000590 self.insert('tko_tests', data, commit=commit)
jadmanski74eebf32008-07-15 20:04:42 +0000591 test_idx = test.test_idx = self.get_last_autonumber_value()
592 data = {'test_idx': test_idx}
mbligh237bed32007-09-05 13:05:57 +0000593
jadmanski0afbb632008-06-06 21:10:57 +0000594 for i in test.iterations:
595 data['iteration'] = i.index
596 for key, value in i.attr_keyval.iteritems():
597 data['attribute'] = key
598 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000599 self.insert('tko_iteration_attributes', data,
jadmanski0afbb632008-06-06 21:10:57 +0000600 commit=commit)
601 for key, value in i.perf_keyval.iteritems():
602 data['attribute'] = key
Allen Li64692a32016-11-04 13:35:19 -0700603 if math.isnan(value) or math.isinf(value):
604 data['value'] = None
605 else:
606 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000607 self.insert('tko_iteration_result', data,
mbligh432bad42007-10-09 19:56:07 +0000608 commit=commit)
mbligh056d0d32006-10-08 22:31:10 +0000609
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700610 data = {'test_idx': test_idx}
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700611
jadmanski0afbb632008-06-06 21:10:57 +0000612 for key, value in test.attributes.iteritems():
613 data = {'test_idx': test_idx, 'attribute': key,
614 'value': value}
showardeab66ce2009-12-23 00:03:56 +0000615 self.insert('tko_test_attributes', data, commit=commit)
mbligh2bd48872007-09-20 18:32:25 +0000616
jadmanski9b6babf2009-04-21 17:57:40 +0000617 if not is_update:
618 for label_index in test.labels:
619 data = {'test_id': test_idx, 'testlabel_id': label_index}
showardeab66ce2009-12-23 00:03:56 +0000620 self.insert('tko_test_labels_tests', data, commit=commit)
jadmanski9b6babf2009-04-21 17:57:40 +0000621
mbligh056d0d32006-10-08 22:31:10 +0000622
jadmanski0afbb632008-06-06 21:10:57 +0000623 def read_machine_map(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700624 """Reads the machine map."""
showard71b94312009-08-20 23:40:02 +0000625 if self.machine_group or not self.machine_map:
626 return
jadmanski0afbb632008-06-06 21:10:57 +0000627 for line in open(self.machine_map, 'r').readlines():
628 (machine, group) = line.split()
629 self.machine_group[machine] = group
mbligh96b9a5a2007-11-24 19:32:20 +0000630
631
showard71b94312009-08-20 23:40:02 +0000632 def machine_info_dict(self, job):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700633 """Reads the machine information of a job.
634
635 @param job: The job object.
636
637 @return: The machine info dictionary.
638 """
jadmanski0afbb632008-06-06 21:10:57 +0000639 hostname = job.machine
showard71b94312009-08-20 23:40:02 +0000640 group = job.machine_group
641 owner = job.machine_owner
jadmanski0afbb632008-06-06 21:10:57 +0000642
643 if not group:
showard71b94312009-08-20 23:40:02 +0000644 self.read_machine_map()
jadmanski0afbb632008-06-06 21:10:57 +0000645 group = self.machine_group.get(hostname, hostname)
showard71b94312009-08-20 23:40:02 +0000646 if group == hostname and owner:
647 group = owner + '/' + hostname
jadmanski0afbb632008-06-06 21:10:57 +0000648
showard71b94312009-08-20 23:40:02 +0000649 return {'hostname': hostname, 'machine_group': group, 'owner': owner}
650
651
Prathmesh Prabhuec96d6e2018-04-18 18:13:50 -0700652 def insert_or_update_machine(self, job, commit=None):
653 """Insert or updates machine information for the given job.
654
655 Also updates the job object with new machine index, if any.
656
657 @param job: tko.models.job object.
658 @param commit: Whether to commit the database transaction.
659 """
660 job.machine_idx = self._lookup_machine(job.machine)
661 if not job.machine_idx:
662 job.machine_idx = self._insert_machine(job, commit=commit)
663 elif job.machine:
664 # Only try to update tko_machines record if machine is set. This
665 # prevents unnecessary db writes for suite jobs.
666 self._update_machine_information(job, commit=commit)
667
668
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700669 def _lookup_task_reference(self, job):
670 """Find the task_reference_id for a given job. Return None if not found.
671
672 @param job: tko.models.job object.
673 """
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700674 if job.job_idx is None:
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700675 return None
676 rows = self.select(
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700677 'id', 'tko_task_references', {'tko_job_idx': job.job_idx})
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700678 if not rows:
679 return None
680 if len(rows) > 1:
681 raise MySQLTooManyRows('Got %d tko_task_references for tko_job %d'
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700682 % (len(rows), job.job_idx))
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700683 return rows[0][0]
684
685
Prathmesh Prabhu432648c2018-04-18 18:10:50 -0700686 def _insert_machine(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700687 """Inserts the job machine.
688
689 @param job: The job object.
690 @param commit: If commit the transaction .
691 """
showard71b94312009-08-20 23:40:02 +0000692 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000693 self.insert('tko_machines', machine_info, commit=commit)
jadmanski0afbb632008-06-06 21:10:57 +0000694 return self.get_last_autonumber_value()
695
696
Prathmesh Prabhu432648c2018-04-18 18:10:50 -0700697 def _update_machine_information(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700698 """Updates the job machine information.
699
700 @param job: The job object.
701 @param commit: If commit the transaction .
702 """
showard71b94312009-08-20 23:40:02 +0000703 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000704 self.update('tko_machines', machine_info,
showard71b94312009-08-20 23:40:02 +0000705 where={'hostname': machine_info['hostname']},
706 commit=commit)
707
708
Prathmesh Prabhu432648c2018-04-18 18:10:50 -0700709 def _lookup_machine(self, hostname):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700710 """Look up the machine information.
711
712 @param hostname: The hostname as string.
713 """
jadmanski0afbb632008-06-06 21:10:57 +0000714 where = { 'hostname' : hostname }
showardeab66ce2009-12-23 00:03:56 +0000715 rows = self.select('machine_idx', 'tko_machines', where)
jadmanski0afbb632008-06-06 21:10:57 +0000716 if rows:
717 return rows[0][0]
718 else:
719 return None
720
721
722 def lookup_kernel(self, kernel):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700723 """Look up the kernel.
724
725 @param kernel: The kernel object.
726 """
showardeab66ce2009-12-23 00:03:56 +0000727 rows = self.select('kernel_idx', 'tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000728 {'kernel_hash':kernel.kernel_hash})
729 if rows:
730 return rows[0][0]
731 else:
732 return None
733
734
735 def insert_kernel(self, kernel, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700736 """Insert a kernel.
737
738 @param kernel: The kernel object.
739 @param commit: If commit the transaction .
740 """
jadmanski0afbb632008-06-06 21:10:57 +0000741 kver = self.lookup_kernel(kernel)
742 if kver:
743 return kver
744
745 # If this kernel has any significant patches, append their hash
746 # as diferentiator.
747 printable = kernel.base
748 patch_count = 0
749 for patch in kernel.patches:
750 match = re.match(r'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$',
751 patch.reference)
752 if not match:
753 patch_count += 1
754
showardeab66ce2009-12-23 00:03:56 +0000755 self.insert('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000756 {'base':kernel.base,
757 'kernel_hash':kernel.kernel_hash,
758 'printable':printable},
759 commit=commit)
760 kver = self.get_last_autonumber_value()
761
762 if patch_count > 0:
763 printable += ' p%d' % (kver)
showardeab66ce2009-12-23 00:03:56 +0000764 self.update('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000765 {'printable':printable},
766 {'kernel_idx':kver})
767
768 for patch in kernel.patches:
769 self.insert_patch(kver, patch, commit=commit)
770 return kver
771
772
773 def insert_patch(self, kver, patch, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700774 """Insert a kernel patch.
775
776 @param kver: The kernel version.
777 @param patch: The kernel patch object.
778 @param commit: If commit the transaction .
779 """
jadmanski0afbb632008-06-06 21:10:57 +0000780 print patch.reference
781 name = os.path.basename(patch.reference)[:80]
showardeab66ce2009-12-23 00:03:56 +0000782 self.insert('tko_patches',
jadmanski0afbb632008-06-06 21:10:57 +0000783 {'kernel_idx': kver,
784 'name':name,
785 'url':patch.reference,
786 'hash':patch.hash},
787 commit=commit)
788
789
jadmanski74eebf32008-07-15 20:04:42 +0000790 def find_test(self, job_idx, testname, subdir):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700791 """Find a test by name.
792
793 @param job_idx: The job index.
794 @param testname: The test name.
795 @param subdir: The test sub directory under the job directory.
796 """
jadmanski74eebf32008-07-15 20:04:42 +0000797 where = {'job_idx': job_idx , 'test': testname, 'subdir': subdir}
showardeab66ce2009-12-23 00:03:56 +0000798 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000799 if rows:
800 return rows[0][0]
801 else:
802 return None
803
804
805 def find_tests(self, job_idx):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700806 """Find all tests by job index.
807
808 @param job_idx: The job index.
809 @return: A list of tests.
810 """
jadmanski0afbb632008-06-06 21:10:57 +0000811 where = { 'job_idx':job_idx }
showardeab66ce2009-12-23 00:03:56 +0000812 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000813 if rows:
814 return [row[0] for row in rows]
815 else:
816 return []
817
818
819 def find_job(self, tag):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700820 """Find a job by tag.
821
822 @param tag: The job tag name.
823 @return: The job object or None.
824 """
showardeab66ce2009-12-23 00:03:56 +0000825 rows = self.select('job_idx', 'tko_jobs', {'tag': tag})
jadmanski0afbb632008-06-06 21:10:57 +0000826 if rows:
827 return rows[0][0]
828 else:
829 return None
mblighaf25f062007-12-03 17:48:35 +0000830
831
mbligh96cf0512008-04-17 15:25:38 +0000832def db(*args, **dargs):
Laurence Goodby0cb61952018-07-04 16:52:06 +0000833 """Creates an instance of the database class with the arguments
834 provided in args and dargs, using the database type specified by
835 the global configuration (defaulting to mysql).
836
837 @param args: The db_type arguments.
838 @param dargs: The db_type named arguments.
839
840 @return: An db object.
841 """
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700842 return db_sql(*args, **dargs)