blob: b4c5aa424dd6d998978451767de2ac1bc7fb7708 [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,
Congbin Guo658e2db2018-09-13 16:18:34 -070070 database=None, user=None, password=None):
jadmanski0afbb632008-06-06 21:10:57 +000071 self.debug = debug
72 self.autocommit = autocommit
Congbin Guo658e2db2018-09-13 16:18:34 -070073 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
Congbin Guo658e2db2018-09-13 16:18:34 -070095 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
Congbin Guo658e2db2018-09-13 16:18:34 -0700104 The setting of 'host' can be a real host, or a unix socket if it starts
105 with '/'.
Congbin Guo097fec62018-08-03 16:51:39 -0700106
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700107 @param host: If set, this host will be used, if not, the host will be
108 retrieved from global_config.
109 @param database: If set, this database will be used, if not, the
110 database will be retrieved from global_config.
111 @param user: If set, this user will be used, if not, the
112 user will be retrieved from global_config.
113 @param password: If set, this password will be used, if not, the
114 password will be retrieved from global_config.
115 """
116 database_settings = database_settings_helper.get_global_db_config()
mbligh65acae52008-04-24 20:21:55 +0000117
jadmanski0afbb632008-06-06 21:10:57 +0000118 # grab the host, database
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700119 self.host = host or database_settings['HOST']
120 self.database = database or database_settings['NAME']
mbligh65acae52008-04-24 20:21:55 +0000121
Congbin Guo658e2db2018-09-13 16:18:34 -0700122 # grab the user and password
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700123 self.user = user or database_settings['USER']
124 self.password = password or database_settings['PASSWORD']
mbligh65acae52008-04-24 20:21:55 +0000125
Michael Spang7a273472014-10-08 12:08:13 -0400126 # grab the timeout configuration
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700127 self.query_timeout =(
128 database_settings.get('OPTIONS', {}).get('timeout', 3600))
129
130 # Using fallback to non-global in order to work without configuration
131 # overhead on non-shard instances.
132 get_value = global_config.global_config.get_config_value_with_fallback
Jakob Juelich475b82b2014-09-30 11:17:07 -0700133 self.min_delay = get_value("AUTOTEST_WEB", "global_db_min_retry_delay",
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700134 "min_retry_delay", type=int, default=20)
Jakob Juelich475b82b2014-09-30 11:17:07 -0700135 self.max_delay = get_value("AUTOTEST_WEB", "global_db_max_retry_delay",
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700136 "max_retry_delay", type=int, default=60)
mbligh65acae52008-04-24 20:21:55 +0000137
Richard Barnette2468fbd2014-11-07 01:12:46 +0000138 # TODO(beeps): Move this to django settings once we have routers.
139 # On test instances mysql connects through a different port. No point
140 # piping this through our entire infrastructure when it is only really
141 # used for testing; Ideally we would specify this through django
142 # settings and default it to the empty string so django will figure out
143 # the default based on the database backend (eg: mysql, 3306), but until
144 # we have database routers in place any django settings will apply to
145 # both tko and afe.
146 # The intended use of this port is to allow a testing shard vm to
147 # update the master vm's database with test results. Specifying
148 # and empty string will fallback to not even specifying the port
149 # to the backend in tko/db.py. Unfortunately this means retries
150 # won't work on the test cluster till we've migrated to routers.
151 self.port = global_config.global_config.get_config_value(
152 "AUTOTEST_WEB", "global_db_port", type=str, default='')
153
mbligh65acae52008-04-24 20:21:55 +0000154
jadmanski0afbb632008-06-06 21:10:57 +0000155 def _init_db(self):
156 # make sure we clean up any existing connection
157 if self.con:
158 self.con.close()
159 self.con = None
mbligh65acae52008-04-24 20:21:55 +0000160
Prathmesh Prabhuba8adaf2017-07-05 13:07:05 -0700161 # create the db connection and cursor
Laurence Goodby0cb61952018-07-04 16:52:06 +0000162 self.con = self.connect(self.host, self.database,
Congbin Guo658e2db2018-09-13 16:18:34 -0700163 self.user, self.password, self.port)
jadmanski0afbb632008-06-06 21:10:57 +0000164 self.cur = self.con.cursor()
mbligh96cf0512008-04-17 15:25:38 +0000165
166
jadmanski0afbb632008-06-06 21:10:57 +0000167 def _random_delay(self):
168 delay = random.randint(self.min_delay, self.max_delay)
169 time.sleep(delay)
mbligh65acae52008-04-24 20:21:55 +0000170
171
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700172 @retry.retry(driver.OperationalError, timeout_min=10,
Aviv Keshetc53e0052018-07-12 18:37:44 -0700173 delay_sec=5, callback=_connection_retry_callback)
Congbin Guo658e2db2018-09-13 16:18:34 -0700174 def connect(self, host, database, user, password, port):
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700175 """Open and return a connection to mysql database."""
176 connection_args = {
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700177 'db': database,
Congbin Guo097fec62018-08-03 16:51:39 -0700178 'user': user,
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700179 'passwd': password,
180 'connect_timeout': 20,
181 }
182 if port:
183 connection_args['port'] = int(port)
Congbin Guo658e2db2018-09-13 16:18:34 -0700184
185 if host.startswith('/'):
186 return driver.connect(unix_socket=host, **connection_args)
Congbin Guo097fec62018-08-03 16:51:39 -0700187
188 return driver.connect(host=host, **connection_args)
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700189
190
jadmanski0afbb632008-06-06 21:10:57 +0000191 def run_with_retry(self, function, *args, **dargs):
192 """Call function(*args, **dargs) until either it passes
193 without an operational error, or a timeout is reached.
194 This will re-connect to the database, so it is NOT safe
195 to use this inside of a database transaction.
jadmanskie7a69092008-05-29 21:03:13 +0000196
jadmanski0afbb632008-06-06 21:10:57 +0000197 It can be safely used with transactions, but the
198 transaction start & end must be completely contained
Michael Tang5f74ffd2016-10-31 10:34:53 -0700199 within the call to 'function'.
200
201 @param function: The function to run with retry.
202 @param args: The arguments
203 @param dargs: The named arguments.
204 """
jadmanski0afbb632008-06-06 21:10:57 +0000205 success = False
206 start_time = time.time()
207 while not success:
208 try:
209 result = function(*args, **dargs)
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700210 except driver.OperationalError, e:
Allen Lie0bb7602016-11-29 13:45:53 -0800211 _log_error("%s; retrying, don't panic yet"
212 % _format_operational_error(e))
jadmanski0afbb632008-06-06 21:10:57 +0000213 stop_time = time.time()
214 elapsed_time = stop_time - start_time
215 if elapsed_time > self.query_timeout:
216 raise
217 else:
218 try:
219 self._random_delay()
220 self._init_db()
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700221 except driver.OperationalError, e:
Allen Lie0bb7602016-11-29 13:45:53 -0800222 _log_error('%s; panic now'
223 % _format_operational_error(e))
jadmanski0afbb632008-06-06 21:10:57 +0000224 else:
225 success = True
226 return result
mbligh96cf0512008-04-17 15:25:38 +0000227
228
jadmanski0afbb632008-06-06 21:10:57 +0000229 def dprint(self, value):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700230 """Print out debug value.
231
232 @param value: The value to print out.
233 """
jadmanski0afbb632008-06-06 21:10:57 +0000234 if self.debug:
235 sys.stdout.write('SQL: ' + str(value) + '\n')
mbligh8e1ab172007-09-13 17:29:56 +0000236
mblighd5c33db2006-10-08 21:34:16 +0000237
Dan Shie8e0c052015-09-01 00:27:27 -0700238 def _commit(self):
239 """Private method for function commit to call for retry.
240 """
241 return self.con.commit()
242
243
jadmanski0afbb632008-06-06 21:10:57 +0000244 def commit(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700245 """Commit the sql transaction."""
Dan Shie8e0c052015-09-01 00:27:27 -0700246 if self.autocommit:
247 return self.run_with_retry(self._commit)
248 else:
249 return self._commit()
mbligh432bad42007-10-09 19:56:07 +0000250
251
Simran Basie129a962012-08-31 13:03:53 -0700252 def rollback(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700253 """Rollback the sql transaction."""
Simran Basie129a962012-08-31 13:03:53 -0700254 self.con.rollback()
255
256
jadmanski0afbb632008-06-06 21:10:57 +0000257 def get_last_autonumber_value(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700258 """Gets the last auto number.
259
260 @return: The last auto number.
261 """
jadmanski0afbb632008-06-06 21:10:57 +0000262 self.cur.execute('SELECT LAST_INSERT_ID()', [])
263 return self.cur.fetchall()[0][0]
mblighe12b8612008-02-12 20:58:14 +0000264
265
showardc1a98d12010-01-15 00:22:22 +0000266 def _quote(self, field):
267 return '`%s`' % field
268
269
270 def _where_clause(self, where):
271 if not where:
272 return '', []
273
274 if isinstance(where, dict):
275 # key/value pairs (which should be equal, or None for null)
276 keys, values = [], []
277 for field, value in where.iteritems():
278 quoted_field = self._quote(field)
279 if value is None:
280 keys.append(quoted_field + ' is null')
281 else:
282 keys.append(quoted_field + '=%s')
283 values.append(value)
284 where_clause = ' and '.join(keys)
285 elif isinstance(where, basestring):
286 # the exact string
287 where_clause = where
288 values = []
289 elif isinstance(where, tuple):
290 # preformatted where clause + values
291 where_clause, values = where
292 assert where_clause
293 else:
294 raise ValueError('Invalid "where" value: %r' % where)
295
296 return ' WHERE ' + where_clause, values
297
298
299
300 def select(self, fields, table, where, distinct=False, group_by=None,
301 max_rows=None):
jadmanski0afbb632008-06-06 21:10:57 +0000302 """\
303 This selects all the fields requested from a
304 specific table with a particular where clause.
305 The where clause can either be a dictionary of
306 field=value pairs, a string, or a tuple of (string,
307 a list of values). The last option is what you
308 should use when accepting user input as it'll
309 protect you against sql injection attacks (if
310 all user data is placed in the array rather than
311 the raw SQL).
mbligh12eebfa2008-01-03 02:01:53 +0000312
jadmanski0afbb632008-06-06 21:10:57 +0000313 For example:
314 where = ("a = %s AND b = %s", ['val', 'val'])
315 is better than
316 where = "a = 'val' AND b = 'val'"
Michael Tang5f74ffd2016-10-31 10:34:53 -0700317
318 @param fields: The list of selected fields string.
319 @param table: The name of the database table.
320 @param where: The where clause string.
321 @param distinct: If select distinct values.
322 @param group_by: Group by clause.
323 @param max_rows: unused.
jadmanski0afbb632008-06-06 21:10:57 +0000324 """
325 cmd = ['select']
326 if distinct:
327 cmd.append('distinct')
328 cmd += [fields, 'from', table]
mbligh608c3252007-08-31 13:53:00 +0000329
showardc1a98d12010-01-15 00:22:22 +0000330 where_clause, values = self._where_clause(where)
331 cmd.append(where_clause)
mbligh96cf0512008-04-17 15:25:38 +0000332
jadmanski0afbb632008-06-06 21:10:57 +0000333 if group_by:
334 cmd.append(' GROUP BY ' + group_by)
mbligh83f63a02007-12-12 19:13:04 +0000335
jadmanski0afbb632008-06-06 21:10:57 +0000336 self.dprint('%s %s' % (' '.join(cmd), values))
mbligh96cf0512008-04-17 15:25:38 +0000337
jadmanski0afbb632008-06-06 21:10:57 +0000338 # create a re-runable function for executing the query
339 def exec_sql():
Michael Tang5f74ffd2016-10-31 10:34:53 -0700340 """Exeuctes an the sql command."""
jadmanski0afbb632008-06-06 21:10:57 +0000341 sql = ' '.join(cmd)
342 numRec = self.cur.execute(sql, values)
mblighd876f452008-12-03 15:09:17 +0000343 if max_rows is not None and numRec > max_rows:
jadmanski0afbb632008-06-06 21:10:57 +0000344 msg = 'Exceeded allowed number of records'
345 raise MySQLTooManyRows(msg)
346 return self.cur.fetchall()
mbligh96cf0512008-04-17 15:25:38 +0000347
jadmanski0afbb632008-06-06 21:10:57 +0000348 # run the query, re-trying after operational errors
349 if self.autocommit:
350 return self.run_with_retry(exec_sql)
351 else:
352 return exec_sql()
mblighd5c33db2006-10-08 21:34:16 +0000353
mbligh056d0d32006-10-08 22:31:10 +0000354
jadmanski0afbb632008-06-06 21:10:57 +0000355 def select_sql(self, fields, table, sql, values):
356 """\
357 select fields from table "sql"
Michael Tang5f74ffd2016-10-31 10:34:53 -0700358
359 @param fields: The list of selected fields string.
360 @param table: The name of the database table.
361 @param sql: The sql string.
362 @param values: The sql string parameter values.
jadmanski0afbb632008-06-06 21:10:57 +0000363 """
364 cmd = 'select %s from %s %s' % (fields, table, sql)
365 self.dprint(cmd)
mbligh414c69e2007-10-05 15:13:06 +0000366
jadmanski0afbb632008-06-06 21:10:57 +0000367 # create a -re-runable function for executing the query
Michael Tang5f74ffd2016-10-31 10:34:53 -0700368 def _exec_sql():
jadmanski0afbb632008-06-06 21:10:57 +0000369 self.cur.execute(cmd, values)
370 return self.cur.fetchall()
mbligh96b9a5a2007-11-24 19:32:20 +0000371
jadmanski0afbb632008-06-06 21:10:57 +0000372 # run the query, re-trying after operational errors
373 if self.autocommit:
Michael Tang5f74ffd2016-10-31 10:34:53 -0700374 return self.run_with_retry(_exec_sql)
jadmanski0afbb632008-06-06 21:10:57 +0000375 else:
Michael Tang5f74ffd2016-10-31 10:34:53 -0700376 return _exec_sql()
mbligh96b9a5a2007-11-24 19:32:20 +0000377
mbligh608c3252007-08-31 13:53:00 +0000378
jadmanski0afbb632008-06-06 21:10:57 +0000379 def _exec_sql_with_commit(self, sql, values, commit):
380 if self.autocommit:
381 # re-run the query until it succeeds
Michael Tang5f74ffd2016-10-31 10:34:53 -0700382 def _exec_sql():
jadmanski0afbb632008-06-06 21:10:57 +0000383 self.cur.execute(sql, values)
384 self.con.commit()
Michael Tang5f74ffd2016-10-31 10:34:53 -0700385 self.run_with_retry(_exec_sql)
jadmanski0afbb632008-06-06 21:10:57 +0000386 else:
387 # take one shot at running the query
388 self.cur.execute(sql, values)
389 if commit:
390 self.con.commit()
mbligh96b9a5a2007-11-24 19:32:20 +0000391
mbligh2bd48872007-09-20 18:32:25 +0000392
jadmanskib591fba2008-09-10 16:19:22 +0000393 def insert(self, table, data, commit=None):
jadmanski0afbb632008-06-06 21:10:57 +0000394 """\
395 'insert into table (keys) values (%s ... %s)', values
mbligh96cf0512008-04-17 15:25:38 +0000396
jadmanski0afbb632008-06-06 21:10:57 +0000397 data:
398 dictionary of fields and data
Michael Tang5f74ffd2016-10-31 10:34:53 -0700399
400 @param table: The name of the table.
401 @param data: The insert data.
402 @param commit: If commit the transaction .
jadmanski0afbb632008-06-06 21:10:57 +0000403 """
404 fields = data.keys()
405 refs = ['%s' for field in fields]
406 values = [data[field] for field in fields]
showardc1a98d12010-01-15 00:22:22 +0000407 cmd = ('insert into %s (%s) values (%s)' %
408 (table, ','.join(self._quote(field) for field in fields),
409 ','.join(refs)))
jadmanski0afbb632008-06-06 21:10:57 +0000410 self.dprint('%s %s' % (cmd, values))
mblighe9cf9d42007-08-31 08:56:00 +0000411
jadmanski0afbb632008-06-06 21:10:57 +0000412 self._exec_sql_with_commit(cmd, values, commit)
mblighe9cf9d42007-08-31 08:56:00 +0000413
mbligh048e1c92007-10-07 00:10:33 +0000414
jadmanski0afbb632008-06-06 21:10:57 +0000415 def delete(self, table, where, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700416 """Delete entries.
417
418 @param table: The name of the table.
419 @param where: The where clause.
420 @param commit: If commit the transaction .
421 """
jadmanski0afbb632008-06-06 21:10:57 +0000422 cmd = ['delete from', table]
mblighd876f452008-12-03 15:09:17 +0000423 if commit is None:
jadmanski0afbb632008-06-06 21:10:57 +0000424 commit = self.autocommit
showardc1a98d12010-01-15 00:22:22 +0000425 where_clause, values = self._where_clause(where)
426 cmd.append(where_clause)
jadmanski0afbb632008-06-06 21:10:57 +0000427 sql = ' '.join(cmd)
428 self.dprint('%s %s' % (sql, values))
mbligh048e1c92007-10-07 00:10:33 +0000429
jadmanski0afbb632008-06-06 21:10:57 +0000430 self._exec_sql_with_commit(sql, values, commit)
mbligh048e1c92007-10-07 00:10:33 +0000431
mbligh7a41a862007-11-30 17:44:24 +0000432
jadmanski0afbb632008-06-06 21:10:57 +0000433 def update(self, table, data, where, commit = None):
434 """\
435 'update table set data values (%s ... %s) where ...'
mbligh2aaeb672007-10-01 14:54:18 +0000436
jadmanski0afbb632008-06-06 21:10:57 +0000437 data:
438 dictionary of fields and data
Michael Tang5f74ffd2016-10-31 10:34:53 -0700439
440 @param table: The name of the table.
441 @param data: The sql parameter values.
442 @param where: The where clause.
443 @param commit: If commit the transaction .
jadmanski0afbb632008-06-06 21:10:57 +0000444 """
mblighd876f452008-12-03 15:09:17 +0000445 if commit is None:
jadmanski0afbb632008-06-06 21:10:57 +0000446 commit = self.autocommit
447 cmd = 'update %s ' % table
448 fields = data.keys()
showardc1a98d12010-01-15 00:22:22 +0000449 data_refs = [self._quote(field) + '=%s' for field in fields]
jadmanski0afbb632008-06-06 21:10:57 +0000450 data_values = [data[field] for field in fields]
jadmanski74eebf32008-07-15 20:04:42 +0000451 cmd += ' set ' + ', '.join(data_refs)
mbligh2aaeb672007-10-01 14:54:18 +0000452
showardc1a98d12010-01-15 00:22:22 +0000453 where_clause, where_values = self._where_clause(where)
454 cmd += where_clause
mbligh2aaeb672007-10-01 14:54:18 +0000455
jadmanski0afbb632008-06-06 21:10:57 +0000456 values = data_values + where_values
jadmanski74eebf32008-07-15 20:04:42 +0000457 self.dprint('%s %s' % (cmd, values))
mbligh2aaeb672007-10-01 14:54:18 +0000458
jadmanski0afbb632008-06-06 21:10:57 +0000459 self._exec_sql_with_commit(cmd, values, commit)
mblighe9cf9d42007-08-31 08:56:00 +0000460
461
jadmanski0afbb632008-06-06 21:10:57 +0000462 def delete_job(self, tag, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700463 """Delete a tko job.
464
465 @param tag: The job tag.
466 @param commit: If commit the transaction .
467 """
jadmanski0afbb632008-06-06 21:10:57 +0000468 job_idx = self.find_job(tag)
469 for test_idx in self.find_tests(job_idx):
470 where = {'test_idx' : test_idx}
showardeab66ce2009-12-23 00:03:56 +0000471 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700472 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000473 self.delete('tko_iteration_attributes', where)
474 self.delete('tko_test_attributes', where)
475 self.delete('tko_test_labels_tests', {'test_id': test_idx})
jadmanski0afbb632008-06-06 21:10:57 +0000476 where = {'job_idx' : job_idx}
showardeab66ce2009-12-23 00:03:56 +0000477 self.delete('tko_tests', where)
478 self.delete('tko_jobs', where)
apw7a7316b2008-02-21 17:42:05 +0000479
apw7a7316b2008-02-21 17:42:05 +0000480
Prathmesh Prabhu30dee862018-04-18 20:24:20 -0700481 def insert_job(self, tag, job, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700482 """Insert a tko job.
483
484 @param tag: The job tag.
485 @param job: The job object.
Michael Tang5f74ffd2016-10-31 10:34:53 -0700486 @param commit: If commit the transaction .
487 """
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700488 data = self._get_common_job_data(tag, job)
489 data.update({
Prathmesh Prabhu30dee862018-04-18 20:24:20 -0700490 'afe_job_id': job.afe_job_id,
491 'afe_parent_job_id': job.afe_parent_job_id,
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700492 })
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700493 if job.job_idx is not None:
494 self.update(
495 'tko_jobs', data, {'job_idx': job.job_idx}, commit=commit)
showard0fec8a02009-12-04 01:19:54 +0000496 else:
showardeab66ce2009-12-23 00:03:56 +0000497 self.insert('tko_jobs', data, commit=commit)
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700498 job.job_idx = self.get_last_autonumber_value()
Shuqian Zhao31425d52016-12-07 09:35:03 -0800499
mbligh237bed32007-09-05 13:05:57 +0000500
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700501 def _get_common_job_data(self, tag, job):
502 """Construct a dictionary with the common data to insert in job/task."""
503 return {
504 'tag':tag,
505 'label': job.label,
506 'username': job.user,
507 'machine_idx': job.machine_idx,
508 'queued_time': job.queued_time,
509 'started_time': job.started_time,
510 'finished_time': job.finished_time,
511 'build': job.build,
512 'build_version': job.build_version,
513 'board': job.board,
514 'suite': job.suite,
515 }
516
517
518 def insert_or_update_task_reference(self, job, reference_type, commit=None):
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700519 """Insert an entry in the tko_task_references table.
520
521 The job should already have been inserted in tko_jobs.
522 @param job: tko.models.job object.
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700523 @param reference_type: The type of reference to insert.
524 One of: {'afe', 'skylab'}
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700525 @param commit: Whether to commit this transaction.
526 """
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700527 assert reference_type in {'afe', 'skylab'}
528 if reference_type == 'afe':
529 task_id = job.afe_job_id
530 parent_task_id = job.afe_parent_job_id
531 else:
532 task_id = job.skylab_task_id
533 parent_task_id = job.skylab_parent_task_id
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700534 data = {
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700535 'reference_type': reference_type,
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700536 'tko_job_idx': job.job_idx,
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700537 'task_id': task_id,
538 'parent_task_id': parent_task_id,
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700539 }
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700540
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700541 task_reference_id = self._lookup_task_reference(job)
542 if task_reference_id is not None:
543 self.update('tko_task_references',
544 data,
545 {'id': task_reference_id},
546 commit=commit)
547 job.task_reference_id = task_reference_id
548 else:
549 self.insert('tko_task_references', data, commit=commit)
550 job.task_reference_id = self.get_last_autonumber_value()
551
552
showardc1a98d12010-01-15 00:22:22 +0000553 def update_job_keyvals(self, job, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700554 """Updates the job key values.
555
556 @param job: The job object.
557 @param commit: If commit the transaction .
558 """
showardc1a98d12010-01-15 00:22:22 +0000559 for key, value in job.keyval_dict.iteritems():
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700560 where = {'job_id': job.job_idx, 'key': key}
showardc1a98d12010-01-15 00:22:22 +0000561 data = dict(where, value=value)
562 exists = self.select('id', 'tko_job_keyvals', where=where)
563
564 if exists:
565 self.update('tko_job_keyvals', data, where=where, commit=commit)
566 else:
567 self.insert('tko_job_keyvals', data, commit=commit)
568
569
jadmanski0afbb632008-06-06 21:10:57 +0000570 def insert_test(self, job, test, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700571 """Inserts a job test.
572
573 @param job: The job object.
574 @param test: The test object.
575 @param commit: If commit the transaction .
576 """
jadmanski0afbb632008-06-06 21:10:57 +0000577 kver = self.insert_kernel(test.kernel, commit=commit)
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700578 data = {'job_idx':job.job_idx, 'test':test.testname,
jadmanski0afbb632008-06-06 21:10:57 +0000579 'subdir':test.subdir, 'kernel_idx':kver,
580 'status':self.status_idx[test.status],
581 'reason':test.reason, 'machine_idx':job.machine_idx,
582 'started_time': test.started_time,
583 'finished_time':test.finished_time}
jadmanski9b6babf2009-04-21 17:57:40 +0000584 is_update = hasattr(test, "test_idx")
585 if is_update:
jadmanski74eebf32008-07-15 20:04:42 +0000586 test_idx = test.test_idx
showardeab66ce2009-12-23 00:03:56 +0000587 self.update('tko_tests', data,
588 {'test_idx': test_idx}, commit=commit)
jadmanskib591fba2008-09-10 16:19:22 +0000589 where = {'test_idx': test_idx}
showardeab66ce2009-12-23 00:03:56 +0000590 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700591 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000592 self.delete('tko_iteration_attributes', where)
showard0fec8a02009-12-04 01:19:54 +0000593 where['user_created'] = 0
showardeab66ce2009-12-23 00:03:56 +0000594 self.delete('tko_test_attributes', where)
jadmanski74eebf32008-07-15 20:04:42 +0000595 else:
showardeab66ce2009-12-23 00:03:56 +0000596 self.insert('tko_tests', data, commit=commit)
jadmanski74eebf32008-07-15 20:04:42 +0000597 test_idx = test.test_idx = self.get_last_autonumber_value()
598 data = {'test_idx': test_idx}
mbligh237bed32007-09-05 13:05:57 +0000599
jadmanski0afbb632008-06-06 21:10:57 +0000600 for i in test.iterations:
601 data['iteration'] = i.index
602 for key, value in i.attr_keyval.iteritems():
603 data['attribute'] = key
604 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000605 self.insert('tko_iteration_attributes', data,
jadmanski0afbb632008-06-06 21:10:57 +0000606 commit=commit)
607 for key, value in i.perf_keyval.iteritems():
608 data['attribute'] = key
Allen Li64692a32016-11-04 13:35:19 -0700609 if math.isnan(value) or math.isinf(value):
610 data['value'] = None
611 else:
612 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000613 self.insert('tko_iteration_result', data,
mbligh432bad42007-10-09 19:56:07 +0000614 commit=commit)
mbligh056d0d32006-10-08 22:31:10 +0000615
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700616 data = {'test_idx': test_idx}
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700617
jadmanski0afbb632008-06-06 21:10:57 +0000618 for key, value in test.attributes.iteritems():
619 data = {'test_idx': test_idx, 'attribute': key,
620 'value': value}
Allen Lie3051e92018-07-30 17:40:54 -0700621 try:
622 self.insert('tko_test_attributes', data, commit=commit)
623 except:
624 _log_error('Uploading attribute %r' % (data))
625 raise
mbligh2bd48872007-09-20 18:32:25 +0000626
jadmanski9b6babf2009-04-21 17:57:40 +0000627 if not is_update:
628 for label_index in test.labels:
629 data = {'test_id': test_idx, 'testlabel_id': label_index}
showardeab66ce2009-12-23 00:03:56 +0000630 self.insert('tko_test_labels_tests', data, commit=commit)
jadmanski9b6babf2009-04-21 17:57:40 +0000631
mbligh056d0d32006-10-08 22:31:10 +0000632
jadmanski0afbb632008-06-06 21:10:57 +0000633 def read_machine_map(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700634 """Reads the machine map."""
showard71b94312009-08-20 23:40:02 +0000635 if self.machine_group or not self.machine_map:
636 return
jadmanski0afbb632008-06-06 21:10:57 +0000637 for line in open(self.machine_map, 'r').readlines():
638 (machine, group) = line.split()
639 self.machine_group[machine] = group
mbligh96b9a5a2007-11-24 19:32:20 +0000640
641
showard71b94312009-08-20 23:40:02 +0000642 def machine_info_dict(self, job):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700643 """Reads the machine information of a job.
644
645 @param job: The job object.
646
647 @return: The machine info dictionary.
648 """
jadmanski0afbb632008-06-06 21:10:57 +0000649 hostname = job.machine
showard71b94312009-08-20 23:40:02 +0000650 group = job.machine_group
651 owner = job.machine_owner
jadmanski0afbb632008-06-06 21:10:57 +0000652
653 if not group:
showard71b94312009-08-20 23:40:02 +0000654 self.read_machine_map()
jadmanski0afbb632008-06-06 21:10:57 +0000655 group = self.machine_group.get(hostname, hostname)
showard71b94312009-08-20 23:40:02 +0000656 if group == hostname and owner:
657 group = owner + '/' + hostname
jadmanski0afbb632008-06-06 21:10:57 +0000658
showard71b94312009-08-20 23:40:02 +0000659 return {'hostname': hostname, 'machine_group': group, 'owner': owner}
660
661
Prathmesh Prabhuec96d6e2018-04-18 18:13:50 -0700662 def insert_or_update_machine(self, job, commit=None):
663 """Insert or updates machine information for the given job.
664
665 Also updates the job object with new machine index, if any.
666
667 @param job: tko.models.job object.
668 @param commit: Whether to commit the database transaction.
669 """
670 job.machine_idx = self._lookup_machine(job.machine)
671 if not job.machine_idx:
672 job.machine_idx = self._insert_machine(job, commit=commit)
673 elif job.machine:
674 # Only try to update tko_machines record if machine is set. This
675 # prevents unnecessary db writes for suite jobs.
676 self._update_machine_information(job, commit=commit)
677
678
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700679 def _lookup_task_reference(self, job):
680 """Find the task_reference_id for a given job. Return None if not found.
681
682 @param job: tko.models.job object.
683 """
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700684 if job.job_idx is None:
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700685 return None
686 rows = self.select(
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700687 'id', 'tko_task_references', {'tko_job_idx': job.job_idx})
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700688 if not rows:
689 return None
690 if len(rows) > 1:
691 raise MySQLTooManyRows('Got %d tko_task_references for tko_job %d'
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700692 % (len(rows), job.job_idx))
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700693 return rows[0][0]
694
695
Prathmesh Prabhu432648c2018-04-18 18:10:50 -0700696 def _insert_machine(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700697 """Inserts the job machine.
698
699 @param job: The job object.
700 @param commit: If commit the transaction .
701 """
showard71b94312009-08-20 23:40:02 +0000702 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000703 self.insert('tko_machines', machine_info, commit=commit)
jadmanski0afbb632008-06-06 21:10:57 +0000704 return self.get_last_autonumber_value()
705
706
Prathmesh Prabhu432648c2018-04-18 18:10:50 -0700707 def _update_machine_information(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700708 """Updates the job machine information.
709
710 @param job: The job object.
711 @param commit: If commit the transaction .
712 """
showard71b94312009-08-20 23:40:02 +0000713 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000714 self.update('tko_machines', machine_info,
showard71b94312009-08-20 23:40:02 +0000715 where={'hostname': machine_info['hostname']},
716 commit=commit)
717
718
Prathmesh Prabhu432648c2018-04-18 18:10:50 -0700719 def _lookup_machine(self, hostname):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700720 """Look up the machine information.
721
722 @param hostname: The hostname as string.
723 """
jadmanski0afbb632008-06-06 21:10:57 +0000724 where = { 'hostname' : hostname }
showardeab66ce2009-12-23 00:03:56 +0000725 rows = self.select('machine_idx', 'tko_machines', where)
jadmanski0afbb632008-06-06 21:10:57 +0000726 if rows:
727 return rows[0][0]
728 else:
729 return None
730
731
732 def lookup_kernel(self, kernel):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700733 """Look up the kernel.
734
735 @param kernel: The kernel object.
736 """
showardeab66ce2009-12-23 00:03:56 +0000737 rows = self.select('kernel_idx', 'tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000738 {'kernel_hash':kernel.kernel_hash})
739 if rows:
740 return rows[0][0]
741 else:
742 return None
743
744
745 def insert_kernel(self, kernel, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700746 """Insert a kernel.
747
748 @param kernel: The kernel object.
749 @param commit: If commit the transaction .
750 """
jadmanski0afbb632008-06-06 21:10:57 +0000751 kver = self.lookup_kernel(kernel)
752 if kver:
753 return kver
754
755 # If this kernel has any significant patches, append their hash
756 # as diferentiator.
757 printable = kernel.base
758 patch_count = 0
759 for patch in kernel.patches:
760 match = re.match(r'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$',
761 patch.reference)
762 if not match:
763 patch_count += 1
764
showardeab66ce2009-12-23 00:03:56 +0000765 self.insert('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000766 {'base':kernel.base,
767 'kernel_hash':kernel.kernel_hash,
768 'printable':printable},
769 commit=commit)
770 kver = self.get_last_autonumber_value()
771
772 if patch_count > 0:
773 printable += ' p%d' % (kver)
showardeab66ce2009-12-23 00:03:56 +0000774 self.update('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000775 {'printable':printable},
776 {'kernel_idx':kver})
777
778 for patch in kernel.patches:
779 self.insert_patch(kver, patch, commit=commit)
780 return kver
781
782
783 def insert_patch(self, kver, patch, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700784 """Insert a kernel patch.
785
786 @param kver: The kernel version.
787 @param patch: The kernel patch object.
788 @param commit: If commit the transaction .
789 """
jadmanski0afbb632008-06-06 21:10:57 +0000790 print patch.reference
791 name = os.path.basename(patch.reference)[:80]
showardeab66ce2009-12-23 00:03:56 +0000792 self.insert('tko_patches',
jadmanski0afbb632008-06-06 21:10:57 +0000793 {'kernel_idx': kver,
794 'name':name,
795 'url':patch.reference,
796 'hash':patch.hash},
797 commit=commit)
798
799
jadmanski74eebf32008-07-15 20:04:42 +0000800 def find_test(self, job_idx, testname, subdir):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700801 """Find a test by name.
802
803 @param job_idx: The job index.
804 @param testname: The test name.
805 @param subdir: The test sub directory under the job directory.
806 """
jadmanski74eebf32008-07-15 20:04:42 +0000807 where = {'job_idx': job_idx , 'test': testname, 'subdir': subdir}
showardeab66ce2009-12-23 00:03:56 +0000808 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000809 if rows:
810 return rows[0][0]
811 else:
812 return None
813
814
815 def find_tests(self, job_idx):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700816 """Find all tests by job index.
817
818 @param job_idx: The job index.
819 @return: A list of tests.
820 """
jadmanski0afbb632008-06-06 21:10:57 +0000821 where = { 'job_idx':job_idx }
showardeab66ce2009-12-23 00:03:56 +0000822 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000823 if rows:
824 return [row[0] for row in rows]
825 else:
826 return []
827
828
829 def find_job(self, tag):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700830 """Find a job by tag.
831
832 @param tag: The job tag name.
833 @return: The job object or None.
834 """
showardeab66ce2009-12-23 00:03:56 +0000835 rows = self.select('job_idx', 'tko_jobs', {'tag': tag})
jadmanski0afbb632008-06-06 21:10:57 +0000836 if rows:
837 return rows[0][0]
838 else:
839 return None
mblighaf25f062007-12-03 17:48:35 +0000840
841
Xixuan Wu8e327e22018-08-10 12:35:28 -0700842 def get_child_tests_by_parent_task_id(self, parent_task_id):
843 """Get the child tests by a parent task id.
844
845 @param parent_task_id: A string parent task id in tko_task_references.
846 @return: A list of view dicts, which has key 'test_name' and 'status'.
847 """
Aviv Keshet35bf9e82019-05-23 14:24:46 -0700848 if not parent_task_id:
849 raise ValueError('no parent_task_id supplied')
Xixuan Wu8e327e22018-08-10 12:35:28 -0700850 rows = self.select('tko_job_idx', 'tko_task_references',
851 {'parent_task_id': parent_task_id})
852 tko_job_ids = [str(r[0]) for r in rows]
Aviv Keshetc41271c2019-05-23 15:24:39 -0700853 if not tko_job_ids:
854 return []
Xixuan Wu8e327e22018-08-10 12:35:28 -0700855 fields = ['test_name', 'status']
856 where = 'job_idx in (%s)' % ', '.join(tko_job_ids)
857 rows = self.select(', '.join(fields), 'tko_test_view_2', where)
858 return [{'test_name': r[0], 'status': r[1]} for r in rows]
859
860
mbligh96cf0512008-04-17 15:25:38 +0000861def db(*args, **dargs):
Laurence Goodby0cb61952018-07-04 16:52:06 +0000862 """Creates an instance of the database class with the arguments
863 provided in args and dargs, using the database type specified by
864 the global configuration (defaulting to mysql).
865
866 @param args: The db_type arguments.
867 @param dargs: The db_type named arguments.
868
869 @return: An db object.
870 """
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700871 return db_sql(*args, **dargs)