blob: 54850d3ce4e8fc5dc10bd7f659ff27ec1f3ba976 [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
Allen Li64692a32016-11-04 13:35:19 -07005import math
6import os
7import random
8import re
9import sys
10import time
mbligh96cf0512008-04-17 15:25:38 +000011
12import common
Prathmesh Prabhu027561f2018-07-02 10:57:20 -070013import MySQLdb
mbligh96cf0512008-04-17 15:25:38 +000014from autotest_lib.client.common_lib import global_config
Prathmesh Prabhu027561f2018-07-02 10:57:20 -070015from autotest_lib.client.common_lib.cros import retry
Jakob Juelich934f0dc2014-10-14 18:21:13 -070016from autotest_lib.frontend import database_settings_helper
mbligh96cf0512008-04-17 15:25:38 +000017
mblighed4d6dd2008-02-27 16:49:43 +000018
Allen Lie0bb7602016-11-29 13:45:53 -080019def _log_error(msg):
20 """Log an error message.
21
22 @param msg: Message string
23 """
24 print >> sys.stderr, msg
25 sys.stderr.flush() # we want these msgs to show up immediately
26
27
28def _format_operational_error(e):
Prathmesh Prabhu027561f2018-07-02 10:57:20 -070029 """Format MySQLdb.OperationalError.
Allen Lie0bb7602016-11-29 13:45:53 -080030
Prathmesh Prabhu027561f2018-07-02 10:57:20 -070031 @param e: MySQLdb.OperationalError instance.
Allen Lie0bb7602016-11-29 13:45:53 -080032 """
33 return ("%s: An operational error occurred during a database "
34 "operation: %s" % (time.strftime("%X %x"), str(e)))
35
36
mblighaea09602008-04-16 22:59:37 +000037class MySQLTooManyRows(Exception):
Michael Tang5f74ffd2016-10-31 10:34:53 -070038 """Too many records."""
jadmanski0afbb632008-06-06 21:10:57 +000039 pass
mblighaea09602008-04-16 22:59:37 +000040
mblighd5c33db2006-10-08 21:34:16 +000041
Prathmesh Prabhu027561f2018-07-02 10:57:20 -070042class _DB(object):
Michael Tang5f74ffd2016-10-31 10:34:53 -070043 """Data access."""
44
jadmanski0afbb632008-06-06 21:10:57 +000045 def __init__(self, debug=False, autocommit=True, host=None,
46 database=None, user=None, password=None):
47 self.debug = debug
48 self.autocommit = autocommit
49 self._load_config(host, database, user, password)
mbligh96cf0512008-04-17 15:25:38 +000050
jadmanski0afbb632008-06-06 21:10:57 +000051 self.con = None
52 self._init_db()
mblighd5c33db2006-10-08 21:34:16 +000053
jadmanski0afbb632008-06-06 21:10:57 +000054 # if not present, insert statuses
55 self.status_idx = {}
56 self.status_word = {}
showardeab66ce2009-12-23 00:03:56 +000057 status_rows = self.select('status_idx, word', 'tko_status', None)
jadmanski0afbb632008-06-06 21:10:57 +000058 for s in status_rows:
59 self.status_idx[s[1]] = s[0]
60 self.status_word[s[0]] = s[1]
mbligh048e1c92007-10-07 00:10:33 +000061
jadmanski0afbb632008-06-06 21:10:57 +000062 machine_map = os.path.join(os.path.dirname(__file__),
63 'machines')
64 if os.path.exists(machine_map):
65 self.machine_map = machine_map
66 else:
67 self.machine_map = None
68 self.machine_group = {}
mbligh048e1c92007-10-07 00:10:33 +000069
mbligh8e1ab172007-09-13 17:29:56 +000070
jadmanski0afbb632008-06-06 21:10:57 +000071 def _load_config(self, host, database, user, password):
Jakob Juelich934f0dc2014-10-14 18:21:13 -070072 """Loads configuration settings required to connect to the database.
73
74 This will try to connect to use the settings prefixed with global_db_.
75 If they do not exist, they un-prefixed settings will be used.
76
77 If parameters are supplied, these will be taken instead of the values
78 in global_config.
79
80 @param host: If set, this host will be used, if not, the host will be
81 retrieved from global_config.
82 @param database: If set, this database will be used, if not, the
83 database will be retrieved from global_config.
84 @param user: If set, this user will be used, if not, the
85 user will be retrieved from global_config.
86 @param password: If set, this password will be used, if not, the
87 password will be retrieved from global_config.
88 """
89 database_settings = database_settings_helper.get_global_db_config()
mbligh65acae52008-04-24 20:21:55 +000090
jadmanski0afbb632008-06-06 21:10:57 +000091 # grab the host, database
Jakob Juelich934f0dc2014-10-14 18:21:13 -070092 self.host = host or database_settings['HOST']
93 self.database = database or database_settings['NAME']
mbligh65acae52008-04-24 20:21:55 +000094
jadmanski0afbb632008-06-06 21:10:57 +000095 # grab the user and password
Jakob Juelich934f0dc2014-10-14 18:21:13 -070096 self.user = user or database_settings['USER']
97 self.password = password or database_settings['PASSWORD']
mbligh65acae52008-04-24 20:21:55 +000098
Michael Spang7a273472014-10-08 12:08:13 -040099 # grab the timeout configuration
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700100 self.query_timeout =(
101 database_settings.get('OPTIONS', {}).get('timeout', 3600))
102
103 # Using fallback to non-global in order to work without configuration
104 # overhead on non-shard instances.
105 get_value = global_config.global_config.get_config_value_with_fallback
Jakob Juelich475b82b2014-09-30 11:17:07 -0700106 self.min_delay = get_value("AUTOTEST_WEB", "global_db_min_retry_delay",
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700107 "min_retry_delay", type=int, default=20)
Jakob Juelich475b82b2014-09-30 11:17:07 -0700108 self.max_delay = get_value("AUTOTEST_WEB", "global_db_max_retry_delay",
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700109 "max_retry_delay", type=int, default=60)
mbligh65acae52008-04-24 20:21:55 +0000110
Richard Barnette2468fbd2014-11-07 01:12:46 +0000111 # TODO(beeps): Move this to django settings once we have routers.
112 # On test instances mysql connects through a different port. No point
113 # piping this through our entire infrastructure when it is only really
114 # used for testing; Ideally we would specify this through django
115 # settings and default it to the empty string so django will figure out
116 # the default based on the database backend (eg: mysql, 3306), but until
117 # we have database routers in place any django settings will apply to
118 # both tko and afe.
119 # The intended use of this port is to allow a testing shard vm to
120 # update the master vm's database with test results. Specifying
121 # and empty string will fallback to not even specifying the port
122 # to the backend in tko/db.py. Unfortunately this means retries
123 # won't work on the test cluster till we've migrated to routers.
124 self.port = global_config.global_config.get_config_value(
125 "AUTOTEST_WEB", "global_db_port", type=str, default='')
126
mbligh65acae52008-04-24 20:21:55 +0000127
jadmanski0afbb632008-06-06 21:10:57 +0000128 def _init_db(self):
129 # make sure we clean up any existing connection
130 if self.con:
131 self.con.close()
132 self.con = None
mbligh65acae52008-04-24 20:21:55 +0000133
Prathmesh Prabhuba8adaf2017-07-05 13:07:05 -0700134 # create the db connection and cursor
Prathmesh Prabhu027561f2018-07-02 10:57:20 -0700135 self.con = self._connect(self.host, self.database,
Prathmesh Prabhuba8adaf2017-07-05 13:07:05 -0700136 self.user, self.password, self.port)
jadmanski0afbb632008-06-06 21:10:57 +0000137 self.cur = self.con.cursor()
mbligh96cf0512008-04-17 15:25:38 +0000138
139
Prathmesh Prabhu027561f2018-07-02 10:57:20 -0700140 @retry.retry(MySQLdb.OperationalError, timeout_min=10, delay_sec=5)
141 def _connect(self, host, database, user, password, port):
142 """Connect to the mysql database."""
143 connection_args = {
144 'host': host,
145 'user': user,
146 'db': database,
147 'passwd': password,
148 'connect_timeout': 20,
149 }
150 if port:
151 connection_args['port'] = int(port)
152 return MySQLdb.connect(**connection_args)
153
154
jadmanski0afbb632008-06-06 21:10:57 +0000155 def _random_delay(self):
156 delay = random.randint(self.min_delay, self.max_delay)
157 time.sleep(delay)
mbligh65acae52008-04-24 20:21:55 +0000158
159
jadmanski0afbb632008-06-06 21:10:57 +0000160 def run_with_retry(self, function, *args, **dargs):
161 """Call function(*args, **dargs) until either it passes
162 without an operational error, or a timeout is reached.
163 This will re-connect to the database, so it is NOT safe
164 to use this inside of a database transaction.
jadmanskie7a69092008-05-29 21:03:13 +0000165
jadmanski0afbb632008-06-06 21:10:57 +0000166 It can be safely used with transactions, but the
167 transaction start & end must be completely contained
Michael Tang5f74ffd2016-10-31 10:34:53 -0700168 within the call to 'function'.
169
170 @param function: The function to run with retry.
171 @param args: The arguments
172 @param dargs: The named arguments.
173 """
jadmanski0afbb632008-06-06 21:10:57 +0000174 success = False
175 start_time = time.time()
176 while not success:
177 try:
178 result = function(*args, **dargs)
Prathmesh Prabhu027561f2018-07-02 10:57:20 -0700179 except MySQLdb.OperationalError, e:
Allen Lie0bb7602016-11-29 13:45:53 -0800180 _log_error("%s; retrying, don't panic yet"
181 % _format_operational_error(e))
jadmanski0afbb632008-06-06 21:10:57 +0000182 stop_time = time.time()
183 elapsed_time = stop_time - start_time
184 if elapsed_time > self.query_timeout:
185 raise
186 else:
187 try:
188 self._random_delay()
189 self._init_db()
Prathmesh Prabhu027561f2018-07-02 10:57:20 -0700190 except MySQLdb.OperationalError, e:
Allen Lie0bb7602016-11-29 13:45:53 -0800191 _log_error('%s; panic now'
192 % _format_operational_error(e))
jadmanski0afbb632008-06-06 21:10:57 +0000193 else:
194 success = True
195 return result
mbligh96cf0512008-04-17 15:25:38 +0000196
197
jadmanski0afbb632008-06-06 21:10:57 +0000198 def dprint(self, value):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700199 """Print out debug value.
200
201 @param value: The value to print out.
202 """
jadmanski0afbb632008-06-06 21:10:57 +0000203 if self.debug:
204 sys.stdout.write('SQL: ' + str(value) + '\n')
mbligh8e1ab172007-09-13 17:29:56 +0000205
mblighd5c33db2006-10-08 21:34:16 +0000206
Dan Shie8e0c052015-09-01 00:27:27 -0700207 def _commit(self):
208 """Private method for function commit to call for retry.
209 """
210 return self.con.commit()
211
212
jadmanski0afbb632008-06-06 21:10:57 +0000213 def commit(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700214 """Commit the sql transaction."""
Dan Shie8e0c052015-09-01 00:27:27 -0700215 if self.autocommit:
216 return self.run_with_retry(self._commit)
217 else:
218 return self._commit()
mbligh432bad42007-10-09 19:56:07 +0000219
220
Simran Basie129a962012-08-31 13:03:53 -0700221 def rollback(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700222 """Rollback the sql transaction."""
Simran Basie129a962012-08-31 13:03:53 -0700223 self.con.rollback()
224
225
jadmanski0afbb632008-06-06 21:10:57 +0000226 def get_last_autonumber_value(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700227 """Gets the last auto number.
228
229 @return: The last auto number.
230 """
jadmanski0afbb632008-06-06 21:10:57 +0000231 self.cur.execute('SELECT LAST_INSERT_ID()', [])
232 return self.cur.fetchall()[0][0]
mblighe12b8612008-02-12 20:58:14 +0000233
234
showardc1a98d12010-01-15 00:22:22 +0000235 def _quote(self, field):
236 return '`%s`' % field
237
238
239 def _where_clause(self, where):
240 if not where:
241 return '', []
242
243 if isinstance(where, dict):
244 # key/value pairs (which should be equal, or None for null)
245 keys, values = [], []
246 for field, value in where.iteritems():
247 quoted_field = self._quote(field)
248 if value is None:
249 keys.append(quoted_field + ' is null')
250 else:
251 keys.append(quoted_field + '=%s')
252 values.append(value)
253 where_clause = ' and '.join(keys)
254 elif isinstance(where, basestring):
255 # the exact string
256 where_clause = where
257 values = []
258 elif isinstance(where, tuple):
259 # preformatted where clause + values
260 where_clause, values = where
261 assert where_clause
262 else:
263 raise ValueError('Invalid "where" value: %r' % where)
264
265 return ' WHERE ' + where_clause, values
266
267
268
269 def select(self, fields, table, where, distinct=False, group_by=None,
270 max_rows=None):
jadmanski0afbb632008-06-06 21:10:57 +0000271 """\
272 This selects all the fields requested from a
273 specific table with a particular where clause.
274 The where clause can either be a dictionary of
275 field=value pairs, a string, or a tuple of (string,
276 a list of values). The last option is what you
277 should use when accepting user input as it'll
278 protect you against sql injection attacks (if
279 all user data is placed in the array rather than
280 the raw SQL).
mbligh12eebfa2008-01-03 02:01:53 +0000281
jadmanski0afbb632008-06-06 21:10:57 +0000282 For example:
283 where = ("a = %s AND b = %s", ['val', 'val'])
284 is better than
285 where = "a = 'val' AND b = 'val'"
Michael Tang5f74ffd2016-10-31 10:34:53 -0700286
287 @param fields: The list of selected fields string.
288 @param table: The name of the database table.
289 @param where: The where clause string.
290 @param distinct: If select distinct values.
291 @param group_by: Group by clause.
292 @param max_rows: unused.
jadmanski0afbb632008-06-06 21:10:57 +0000293 """
294 cmd = ['select']
295 if distinct:
296 cmd.append('distinct')
297 cmd += [fields, 'from', table]
mbligh608c3252007-08-31 13:53:00 +0000298
showardc1a98d12010-01-15 00:22:22 +0000299 where_clause, values = self._where_clause(where)
300 cmd.append(where_clause)
mbligh96cf0512008-04-17 15:25:38 +0000301
jadmanski0afbb632008-06-06 21:10:57 +0000302 if group_by:
303 cmd.append(' GROUP BY ' + group_by)
mbligh83f63a02007-12-12 19:13:04 +0000304
jadmanski0afbb632008-06-06 21:10:57 +0000305 self.dprint('%s %s' % (' '.join(cmd), values))
mbligh96cf0512008-04-17 15:25:38 +0000306
jadmanski0afbb632008-06-06 21:10:57 +0000307 # create a re-runable function for executing the query
308 def exec_sql():
Michael Tang5f74ffd2016-10-31 10:34:53 -0700309 """Exeuctes an the sql command."""
jadmanski0afbb632008-06-06 21:10:57 +0000310 sql = ' '.join(cmd)
311 numRec = self.cur.execute(sql, values)
mblighd876f452008-12-03 15:09:17 +0000312 if max_rows is not None and numRec > max_rows:
jadmanski0afbb632008-06-06 21:10:57 +0000313 msg = 'Exceeded allowed number of records'
314 raise MySQLTooManyRows(msg)
315 return self.cur.fetchall()
mbligh96cf0512008-04-17 15:25:38 +0000316
jadmanski0afbb632008-06-06 21:10:57 +0000317 # run the query, re-trying after operational errors
318 if self.autocommit:
319 return self.run_with_retry(exec_sql)
320 else:
321 return exec_sql()
mblighd5c33db2006-10-08 21:34:16 +0000322
mbligh056d0d32006-10-08 22:31:10 +0000323
jadmanski0afbb632008-06-06 21:10:57 +0000324 def select_sql(self, fields, table, sql, values):
325 """\
326 select fields from table "sql"
Michael Tang5f74ffd2016-10-31 10:34:53 -0700327
328 @param fields: The list of selected fields string.
329 @param table: The name of the database table.
330 @param sql: The sql string.
331 @param values: The sql string parameter values.
jadmanski0afbb632008-06-06 21:10:57 +0000332 """
333 cmd = 'select %s from %s %s' % (fields, table, sql)
334 self.dprint(cmd)
mbligh414c69e2007-10-05 15:13:06 +0000335
jadmanski0afbb632008-06-06 21:10:57 +0000336 # create a -re-runable function for executing the query
Michael Tang5f74ffd2016-10-31 10:34:53 -0700337 def _exec_sql():
jadmanski0afbb632008-06-06 21:10:57 +0000338 self.cur.execute(cmd, values)
339 return self.cur.fetchall()
mbligh96b9a5a2007-11-24 19:32:20 +0000340
jadmanski0afbb632008-06-06 21:10:57 +0000341 # run the query, re-trying after operational errors
342 if self.autocommit:
Michael Tang5f74ffd2016-10-31 10:34:53 -0700343 return self.run_with_retry(_exec_sql)
jadmanski0afbb632008-06-06 21:10:57 +0000344 else:
Michael Tang5f74ffd2016-10-31 10:34:53 -0700345 return _exec_sql()
mbligh96b9a5a2007-11-24 19:32:20 +0000346
mbligh608c3252007-08-31 13:53:00 +0000347
jadmanski0afbb632008-06-06 21:10:57 +0000348 def _exec_sql_with_commit(self, sql, values, commit):
349 if self.autocommit:
350 # re-run the query until it succeeds
Michael Tang5f74ffd2016-10-31 10:34:53 -0700351 def _exec_sql():
jadmanski0afbb632008-06-06 21:10:57 +0000352 self.cur.execute(sql, values)
353 self.con.commit()
Michael Tang5f74ffd2016-10-31 10:34:53 -0700354 self.run_with_retry(_exec_sql)
jadmanski0afbb632008-06-06 21:10:57 +0000355 else:
356 # take one shot at running the query
357 self.cur.execute(sql, values)
358 if commit:
359 self.con.commit()
mbligh96b9a5a2007-11-24 19:32:20 +0000360
mbligh2bd48872007-09-20 18:32:25 +0000361
jadmanskib591fba2008-09-10 16:19:22 +0000362 def insert(self, table, data, commit=None):
jadmanski0afbb632008-06-06 21:10:57 +0000363 """\
364 'insert into table (keys) values (%s ... %s)', values
mbligh96cf0512008-04-17 15:25:38 +0000365
jadmanski0afbb632008-06-06 21:10:57 +0000366 data:
367 dictionary of fields and data
Michael Tang5f74ffd2016-10-31 10:34:53 -0700368
369 @param table: The name of the table.
370 @param data: The insert data.
371 @param commit: If commit the transaction .
jadmanski0afbb632008-06-06 21:10:57 +0000372 """
373 fields = data.keys()
374 refs = ['%s' for field in fields]
375 values = [data[field] for field in fields]
showardc1a98d12010-01-15 00:22:22 +0000376 cmd = ('insert into %s (%s) values (%s)' %
377 (table, ','.join(self._quote(field) for field in fields),
378 ','.join(refs)))
jadmanski0afbb632008-06-06 21:10:57 +0000379 self.dprint('%s %s' % (cmd, values))
mblighe9cf9d42007-08-31 08:56:00 +0000380
jadmanski0afbb632008-06-06 21:10:57 +0000381 self._exec_sql_with_commit(cmd, values, commit)
mblighe9cf9d42007-08-31 08:56:00 +0000382
mbligh048e1c92007-10-07 00:10:33 +0000383
jadmanski0afbb632008-06-06 21:10:57 +0000384 def delete(self, table, where, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700385 """Delete entries.
386
387 @param table: The name of the table.
388 @param where: The where clause.
389 @param commit: If commit the transaction .
390 """
jadmanski0afbb632008-06-06 21:10:57 +0000391 cmd = ['delete from', table]
mblighd876f452008-12-03 15:09:17 +0000392 if commit is None:
jadmanski0afbb632008-06-06 21:10:57 +0000393 commit = self.autocommit
showardc1a98d12010-01-15 00:22:22 +0000394 where_clause, values = self._where_clause(where)
395 cmd.append(where_clause)
jadmanski0afbb632008-06-06 21:10:57 +0000396 sql = ' '.join(cmd)
397 self.dprint('%s %s' % (sql, values))
mbligh048e1c92007-10-07 00:10:33 +0000398
jadmanski0afbb632008-06-06 21:10:57 +0000399 self._exec_sql_with_commit(sql, values, commit)
mbligh048e1c92007-10-07 00:10:33 +0000400
mbligh7a41a862007-11-30 17:44:24 +0000401
jadmanski0afbb632008-06-06 21:10:57 +0000402 def update(self, table, data, where, commit = None):
403 """\
404 'update table set data values (%s ... %s) where ...'
mbligh2aaeb672007-10-01 14:54:18 +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 sql parameter values.
411 @param where: The where clause.
412 @param commit: If commit the transaction .
jadmanski0afbb632008-06-06 21:10:57 +0000413 """
mblighd876f452008-12-03 15:09:17 +0000414 if commit is None:
jadmanski0afbb632008-06-06 21:10:57 +0000415 commit = self.autocommit
416 cmd = 'update %s ' % table
417 fields = data.keys()
showardc1a98d12010-01-15 00:22:22 +0000418 data_refs = [self._quote(field) + '=%s' for field in fields]
jadmanski0afbb632008-06-06 21:10:57 +0000419 data_values = [data[field] for field in fields]
jadmanski74eebf32008-07-15 20:04:42 +0000420 cmd += ' set ' + ', '.join(data_refs)
mbligh2aaeb672007-10-01 14:54:18 +0000421
showardc1a98d12010-01-15 00:22:22 +0000422 where_clause, where_values = self._where_clause(where)
423 cmd += where_clause
mbligh2aaeb672007-10-01 14:54:18 +0000424
jadmanski0afbb632008-06-06 21:10:57 +0000425 values = data_values + where_values
jadmanski74eebf32008-07-15 20:04:42 +0000426 self.dprint('%s %s' % (cmd, values))
mbligh2aaeb672007-10-01 14:54:18 +0000427
jadmanski0afbb632008-06-06 21:10:57 +0000428 self._exec_sql_with_commit(cmd, values, commit)
mblighe9cf9d42007-08-31 08:56:00 +0000429
430
jadmanski0afbb632008-06-06 21:10:57 +0000431 def delete_job(self, tag, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700432 """Delete a tko job.
433
434 @param tag: The job tag.
435 @param commit: If commit the transaction .
436 """
jadmanski0afbb632008-06-06 21:10:57 +0000437 job_idx = self.find_job(tag)
438 for test_idx in self.find_tests(job_idx):
439 where = {'test_idx' : test_idx}
showardeab66ce2009-12-23 00:03:56 +0000440 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700441 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000442 self.delete('tko_iteration_attributes', where)
443 self.delete('tko_test_attributes', where)
444 self.delete('tko_test_labels_tests', {'test_id': test_idx})
jadmanski0afbb632008-06-06 21:10:57 +0000445 where = {'job_idx' : job_idx}
showardeab66ce2009-12-23 00:03:56 +0000446 self.delete('tko_tests', where)
447 self.delete('tko_jobs', where)
apw7a7316b2008-02-21 17:42:05 +0000448
apw7a7316b2008-02-21 17:42:05 +0000449
Prathmesh Prabhu30dee862018-04-18 20:24:20 -0700450 def insert_job(self, tag, job, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700451 """Insert a tko job.
452
453 @param tag: The job tag.
454 @param job: The job object.
Michael Tang5f74ffd2016-10-31 10:34:53 -0700455 @param commit: If commit the transaction .
456 """
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700457 data = self._get_common_job_data(tag, job)
458 data.update({
Prathmesh Prabhu30dee862018-04-18 20:24:20 -0700459 'afe_job_id': job.afe_job_id,
460 'afe_parent_job_id': job.afe_parent_job_id,
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700461 })
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700462 if job.job_idx is not None:
463 self.update(
464 'tko_jobs', data, {'job_idx': job.job_idx}, commit=commit)
showard0fec8a02009-12-04 01:19:54 +0000465 else:
showardeab66ce2009-12-23 00:03:56 +0000466 self.insert('tko_jobs', data, commit=commit)
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700467 job.job_idx = self.get_last_autonumber_value()
Shuqian Zhao31425d52016-12-07 09:35:03 -0800468
mbligh237bed32007-09-05 13:05:57 +0000469
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700470 def _get_common_job_data(self, tag, job):
471 """Construct a dictionary with the common data to insert in job/task."""
472 return {
473 'tag':tag,
474 'label': job.label,
475 'username': job.user,
476 'machine_idx': job.machine_idx,
477 'queued_time': job.queued_time,
478 'started_time': job.started_time,
479 'finished_time': job.finished_time,
480 'build': job.build,
481 'build_version': job.build_version,
482 'board': job.board,
483 'suite': job.suite,
484 }
485
486
487 def insert_or_update_task_reference(self, job, reference_type, commit=None):
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700488 """Insert an entry in the tko_task_references table.
489
490 The job should already have been inserted in tko_jobs.
491 @param job: tko.models.job object.
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700492 @param reference_type: The type of reference to insert.
493 One of: {'afe', 'skylab'}
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700494 @param commit: Whether to commit this transaction.
495 """
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700496 assert reference_type in {'afe', 'skylab'}
497 if reference_type == 'afe':
498 task_id = job.afe_job_id
499 parent_task_id = job.afe_parent_job_id
500 else:
501 task_id = job.skylab_task_id
502 parent_task_id = job.skylab_parent_task_id
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700503 data = {
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700504 'reference_type': reference_type,
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700505 'tko_job_idx': job.job_idx,
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700506 'task_id': task_id,
507 'parent_task_id': parent_task_id,
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700508 }
Prathmesh Prabhu17905882018-04-18 22:09:08 -0700509
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700510 task_reference_id = self._lookup_task_reference(job)
511 if task_reference_id is not None:
512 self.update('tko_task_references',
513 data,
514 {'id': task_reference_id},
515 commit=commit)
516 job.task_reference_id = task_reference_id
517 else:
518 self.insert('tko_task_references', data, commit=commit)
519 job.task_reference_id = self.get_last_autonumber_value()
520
521
showardc1a98d12010-01-15 00:22:22 +0000522 def update_job_keyvals(self, job, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700523 """Updates the job key values.
524
525 @param job: The job object.
526 @param commit: If commit the transaction .
527 """
showardc1a98d12010-01-15 00:22:22 +0000528 for key, value in job.keyval_dict.iteritems():
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700529 where = {'job_id': job.job_idx, 'key': key}
showardc1a98d12010-01-15 00:22:22 +0000530 data = dict(where, value=value)
531 exists = self.select('id', 'tko_job_keyvals', where=where)
532
533 if exists:
534 self.update('tko_job_keyvals', data, where=where, commit=commit)
535 else:
536 self.insert('tko_job_keyvals', data, commit=commit)
537
538
jadmanski0afbb632008-06-06 21:10:57 +0000539 def insert_test(self, job, test, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700540 """Inserts a job test.
541
542 @param job: The job object.
543 @param test: The test object.
544 @param commit: If commit the transaction .
545 """
jadmanski0afbb632008-06-06 21:10:57 +0000546 kver = self.insert_kernel(test.kernel, commit=commit)
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700547 data = {'job_idx':job.job_idx, 'test':test.testname,
jadmanski0afbb632008-06-06 21:10:57 +0000548 'subdir':test.subdir, 'kernel_idx':kver,
549 'status':self.status_idx[test.status],
550 'reason':test.reason, 'machine_idx':job.machine_idx,
551 'started_time': test.started_time,
552 'finished_time':test.finished_time}
jadmanski9b6babf2009-04-21 17:57:40 +0000553 is_update = hasattr(test, "test_idx")
554 if is_update:
jadmanski74eebf32008-07-15 20:04:42 +0000555 test_idx = test.test_idx
showardeab66ce2009-12-23 00:03:56 +0000556 self.update('tko_tests', data,
557 {'test_idx': test_idx}, commit=commit)
jadmanskib591fba2008-09-10 16:19:22 +0000558 where = {'test_idx': test_idx}
showardeab66ce2009-12-23 00:03:56 +0000559 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700560 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000561 self.delete('tko_iteration_attributes', where)
showard0fec8a02009-12-04 01:19:54 +0000562 where['user_created'] = 0
showardeab66ce2009-12-23 00:03:56 +0000563 self.delete('tko_test_attributes', where)
jadmanski74eebf32008-07-15 20:04:42 +0000564 else:
showardeab66ce2009-12-23 00:03:56 +0000565 self.insert('tko_tests', data, commit=commit)
jadmanski74eebf32008-07-15 20:04:42 +0000566 test_idx = test.test_idx = self.get_last_autonumber_value()
567 data = {'test_idx': test_idx}
mbligh237bed32007-09-05 13:05:57 +0000568
jadmanski0afbb632008-06-06 21:10:57 +0000569 for i in test.iterations:
570 data['iteration'] = i.index
571 for key, value in i.attr_keyval.iteritems():
572 data['attribute'] = key
573 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000574 self.insert('tko_iteration_attributes', data,
jadmanski0afbb632008-06-06 21:10:57 +0000575 commit=commit)
576 for key, value in i.perf_keyval.iteritems():
577 data['attribute'] = key
Allen Li64692a32016-11-04 13:35:19 -0700578 if math.isnan(value) or math.isinf(value):
579 data['value'] = None
580 else:
581 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000582 self.insert('tko_iteration_result', data,
mbligh432bad42007-10-09 19:56:07 +0000583 commit=commit)
mbligh056d0d32006-10-08 22:31:10 +0000584
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700585 data = {'test_idx': test_idx}
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700586
jadmanski0afbb632008-06-06 21:10:57 +0000587 for key, value in test.attributes.iteritems():
588 data = {'test_idx': test_idx, 'attribute': key,
589 'value': value}
showardeab66ce2009-12-23 00:03:56 +0000590 self.insert('tko_test_attributes', data, commit=commit)
mbligh2bd48872007-09-20 18:32:25 +0000591
jadmanski9b6babf2009-04-21 17:57:40 +0000592 if not is_update:
593 for label_index in test.labels:
594 data = {'test_id': test_idx, 'testlabel_id': label_index}
showardeab66ce2009-12-23 00:03:56 +0000595 self.insert('tko_test_labels_tests', data, commit=commit)
jadmanski9b6babf2009-04-21 17:57:40 +0000596
mbligh056d0d32006-10-08 22:31:10 +0000597
jadmanski0afbb632008-06-06 21:10:57 +0000598 def read_machine_map(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700599 """Reads the machine map."""
showard71b94312009-08-20 23:40:02 +0000600 if self.machine_group or not self.machine_map:
601 return
jadmanski0afbb632008-06-06 21:10:57 +0000602 for line in open(self.machine_map, 'r').readlines():
603 (machine, group) = line.split()
604 self.machine_group[machine] = group
mbligh96b9a5a2007-11-24 19:32:20 +0000605
606
showard71b94312009-08-20 23:40:02 +0000607 def machine_info_dict(self, job):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700608 """Reads the machine information of a job.
609
610 @param job: The job object.
611
612 @return: The machine info dictionary.
613 """
jadmanski0afbb632008-06-06 21:10:57 +0000614 hostname = job.machine
showard71b94312009-08-20 23:40:02 +0000615 group = job.machine_group
616 owner = job.machine_owner
jadmanski0afbb632008-06-06 21:10:57 +0000617
618 if not group:
showard71b94312009-08-20 23:40:02 +0000619 self.read_machine_map()
jadmanski0afbb632008-06-06 21:10:57 +0000620 group = self.machine_group.get(hostname, hostname)
showard71b94312009-08-20 23:40:02 +0000621 if group == hostname and owner:
622 group = owner + '/' + hostname
jadmanski0afbb632008-06-06 21:10:57 +0000623
showard71b94312009-08-20 23:40:02 +0000624 return {'hostname': hostname, 'machine_group': group, 'owner': owner}
625
626
Prathmesh Prabhuec96d6e2018-04-18 18:13:50 -0700627 def insert_or_update_machine(self, job, commit=None):
628 """Insert or updates machine information for the given job.
629
630 Also updates the job object with new machine index, if any.
631
632 @param job: tko.models.job object.
633 @param commit: Whether to commit the database transaction.
634 """
635 job.machine_idx = self._lookup_machine(job.machine)
636 if not job.machine_idx:
637 job.machine_idx = self._insert_machine(job, commit=commit)
638 elif job.machine:
639 # Only try to update tko_machines record if machine is set. This
640 # prevents unnecessary db writes for suite jobs.
641 self._update_machine_information(job, commit=commit)
642
643
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700644 def _lookup_task_reference(self, job):
645 """Find the task_reference_id for a given job. Return None if not found.
646
647 @param job: tko.models.job object.
648 """
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700649 if job.job_idx is None:
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700650 return None
651 rows = self.select(
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700652 'id', 'tko_task_references', {'tko_job_idx': job.job_idx})
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700653 if not rows:
654 return None
655 if len(rows) > 1:
656 raise MySQLTooManyRows('Got %d tko_task_references for tko_job %d'
Prathmesh Prabhuc2a8a6a2018-04-19 16:23:32 -0700657 % (len(rows), job.job_idx))
Prathmesh Prabhuf5030d32018-04-19 16:08:55 -0700658 return rows[0][0]
659
660
Prathmesh Prabhu432648c2018-04-18 18:10:50 -0700661 def _insert_machine(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700662 """Inserts the job machine.
663
664 @param job: The job object.
665 @param commit: If commit the transaction .
666 """
showard71b94312009-08-20 23:40:02 +0000667 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000668 self.insert('tko_machines', machine_info, commit=commit)
jadmanski0afbb632008-06-06 21:10:57 +0000669 return self.get_last_autonumber_value()
670
671
Prathmesh Prabhu432648c2018-04-18 18:10:50 -0700672 def _update_machine_information(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700673 """Updates the job machine information.
674
675 @param job: The job object.
676 @param commit: If commit the transaction .
677 """
showard71b94312009-08-20 23:40:02 +0000678 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000679 self.update('tko_machines', machine_info,
showard71b94312009-08-20 23:40:02 +0000680 where={'hostname': machine_info['hostname']},
681 commit=commit)
682
683
Prathmesh Prabhu432648c2018-04-18 18:10:50 -0700684 def _lookup_machine(self, hostname):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700685 """Look up the machine information.
686
687 @param hostname: The hostname as string.
688 """
jadmanski0afbb632008-06-06 21:10:57 +0000689 where = { 'hostname' : hostname }
showardeab66ce2009-12-23 00:03:56 +0000690 rows = self.select('machine_idx', 'tko_machines', where)
jadmanski0afbb632008-06-06 21:10:57 +0000691 if rows:
692 return rows[0][0]
693 else:
694 return None
695
696
697 def lookup_kernel(self, kernel):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700698 """Look up the kernel.
699
700 @param kernel: The kernel object.
701 """
showardeab66ce2009-12-23 00:03:56 +0000702 rows = self.select('kernel_idx', 'tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000703 {'kernel_hash':kernel.kernel_hash})
704 if rows:
705 return rows[0][0]
706 else:
707 return None
708
709
710 def insert_kernel(self, kernel, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700711 """Insert a kernel.
712
713 @param kernel: The kernel object.
714 @param commit: If commit the transaction .
715 """
jadmanski0afbb632008-06-06 21:10:57 +0000716 kver = self.lookup_kernel(kernel)
717 if kver:
718 return kver
719
720 # If this kernel has any significant patches, append their hash
721 # as diferentiator.
722 printable = kernel.base
723 patch_count = 0
724 for patch in kernel.patches:
725 match = re.match(r'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$',
726 patch.reference)
727 if not match:
728 patch_count += 1
729
showardeab66ce2009-12-23 00:03:56 +0000730 self.insert('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000731 {'base':kernel.base,
732 'kernel_hash':kernel.kernel_hash,
733 'printable':printable},
734 commit=commit)
735 kver = self.get_last_autonumber_value()
736
737 if patch_count > 0:
738 printable += ' p%d' % (kver)
showardeab66ce2009-12-23 00:03:56 +0000739 self.update('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000740 {'printable':printable},
741 {'kernel_idx':kver})
742
743 for patch in kernel.patches:
744 self.insert_patch(kver, patch, commit=commit)
745 return kver
746
747
748 def insert_patch(self, kver, patch, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700749 """Insert a kernel patch.
750
751 @param kver: The kernel version.
752 @param patch: The kernel patch object.
753 @param commit: If commit the transaction .
754 """
jadmanski0afbb632008-06-06 21:10:57 +0000755 print patch.reference
756 name = os.path.basename(patch.reference)[:80]
showardeab66ce2009-12-23 00:03:56 +0000757 self.insert('tko_patches',
jadmanski0afbb632008-06-06 21:10:57 +0000758 {'kernel_idx': kver,
759 'name':name,
760 'url':patch.reference,
761 'hash':patch.hash},
762 commit=commit)
763
764
jadmanski74eebf32008-07-15 20:04:42 +0000765 def find_test(self, job_idx, testname, subdir):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700766 """Find a test by name.
767
768 @param job_idx: The job index.
769 @param testname: The test name.
770 @param subdir: The test sub directory under the job directory.
771 """
jadmanski74eebf32008-07-15 20:04:42 +0000772 where = {'job_idx': job_idx , 'test': testname, 'subdir': subdir}
showardeab66ce2009-12-23 00:03:56 +0000773 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000774 if rows:
775 return rows[0][0]
776 else:
777 return None
778
779
780 def find_tests(self, job_idx):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700781 """Find all tests by job index.
782
783 @param job_idx: The job index.
784 @return: A list of tests.
785 """
jadmanski0afbb632008-06-06 21:10:57 +0000786 where = { 'job_idx':job_idx }
showardeab66ce2009-12-23 00:03:56 +0000787 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000788 if rows:
789 return [row[0] for row in rows]
790 else:
791 return []
792
793
794 def find_job(self, tag):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700795 """Find a job by tag.
796
797 @param tag: The job tag name.
798 @return: The job object or None.
799 """
showardeab66ce2009-12-23 00:03:56 +0000800 rows = self.select('job_idx', 'tko_jobs', {'tag': tag})
jadmanski0afbb632008-06-06 21:10:57 +0000801 if rows:
802 return rows[0][0]
803 else:
804 return None
mblighaf25f062007-12-03 17:48:35 +0000805
806
mbligh96cf0512008-04-17 15:25:38 +0000807def db(*args, **dargs):
Prathmesh Prabhu027561f2018-07-02 10:57:20 -0700808 """Forwards to _DB constructor. Kept here tomigrate callers slowly."""
809 return _DB(*args, **dargs)