blob: 1e8af23053899a9a909d296c370506ddd7d8f6b0 [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 Keshet7f4071c2018-07-12 14:15:11 -070028from autotest_lib.client.common_lib.cros import retry
Jakob Juelich934f0dc2014-10-14 18:21:13 -070029from autotest_lib.frontend import database_settings_helper
mblighed4d6dd2008-02-27 16:49:43 +000030
Allen Lie0bb7602016-11-29 13:45:53 -080031def _log_error(msg):
32 """Log an error message.
33
34 @param msg: Message string
35 """
36 print >> sys.stderr, msg
37 sys.stderr.flush() # we want these msgs to show up immediately
38
39
40def _format_operational_error(e):
Laurence Goodby0cb61952018-07-04 16:52:06 +000041 """Format OperationalError.
Allen Lie0bb7602016-11-29 13:45:53 -080042
Laurence Goodby0cb61952018-07-04 16:52:06 +000043 @param e: OperationalError instance.
Allen Lie0bb7602016-11-29 13:45:53 -080044 """
45 return ("%s: An operational error occurred during a database "
46 "operation: %s" % (time.strftime("%X %x"), str(e)))
47
48
mblighaea09602008-04-16 22:59:37 +000049class MySQLTooManyRows(Exception):
Michael Tang5f74ffd2016-10-31 10:34:53 -070050 """Too many records."""
jadmanski0afbb632008-06-06 21:10:57 +000051 pass
mblighaea09602008-04-16 22:59:37 +000052
mblighd5c33db2006-10-08 21:34:16 +000053
Laurence Goodby0cb61952018-07-04 16:52:06 +000054class db_sql(object):
Michael Tang5f74ffd2016-10-31 10:34:53 -070055 """Data access."""
56
jadmanski0afbb632008-06-06 21:10:57 +000057 def __init__(self, debug=False, autocommit=True, host=None,
58 database=None, user=None, password=None):
59 self.debug = debug
60 self.autocommit = autocommit
61 self._load_config(host, database, user, password)
mbligh96cf0512008-04-17 15:25:38 +000062
jadmanski0afbb632008-06-06 21:10:57 +000063 self.con = None
64 self._init_db()
mblighd5c33db2006-10-08 21:34:16 +000065
jadmanski0afbb632008-06-06 21:10:57 +000066 # if not present, insert statuses
67 self.status_idx = {}
68 self.status_word = {}
showardeab66ce2009-12-23 00:03:56 +000069 status_rows = self.select('status_idx, word', 'tko_status', None)
jadmanski0afbb632008-06-06 21:10:57 +000070 for s in status_rows:
71 self.status_idx[s[1]] = s[0]
72 self.status_word[s[0]] = s[1]
mbligh048e1c92007-10-07 00:10:33 +000073
jadmanski0afbb632008-06-06 21:10:57 +000074 machine_map = os.path.join(os.path.dirname(__file__),
75 'machines')
76 if os.path.exists(machine_map):
77 self.machine_map = machine_map
78 else:
79 self.machine_map = None
80 self.machine_group = {}
mbligh048e1c92007-10-07 00:10:33 +000081
mbligh8e1ab172007-09-13 17:29:56 +000082
jadmanski0afbb632008-06-06 21:10:57 +000083 def _load_config(self, host, database, user, password):
Jakob Juelich934f0dc2014-10-14 18:21:13 -070084 """Loads configuration settings required to connect to the database.
85
86 This will try to connect to use the settings prefixed with global_db_.
87 If they do not exist, they un-prefixed settings will be used.
88
89 If parameters are supplied, these will be taken instead of the values
90 in global_config.
91
92 @param host: If set, this host will be used, if not, the host will be
93 retrieved from global_config.
94 @param database: If set, this database will be used, if not, the
95 database will be retrieved from global_config.
96 @param user: If set, this user will be used, if not, the
97 user will be retrieved from global_config.
98 @param password: If set, this password will be used, if not, the
99 password will be retrieved from global_config.
100 """
101 database_settings = database_settings_helper.get_global_db_config()
mbligh65acae52008-04-24 20:21:55 +0000102
jadmanski0afbb632008-06-06 21:10:57 +0000103 # grab the host, database
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700104 self.host = host or database_settings['HOST']
105 self.database = database or database_settings['NAME']
mbligh65acae52008-04-24 20:21:55 +0000106
jadmanski0afbb632008-06-06 21:10:57 +0000107 # grab the user and password
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700108 self.user = user or database_settings['USER']
109 self.password = password or database_settings['PASSWORD']
mbligh65acae52008-04-24 20:21:55 +0000110
Michael Spang7a273472014-10-08 12:08:13 -0400111 # grab the timeout configuration
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700112 self.query_timeout =(
113 database_settings.get('OPTIONS', {}).get('timeout', 3600))
114
115 # Using fallback to non-global in order to work without configuration
116 # overhead on non-shard instances.
117 get_value = global_config.global_config.get_config_value_with_fallback
Jakob Juelich475b82b2014-09-30 11:17:07 -0700118 self.min_delay = get_value("AUTOTEST_WEB", "global_db_min_retry_delay",
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700119 "min_retry_delay", type=int, default=20)
Jakob Juelich475b82b2014-09-30 11:17:07 -0700120 self.max_delay = get_value("AUTOTEST_WEB", "global_db_max_retry_delay",
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700121 "max_retry_delay", type=int, default=60)
mbligh65acae52008-04-24 20:21:55 +0000122
Richard Barnette2468fbd2014-11-07 01:12:46 +0000123 # TODO(beeps): Move this to django settings once we have routers.
124 # On test instances mysql connects through a different port. No point
125 # piping this through our entire infrastructure when it is only really
126 # used for testing; Ideally we would specify this through django
127 # settings and default it to the empty string so django will figure out
128 # the default based on the database backend (eg: mysql, 3306), but until
129 # we have database routers in place any django settings will apply to
130 # both tko and afe.
131 # The intended use of this port is to allow a testing shard vm to
132 # update the master vm's database with test results. Specifying
133 # and empty string will fallback to not even specifying the port
134 # to the backend in tko/db.py. Unfortunately this means retries
135 # won't work on the test cluster till we've migrated to routers.
136 self.port = global_config.global_config.get_config_value(
137 "AUTOTEST_WEB", "global_db_port", type=str, default='')
138
mbligh65acae52008-04-24 20:21:55 +0000139
jadmanski0afbb632008-06-06 21:10:57 +0000140 def _init_db(self):
141 # make sure we clean up any existing connection
142 if self.con:
143 self.con.close()
144 self.con = None
mbligh65acae52008-04-24 20:21:55 +0000145
Prathmesh Prabhuba8adaf2017-07-05 13:07:05 -0700146 # create the db connection and cursor
Laurence Goodby0cb61952018-07-04 16:52:06 +0000147 self.con = self.connect(self.host, self.database,
Prathmesh Prabhuba8adaf2017-07-05 13:07:05 -0700148 self.user, self.password, self.port)
jadmanski0afbb632008-06-06 21:10:57 +0000149 self.cur = self.con.cursor()
mbligh96cf0512008-04-17 15:25:38 +0000150
151
jadmanski0afbb632008-06-06 21:10:57 +0000152 def _random_delay(self):
153 delay = random.randint(self.min_delay, self.max_delay)
154 time.sleep(delay)
mbligh65acae52008-04-24 20:21:55 +0000155
156
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700157 @retry.retry(driver.OperationalError, timeout_min=10,
158 delay_sec=5)
159 def connect(self, host, database, user, password, port):
160 """Open and return a connection to mysql database."""
161 connection_args = {
162 'host': host,
163 'user': user,
164 'db': database,
165 'passwd': password,
166 'connect_timeout': 20,
167 }
168 if port:
169 connection_args['port'] = int(port)
170 return driver.connect(**connection_args)
171
172
jadmanski0afbb632008-06-06 21:10:57 +0000173 def run_with_retry(self, function, *args, **dargs):
174 """Call function(*args, **dargs) until either it passes
175 without an operational error, or a timeout is reached.
176 This will re-connect to the database, so it is NOT safe
177 to use this inside of a database transaction.
jadmanskie7a69092008-05-29 21:03:13 +0000178
jadmanski0afbb632008-06-06 21:10:57 +0000179 It can be safely used with transactions, but the
180 transaction start & end must be completely contained
Michael Tang5f74ffd2016-10-31 10:34:53 -0700181 within the call to 'function'.
182
183 @param function: The function to run with retry.
184 @param args: The arguments
185 @param dargs: The named arguments.
186 """
jadmanski0afbb632008-06-06 21:10:57 +0000187 success = False
188 start_time = time.time()
189 while not success:
190 try:
191 result = function(*args, **dargs)
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700192 except driver.OperationalError, e:
Allen Lie0bb7602016-11-29 13:45:53 -0800193 _log_error("%s; retrying, don't panic yet"
194 % _format_operational_error(e))
jadmanski0afbb632008-06-06 21:10:57 +0000195 stop_time = time.time()
196 elapsed_time = stop_time - start_time
197 if elapsed_time > self.query_timeout:
198 raise
199 else:
200 try:
201 self._random_delay()
202 self._init_db()
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700203 except driver.OperationalError, e:
Allen Lie0bb7602016-11-29 13:45:53 -0800204 _log_error('%s; panic now'
205 % _format_operational_error(e))
jadmanski0afbb632008-06-06 21:10:57 +0000206 else:
207 success = True
208 return result
mbligh96cf0512008-04-17 15:25:38 +0000209
210
jadmanski0afbb632008-06-06 21:10:57 +0000211 def dprint(self, value):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700212 """Print out debug value.
213
214 @param value: The value to print out.
215 """
jadmanski0afbb632008-06-06 21:10:57 +0000216 if self.debug:
217 sys.stdout.write('SQL: ' + str(value) + '\n')
mbligh8e1ab172007-09-13 17:29:56 +0000218
mblighd5c33db2006-10-08 21:34:16 +0000219
Dan Shie8e0c052015-09-01 00:27:27 -0700220 def _commit(self):
221 """Private method for function commit to call for retry.
222 """
223 return self.con.commit()
224
225
jadmanski0afbb632008-06-06 21:10:57 +0000226 def commit(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700227 """Commit the sql transaction."""
Dan Shie8e0c052015-09-01 00:27:27 -0700228 if self.autocommit:
229 return self.run_with_retry(self._commit)
230 else:
231 return self._commit()
mbligh432bad42007-10-09 19:56:07 +0000232
233
Simran Basie129a962012-08-31 13:03:53 -0700234 def rollback(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700235 """Rollback the sql transaction."""
Simran Basie129a962012-08-31 13:03:53 -0700236 self.con.rollback()
237
238
jadmanski0afbb632008-06-06 21:10:57 +0000239 def get_last_autonumber_value(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700240 """Gets the last auto number.
241
242 @return: The last auto number.
243 """
jadmanski0afbb632008-06-06 21:10:57 +0000244 self.cur.execute('SELECT LAST_INSERT_ID()', [])
245 return self.cur.fetchall()[0][0]
mblighe12b8612008-02-12 20:58:14 +0000246
247
showardc1a98d12010-01-15 00:22:22 +0000248 def _quote(self, field):
249 return '`%s`' % field
250
251
252 def _where_clause(self, where):
253 if not where:
254 return '', []
255
256 if isinstance(where, dict):
257 # key/value pairs (which should be equal, or None for null)
258 keys, values = [], []
259 for field, value in where.iteritems():
260 quoted_field = self._quote(field)
261 if value is None:
262 keys.append(quoted_field + ' is null')
263 else:
264 keys.append(quoted_field + '=%s')
265 values.append(value)
266 where_clause = ' and '.join(keys)
267 elif isinstance(where, basestring):
268 # the exact string
269 where_clause = where
270 values = []
271 elif isinstance(where, tuple):
272 # preformatted where clause + values
273 where_clause, values = where
274 assert where_clause
275 else:
276 raise ValueError('Invalid "where" value: %r' % where)
277
278 return ' WHERE ' + where_clause, values
279
280
281
282 def select(self, fields, table, where, distinct=False, group_by=None,
283 max_rows=None):
jadmanski0afbb632008-06-06 21:10:57 +0000284 """\
285 This selects all the fields requested from a
286 specific table with a particular where clause.
287 The where clause can either be a dictionary of
288 field=value pairs, a string, or a tuple of (string,
289 a list of values). The last option is what you
290 should use when accepting user input as it'll
291 protect you against sql injection attacks (if
292 all user data is placed in the array rather than
293 the raw SQL).
mbligh12eebfa2008-01-03 02:01:53 +0000294
jadmanski0afbb632008-06-06 21:10:57 +0000295 For example:
296 where = ("a = %s AND b = %s", ['val', 'val'])
297 is better than
298 where = "a = 'val' AND b = 'val'"
Michael Tang5f74ffd2016-10-31 10:34:53 -0700299
300 @param fields: The list of selected fields string.
301 @param table: The name of the database table.
302 @param where: The where clause string.
303 @param distinct: If select distinct values.
304 @param group_by: Group by clause.
305 @param max_rows: unused.
jadmanski0afbb632008-06-06 21:10:57 +0000306 """
307 cmd = ['select']
308 if distinct:
309 cmd.append('distinct')
310 cmd += [fields, 'from', table]
mbligh608c3252007-08-31 13:53:00 +0000311
showardc1a98d12010-01-15 00:22:22 +0000312 where_clause, values = self._where_clause(where)
313 cmd.append(where_clause)
mbligh96cf0512008-04-17 15:25:38 +0000314
jadmanski0afbb632008-06-06 21:10:57 +0000315 if group_by:
316 cmd.append(' GROUP BY ' + group_by)
mbligh83f63a02007-12-12 19:13:04 +0000317
jadmanski0afbb632008-06-06 21:10:57 +0000318 self.dprint('%s %s' % (' '.join(cmd), values))
mbligh96cf0512008-04-17 15:25:38 +0000319
jadmanski0afbb632008-06-06 21:10:57 +0000320 # create a re-runable function for executing the query
321 def exec_sql():
Michael Tang5f74ffd2016-10-31 10:34:53 -0700322 """Exeuctes an the sql command."""
jadmanski0afbb632008-06-06 21:10:57 +0000323 sql = ' '.join(cmd)
324 numRec = self.cur.execute(sql, values)
mblighd876f452008-12-03 15:09:17 +0000325 if max_rows is not None and numRec > max_rows:
jadmanski0afbb632008-06-06 21:10:57 +0000326 msg = 'Exceeded allowed number of records'
327 raise MySQLTooManyRows(msg)
328 return self.cur.fetchall()
mbligh96cf0512008-04-17 15:25:38 +0000329
jadmanski0afbb632008-06-06 21:10:57 +0000330 # run the query, re-trying after operational errors
331 if self.autocommit:
332 return self.run_with_retry(exec_sql)
333 else:
334 return exec_sql()
mblighd5c33db2006-10-08 21:34:16 +0000335
mbligh056d0d32006-10-08 22:31:10 +0000336
jadmanski0afbb632008-06-06 21:10:57 +0000337 def select_sql(self, fields, table, sql, values):
338 """\
339 select fields from table "sql"
Michael Tang5f74ffd2016-10-31 10:34:53 -0700340
341 @param fields: The list of selected fields string.
342 @param table: The name of the database table.
343 @param sql: The sql string.
344 @param values: The sql string parameter values.
jadmanski0afbb632008-06-06 21:10:57 +0000345 """
346 cmd = 'select %s from %s %s' % (fields, table, sql)
347 self.dprint(cmd)
mbligh414c69e2007-10-05 15:13:06 +0000348
jadmanski0afbb632008-06-06 21:10:57 +0000349 # create a -re-runable function for executing the query
Michael Tang5f74ffd2016-10-31 10:34:53 -0700350 def _exec_sql():
jadmanski0afbb632008-06-06 21:10:57 +0000351 self.cur.execute(cmd, values)
352 return self.cur.fetchall()
mbligh96b9a5a2007-11-24 19:32:20 +0000353
jadmanski0afbb632008-06-06 21:10:57 +0000354 # run the query, re-trying after operational errors
355 if self.autocommit:
Michael Tang5f74ffd2016-10-31 10:34:53 -0700356 return self.run_with_retry(_exec_sql)
jadmanski0afbb632008-06-06 21:10:57 +0000357 else:
Michael Tang5f74ffd2016-10-31 10:34:53 -0700358 return _exec_sql()
mbligh96b9a5a2007-11-24 19:32:20 +0000359
mbligh608c3252007-08-31 13:53:00 +0000360
jadmanski0afbb632008-06-06 21:10:57 +0000361 def _exec_sql_with_commit(self, sql, values, commit):
362 if self.autocommit:
363 # re-run the query until it succeeds
Michael Tang5f74ffd2016-10-31 10:34:53 -0700364 def _exec_sql():
jadmanski0afbb632008-06-06 21:10:57 +0000365 self.cur.execute(sql, values)
366 self.con.commit()
Michael Tang5f74ffd2016-10-31 10:34:53 -0700367 self.run_with_retry(_exec_sql)
jadmanski0afbb632008-06-06 21:10:57 +0000368 else:
369 # take one shot at running the query
370 self.cur.execute(sql, values)
371 if commit:
372 self.con.commit()
mbligh96b9a5a2007-11-24 19:32:20 +0000373
mbligh2bd48872007-09-20 18:32:25 +0000374
jadmanskib591fba2008-09-10 16:19:22 +0000375 def insert(self, table, data, commit=None):
jadmanski0afbb632008-06-06 21:10:57 +0000376 """\
377 'insert into table (keys) values (%s ... %s)', values
mbligh96cf0512008-04-17 15:25:38 +0000378
jadmanski0afbb632008-06-06 21:10:57 +0000379 data:
380 dictionary of fields and data
Michael Tang5f74ffd2016-10-31 10:34:53 -0700381
382 @param table: The name of the table.
383 @param data: The insert data.
384 @param commit: If commit the transaction .
jadmanski0afbb632008-06-06 21:10:57 +0000385 """
386 fields = data.keys()
387 refs = ['%s' for field in fields]
388 values = [data[field] for field in fields]
showardc1a98d12010-01-15 00:22:22 +0000389 cmd = ('insert into %s (%s) values (%s)' %
390 (table, ','.join(self._quote(field) for field in fields),
391 ','.join(refs)))
jadmanski0afbb632008-06-06 21:10:57 +0000392 self.dprint('%s %s' % (cmd, values))
mblighe9cf9d42007-08-31 08:56:00 +0000393
jadmanski0afbb632008-06-06 21:10:57 +0000394 self._exec_sql_with_commit(cmd, values, commit)
mblighe9cf9d42007-08-31 08:56:00 +0000395
mbligh048e1c92007-10-07 00:10:33 +0000396
jadmanski0afbb632008-06-06 21:10:57 +0000397 def delete(self, table, where, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700398 """Delete entries.
399
400 @param table: The name of the table.
401 @param where: The where clause.
402 @param commit: If commit the transaction .
403 """
jadmanski0afbb632008-06-06 21:10:57 +0000404 cmd = ['delete from', table]
mblighd876f452008-12-03 15:09:17 +0000405 if commit is None:
jadmanski0afbb632008-06-06 21:10:57 +0000406 commit = self.autocommit
showardc1a98d12010-01-15 00:22:22 +0000407 where_clause, values = self._where_clause(where)
408 cmd.append(where_clause)
jadmanski0afbb632008-06-06 21:10:57 +0000409 sql = ' '.join(cmd)
410 self.dprint('%s %s' % (sql, values))
mbligh048e1c92007-10-07 00:10:33 +0000411
jadmanski0afbb632008-06-06 21:10:57 +0000412 self._exec_sql_with_commit(sql, values, commit)
mbligh048e1c92007-10-07 00:10:33 +0000413
mbligh7a41a862007-11-30 17:44:24 +0000414
jadmanski0afbb632008-06-06 21:10:57 +0000415 def update(self, table, data, where, commit = None):
416 """\
417 'update table set data values (%s ... %s) where ...'
mbligh2aaeb672007-10-01 14:54:18 +0000418
jadmanski0afbb632008-06-06 21:10:57 +0000419 data:
420 dictionary of fields and data
Michael Tang5f74ffd2016-10-31 10:34:53 -0700421
422 @param table: The name of the table.
423 @param data: The sql parameter values.
424 @param where: The where clause.
425 @param commit: If commit the transaction .
jadmanski0afbb632008-06-06 21:10:57 +0000426 """
mblighd876f452008-12-03 15:09:17 +0000427 if commit is None:
jadmanski0afbb632008-06-06 21:10:57 +0000428 commit = self.autocommit
429 cmd = 'update %s ' % table
430 fields = data.keys()
showardc1a98d12010-01-15 00:22:22 +0000431 data_refs = [self._quote(field) + '=%s' for field in fields]
jadmanski0afbb632008-06-06 21:10:57 +0000432 data_values = [data[field] for field in fields]
jadmanski74eebf32008-07-15 20:04:42 +0000433 cmd += ' set ' + ', '.join(data_refs)
mbligh2aaeb672007-10-01 14:54:18 +0000434
showardc1a98d12010-01-15 00:22:22 +0000435 where_clause, where_values = self._where_clause(where)
436 cmd += where_clause
mbligh2aaeb672007-10-01 14:54:18 +0000437
jadmanski0afbb632008-06-06 21:10:57 +0000438 values = data_values + where_values
jadmanski74eebf32008-07-15 20:04:42 +0000439 self.dprint('%s %s' % (cmd, values))
mbligh2aaeb672007-10-01 14:54:18 +0000440
jadmanski0afbb632008-06-06 21:10:57 +0000441 self._exec_sql_with_commit(cmd, values, commit)
mblighe9cf9d42007-08-31 08:56:00 +0000442
443
jadmanski0afbb632008-06-06 21:10:57 +0000444 def delete_job(self, tag, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700445 """Delete a tko job.
446
447 @param tag: The job tag.
448 @param commit: If commit the transaction .
449 """
jadmanski0afbb632008-06-06 21:10:57 +0000450 job_idx = self.find_job(tag)
451 for test_idx in self.find_tests(job_idx):
452 where = {'test_idx' : test_idx}
showardeab66ce2009-12-23 00:03:56 +0000453 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700454 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000455 self.delete('tko_iteration_attributes', where)
456 self.delete('tko_test_attributes', where)
457 self.delete('tko_test_labels_tests', {'test_id': test_idx})
jadmanski0afbb632008-06-06 21:10:57 +0000458 where = {'job_idx' : job_idx}
showardeab66ce2009-12-23 00:03:56 +0000459 self.delete('tko_tests', where)
460 self.delete('tko_jobs', where)
apw7a7316b2008-02-21 17:42:05 +0000461
apw7a7316b2008-02-21 17:42:05 +0000462
Prathmesh Prabhu30dee862018-04-18 20:24:20 -0700463 def insert_job(self, tag, job, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700464 """Insert a tko job.
465
466 @param tag: The job tag.
467 @param job: The job object.
Michael Tang5f74ffd2016-10-31 10:34:53 -0700468 @param commit: If commit the transaction .
469 """
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700470 data = self._get_common_job_data(tag, job)
471 data.update({
Prathmesh Prabhu30dee862018-04-18 20:24:20 -0700472 'afe_job_id': job.afe_job_id,
473 'afe_parent_job_id': job.afe_parent_job_id,
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700474 })
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700475 if job.job_idx is not None:
476 self.update(
477 'tko_jobs', data, {'job_idx': job.job_idx}, commit=commit)
showard0fec8a02009-12-04 01:19:54 +0000478 else:
showardeab66ce2009-12-23 00:03:56 +0000479 self.insert('tko_jobs', data, commit=commit)
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700480 job.job_idx = self.get_last_autonumber_value()
Shuqian Zhao31425d52016-12-07 09:35:03 -0800481
mbligh237bed32007-09-05 13:05:57 +0000482
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700483 def _get_common_job_data(self, tag, job):
484 """Construct a dictionary with the common data to insert in job/task."""
485 return {
486 'tag':tag,
487 'label': job.label,
488 'username': job.user,
489 'machine_idx': job.machine_idx,
490 'queued_time': job.queued_time,
491 'started_time': job.started_time,
492 'finished_time': job.finished_time,
493 'build': job.build,
494 'build_version': job.build_version,
495 'board': job.board,
496 'suite': job.suite,
497 }
498
499
500 def insert_or_update_task_reference(self, job, reference_type, commit=None):
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700501 """Insert an entry in the tko_task_references table.
502
503 The job should already have been inserted in tko_jobs.
504 @param job: tko.models.job object.
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700505 @param reference_type: The type of reference to insert.
506 One of: {'afe', 'skylab'}
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700507 @param commit: Whether to commit this transaction.
508 """
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700509 assert reference_type in {'afe', 'skylab'}
510 if reference_type == 'afe':
511 task_id = job.afe_job_id
512 parent_task_id = job.afe_parent_job_id
513 else:
514 task_id = job.skylab_task_id
515 parent_task_id = job.skylab_parent_task_id
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700516 data = {
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700517 'reference_type': reference_type,
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700518 'tko_job_idx': job.job_idx,
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700519 'task_id': task_id,
520 'parent_task_id': parent_task_id,
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700521 }
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700522
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700523 task_reference_id = self._lookup_task_reference(job)
524 if task_reference_id is not None:
525 self.update('tko_task_references',
526 data,
527 {'id': task_reference_id},
528 commit=commit)
529 job.task_reference_id = task_reference_id
530 else:
531 self.insert('tko_task_references', data, commit=commit)
532 job.task_reference_id = self.get_last_autonumber_value()
533
534
showardc1a98d12010-01-15 00:22:22 +0000535 def update_job_keyvals(self, job, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700536 """Updates the job key values.
537
538 @param job: The job object.
539 @param commit: If commit the transaction .
540 """
showardc1a98d12010-01-15 00:22:22 +0000541 for key, value in job.keyval_dict.iteritems():
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700542 where = {'job_id': job.job_idx, 'key': key}
showardc1a98d12010-01-15 00:22:22 +0000543 data = dict(where, value=value)
544 exists = self.select('id', 'tko_job_keyvals', where=where)
545
546 if exists:
547 self.update('tko_job_keyvals', data, where=where, commit=commit)
548 else:
549 self.insert('tko_job_keyvals', data, commit=commit)
550
551
jadmanski0afbb632008-06-06 21:10:57 +0000552 def insert_test(self, job, test, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700553 """Inserts a job test.
554
555 @param job: The job object.
556 @param test: The test object.
557 @param commit: If commit the transaction .
558 """
jadmanski0afbb632008-06-06 21:10:57 +0000559 kver = self.insert_kernel(test.kernel, commit=commit)
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700560 data = {'job_idx':job.job_idx, 'test':test.testname,
jadmanski0afbb632008-06-06 21:10:57 +0000561 'subdir':test.subdir, 'kernel_idx':kver,
562 'status':self.status_idx[test.status],
563 'reason':test.reason, 'machine_idx':job.machine_idx,
564 'started_time': test.started_time,
565 'finished_time':test.finished_time}
jadmanski9b6babf2009-04-21 17:57:40 +0000566 is_update = hasattr(test, "test_idx")
567 if is_update:
jadmanski74eebf32008-07-15 20:04:42 +0000568 test_idx = test.test_idx
showardeab66ce2009-12-23 00:03:56 +0000569 self.update('tko_tests', data,
570 {'test_idx': test_idx}, commit=commit)
jadmanskib591fba2008-09-10 16:19:22 +0000571 where = {'test_idx': test_idx}
showardeab66ce2009-12-23 00:03:56 +0000572 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700573 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000574 self.delete('tko_iteration_attributes', where)
showard0fec8a02009-12-04 01:19:54 +0000575 where['user_created'] = 0
showardeab66ce2009-12-23 00:03:56 +0000576 self.delete('tko_test_attributes', where)
jadmanski74eebf32008-07-15 20:04:42 +0000577 else:
showardeab66ce2009-12-23 00:03:56 +0000578 self.insert('tko_tests', data, commit=commit)
jadmanski74eebf32008-07-15 20:04:42 +0000579 test_idx = test.test_idx = self.get_last_autonumber_value()
580 data = {'test_idx': test_idx}
mbligh237bed32007-09-05 13:05:57 +0000581
jadmanski0afbb632008-06-06 21:10:57 +0000582 for i in test.iterations:
583 data['iteration'] = i.index
584 for key, value in i.attr_keyval.iteritems():
585 data['attribute'] = key
586 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000587 self.insert('tko_iteration_attributes', data,
jadmanski0afbb632008-06-06 21:10:57 +0000588 commit=commit)
589 for key, value in i.perf_keyval.iteritems():
590 data['attribute'] = key
Allen Li64692a32016-11-04 13:35:19 -0700591 if math.isnan(value) or math.isinf(value):
592 data['value'] = None
593 else:
594 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000595 self.insert('tko_iteration_result', data,
mbligh432bad42007-10-09 19:56:07 +0000596 commit=commit)
mbligh056d0d32006-10-08 22:31:10 +0000597
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700598 data = {'test_idx': test_idx}
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700599
jadmanski0afbb632008-06-06 21:10:57 +0000600 for key, value in test.attributes.iteritems():
601 data = {'test_idx': test_idx, 'attribute': key,
602 'value': value}
showardeab66ce2009-12-23 00:03:56 +0000603 self.insert('tko_test_attributes', data, commit=commit)
mbligh2bd48872007-09-20 18:32:25 +0000604
jadmanski9b6babf2009-04-21 17:57:40 +0000605 if not is_update:
606 for label_index in test.labels:
607 data = {'test_id': test_idx, 'testlabel_id': label_index}
showardeab66ce2009-12-23 00:03:56 +0000608 self.insert('tko_test_labels_tests', data, commit=commit)
jadmanski9b6babf2009-04-21 17:57:40 +0000609
mbligh056d0d32006-10-08 22:31:10 +0000610
jadmanski0afbb632008-06-06 21:10:57 +0000611 def read_machine_map(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700612 """Reads the machine map."""
showard71b94312009-08-20 23:40:02 +0000613 if self.machine_group or not self.machine_map:
614 return
jadmanski0afbb632008-06-06 21:10:57 +0000615 for line in open(self.machine_map, 'r').readlines():
616 (machine, group) = line.split()
617 self.machine_group[machine] = group
mbligh96b9a5a2007-11-24 19:32:20 +0000618
619
showard71b94312009-08-20 23:40:02 +0000620 def machine_info_dict(self, job):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700621 """Reads the machine information of a job.
622
623 @param job: The job object.
624
625 @return: The machine info dictionary.
626 """
jadmanski0afbb632008-06-06 21:10:57 +0000627 hostname = job.machine
showard71b94312009-08-20 23:40:02 +0000628 group = job.machine_group
629 owner = job.machine_owner
jadmanski0afbb632008-06-06 21:10:57 +0000630
631 if not group:
showard71b94312009-08-20 23:40:02 +0000632 self.read_machine_map()
jadmanski0afbb632008-06-06 21:10:57 +0000633 group = self.machine_group.get(hostname, hostname)
showard71b94312009-08-20 23:40:02 +0000634 if group == hostname and owner:
635 group = owner + '/' + hostname
jadmanski0afbb632008-06-06 21:10:57 +0000636
showard71b94312009-08-20 23:40:02 +0000637 return {'hostname': hostname, 'machine_group': group, 'owner': owner}
638
639
Prathmesh Prabhuec96d6e2018-04-18 18:13:50 -0700640 def insert_or_update_machine(self, job, commit=None):
641 """Insert or updates machine information for the given job.
642
643 Also updates the job object with new machine index, if any.
644
645 @param job: tko.models.job object.
646 @param commit: Whether to commit the database transaction.
647 """
648 job.machine_idx = self._lookup_machine(job.machine)
649 if not job.machine_idx:
650 job.machine_idx = self._insert_machine(job, commit=commit)
651 elif job.machine:
652 # Only try to update tko_machines record if machine is set. This
653 # prevents unnecessary db writes for suite jobs.
654 self._update_machine_information(job, commit=commit)
655
656
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700657 def _lookup_task_reference(self, job):
658 """Find the task_reference_id for a given job. Return None if not found.
659
660 @param job: tko.models.job object.
661 """
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700662 if job.job_idx is None:
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700663 return None
664 rows = self.select(
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700665 'id', 'tko_task_references', {'tko_job_idx': job.job_idx})
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700666 if not rows:
667 return None
668 if len(rows) > 1:
669 raise MySQLTooManyRows('Got %d tko_task_references for tko_job %d'
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700670 % (len(rows), job.job_idx))
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700671 return rows[0][0]
672
673
Prathmesh Prabhu432648c2018-04-18 18:10:50 -0700674 def _insert_machine(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700675 """Inserts the job machine.
676
677 @param job: The job object.
678 @param commit: If commit the transaction .
679 """
showard71b94312009-08-20 23:40:02 +0000680 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000681 self.insert('tko_machines', machine_info, commit=commit)
jadmanski0afbb632008-06-06 21:10:57 +0000682 return self.get_last_autonumber_value()
683
684
Prathmesh Prabhu432648c2018-04-18 18:10:50 -0700685 def _update_machine_information(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700686 """Updates the job machine information.
687
688 @param job: The job object.
689 @param commit: If commit the transaction .
690 """
showard71b94312009-08-20 23:40:02 +0000691 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000692 self.update('tko_machines', machine_info,
showard71b94312009-08-20 23:40:02 +0000693 where={'hostname': machine_info['hostname']},
694 commit=commit)
695
696
Prathmesh Prabhu432648c2018-04-18 18:10:50 -0700697 def _lookup_machine(self, hostname):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700698 """Look up the machine information.
699
700 @param hostname: The hostname as string.
701 """
jadmanski0afbb632008-06-06 21:10:57 +0000702 where = { 'hostname' : hostname }
showardeab66ce2009-12-23 00:03:56 +0000703 rows = self.select('machine_idx', 'tko_machines', where)
jadmanski0afbb632008-06-06 21:10:57 +0000704 if rows:
705 return rows[0][0]
706 else:
707 return None
708
709
710 def lookup_kernel(self, kernel):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700711 """Look up the kernel.
712
713 @param kernel: The kernel object.
714 """
showardeab66ce2009-12-23 00:03:56 +0000715 rows = self.select('kernel_idx', 'tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000716 {'kernel_hash':kernel.kernel_hash})
717 if rows:
718 return rows[0][0]
719 else:
720 return None
721
722
723 def insert_kernel(self, kernel, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700724 """Insert a kernel.
725
726 @param kernel: The kernel object.
727 @param commit: If commit the transaction .
728 """
jadmanski0afbb632008-06-06 21:10:57 +0000729 kver = self.lookup_kernel(kernel)
730 if kver:
731 return kver
732
733 # If this kernel has any significant patches, append their hash
734 # as diferentiator.
735 printable = kernel.base
736 patch_count = 0
737 for patch in kernel.patches:
738 match = re.match(r'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$',
739 patch.reference)
740 if not match:
741 patch_count += 1
742
showardeab66ce2009-12-23 00:03:56 +0000743 self.insert('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000744 {'base':kernel.base,
745 'kernel_hash':kernel.kernel_hash,
746 'printable':printable},
747 commit=commit)
748 kver = self.get_last_autonumber_value()
749
750 if patch_count > 0:
751 printable += ' p%d' % (kver)
showardeab66ce2009-12-23 00:03:56 +0000752 self.update('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000753 {'printable':printable},
754 {'kernel_idx':kver})
755
756 for patch in kernel.patches:
757 self.insert_patch(kver, patch, commit=commit)
758 return kver
759
760
761 def insert_patch(self, kver, patch, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700762 """Insert a kernel patch.
763
764 @param kver: The kernel version.
765 @param patch: The kernel patch object.
766 @param commit: If commit the transaction .
767 """
jadmanski0afbb632008-06-06 21:10:57 +0000768 print patch.reference
769 name = os.path.basename(patch.reference)[:80]
showardeab66ce2009-12-23 00:03:56 +0000770 self.insert('tko_patches',
jadmanski0afbb632008-06-06 21:10:57 +0000771 {'kernel_idx': kver,
772 'name':name,
773 'url':patch.reference,
774 'hash':patch.hash},
775 commit=commit)
776
777
jadmanski74eebf32008-07-15 20:04:42 +0000778 def find_test(self, job_idx, testname, subdir):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700779 """Find a test by name.
780
781 @param job_idx: The job index.
782 @param testname: The test name.
783 @param subdir: The test sub directory under the job directory.
784 """
jadmanski74eebf32008-07-15 20:04:42 +0000785 where = {'job_idx': job_idx , 'test': testname, 'subdir': subdir}
showardeab66ce2009-12-23 00:03:56 +0000786 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000787 if rows:
788 return rows[0][0]
789 else:
790 return None
791
792
793 def find_tests(self, job_idx):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700794 """Find all tests by job index.
795
796 @param job_idx: The job index.
797 @return: A list of tests.
798 """
jadmanski0afbb632008-06-06 21:10:57 +0000799 where = { 'job_idx':job_idx }
showardeab66ce2009-12-23 00:03:56 +0000800 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000801 if rows:
802 return [row[0] for row in rows]
803 else:
804 return []
805
806
807 def find_job(self, tag):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700808 """Find a job by tag.
809
810 @param tag: The job tag name.
811 @return: The job object or None.
812 """
showardeab66ce2009-12-23 00:03:56 +0000813 rows = self.select('job_idx', 'tko_jobs', {'tag': tag})
jadmanski0afbb632008-06-06 21:10:57 +0000814 if rows:
815 return rows[0][0]
816 else:
817 return None
mblighaf25f062007-12-03 17:48:35 +0000818
819
mbligh96cf0512008-04-17 15:25:38 +0000820def db(*args, **dargs):
Laurence Goodby0cb61952018-07-04 16:52:06 +0000821 """Creates an instance of the database class with the arguments
822 provided in args and dargs, using the database type specified by
823 the global configuration (defaulting to mysql).
824
825 @param args: The db_type arguments.
826 @param dargs: The db_type named arguments.
827
828 @return: An db object.
829 """
Aviv Keshet7f4071c2018-07-12 14:15:11 -0700830 return db_sql(*args, **dargs)