blob: 2b3b58b5b3c70d10ca341c1ecbfab6b86dfcf7bc [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 Guo097fec62018-08-03 16:51:39 -070070 database=None, user=None, password=None, proxy_socket=None):
jadmanski0afbb632008-06-06 21:10:57 +000071 self.debug = debug
72 self.autocommit = autocommit
Congbin Guo097fec62018-08-03 16:51:39 -070073 self._load_config(host, database, user, password, proxy_socket)
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 Guo097fec62018-08-03 16:51:39 -070095 def _load_config(self, host, database, user, password, proxy_socket):
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 Guo097fec62018-08-03 16:51:39 -0700104 When proxy_socket is set, 'host' will be bypassed.
105
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700106 @param host: If set, this host will be used, if not, the host will be
107 retrieved from global_config.
108 @param database: If set, this database will be used, if not, the
109 database will be retrieved from global_config.
110 @param user: If set, this user will be used, if not, the
111 user will be retrieved from global_config.
112 @param password: If set, this password will be used, if not, the
113 password will be retrieved from global_config.
Congbin Guo097fec62018-08-03 16:51:39 -0700114 @param proxy_socket: If set, this proxy_socket will be used, if not, the
115 proxy_socket will be retrieved from global_config.
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700116 """
117 database_settings = database_settings_helper.get_global_db_config()
mbligh65acae52008-04-24 20:21:55 +0000118
jadmanski0afbb632008-06-06 21:10:57 +0000119 # grab the host, database
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700120 self.host = host or database_settings['HOST']
121 self.database = database or database_settings['NAME']
mbligh65acae52008-04-24 20:21:55 +0000122
Congbin Guo097fec62018-08-03 16:51:39 -0700123 # grab authentication information
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700124 self.user = user or database_settings['USER']
125 self.password = password or database_settings['PASSWORD']
Congbin Guo097fec62018-08-03 16:51:39 -0700126 self.proxy_socket = proxy_socket or database_settings['PROXY_SOCKET']
mbligh65acae52008-04-24 20:21:55 +0000127
Michael Spang7a273472014-10-08 12:08:13 -0400128 # grab the timeout configuration
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700129 self.query_timeout =(
130 database_settings.get('OPTIONS', {}).get('timeout', 3600))
131
132 # Using fallback to non-global in order to work without configuration
133 # overhead on non-shard instances.
134 get_value = global_config.global_config.get_config_value_with_fallback
Jakob Juelich475b82b2014-09-30 11:17:07 -0700135 self.min_delay = get_value("AUTOTEST_WEB", "global_db_min_retry_delay",
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700136 "min_retry_delay", type=int, default=20)
Jakob Juelich475b82b2014-09-30 11:17:07 -0700137 self.max_delay = get_value("AUTOTEST_WEB", "global_db_max_retry_delay",
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700138 "max_retry_delay", type=int, default=60)
mbligh65acae52008-04-24 20:21:55 +0000139
Richard Barnette2468fbd2014-11-07 01:12:46 +0000140 # TODO(beeps): Move this to django settings once we have routers.
141 # On test instances mysql connects through a different port. No point
142 # piping this through our entire infrastructure when it is only really
143 # used for testing; Ideally we would specify this through django
144 # settings and default it to the empty string so django will figure out
145 # the default based on the database backend (eg: mysql, 3306), but until
146 # we have database routers in place any django settings will apply to
147 # both tko and afe.
148 # The intended use of this port is to allow a testing shard vm to
149 # update the master vm's database with test results. Specifying
150 # and empty string will fallback to not even specifying the port
151 # to the backend in tko/db.py. Unfortunately this means retries
152 # won't work on the test cluster till we've migrated to routers.
153 self.port = global_config.global_config.get_config_value(
154 "AUTOTEST_WEB", "global_db_port", type=str, default='')
155
mbligh65acae52008-04-24 20:21:55 +0000156
jadmanski0afbb632008-06-06 21:10:57 +0000157 def _init_db(self):
158 # make sure we clean up any existing connection
159 if self.con:
160 self.con.close()
161 self.con = None
mbligh65acae52008-04-24 20:21:55 +0000162
Prathmesh Prabhuba8adaf2017-07-05 13:07:05 -0700163 # create the db connection and cursor
Laurence Goodby0cb61952018-07-04 16:52:06 +0000164 self.con = self.connect(self.host, self.database,
Congbin Guo097fec62018-08-03 16:51:39 -0700165 self.user, self.password, self.port,
166 self.proxy_socket)
jadmanski0afbb632008-06-06 21:10:57 +0000167 self.cur = self.con.cursor()
mbligh96cf0512008-04-17 15:25:38 +0000168
169
jadmanski0afbb632008-06-06 21:10:57 +0000170 def _random_delay(self):
171 delay = random.randint(self.min_delay, self.max_delay)
172 time.sleep(delay)
mbligh65acae52008-04-24 20:21:55 +0000173
174
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700175 @retry.retry(driver.OperationalError, timeout_min=10,
Aviv Keshetc53e0052018-07-12 18:37:44 -0700176 delay_sec=5, callback=_connection_retry_callback)
Congbin Guo097fec62018-08-03 16:51:39 -0700177 def connect(self, host, database, user, password, port, proxy_socket):
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700178 """Open and return a connection to mysql database."""
179 connection_args = {
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700180 'db': database,
Congbin Guo097fec62018-08-03 16:51:39 -0700181 'user': user,
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700182 'passwd': password,
183 'connect_timeout': 20,
184 }
185 if port:
186 connection_args['port'] = int(port)
Congbin Guo097fec62018-08-03 16:51:39 -0700187 # Connect using proxy socket if possible.
188 if proxy_socket:
189 try:
190 return driver.connect(unix_socket=proxy_socket,
191 **connection_args)
192 # pylint: disable=catching-non-exception
193 except driver.OperationalError:
194 # Fallback to connect using user/host/password.
195 pass
196
197 return driver.connect(host=host, **connection_args)
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700198
199
jadmanski0afbb632008-06-06 21:10:57 +0000200 def run_with_retry(self, function, *args, **dargs):
201 """Call function(*args, **dargs) until either it passes
202 without an operational error, or a timeout is reached.
203 This will re-connect to the database, so it is NOT safe
204 to use this inside of a database transaction.
jadmanskie7a69092008-05-29 21:03:13 +0000205
jadmanski0afbb632008-06-06 21:10:57 +0000206 It can be safely used with transactions, but the
207 transaction start & end must be completely contained
Michael Tang5f74ffd2016-10-31 10:34:53 -0700208 within the call to 'function'.
209
210 @param function: The function to run with retry.
211 @param args: The arguments
212 @param dargs: The named arguments.
213 """
jadmanski0afbb632008-06-06 21:10:57 +0000214 success = False
215 start_time = time.time()
216 while not success:
217 try:
218 result = function(*args, **dargs)
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700219 except driver.OperationalError, e:
Allen Lie0bb7602016-11-29 13:45:53 -0800220 _log_error("%s; retrying, don't panic yet"
221 % _format_operational_error(e))
jadmanski0afbb632008-06-06 21:10:57 +0000222 stop_time = time.time()
223 elapsed_time = stop_time - start_time
224 if elapsed_time > self.query_timeout:
225 raise
226 else:
227 try:
228 self._random_delay()
229 self._init_db()
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700230 except driver.OperationalError, e:
Allen Lie0bb7602016-11-29 13:45:53 -0800231 _log_error('%s; panic now'
232 % _format_operational_error(e))
jadmanski0afbb632008-06-06 21:10:57 +0000233 else:
234 success = True
235 return result
mbligh96cf0512008-04-17 15:25:38 +0000236
237
jadmanski0afbb632008-06-06 21:10:57 +0000238 def dprint(self, value):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700239 """Print out debug value.
240
241 @param value: The value to print out.
242 """
jadmanski0afbb632008-06-06 21:10:57 +0000243 if self.debug:
244 sys.stdout.write('SQL: ' + str(value) + '\n')
mbligh8e1ab172007-09-13 17:29:56 +0000245
mblighd5c33db2006-10-08 21:34:16 +0000246
Dan Shie8e0c052015-09-01 00:27:27 -0700247 def _commit(self):
248 """Private method for function commit to call for retry.
249 """
250 return self.con.commit()
251
252
jadmanski0afbb632008-06-06 21:10:57 +0000253 def commit(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700254 """Commit the sql transaction."""
Dan Shie8e0c052015-09-01 00:27:27 -0700255 if self.autocommit:
256 return self.run_with_retry(self._commit)
257 else:
258 return self._commit()
mbligh432bad42007-10-09 19:56:07 +0000259
260
Simran Basie129a962012-08-31 13:03:53 -0700261 def rollback(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700262 """Rollback the sql transaction."""
Simran Basie129a962012-08-31 13:03:53 -0700263 self.con.rollback()
264
265
jadmanski0afbb632008-06-06 21:10:57 +0000266 def get_last_autonumber_value(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700267 """Gets the last auto number.
268
269 @return: The last auto number.
270 """
jadmanski0afbb632008-06-06 21:10:57 +0000271 self.cur.execute('SELECT LAST_INSERT_ID()', [])
272 return self.cur.fetchall()[0][0]
mblighe12b8612008-02-12 20:58:14 +0000273
274
showardc1a98d12010-01-15 00:22:22 +0000275 def _quote(self, field):
276 return '`%s`' % field
277
278
279 def _where_clause(self, where):
280 if not where:
281 return '', []
282
283 if isinstance(where, dict):
284 # key/value pairs (which should be equal, or None for null)
285 keys, values = [], []
286 for field, value in where.iteritems():
287 quoted_field = self._quote(field)
288 if value is None:
289 keys.append(quoted_field + ' is null')
290 else:
291 keys.append(quoted_field + '=%s')
292 values.append(value)
293 where_clause = ' and '.join(keys)
294 elif isinstance(where, basestring):
295 # the exact string
296 where_clause = where
297 values = []
298 elif isinstance(where, tuple):
299 # preformatted where clause + values
300 where_clause, values = where
301 assert where_clause
302 else:
303 raise ValueError('Invalid "where" value: %r' % where)
304
305 return ' WHERE ' + where_clause, values
306
307
308
309 def select(self, fields, table, where, distinct=False, group_by=None,
310 max_rows=None):
jadmanski0afbb632008-06-06 21:10:57 +0000311 """\
312 This selects all the fields requested from a
313 specific table with a particular where clause.
314 The where clause can either be a dictionary of
315 field=value pairs, a string, or a tuple of (string,
316 a list of values). The last option is what you
317 should use when accepting user input as it'll
318 protect you against sql injection attacks (if
319 all user data is placed in the array rather than
320 the raw SQL).
mbligh12eebfa2008-01-03 02:01:53 +0000321
jadmanski0afbb632008-06-06 21:10:57 +0000322 For example:
323 where = ("a = %s AND b = %s", ['val', 'val'])
324 is better than
325 where = "a = 'val' AND b = 'val'"
Michael Tang5f74ffd2016-10-31 10:34:53 -0700326
327 @param fields: The list of selected fields string.
328 @param table: The name of the database table.
329 @param where: The where clause string.
330 @param distinct: If select distinct values.
331 @param group_by: Group by clause.
332 @param max_rows: unused.
jadmanski0afbb632008-06-06 21:10:57 +0000333 """
334 cmd = ['select']
335 if distinct:
336 cmd.append('distinct')
337 cmd += [fields, 'from', table]
mbligh608c3252007-08-31 13:53:00 +0000338
showardc1a98d12010-01-15 00:22:22 +0000339 where_clause, values = self._where_clause(where)
340 cmd.append(where_clause)
mbligh96cf0512008-04-17 15:25:38 +0000341
jadmanski0afbb632008-06-06 21:10:57 +0000342 if group_by:
343 cmd.append(' GROUP BY ' + group_by)
mbligh83f63a02007-12-12 19:13:04 +0000344
jadmanski0afbb632008-06-06 21:10:57 +0000345 self.dprint('%s %s' % (' '.join(cmd), values))
mbligh96cf0512008-04-17 15:25:38 +0000346
jadmanski0afbb632008-06-06 21:10:57 +0000347 # create a re-runable function for executing the query
348 def exec_sql():
Michael Tang5f74ffd2016-10-31 10:34:53 -0700349 """Exeuctes an the sql command."""
jadmanski0afbb632008-06-06 21:10:57 +0000350 sql = ' '.join(cmd)
351 numRec = self.cur.execute(sql, values)
mblighd876f452008-12-03 15:09:17 +0000352 if max_rows is not None and numRec > max_rows:
jadmanski0afbb632008-06-06 21:10:57 +0000353 msg = 'Exceeded allowed number of records'
354 raise MySQLTooManyRows(msg)
355 return self.cur.fetchall()
mbligh96cf0512008-04-17 15:25:38 +0000356
jadmanski0afbb632008-06-06 21:10:57 +0000357 # run the query, re-trying after operational errors
358 if self.autocommit:
359 return self.run_with_retry(exec_sql)
360 else:
361 return exec_sql()
mblighd5c33db2006-10-08 21:34:16 +0000362
mbligh056d0d32006-10-08 22:31:10 +0000363
jadmanski0afbb632008-06-06 21:10:57 +0000364 def select_sql(self, fields, table, sql, values):
365 """\
366 select fields from table "sql"
Michael Tang5f74ffd2016-10-31 10:34:53 -0700367
368 @param fields: The list of selected fields string.
369 @param table: The name of the database table.
370 @param sql: The sql string.
371 @param values: The sql string parameter values.
jadmanski0afbb632008-06-06 21:10:57 +0000372 """
373 cmd = 'select %s from %s %s' % (fields, table, sql)
374 self.dprint(cmd)
mbligh414c69e2007-10-05 15:13:06 +0000375
jadmanski0afbb632008-06-06 21:10:57 +0000376 # create a -re-runable function for executing the query
Michael Tang5f74ffd2016-10-31 10:34:53 -0700377 def _exec_sql():
jadmanski0afbb632008-06-06 21:10:57 +0000378 self.cur.execute(cmd, values)
379 return self.cur.fetchall()
mbligh96b9a5a2007-11-24 19:32:20 +0000380
jadmanski0afbb632008-06-06 21:10:57 +0000381 # run the query, re-trying after operational errors
382 if self.autocommit:
Michael Tang5f74ffd2016-10-31 10:34:53 -0700383 return self.run_with_retry(_exec_sql)
jadmanski0afbb632008-06-06 21:10:57 +0000384 else:
Michael Tang5f74ffd2016-10-31 10:34:53 -0700385 return _exec_sql()
mbligh96b9a5a2007-11-24 19:32:20 +0000386
mbligh608c3252007-08-31 13:53:00 +0000387
jadmanski0afbb632008-06-06 21:10:57 +0000388 def _exec_sql_with_commit(self, sql, values, commit):
389 if self.autocommit:
390 # re-run the query until it succeeds
Michael Tang5f74ffd2016-10-31 10:34:53 -0700391 def _exec_sql():
jadmanski0afbb632008-06-06 21:10:57 +0000392 self.cur.execute(sql, values)
393 self.con.commit()
Michael Tang5f74ffd2016-10-31 10:34:53 -0700394 self.run_with_retry(_exec_sql)
jadmanski0afbb632008-06-06 21:10:57 +0000395 else:
396 # take one shot at running the query
397 self.cur.execute(sql, values)
398 if commit:
399 self.con.commit()
mbligh96b9a5a2007-11-24 19:32:20 +0000400
mbligh2bd48872007-09-20 18:32:25 +0000401
jadmanskib591fba2008-09-10 16:19:22 +0000402 def insert(self, table, data, commit=None):
jadmanski0afbb632008-06-06 21:10:57 +0000403 """\
404 'insert into table (keys) values (%s ... %s)', values
mbligh96cf0512008-04-17 15:25:38 +0000405
jadmanski0afbb632008-06-06 21:10:57 +0000406 data:
407 dictionary of fields and data
Michael Tang5f74ffd2016-10-31 10:34:53 -0700408
409 @param table: The name of the table.
410 @param data: The insert data.
411 @param commit: If commit the transaction .
jadmanski0afbb632008-06-06 21:10:57 +0000412 """
413 fields = data.keys()
414 refs = ['%s' for field in fields]
415 values = [data[field] for field in fields]
showardc1a98d12010-01-15 00:22:22 +0000416 cmd = ('insert into %s (%s) values (%s)' %
417 (table, ','.join(self._quote(field) for field in fields),
418 ','.join(refs)))
jadmanski0afbb632008-06-06 21:10:57 +0000419 self.dprint('%s %s' % (cmd, values))
mblighe9cf9d42007-08-31 08:56:00 +0000420
jadmanski0afbb632008-06-06 21:10:57 +0000421 self._exec_sql_with_commit(cmd, values, commit)
mblighe9cf9d42007-08-31 08:56:00 +0000422
mbligh048e1c92007-10-07 00:10:33 +0000423
jadmanski0afbb632008-06-06 21:10:57 +0000424 def delete(self, table, where, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700425 """Delete entries.
426
427 @param table: The name of the table.
428 @param where: The where clause.
429 @param commit: If commit the transaction .
430 """
jadmanski0afbb632008-06-06 21:10:57 +0000431 cmd = ['delete from', table]
mblighd876f452008-12-03 15:09:17 +0000432 if commit is None:
jadmanski0afbb632008-06-06 21:10:57 +0000433 commit = self.autocommit
showardc1a98d12010-01-15 00:22:22 +0000434 where_clause, values = self._where_clause(where)
435 cmd.append(where_clause)
jadmanski0afbb632008-06-06 21:10:57 +0000436 sql = ' '.join(cmd)
437 self.dprint('%s %s' % (sql, values))
mbligh048e1c92007-10-07 00:10:33 +0000438
jadmanski0afbb632008-06-06 21:10:57 +0000439 self._exec_sql_with_commit(sql, values, commit)
mbligh048e1c92007-10-07 00:10:33 +0000440
mbligh7a41a862007-11-30 17:44:24 +0000441
jadmanski0afbb632008-06-06 21:10:57 +0000442 def update(self, table, data, where, commit = None):
443 """\
444 'update table set data values (%s ... %s) where ...'
mbligh2aaeb672007-10-01 14:54:18 +0000445
jadmanski0afbb632008-06-06 21:10:57 +0000446 data:
447 dictionary of fields and data
Michael Tang5f74ffd2016-10-31 10:34:53 -0700448
449 @param table: The name of the table.
450 @param data: The sql parameter values.
451 @param where: The where clause.
452 @param commit: If commit the transaction .
jadmanski0afbb632008-06-06 21:10:57 +0000453 """
mblighd876f452008-12-03 15:09:17 +0000454 if commit is None:
jadmanski0afbb632008-06-06 21:10:57 +0000455 commit = self.autocommit
456 cmd = 'update %s ' % table
457 fields = data.keys()
showardc1a98d12010-01-15 00:22:22 +0000458 data_refs = [self._quote(field) + '=%s' for field in fields]
jadmanski0afbb632008-06-06 21:10:57 +0000459 data_values = [data[field] for field in fields]
jadmanski74eebf32008-07-15 20:04:42 +0000460 cmd += ' set ' + ', '.join(data_refs)
mbligh2aaeb672007-10-01 14:54:18 +0000461
showardc1a98d12010-01-15 00:22:22 +0000462 where_clause, where_values = self._where_clause(where)
463 cmd += where_clause
mbligh2aaeb672007-10-01 14:54:18 +0000464
jadmanski0afbb632008-06-06 21:10:57 +0000465 values = data_values + where_values
jadmanski74eebf32008-07-15 20:04:42 +0000466 self.dprint('%s %s' % (cmd, values))
mbligh2aaeb672007-10-01 14:54:18 +0000467
jadmanski0afbb632008-06-06 21:10:57 +0000468 self._exec_sql_with_commit(cmd, values, commit)
mblighe9cf9d42007-08-31 08:56:00 +0000469
470
jadmanski0afbb632008-06-06 21:10:57 +0000471 def delete_job(self, tag, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700472 """Delete a tko job.
473
474 @param tag: The job tag.
475 @param commit: If commit the transaction .
476 """
jadmanski0afbb632008-06-06 21:10:57 +0000477 job_idx = self.find_job(tag)
478 for test_idx in self.find_tests(job_idx):
479 where = {'test_idx' : test_idx}
showardeab66ce2009-12-23 00:03:56 +0000480 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700481 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000482 self.delete('tko_iteration_attributes', where)
483 self.delete('tko_test_attributes', where)
484 self.delete('tko_test_labels_tests', {'test_id': test_idx})
jadmanski0afbb632008-06-06 21:10:57 +0000485 where = {'job_idx' : job_idx}
showardeab66ce2009-12-23 00:03:56 +0000486 self.delete('tko_tests', where)
487 self.delete('tko_jobs', where)
apw7a7316b2008-02-21 17:42:05 +0000488
apw7a7316b2008-02-21 17:42:05 +0000489
Prathmesh Prabhu30dee862018-04-18 20:24:20 -0700490 def insert_job(self, tag, job, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700491 """Insert a tko job.
492
493 @param tag: The job tag.
494 @param job: The job object.
Michael Tang5f74ffd2016-10-31 10:34:53 -0700495 @param commit: If commit the transaction .
496 """
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700497 data = self._get_common_job_data(tag, job)
498 data.update({
Prathmesh Prabhu30dee862018-04-18 20:24:20 -0700499 'afe_job_id': job.afe_job_id,
500 'afe_parent_job_id': job.afe_parent_job_id,
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700501 })
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700502 if job.job_idx is not None:
503 self.update(
504 'tko_jobs', data, {'job_idx': job.job_idx}, commit=commit)
showard0fec8a02009-12-04 01:19:54 +0000505 else:
showardeab66ce2009-12-23 00:03:56 +0000506 self.insert('tko_jobs', data, commit=commit)
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700507 job.job_idx = self.get_last_autonumber_value()
Shuqian Zhao31425d52016-12-07 09:35:03 -0800508
mbligh237bed32007-09-05 13:05:57 +0000509
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700510 def _get_common_job_data(self, tag, job):
511 """Construct a dictionary with the common data to insert in job/task."""
512 return {
513 'tag':tag,
514 'label': job.label,
515 'username': job.user,
516 'machine_idx': job.machine_idx,
517 'queued_time': job.queued_time,
518 'started_time': job.started_time,
519 'finished_time': job.finished_time,
520 'build': job.build,
521 'build_version': job.build_version,
522 'board': job.board,
523 'suite': job.suite,
524 }
525
526
527 def insert_or_update_task_reference(self, job, reference_type, commit=None):
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700528 """Insert an entry in the tko_task_references table.
529
530 The job should already have been inserted in tko_jobs.
531 @param job: tko.models.job object.
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700532 @param reference_type: The type of reference to insert.
533 One of: {'afe', 'skylab'}
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700534 @param commit: Whether to commit this transaction.
535 """
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700536 assert reference_type in {'afe', 'skylab'}
537 if reference_type == 'afe':
538 task_id = job.afe_job_id
539 parent_task_id = job.afe_parent_job_id
540 else:
541 task_id = job.skylab_task_id
542 parent_task_id = job.skylab_parent_task_id
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700543 data = {
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700544 'reference_type': reference_type,
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700545 'tko_job_idx': job.job_idx,
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700546 'task_id': task_id,
547 'parent_task_id': parent_task_id,
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700548 }
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700549
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700550 task_reference_id = self._lookup_task_reference(job)
551 if task_reference_id is not None:
552 self.update('tko_task_references',
553 data,
554 {'id': task_reference_id},
555 commit=commit)
556 job.task_reference_id = task_reference_id
557 else:
558 self.insert('tko_task_references', data, commit=commit)
559 job.task_reference_id = self.get_last_autonumber_value()
560
561
showardc1a98d12010-01-15 00:22:22 +0000562 def update_job_keyvals(self, job, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700563 """Updates the job key values.
564
565 @param job: The job object.
566 @param commit: If commit the transaction .
567 """
showardc1a98d12010-01-15 00:22:22 +0000568 for key, value in job.keyval_dict.iteritems():
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700569 where = {'job_id': job.job_idx, 'key': key}
showardc1a98d12010-01-15 00:22:22 +0000570 data = dict(where, value=value)
571 exists = self.select('id', 'tko_job_keyvals', where=where)
572
573 if exists:
574 self.update('tko_job_keyvals', data, where=where, commit=commit)
575 else:
576 self.insert('tko_job_keyvals', data, commit=commit)
577
578
jadmanski0afbb632008-06-06 21:10:57 +0000579 def insert_test(self, job, test, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700580 """Inserts a job test.
581
582 @param job: The job object.
583 @param test: The test object.
584 @param commit: If commit the transaction .
585 """
jadmanski0afbb632008-06-06 21:10:57 +0000586 kver = self.insert_kernel(test.kernel, commit=commit)
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700587 data = {'job_idx':job.job_idx, 'test':test.testname,
jadmanski0afbb632008-06-06 21:10:57 +0000588 'subdir':test.subdir, 'kernel_idx':kver,
589 'status':self.status_idx[test.status],
590 'reason':test.reason, 'machine_idx':job.machine_idx,
591 'started_time': test.started_time,
592 'finished_time':test.finished_time}
jadmanski9b6babf2009-04-21 17:57:40 +0000593 is_update = hasattr(test, "test_idx")
594 if is_update:
jadmanski74eebf32008-07-15 20:04:42 +0000595 test_idx = test.test_idx
showardeab66ce2009-12-23 00:03:56 +0000596 self.update('tko_tests', data,
597 {'test_idx': test_idx}, commit=commit)
jadmanskib591fba2008-09-10 16:19:22 +0000598 where = {'test_idx': test_idx}
showardeab66ce2009-12-23 00:03:56 +0000599 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700600 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000601 self.delete('tko_iteration_attributes', where)
showard0fec8a02009-12-04 01:19:54 +0000602 where['user_created'] = 0
showardeab66ce2009-12-23 00:03:56 +0000603 self.delete('tko_test_attributes', where)
jadmanski74eebf32008-07-15 20:04:42 +0000604 else:
showardeab66ce2009-12-23 00:03:56 +0000605 self.insert('tko_tests', data, commit=commit)
jadmanski74eebf32008-07-15 20:04:42 +0000606 test_idx = test.test_idx = self.get_last_autonumber_value()
607 data = {'test_idx': test_idx}
mbligh237bed32007-09-05 13:05:57 +0000608
jadmanski0afbb632008-06-06 21:10:57 +0000609 for i in test.iterations:
610 data['iteration'] = i.index
611 for key, value in i.attr_keyval.iteritems():
612 data['attribute'] = key
613 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000614 self.insert('tko_iteration_attributes', data,
jadmanski0afbb632008-06-06 21:10:57 +0000615 commit=commit)
616 for key, value in i.perf_keyval.iteritems():
617 data['attribute'] = key
Allen Li64692a32016-11-04 13:35:19 -0700618 if math.isnan(value) or math.isinf(value):
619 data['value'] = None
620 else:
621 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000622 self.insert('tko_iteration_result', data,
mbligh432bad42007-10-09 19:56:07 +0000623 commit=commit)
mbligh056d0d32006-10-08 22:31:10 +0000624
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700625 data = {'test_idx': test_idx}
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700626
jadmanski0afbb632008-06-06 21:10:57 +0000627 for key, value in test.attributes.iteritems():
628 data = {'test_idx': test_idx, 'attribute': key,
629 'value': value}
Allen Lie3051e92018-07-30 17:40:54 -0700630 try:
631 self.insert('tko_test_attributes', data, commit=commit)
632 except:
633 _log_error('Uploading attribute %r' % (data))
634 raise
mbligh2bd48872007-09-20 18:32:25 +0000635
jadmanski9b6babf2009-04-21 17:57:40 +0000636 if not is_update:
637 for label_index in test.labels:
638 data = {'test_id': test_idx, 'testlabel_id': label_index}
showardeab66ce2009-12-23 00:03:56 +0000639 self.insert('tko_test_labels_tests', data, commit=commit)
jadmanski9b6babf2009-04-21 17:57:40 +0000640
mbligh056d0d32006-10-08 22:31:10 +0000641
jadmanski0afbb632008-06-06 21:10:57 +0000642 def read_machine_map(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700643 """Reads the machine map."""
showard71b94312009-08-20 23:40:02 +0000644 if self.machine_group or not self.machine_map:
645 return
jadmanski0afbb632008-06-06 21:10:57 +0000646 for line in open(self.machine_map, 'r').readlines():
647 (machine, group) = line.split()
648 self.machine_group[machine] = group
mbligh96b9a5a2007-11-24 19:32:20 +0000649
650
showard71b94312009-08-20 23:40:02 +0000651 def machine_info_dict(self, job):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700652 """Reads the machine information of a job.
653
654 @param job: The job object.
655
656 @return: The machine info dictionary.
657 """
jadmanski0afbb632008-06-06 21:10:57 +0000658 hostname = job.machine
showard71b94312009-08-20 23:40:02 +0000659 group = job.machine_group
660 owner = job.machine_owner
jadmanski0afbb632008-06-06 21:10:57 +0000661
662 if not group:
showard71b94312009-08-20 23:40:02 +0000663 self.read_machine_map()
jadmanski0afbb632008-06-06 21:10:57 +0000664 group = self.machine_group.get(hostname, hostname)
showard71b94312009-08-20 23:40:02 +0000665 if group == hostname and owner:
666 group = owner + '/' + hostname
jadmanski0afbb632008-06-06 21:10:57 +0000667
showard71b94312009-08-20 23:40:02 +0000668 return {'hostname': hostname, 'machine_group': group, 'owner': owner}
669
670
Prathmesh Prabhuec96d6e2018-04-18 18:13:50 -0700671 def insert_or_update_machine(self, job, commit=None):
672 """Insert or updates machine information for the given job.
673
674 Also updates the job object with new machine index, if any.
675
676 @param job: tko.models.job object.
677 @param commit: Whether to commit the database transaction.
678 """
679 job.machine_idx = self._lookup_machine(job.machine)
680 if not job.machine_idx:
681 job.machine_idx = self._insert_machine(job, commit=commit)
682 elif job.machine:
683 # Only try to update tko_machines record if machine is set. This
684 # prevents unnecessary db writes for suite jobs.
685 self._update_machine_information(job, commit=commit)
686
687
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700688 def _lookup_task_reference(self, job):
689 """Find the task_reference_id for a given job. Return None if not found.
690
691 @param job: tko.models.job object.
692 """
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700693 if job.job_idx is None:
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700694 return None
695 rows = self.select(
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700696 'id', 'tko_task_references', {'tko_job_idx': job.job_idx})
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700697 if not rows:
698 return None
699 if len(rows) > 1:
700 raise MySQLTooManyRows('Got %d tko_task_references for tko_job %d'
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700701 % (len(rows), job.job_idx))
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700702 return rows[0][0]
703
704
Prathmesh Prabhu432648c2018-04-18 18:10:50 -0700705 def _insert_machine(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700706 """Inserts the job machine.
707
708 @param job: The job object.
709 @param commit: If commit the transaction .
710 """
showard71b94312009-08-20 23:40:02 +0000711 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000712 self.insert('tko_machines', machine_info, commit=commit)
jadmanski0afbb632008-06-06 21:10:57 +0000713 return self.get_last_autonumber_value()
714
715
Prathmesh Prabhu432648c2018-04-18 18:10:50 -0700716 def _update_machine_information(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700717 """Updates the job machine information.
718
719 @param job: The job object.
720 @param commit: If commit the transaction .
721 """
showard71b94312009-08-20 23:40:02 +0000722 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000723 self.update('tko_machines', machine_info,
showard71b94312009-08-20 23:40:02 +0000724 where={'hostname': machine_info['hostname']},
725 commit=commit)
726
727
Prathmesh Prabhu432648c2018-04-18 18:10:50 -0700728 def _lookup_machine(self, hostname):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700729 """Look up the machine information.
730
731 @param hostname: The hostname as string.
732 """
jadmanski0afbb632008-06-06 21:10:57 +0000733 where = { 'hostname' : hostname }
showardeab66ce2009-12-23 00:03:56 +0000734 rows = self.select('machine_idx', 'tko_machines', where)
jadmanski0afbb632008-06-06 21:10:57 +0000735 if rows:
736 return rows[0][0]
737 else:
738 return None
739
740
741 def lookup_kernel(self, kernel):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700742 """Look up the kernel.
743
744 @param kernel: The kernel object.
745 """
showardeab66ce2009-12-23 00:03:56 +0000746 rows = self.select('kernel_idx', 'tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000747 {'kernel_hash':kernel.kernel_hash})
748 if rows:
749 return rows[0][0]
750 else:
751 return None
752
753
754 def insert_kernel(self, kernel, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700755 """Insert a kernel.
756
757 @param kernel: The kernel object.
758 @param commit: If commit the transaction .
759 """
jadmanski0afbb632008-06-06 21:10:57 +0000760 kver = self.lookup_kernel(kernel)
761 if kver:
762 return kver
763
764 # If this kernel has any significant patches, append their hash
765 # as diferentiator.
766 printable = kernel.base
767 patch_count = 0
768 for patch in kernel.patches:
769 match = re.match(r'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$',
770 patch.reference)
771 if not match:
772 patch_count += 1
773
showardeab66ce2009-12-23 00:03:56 +0000774 self.insert('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000775 {'base':kernel.base,
776 'kernel_hash':kernel.kernel_hash,
777 'printable':printable},
778 commit=commit)
779 kver = self.get_last_autonumber_value()
780
781 if patch_count > 0:
782 printable += ' p%d' % (kver)
showardeab66ce2009-12-23 00:03:56 +0000783 self.update('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000784 {'printable':printable},
785 {'kernel_idx':kver})
786
787 for patch in kernel.patches:
788 self.insert_patch(kver, patch, commit=commit)
789 return kver
790
791
792 def insert_patch(self, kver, patch, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700793 """Insert a kernel patch.
794
795 @param kver: The kernel version.
796 @param patch: The kernel patch object.
797 @param commit: If commit the transaction .
798 """
jadmanski0afbb632008-06-06 21:10:57 +0000799 print patch.reference
800 name = os.path.basename(patch.reference)[:80]
showardeab66ce2009-12-23 00:03:56 +0000801 self.insert('tko_patches',
jadmanski0afbb632008-06-06 21:10:57 +0000802 {'kernel_idx': kver,
803 'name':name,
804 'url':patch.reference,
805 'hash':patch.hash},
806 commit=commit)
807
808
jadmanski74eebf32008-07-15 20:04:42 +0000809 def find_test(self, job_idx, testname, subdir):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700810 """Find a test by name.
811
812 @param job_idx: The job index.
813 @param testname: The test name.
814 @param subdir: The test sub directory under the job directory.
815 """
jadmanski74eebf32008-07-15 20:04:42 +0000816 where = {'job_idx': job_idx , 'test': testname, 'subdir': subdir}
showardeab66ce2009-12-23 00:03:56 +0000817 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000818 if rows:
819 return rows[0][0]
820 else:
821 return None
822
823
824 def find_tests(self, job_idx):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700825 """Find all tests by job index.
826
827 @param job_idx: The job index.
828 @return: A list of tests.
829 """
jadmanski0afbb632008-06-06 21:10:57 +0000830 where = { 'job_idx':job_idx }
showardeab66ce2009-12-23 00:03:56 +0000831 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000832 if rows:
833 return [row[0] for row in rows]
834 else:
835 return []
836
837
838 def find_job(self, tag):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700839 """Find a job by tag.
840
841 @param tag: The job tag name.
842 @return: The job object or None.
843 """
showardeab66ce2009-12-23 00:03:56 +0000844 rows = self.select('job_idx', 'tko_jobs', {'tag': tag})
jadmanski0afbb632008-06-06 21:10:57 +0000845 if rows:
846 return rows[0][0]
847 else:
848 return None
mblighaf25f062007-12-03 17:48:35 +0000849
850
mbligh96cf0512008-04-17 15:25:38 +0000851def db(*args, **dargs):
Laurence Goodby0cb61952018-07-04 16:52:06 +0000852 """Creates an instance of the database class with the arguments
853 provided in args and dargs, using the database type specified by
854 the global configuration (defaulting to mysql).
855
856 @param args: The db_type arguments.
857 @param dargs: The db_type named arguments.
858
859 @return: An db object.
860 """
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700861 return db_sql(*args, **dargs)