blob: c5047f33a757417ce2f32e2f3ee6482fad85ea7f [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,
Allen Lif1e35842018-09-13 22:55:23 +000070 database=None, user=None, password=None, proxy_socket=None):
jadmanski0afbb632008-06-06 21:10:57 +000071 self.debug = debug
72 self.autocommit = autocommit
Allen Lif1e35842018-09-13 22:55:23 +000073 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
Allen Lif1e35842018-09-13 22:55:23 +000095 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
Allen Lif1e35842018-09-13 22:55:23 +0000104 When proxy_socket is set, 'host' will be bypassed.
Congbin Guo097fec62018-08-03 16:51:39 -0700105
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.
Allen Lif1e35842018-09-13 22:55:23 +0000114 @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
Allen Lif1e35842018-09-13 22:55:23 +0000123 # 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']
Allen Lif1e35842018-09-13 22:55:23 +0000126 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,
Allen Lif1e35842018-09-13 22:55:23 +0000165 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)
Allen Lif1e35842018-09-13 22:55:23 +0000177 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)
Allen Lif1e35842018-09-13 22:55:23 +0000187 # Connect using proxy socket if possible.
188 if proxy_socket:
189 try:
190 with metrics.SuccessCounter(
191 'chromeos/autotest/tko/connection_using_socket',
192 fields={'socket': proxy_socket}):
193 return driver.connect(unix_socket=proxy_socket,
194 **connection_args)
195 # pylint: disable=catching-non-exception
196 except driver.OperationalError:
197 # Fallback to connect using user/host/password.
198 pass
Congbin Guo097fec62018-08-03 16:51:39 -0700199
200 return driver.connect(host=host, **connection_args)
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700201
202
jadmanski0afbb632008-06-06 21:10:57 +0000203 def run_with_retry(self, function, *args, **dargs):
204 """Call function(*args, **dargs) until either it passes
205 without an operational error, or a timeout is reached.
206 This will re-connect to the database, so it is NOT safe
207 to use this inside of a database transaction.
jadmanskie7a69092008-05-29 21:03:13 +0000208
jadmanski0afbb632008-06-06 21:10:57 +0000209 It can be safely used with transactions, but the
210 transaction start & end must be completely contained
Michael Tang5f74ffd2016-10-31 10:34:53 -0700211 within the call to 'function'.
212
213 @param function: The function to run with retry.
214 @param args: The arguments
215 @param dargs: The named arguments.
216 """
jadmanski0afbb632008-06-06 21:10:57 +0000217 success = False
218 start_time = time.time()
219 while not success:
220 try:
221 result = function(*args, **dargs)
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700222 except driver.OperationalError, e:
Allen Lie0bb7602016-11-29 13:45:53 -0800223 _log_error("%s; retrying, don't panic yet"
224 % _format_operational_error(e))
jadmanski0afbb632008-06-06 21:10:57 +0000225 stop_time = time.time()
226 elapsed_time = stop_time - start_time
227 if elapsed_time > self.query_timeout:
228 raise
229 else:
230 try:
231 self._random_delay()
232 self._init_db()
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700233 except driver.OperationalError, e:
Allen Lie0bb7602016-11-29 13:45:53 -0800234 _log_error('%s; panic now'
235 % _format_operational_error(e))
jadmanski0afbb632008-06-06 21:10:57 +0000236 else:
237 success = True
238 return result
mbligh96cf0512008-04-17 15:25:38 +0000239
240
jadmanski0afbb632008-06-06 21:10:57 +0000241 def dprint(self, value):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700242 """Print out debug value.
243
244 @param value: The value to print out.
245 """
jadmanski0afbb632008-06-06 21:10:57 +0000246 if self.debug:
247 sys.stdout.write('SQL: ' + str(value) + '\n')
mbligh8e1ab172007-09-13 17:29:56 +0000248
mblighd5c33db2006-10-08 21:34:16 +0000249
Dan Shie8e0c052015-09-01 00:27:27 -0700250 def _commit(self):
251 """Private method for function commit to call for retry.
252 """
253 return self.con.commit()
254
255
jadmanski0afbb632008-06-06 21:10:57 +0000256 def commit(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700257 """Commit the sql transaction."""
Dan Shie8e0c052015-09-01 00:27:27 -0700258 if self.autocommit:
259 return self.run_with_retry(self._commit)
260 else:
261 return self._commit()
mbligh432bad42007-10-09 19:56:07 +0000262
263
Simran Basie129a962012-08-31 13:03:53 -0700264 def rollback(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700265 """Rollback the sql transaction."""
Simran Basie129a962012-08-31 13:03:53 -0700266 self.con.rollback()
267
268
jadmanski0afbb632008-06-06 21:10:57 +0000269 def get_last_autonumber_value(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700270 """Gets the last auto number.
271
272 @return: The last auto number.
273 """
jadmanski0afbb632008-06-06 21:10:57 +0000274 self.cur.execute('SELECT LAST_INSERT_ID()', [])
275 return self.cur.fetchall()[0][0]
mblighe12b8612008-02-12 20:58:14 +0000276
277
showardc1a98d12010-01-15 00:22:22 +0000278 def _quote(self, field):
279 return '`%s`' % field
280
281
282 def _where_clause(self, where):
283 if not where:
284 return '', []
285
286 if isinstance(where, dict):
287 # key/value pairs (which should be equal, or None for null)
288 keys, values = [], []
289 for field, value in where.iteritems():
290 quoted_field = self._quote(field)
291 if value is None:
292 keys.append(quoted_field + ' is null')
293 else:
294 keys.append(quoted_field + '=%s')
295 values.append(value)
296 where_clause = ' and '.join(keys)
297 elif isinstance(where, basestring):
298 # the exact string
299 where_clause = where
300 values = []
301 elif isinstance(where, tuple):
302 # preformatted where clause + values
303 where_clause, values = where
304 assert where_clause
305 else:
306 raise ValueError('Invalid "where" value: %r' % where)
307
308 return ' WHERE ' + where_clause, values
309
310
311
312 def select(self, fields, table, where, distinct=False, group_by=None,
313 max_rows=None):
jadmanski0afbb632008-06-06 21:10:57 +0000314 """\
315 This selects all the fields requested from a
316 specific table with a particular where clause.
317 The where clause can either be a dictionary of
318 field=value pairs, a string, or a tuple of (string,
319 a list of values). The last option is what you
320 should use when accepting user input as it'll
321 protect you against sql injection attacks (if
322 all user data is placed in the array rather than
323 the raw SQL).
mbligh12eebfa2008-01-03 02:01:53 +0000324
jadmanski0afbb632008-06-06 21:10:57 +0000325 For example:
326 where = ("a = %s AND b = %s", ['val', 'val'])
327 is better than
328 where = "a = 'val' AND b = 'val'"
Michael Tang5f74ffd2016-10-31 10:34:53 -0700329
330 @param fields: The list of selected fields string.
331 @param table: The name of the database table.
332 @param where: The where clause string.
333 @param distinct: If select distinct values.
334 @param group_by: Group by clause.
335 @param max_rows: unused.
jadmanski0afbb632008-06-06 21:10:57 +0000336 """
337 cmd = ['select']
338 if distinct:
339 cmd.append('distinct')
340 cmd += [fields, 'from', table]
mbligh608c3252007-08-31 13:53:00 +0000341
showardc1a98d12010-01-15 00:22:22 +0000342 where_clause, values = self._where_clause(where)
343 cmd.append(where_clause)
mbligh96cf0512008-04-17 15:25:38 +0000344
jadmanski0afbb632008-06-06 21:10:57 +0000345 if group_by:
346 cmd.append(' GROUP BY ' + group_by)
mbligh83f63a02007-12-12 19:13:04 +0000347
jadmanski0afbb632008-06-06 21:10:57 +0000348 self.dprint('%s %s' % (' '.join(cmd), values))
mbligh96cf0512008-04-17 15:25:38 +0000349
jadmanski0afbb632008-06-06 21:10:57 +0000350 # create a re-runable function for executing the query
351 def exec_sql():
Michael Tang5f74ffd2016-10-31 10:34:53 -0700352 """Exeuctes an the sql command."""
jadmanski0afbb632008-06-06 21:10:57 +0000353 sql = ' '.join(cmd)
354 numRec = self.cur.execute(sql, values)
mblighd876f452008-12-03 15:09:17 +0000355 if max_rows is not None and numRec > max_rows:
jadmanski0afbb632008-06-06 21:10:57 +0000356 msg = 'Exceeded allowed number of records'
357 raise MySQLTooManyRows(msg)
358 return self.cur.fetchall()
mbligh96cf0512008-04-17 15:25:38 +0000359
jadmanski0afbb632008-06-06 21:10:57 +0000360 # run the query, re-trying after operational errors
361 if self.autocommit:
362 return self.run_with_retry(exec_sql)
363 else:
364 return exec_sql()
mblighd5c33db2006-10-08 21:34:16 +0000365
mbligh056d0d32006-10-08 22:31:10 +0000366
jadmanski0afbb632008-06-06 21:10:57 +0000367 def select_sql(self, fields, table, sql, values):
368 """\
369 select fields from table "sql"
Michael Tang5f74ffd2016-10-31 10:34:53 -0700370
371 @param fields: The list of selected fields string.
372 @param table: The name of the database table.
373 @param sql: The sql string.
374 @param values: The sql string parameter values.
jadmanski0afbb632008-06-06 21:10:57 +0000375 """
376 cmd = 'select %s from %s %s' % (fields, table, sql)
377 self.dprint(cmd)
mbligh414c69e2007-10-05 15:13:06 +0000378
jadmanski0afbb632008-06-06 21:10:57 +0000379 # create a -re-runable function for executing the query
Michael Tang5f74ffd2016-10-31 10:34:53 -0700380 def _exec_sql():
jadmanski0afbb632008-06-06 21:10:57 +0000381 self.cur.execute(cmd, values)
382 return self.cur.fetchall()
mbligh96b9a5a2007-11-24 19:32:20 +0000383
jadmanski0afbb632008-06-06 21:10:57 +0000384 # run the query, re-trying after operational errors
385 if self.autocommit:
Michael Tang5f74ffd2016-10-31 10:34:53 -0700386 return self.run_with_retry(_exec_sql)
jadmanski0afbb632008-06-06 21:10:57 +0000387 else:
Michael Tang5f74ffd2016-10-31 10:34:53 -0700388 return _exec_sql()
mbligh96b9a5a2007-11-24 19:32:20 +0000389
mbligh608c3252007-08-31 13:53:00 +0000390
jadmanski0afbb632008-06-06 21:10:57 +0000391 def _exec_sql_with_commit(self, sql, values, commit):
392 if self.autocommit:
393 # re-run the query until it succeeds
Michael Tang5f74ffd2016-10-31 10:34:53 -0700394 def _exec_sql():
jadmanski0afbb632008-06-06 21:10:57 +0000395 self.cur.execute(sql, values)
396 self.con.commit()
Michael Tang5f74ffd2016-10-31 10:34:53 -0700397 self.run_with_retry(_exec_sql)
jadmanski0afbb632008-06-06 21:10:57 +0000398 else:
399 # take one shot at running the query
400 self.cur.execute(sql, values)
401 if commit:
402 self.con.commit()
mbligh96b9a5a2007-11-24 19:32:20 +0000403
mbligh2bd48872007-09-20 18:32:25 +0000404
jadmanskib591fba2008-09-10 16:19:22 +0000405 def insert(self, table, data, commit=None):
jadmanski0afbb632008-06-06 21:10:57 +0000406 """\
407 'insert into table (keys) values (%s ... %s)', values
mbligh96cf0512008-04-17 15:25:38 +0000408
jadmanski0afbb632008-06-06 21:10:57 +0000409 data:
410 dictionary of fields and data
Michael Tang5f74ffd2016-10-31 10:34:53 -0700411
412 @param table: The name of the table.
413 @param data: The insert data.
414 @param commit: If commit the transaction .
jadmanski0afbb632008-06-06 21:10:57 +0000415 """
416 fields = data.keys()
417 refs = ['%s' for field in fields]
418 values = [data[field] for field in fields]
showardc1a98d12010-01-15 00:22:22 +0000419 cmd = ('insert into %s (%s) values (%s)' %
420 (table, ','.join(self._quote(field) for field in fields),
421 ','.join(refs)))
jadmanski0afbb632008-06-06 21:10:57 +0000422 self.dprint('%s %s' % (cmd, values))
mblighe9cf9d42007-08-31 08:56:00 +0000423
jadmanski0afbb632008-06-06 21:10:57 +0000424 self._exec_sql_with_commit(cmd, values, commit)
mblighe9cf9d42007-08-31 08:56:00 +0000425
mbligh048e1c92007-10-07 00:10:33 +0000426
jadmanski0afbb632008-06-06 21:10:57 +0000427 def delete(self, table, where, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700428 """Delete entries.
429
430 @param table: The name of the table.
431 @param where: The where clause.
432 @param commit: If commit the transaction .
433 """
jadmanski0afbb632008-06-06 21:10:57 +0000434 cmd = ['delete from', table]
mblighd876f452008-12-03 15:09:17 +0000435 if commit is None:
jadmanski0afbb632008-06-06 21:10:57 +0000436 commit = self.autocommit
showardc1a98d12010-01-15 00:22:22 +0000437 where_clause, values = self._where_clause(where)
438 cmd.append(where_clause)
jadmanski0afbb632008-06-06 21:10:57 +0000439 sql = ' '.join(cmd)
440 self.dprint('%s %s' % (sql, values))
mbligh048e1c92007-10-07 00:10:33 +0000441
jadmanski0afbb632008-06-06 21:10:57 +0000442 self._exec_sql_with_commit(sql, values, commit)
mbligh048e1c92007-10-07 00:10:33 +0000443
mbligh7a41a862007-11-30 17:44:24 +0000444
jadmanski0afbb632008-06-06 21:10:57 +0000445 def update(self, table, data, where, commit = None):
446 """\
447 'update table set data values (%s ... %s) where ...'
mbligh2aaeb672007-10-01 14:54:18 +0000448
jadmanski0afbb632008-06-06 21:10:57 +0000449 data:
450 dictionary of fields and data
Michael Tang5f74ffd2016-10-31 10:34:53 -0700451
452 @param table: The name of the table.
453 @param data: The sql parameter values.
454 @param where: The where clause.
455 @param commit: If commit the transaction .
jadmanski0afbb632008-06-06 21:10:57 +0000456 """
mblighd876f452008-12-03 15:09:17 +0000457 if commit is None:
jadmanski0afbb632008-06-06 21:10:57 +0000458 commit = self.autocommit
459 cmd = 'update %s ' % table
460 fields = data.keys()
showardc1a98d12010-01-15 00:22:22 +0000461 data_refs = [self._quote(field) + '=%s' for field in fields]
jadmanski0afbb632008-06-06 21:10:57 +0000462 data_values = [data[field] for field in fields]
jadmanski74eebf32008-07-15 20:04:42 +0000463 cmd += ' set ' + ', '.join(data_refs)
mbligh2aaeb672007-10-01 14:54:18 +0000464
showardc1a98d12010-01-15 00:22:22 +0000465 where_clause, where_values = self._where_clause(where)
466 cmd += where_clause
mbligh2aaeb672007-10-01 14:54:18 +0000467
jadmanski0afbb632008-06-06 21:10:57 +0000468 values = data_values + where_values
jadmanski74eebf32008-07-15 20:04:42 +0000469 self.dprint('%s %s' % (cmd, values))
mbligh2aaeb672007-10-01 14:54:18 +0000470
jadmanski0afbb632008-06-06 21:10:57 +0000471 self._exec_sql_with_commit(cmd, values, commit)
mblighe9cf9d42007-08-31 08:56:00 +0000472
473
jadmanski0afbb632008-06-06 21:10:57 +0000474 def delete_job(self, tag, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700475 """Delete a tko job.
476
477 @param tag: The job tag.
478 @param commit: If commit the transaction .
479 """
jadmanski0afbb632008-06-06 21:10:57 +0000480 job_idx = self.find_job(tag)
481 for test_idx in self.find_tests(job_idx):
482 where = {'test_idx' : test_idx}
showardeab66ce2009-12-23 00:03:56 +0000483 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700484 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000485 self.delete('tko_iteration_attributes', where)
486 self.delete('tko_test_attributes', where)
487 self.delete('tko_test_labels_tests', {'test_id': test_idx})
jadmanski0afbb632008-06-06 21:10:57 +0000488 where = {'job_idx' : job_idx}
showardeab66ce2009-12-23 00:03:56 +0000489 self.delete('tko_tests', where)
490 self.delete('tko_jobs', where)
apw7a7316b2008-02-21 17:42:05 +0000491
apw7a7316b2008-02-21 17:42:05 +0000492
Prathmesh Prabhu30dee862018-04-18 20:24:20 -0700493 def insert_job(self, tag, job, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700494 """Insert a tko job.
495
496 @param tag: The job tag.
497 @param job: The job object.
Michael Tang5f74ffd2016-10-31 10:34:53 -0700498 @param commit: If commit the transaction .
499 """
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700500 data = self._get_common_job_data(tag, job)
501 data.update({
Prathmesh Prabhu30dee862018-04-18 20:24:20 -0700502 'afe_job_id': job.afe_job_id,
503 'afe_parent_job_id': job.afe_parent_job_id,
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700504 })
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700505 if job.job_idx is not None:
506 self.update(
507 'tko_jobs', data, {'job_idx': job.job_idx}, commit=commit)
showard0fec8a02009-12-04 01:19:54 +0000508 else:
showardeab66ce2009-12-23 00:03:56 +0000509 self.insert('tko_jobs', data, commit=commit)
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700510 job.job_idx = self.get_last_autonumber_value()
Shuqian Zhao31425d52016-12-07 09:35:03 -0800511
mbligh237bed32007-09-05 13:05:57 +0000512
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700513 def _get_common_job_data(self, tag, job):
514 """Construct a dictionary with the common data to insert in job/task."""
515 return {
516 'tag':tag,
517 'label': job.label,
518 'username': job.user,
519 'machine_idx': job.machine_idx,
520 'queued_time': job.queued_time,
521 'started_time': job.started_time,
522 'finished_time': job.finished_time,
523 'build': job.build,
524 'build_version': job.build_version,
525 'board': job.board,
526 'suite': job.suite,
527 }
528
529
530 def insert_or_update_task_reference(self, job, reference_type, commit=None):
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700531 """Insert an entry in the tko_task_references table.
532
533 The job should already have been inserted in tko_jobs.
534 @param job: tko.models.job object.
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700535 @param reference_type: The type of reference to insert.
536 One of: {'afe', 'skylab'}
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700537 @param commit: Whether to commit this transaction.
538 """
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700539 assert reference_type in {'afe', 'skylab'}
540 if reference_type == 'afe':
541 task_id = job.afe_job_id
542 parent_task_id = job.afe_parent_job_id
543 else:
544 task_id = job.skylab_task_id
545 parent_task_id = job.skylab_parent_task_id
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700546 data = {
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700547 'reference_type': reference_type,
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700548 'tko_job_idx': job.job_idx,
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700549 'task_id': task_id,
550 'parent_task_id': parent_task_id,
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700551 }
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700552
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700553 task_reference_id = self._lookup_task_reference(job)
554 if task_reference_id is not None:
555 self.update('tko_task_references',
556 data,
557 {'id': task_reference_id},
558 commit=commit)
559 job.task_reference_id = task_reference_id
560 else:
561 self.insert('tko_task_references', data, commit=commit)
562 job.task_reference_id = self.get_last_autonumber_value()
563
564
showardc1a98d12010-01-15 00:22:22 +0000565 def update_job_keyvals(self, job, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700566 """Updates the job key values.
567
568 @param job: The job object.
569 @param commit: If commit the transaction .
570 """
showardc1a98d12010-01-15 00:22:22 +0000571 for key, value in job.keyval_dict.iteritems():
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700572 where = {'job_id': job.job_idx, 'key': key}
showardc1a98d12010-01-15 00:22:22 +0000573 data = dict(where, value=value)
574 exists = self.select('id', 'tko_job_keyvals', where=where)
575
576 if exists:
577 self.update('tko_job_keyvals', data, where=where, commit=commit)
578 else:
579 self.insert('tko_job_keyvals', data, commit=commit)
580
581
jadmanski0afbb632008-06-06 21:10:57 +0000582 def insert_test(self, job, test, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700583 """Inserts a job test.
584
585 @param job: The job object.
586 @param test: The test object.
587 @param commit: If commit the transaction .
588 """
jadmanski0afbb632008-06-06 21:10:57 +0000589 kver = self.insert_kernel(test.kernel, commit=commit)
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700590 data = {'job_idx':job.job_idx, 'test':test.testname,
jadmanski0afbb632008-06-06 21:10:57 +0000591 'subdir':test.subdir, 'kernel_idx':kver,
592 'status':self.status_idx[test.status],
593 'reason':test.reason, 'machine_idx':job.machine_idx,
594 'started_time': test.started_time,
595 'finished_time':test.finished_time}
jadmanski9b6babf2009-04-21 17:57:40 +0000596 is_update = hasattr(test, "test_idx")
597 if is_update:
jadmanski74eebf32008-07-15 20:04:42 +0000598 test_idx = test.test_idx
showardeab66ce2009-12-23 00:03:56 +0000599 self.update('tko_tests', data,
600 {'test_idx': test_idx}, commit=commit)
jadmanskib591fba2008-09-10 16:19:22 +0000601 where = {'test_idx': test_idx}
showardeab66ce2009-12-23 00:03:56 +0000602 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700603 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000604 self.delete('tko_iteration_attributes', where)
showard0fec8a02009-12-04 01:19:54 +0000605 where['user_created'] = 0
showardeab66ce2009-12-23 00:03:56 +0000606 self.delete('tko_test_attributes', where)
jadmanski74eebf32008-07-15 20:04:42 +0000607 else:
showardeab66ce2009-12-23 00:03:56 +0000608 self.insert('tko_tests', data, commit=commit)
jadmanski74eebf32008-07-15 20:04:42 +0000609 test_idx = test.test_idx = self.get_last_autonumber_value()
610 data = {'test_idx': test_idx}
mbligh237bed32007-09-05 13:05:57 +0000611
jadmanski0afbb632008-06-06 21:10:57 +0000612 for i in test.iterations:
613 data['iteration'] = i.index
614 for key, value in i.attr_keyval.iteritems():
615 data['attribute'] = key
616 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000617 self.insert('tko_iteration_attributes', data,
jadmanski0afbb632008-06-06 21:10:57 +0000618 commit=commit)
619 for key, value in i.perf_keyval.iteritems():
620 data['attribute'] = key
Allen Li64692a32016-11-04 13:35:19 -0700621 if math.isnan(value) or math.isinf(value):
622 data['value'] = None
623 else:
624 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000625 self.insert('tko_iteration_result', data,
mbligh432bad42007-10-09 19:56:07 +0000626 commit=commit)
mbligh056d0d32006-10-08 22:31:10 +0000627
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700628 data = {'test_idx': test_idx}
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700629
jadmanski0afbb632008-06-06 21:10:57 +0000630 for key, value in test.attributes.iteritems():
631 data = {'test_idx': test_idx, 'attribute': key,
632 'value': value}
Allen Lie3051e92018-07-30 17:40:54 -0700633 try:
634 self.insert('tko_test_attributes', data, commit=commit)
635 except:
636 _log_error('Uploading attribute %r' % (data))
637 raise
mbligh2bd48872007-09-20 18:32:25 +0000638
jadmanski9b6babf2009-04-21 17:57:40 +0000639 if not is_update:
640 for label_index in test.labels:
641 data = {'test_id': test_idx, 'testlabel_id': label_index}
showardeab66ce2009-12-23 00:03:56 +0000642 self.insert('tko_test_labels_tests', data, commit=commit)
jadmanski9b6babf2009-04-21 17:57:40 +0000643
mbligh056d0d32006-10-08 22:31:10 +0000644
jadmanski0afbb632008-06-06 21:10:57 +0000645 def read_machine_map(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700646 """Reads the machine map."""
showard71b94312009-08-20 23:40:02 +0000647 if self.machine_group or not self.machine_map:
648 return
jadmanski0afbb632008-06-06 21:10:57 +0000649 for line in open(self.machine_map, 'r').readlines():
650 (machine, group) = line.split()
651 self.machine_group[machine] = group
mbligh96b9a5a2007-11-24 19:32:20 +0000652
653
showard71b94312009-08-20 23:40:02 +0000654 def machine_info_dict(self, job):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700655 """Reads the machine information of a job.
656
657 @param job: The job object.
658
659 @return: The machine info dictionary.
660 """
jadmanski0afbb632008-06-06 21:10:57 +0000661 hostname = job.machine
showard71b94312009-08-20 23:40:02 +0000662 group = job.machine_group
663 owner = job.machine_owner
jadmanski0afbb632008-06-06 21:10:57 +0000664
665 if not group:
showard71b94312009-08-20 23:40:02 +0000666 self.read_machine_map()
jadmanski0afbb632008-06-06 21:10:57 +0000667 group = self.machine_group.get(hostname, hostname)
showard71b94312009-08-20 23:40:02 +0000668 if group == hostname and owner:
669 group = owner + '/' + hostname
jadmanski0afbb632008-06-06 21:10:57 +0000670
showard71b94312009-08-20 23:40:02 +0000671 return {'hostname': hostname, 'machine_group': group, 'owner': owner}
672
673
Prathmesh Prabhuec96d6e2018-04-18 18:13:50 -0700674 def insert_or_update_machine(self, job, commit=None):
675 """Insert or updates machine information for the given job.
676
677 Also updates the job object with new machine index, if any.
678
679 @param job: tko.models.job object.
680 @param commit: Whether to commit the database transaction.
681 """
682 job.machine_idx = self._lookup_machine(job.machine)
683 if not job.machine_idx:
684 job.machine_idx = self._insert_machine(job, commit=commit)
685 elif job.machine:
686 # Only try to update tko_machines record if machine is set. This
687 # prevents unnecessary db writes for suite jobs.
688 self._update_machine_information(job, commit=commit)
689
690
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700691 def _lookup_task_reference(self, job):
692 """Find the task_reference_id for a given job. Return None if not found.
693
694 @param job: tko.models.job object.
695 """
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700696 if job.job_idx is None:
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700697 return None
698 rows = self.select(
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700699 'id', 'tko_task_references', {'tko_job_idx': job.job_idx})
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700700 if not rows:
701 return None
702 if len(rows) > 1:
703 raise MySQLTooManyRows('Got %d tko_task_references for tko_job %d'
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700704 % (len(rows), job.job_idx))
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700705 return rows[0][0]
706
707
Prathmesh Prabhu432648c2018-04-18 18:10:50 -0700708 def _insert_machine(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700709 """Inserts the job machine.
710
711 @param job: The job object.
712 @param commit: If commit the transaction .
713 """
showard71b94312009-08-20 23:40:02 +0000714 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000715 self.insert('tko_machines', machine_info, commit=commit)
jadmanski0afbb632008-06-06 21:10:57 +0000716 return self.get_last_autonumber_value()
717
718
Prathmesh Prabhu432648c2018-04-18 18:10:50 -0700719 def _update_machine_information(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700720 """Updates the job machine information.
721
722 @param job: The job object.
723 @param commit: If commit the transaction .
724 """
showard71b94312009-08-20 23:40:02 +0000725 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000726 self.update('tko_machines', machine_info,
showard71b94312009-08-20 23:40:02 +0000727 where={'hostname': machine_info['hostname']},
728 commit=commit)
729
730
Prathmesh Prabhu432648c2018-04-18 18:10:50 -0700731 def _lookup_machine(self, hostname):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700732 """Look up the machine information.
733
734 @param hostname: The hostname as string.
735 """
jadmanski0afbb632008-06-06 21:10:57 +0000736 where = { 'hostname' : hostname }
showardeab66ce2009-12-23 00:03:56 +0000737 rows = self.select('machine_idx', 'tko_machines', where)
jadmanski0afbb632008-06-06 21:10:57 +0000738 if rows:
739 return rows[0][0]
740 else:
741 return None
742
743
744 def lookup_kernel(self, kernel):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700745 """Look up the kernel.
746
747 @param kernel: The kernel object.
748 """
showardeab66ce2009-12-23 00:03:56 +0000749 rows = self.select('kernel_idx', 'tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000750 {'kernel_hash':kernel.kernel_hash})
751 if rows:
752 return rows[0][0]
753 else:
754 return None
755
756
757 def insert_kernel(self, kernel, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700758 """Insert a kernel.
759
760 @param kernel: The kernel object.
761 @param commit: If commit the transaction .
762 """
jadmanski0afbb632008-06-06 21:10:57 +0000763 kver = self.lookup_kernel(kernel)
764 if kver:
765 return kver
766
767 # If this kernel has any significant patches, append their hash
768 # as diferentiator.
769 printable = kernel.base
770 patch_count = 0
771 for patch in kernel.patches:
772 match = re.match(r'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$',
773 patch.reference)
774 if not match:
775 patch_count += 1
776
showardeab66ce2009-12-23 00:03:56 +0000777 self.insert('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000778 {'base':kernel.base,
779 'kernel_hash':kernel.kernel_hash,
780 'printable':printable},
781 commit=commit)
782 kver = self.get_last_autonumber_value()
783
784 if patch_count > 0:
785 printable += ' p%d' % (kver)
showardeab66ce2009-12-23 00:03:56 +0000786 self.update('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000787 {'printable':printable},
788 {'kernel_idx':kver})
789
790 for patch in kernel.patches:
791 self.insert_patch(kver, patch, commit=commit)
792 return kver
793
794
795 def insert_patch(self, kver, patch, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700796 """Insert a kernel patch.
797
798 @param kver: The kernel version.
799 @param patch: The kernel patch object.
800 @param commit: If commit the transaction .
801 """
jadmanski0afbb632008-06-06 21:10:57 +0000802 print patch.reference
803 name = os.path.basename(patch.reference)[:80]
showardeab66ce2009-12-23 00:03:56 +0000804 self.insert('tko_patches',
jadmanski0afbb632008-06-06 21:10:57 +0000805 {'kernel_idx': kver,
806 'name':name,
807 'url':patch.reference,
808 'hash':patch.hash},
809 commit=commit)
810
811
jadmanski74eebf32008-07-15 20:04:42 +0000812 def find_test(self, job_idx, testname, subdir):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700813 """Find a test by name.
814
815 @param job_idx: The job index.
816 @param testname: The test name.
817 @param subdir: The test sub directory under the job directory.
818 """
jadmanski74eebf32008-07-15 20:04:42 +0000819 where = {'job_idx': job_idx , 'test': testname, 'subdir': subdir}
showardeab66ce2009-12-23 00:03:56 +0000820 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000821 if rows:
822 return rows[0][0]
823 else:
824 return None
825
826
827 def find_tests(self, job_idx):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700828 """Find all tests by job index.
829
830 @param job_idx: The job index.
831 @return: A list of tests.
832 """
jadmanski0afbb632008-06-06 21:10:57 +0000833 where = { 'job_idx':job_idx }
showardeab66ce2009-12-23 00:03:56 +0000834 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000835 if rows:
836 return [row[0] for row in rows]
837 else:
838 return []
839
840
841 def find_job(self, tag):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700842 """Find a job by tag.
843
844 @param tag: The job tag name.
845 @return: The job object or None.
846 """
showardeab66ce2009-12-23 00:03:56 +0000847 rows = self.select('job_idx', 'tko_jobs', {'tag': tag})
jadmanski0afbb632008-06-06 21:10:57 +0000848 if rows:
849 return rows[0][0]
850 else:
851 return None
mblighaf25f062007-12-03 17:48:35 +0000852
853
Xixuan Wu8e327e22018-08-10 12:35:28 -0700854 def get_child_tests_by_parent_task_id(self, parent_task_id):
855 """Get the child tests by a parent task id.
856
857 @param parent_task_id: A string parent task id in tko_task_references.
858 @return: A list of view dicts, which has key 'test_name' and 'status'.
859 """
860 rows = self.select('tko_job_idx', 'tko_task_references',
861 {'parent_task_id': parent_task_id})
862 tko_job_ids = [str(r[0]) for r in rows]
863 fields = ['test_name', 'status']
864 where = 'job_idx in (%s)' % ', '.join(tko_job_ids)
865 rows = self.select(', '.join(fields), 'tko_test_view_2', where)
866 return [{'test_name': r[0], 'status': r[1]} for r in rows]
867
868
mbligh96cf0512008-04-17 15:25:38 +0000869def db(*args, **dargs):
Laurence Goodby0cb61952018-07-04 16:52:06 +0000870 """Creates an instance of the database class with the arguments
871 provided in args and dargs, using the database type specified by
872 the global configuration (defaulting to mysql).
873
874 @param args: The db_type arguments.
875 @param dargs: The db_type named arguments.
876
877 @return: An db object.
878 """
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700879 return db_sql(*args, **dargs)