blob: a70774dcc9807061ead84a1ad53ee9c6431f9932 [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
Fang Dengcdee2512014-12-09 15:11:25 -08005import re, os, sys, time, random
mbligh96cf0512008-04-17 15:25:38 +00006
7import common
8from autotest_lib.client.common_lib import global_config
Dan Shie8e0c052015-09-01 00:27:27 -07009from autotest_lib.client.common_lib.cros.graphite import autotest_stats
Jakob Juelich934f0dc2014-10-14 18:21:13 -070010from autotest_lib.frontend import database_settings_helper
Dan Shi70647ca2015-07-16 22:52:35 -070011from autotest_lib.server import site_utils
jamesrena12b8a02010-06-16 23:28:23 +000012from autotest_lib.tko import utils
mbligh96cf0512008-04-17 15:25:38 +000013
mblighed4d6dd2008-02-27 16:49:43 +000014
mblighaea09602008-04-16 22:59:37 +000015class MySQLTooManyRows(Exception):
Michael Tang5f74ffd2016-10-31 10:34:53 -070016 """Too many records."""
jadmanski0afbb632008-06-06 21:10:57 +000017 pass
mblighaea09602008-04-16 22:59:37 +000018
mblighd5c33db2006-10-08 21:34:16 +000019
mbligh7636b3a2008-06-11 15:44:01 +000020class db_sql(object):
Michael Tang5f74ffd2016-10-31 10:34:53 -070021 """Data access."""
22
jadmanski0afbb632008-06-06 21:10:57 +000023 def __init__(self, debug=False, autocommit=True, host=None,
24 database=None, user=None, password=None):
25 self.debug = debug
26 self.autocommit = autocommit
27 self._load_config(host, database, user, password)
mbligh96cf0512008-04-17 15:25:38 +000028
jadmanski0afbb632008-06-06 21:10:57 +000029 self.con = None
30 self._init_db()
mblighd5c33db2006-10-08 21:34:16 +000031
jadmanski0afbb632008-06-06 21:10:57 +000032 # if not present, insert statuses
33 self.status_idx = {}
34 self.status_word = {}
showardeab66ce2009-12-23 00:03:56 +000035 status_rows = self.select('status_idx, word', 'tko_status', None)
jadmanski0afbb632008-06-06 21:10:57 +000036 for s in status_rows:
37 self.status_idx[s[1]] = s[0]
38 self.status_word[s[0]] = s[1]
mbligh048e1c92007-10-07 00:10:33 +000039
jadmanski0afbb632008-06-06 21:10:57 +000040 machine_map = os.path.join(os.path.dirname(__file__),
41 'machines')
42 if os.path.exists(machine_map):
43 self.machine_map = machine_map
44 else:
45 self.machine_map = None
46 self.machine_group = {}
mbligh048e1c92007-10-07 00:10:33 +000047
mbligh8e1ab172007-09-13 17:29:56 +000048
jadmanski0afbb632008-06-06 21:10:57 +000049 def _load_config(self, host, database, user, password):
Jakob Juelich934f0dc2014-10-14 18:21:13 -070050 """Loads configuration settings required to connect to the database.
51
52 This will try to connect to use the settings prefixed with global_db_.
53 If they do not exist, they un-prefixed settings will be used.
54
55 If parameters are supplied, these will be taken instead of the values
56 in global_config.
57
58 @param host: If set, this host will be used, if not, the host will be
59 retrieved from global_config.
60 @param database: If set, this database will be used, if not, the
61 database will be retrieved from global_config.
62 @param user: If set, this user will be used, if not, the
63 user will be retrieved from global_config.
64 @param password: If set, this password will be used, if not, the
65 password will be retrieved from global_config.
66 """
67 database_settings = database_settings_helper.get_global_db_config()
mbligh65acae52008-04-24 20:21:55 +000068
jadmanski0afbb632008-06-06 21:10:57 +000069 # grab the host, database
Jakob Juelich934f0dc2014-10-14 18:21:13 -070070 self.host = host or database_settings['HOST']
71 self.database = database or database_settings['NAME']
mbligh65acae52008-04-24 20:21:55 +000072
jadmanski0afbb632008-06-06 21:10:57 +000073 # grab the user and password
Jakob Juelich934f0dc2014-10-14 18:21:13 -070074 self.user = user or database_settings['USER']
75 self.password = password or database_settings['PASSWORD']
mbligh65acae52008-04-24 20:21:55 +000076
Michael Spang7a273472014-10-08 12:08:13 -040077 # grab the timeout configuration
Jakob Juelich934f0dc2014-10-14 18:21:13 -070078 self.query_timeout =(
79 database_settings.get('OPTIONS', {}).get('timeout', 3600))
80
81 # Using fallback to non-global in order to work without configuration
82 # overhead on non-shard instances.
83 get_value = global_config.global_config.get_config_value_with_fallback
Jakob Juelich475b82b2014-09-30 11:17:07 -070084 self.min_delay = get_value("AUTOTEST_WEB", "global_db_min_retry_delay",
Jakob Juelich934f0dc2014-10-14 18:21:13 -070085 "min_retry_delay", type=int, default=20)
Jakob Juelich475b82b2014-09-30 11:17:07 -070086 self.max_delay = get_value("AUTOTEST_WEB", "global_db_max_retry_delay",
Jakob Juelich934f0dc2014-10-14 18:21:13 -070087 "max_retry_delay", type=int, default=60)
mbligh65acae52008-04-24 20:21:55 +000088
Richard Barnette2468fbd2014-11-07 01:12:46 +000089 # TODO(beeps): Move this to django settings once we have routers.
90 # On test instances mysql connects through a different port. No point
91 # piping this through our entire infrastructure when it is only really
92 # used for testing; Ideally we would specify this through django
93 # settings and default it to the empty string so django will figure out
94 # the default based on the database backend (eg: mysql, 3306), but until
95 # we have database routers in place any django settings will apply to
96 # both tko and afe.
97 # The intended use of this port is to allow a testing shard vm to
98 # update the master vm's database with test results. Specifying
99 # and empty string will fallback to not even specifying the port
100 # to the backend in tko/db.py. Unfortunately this means retries
101 # won't work on the test cluster till we've migrated to routers.
102 self.port = global_config.global_config.get_config_value(
103 "AUTOTEST_WEB", "global_db_port", type=str, default='')
104
mbligh65acae52008-04-24 20:21:55 +0000105
jadmanski0afbb632008-06-06 21:10:57 +0000106 def _init_db(self):
107 # make sure we clean up any existing connection
108 if self.con:
109 self.con.close()
110 self.con = None
mbligh65acae52008-04-24 20:21:55 +0000111
Dan Shi8684b922015-10-06 13:29:18 -0700112 try:
113 # create the db connection and cursor
114 self.con = self.connect(self.host, self.database,
115 self.user, self.password, self.port)
116 except:
117 autotest_stats.Counter('tko_db_con_error').increment()
118 raise
jadmanski0afbb632008-06-06 21:10:57 +0000119 self.cur = self.con.cursor()
mbligh96cf0512008-04-17 15:25:38 +0000120
121
jadmanski0afbb632008-06-06 21:10:57 +0000122 def _random_delay(self):
123 delay = random.randint(self.min_delay, self.max_delay)
124 time.sleep(delay)
mbligh65acae52008-04-24 20:21:55 +0000125
126
jadmanski0afbb632008-06-06 21:10:57 +0000127 def run_with_retry(self, function, *args, **dargs):
128 """Call function(*args, **dargs) until either it passes
129 without an operational error, or a timeout is reached.
130 This will re-connect to the database, so it is NOT safe
131 to use this inside of a database transaction.
jadmanskie7a69092008-05-29 21:03:13 +0000132
jadmanski0afbb632008-06-06 21:10:57 +0000133 It can be safely used with transactions, but the
134 transaction start & end must be completely contained
Michael Tang5f74ffd2016-10-31 10:34:53 -0700135 within the call to 'function'.
136
137 @param function: The function to run with retry.
138 @param args: The arguments
139 @param dargs: The named arguments.
140 """
jadmanski0afbb632008-06-06 21:10:57 +0000141 OperationalError = _get_error_class("OperationalError")
mbligh65acae52008-04-24 20:21:55 +0000142
jadmanski0afbb632008-06-06 21:10:57 +0000143 success = False
144 start_time = time.time()
145 while not success:
146 try:
147 result = function(*args, **dargs)
148 except OperationalError, e:
149 self._log_operational_error(e)
150 stop_time = time.time()
151 elapsed_time = stop_time - start_time
152 if elapsed_time > self.query_timeout:
153 raise
154 else:
155 try:
156 self._random_delay()
157 self._init_db()
Dan Shie8e0c052015-09-01 00:27:27 -0700158 autotest_stats.Counter('tko_db_error').increment()
jadmanski0afbb632008-06-06 21:10:57 +0000159 except OperationalError, e:
160 self._log_operational_error(e)
161 else:
162 success = True
163 return result
mbligh96cf0512008-04-17 15:25:38 +0000164
165
jadmanski0afbb632008-06-06 21:10:57 +0000166 def _log_operational_error(self, e):
mbligh097407d2009-02-17 15:49:37 +0000167 msg = ("%s: An operational error occured during a database "
jadmanski5d4c27e2009-03-02 16:45:42 +0000168 "operation: %s" % (time.strftime("%X %x"), str(e)))
jadmanski0afbb632008-06-06 21:10:57 +0000169 print >> sys.stderr, msg
170 sys.stderr.flush() # we want these msgs to show up immediately
jadmanski60d4fa62008-05-06 22:49:41 +0000171
172
jadmanski0afbb632008-06-06 21:10:57 +0000173 def dprint(self, value):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700174 """Print out debug value.
175
176 @param value: The value to print out.
177 """
jadmanski0afbb632008-06-06 21:10:57 +0000178 if self.debug:
179 sys.stdout.write('SQL: ' + str(value) + '\n')
mbligh8e1ab172007-09-13 17:29:56 +0000180
mblighd5c33db2006-10-08 21:34:16 +0000181
Dan Shie8e0c052015-09-01 00:27:27 -0700182 def _commit(self):
183 """Private method for function commit to call for retry.
184 """
185 return self.con.commit()
186
187
jadmanski0afbb632008-06-06 21:10:57 +0000188 def commit(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700189 """Commit the sql transaction."""
Dan Shie8e0c052015-09-01 00:27:27 -0700190 if self.autocommit:
191 return self.run_with_retry(self._commit)
192 else:
193 return self._commit()
mbligh432bad42007-10-09 19:56:07 +0000194
195
Simran Basie129a962012-08-31 13:03:53 -0700196 def rollback(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700197 """Rollback the sql transaction."""
Simran Basie129a962012-08-31 13:03:53 -0700198 self.con.rollback()
199
200
jadmanski0afbb632008-06-06 21:10:57 +0000201 def get_last_autonumber_value(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700202 """Gets the last auto number.
203
204 @return: The last auto number.
205 """
jadmanski0afbb632008-06-06 21:10:57 +0000206 self.cur.execute('SELECT LAST_INSERT_ID()', [])
207 return self.cur.fetchall()[0][0]
mblighe12b8612008-02-12 20:58:14 +0000208
209
showardc1a98d12010-01-15 00:22:22 +0000210 def _quote(self, field):
211 return '`%s`' % field
212
213
214 def _where_clause(self, where):
215 if not where:
216 return '', []
217
218 if isinstance(where, dict):
219 # key/value pairs (which should be equal, or None for null)
220 keys, values = [], []
221 for field, value in where.iteritems():
222 quoted_field = self._quote(field)
223 if value is None:
224 keys.append(quoted_field + ' is null')
225 else:
226 keys.append(quoted_field + '=%s')
227 values.append(value)
228 where_clause = ' and '.join(keys)
229 elif isinstance(where, basestring):
230 # the exact string
231 where_clause = where
232 values = []
233 elif isinstance(where, tuple):
234 # preformatted where clause + values
235 where_clause, values = where
236 assert where_clause
237 else:
238 raise ValueError('Invalid "where" value: %r' % where)
239
240 return ' WHERE ' + where_clause, values
241
242
243
244 def select(self, fields, table, where, distinct=False, group_by=None,
245 max_rows=None):
jadmanski0afbb632008-06-06 21:10:57 +0000246 """\
247 This selects all the fields requested from a
248 specific table with a particular where clause.
249 The where clause can either be a dictionary of
250 field=value pairs, a string, or a tuple of (string,
251 a list of values). The last option is what you
252 should use when accepting user input as it'll
253 protect you against sql injection attacks (if
254 all user data is placed in the array rather than
255 the raw SQL).
mbligh12eebfa2008-01-03 02:01:53 +0000256
jadmanski0afbb632008-06-06 21:10:57 +0000257 For example:
258 where = ("a = %s AND b = %s", ['val', 'val'])
259 is better than
260 where = "a = 'val' AND b = 'val'"
Michael Tang5f74ffd2016-10-31 10:34:53 -0700261
262 @param fields: The list of selected fields string.
263 @param table: The name of the database table.
264 @param where: The where clause string.
265 @param distinct: If select distinct values.
266 @param group_by: Group by clause.
267 @param max_rows: unused.
jadmanski0afbb632008-06-06 21:10:57 +0000268 """
269 cmd = ['select']
270 if distinct:
271 cmd.append('distinct')
272 cmd += [fields, 'from', table]
mbligh608c3252007-08-31 13:53:00 +0000273
showardc1a98d12010-01-15 00:22:22 +0000274 where_clause, values = self._where_clause(where)
275 cmd.append(where_clause)
mbligh96cf0512008-04-17 15:25:38 +0000276
jadmanski0afbb632008-06-06 21:10:57 +0000277 if group_by:
278 cmd.append(' GROUP BY ' + group_by)
mbligh83f63a02007-12-12 19:13:04 +0000279
jadmanski0afbb632008-06-06 21:10:57 +0000280 self.dprint('%s %s' % (' '.join(cmd), values))
mbligh96cf0512008-04-17 15:25:38 +0000281
jadmanski0afbb632008-06-06 21:10:57 +0000282 # create a re-runable function for executing the query
283 def exec_sql():
Michael Tang5f74ffd2016-10-31 10:34:53 -0700284 """Exeuctes an the sql command."""
jadmanski0afbb632008-06-06 21:10:57 +0000285 sql = ' '.join(cmd)
286 numRec = self.cur.execute(sql, values)
mblighd876f452008-12-03 15:09:17 +0000287 if max_rows is not None and numRec > max_rows:
jadmanski0afbb632008-06-06 21:10:57 +0000288 msg = 'Exceeded allowed number of records'
289 raise MySQLTooManyRows(msg)
290 return self.cur.fetchall()
mbligh96cf0512008-04-17 15:25:38 +0000291
jadmanski0afbb632008-06-06 21:10:57 +0000292 # run the query, re-trying after operational errors
293 if self.autocommit:
294 return self.run_with_retry(exec_sql)
295 else:
296 return exec_sql()
mblighd5c33db2006-10-08 21:34:16 +0000297
mbligh056d0d32006-10-08 22:31:10 +0000298
jadmanski0afbb632008-06-06 21:10:57 +0000299 def select_sql(self, fields, table, sql, values):
300 """\
301 select fields from table "sql"
Michael Tang5f74ffd2016-10-31 10:34:53 -0700302
303 @param fields: The list of selected fields string.
304 @param table: The name of the database table.
305 @param sql: The sql string.
306 @param values: The sql string parameter values.
jadmanski0afbb632008-06-06 21:10:57 +0000307 """
308 cmd = 'select %s from %s %s' % (fields, table, sql)
309 self.dprint(cmd)
mbligh414c69e2007-10-05 15:13:06 +0000310
jadmanski0afbb632008-06-06 21:10:57 +0000311 # create a -re-runable function for executing the query
Michael Tang5f74ffd2016-10-31 10:34:53 -0700312 def _exec_sql():
jadmanski0afbb632008-06-06 21:10:57 +0000313 self.cur.execute(cmd, values)
314 return self.cur.fetchall()
mbligh96b9a5a2007-11-24 19:32:20 +0000315
jadmanski0afbb632008-06-06 21:10:57 +0000316 # run the query, re-trying after operational errors
317 if self.autocommit:
Michael Tang5f74ffd2016-10-31 10:34:53 -0700318 return self.run_with_retry(_exec_sql)
jadmanski0afbb632008-06-06 21:10:57 +0000319 else:
Michael Tang5f74ffd2016-10-31 10:34:53 -0700320 return _exec_sql()
mbligh96b9a5a2007-11-24 19:32:20 +0000321
mbligh608c3252007-08-31 13:53:00 +0000322
jadmanski0afbb632008-06-06 21:10:57 +0000323 def _exec_sql_with_commit(self, sql, values, commit):
324 if self.autocommit:
325 # re-run the query until it succeeds
Michael Tang5f74ffd2016-10-31 10:34:53 -0700326 def _exec_sql():
jadmanski0afbb632008-06-06 21:10:57 +0000327 self.cur.execute(sql, values)
328 self.con.commit()
Michael Tang5f74ffd2016-10-31 10:34:53 -0700329 self.run_with_retry(_exec_sql)
jadmanski0afbb632008-06-06 21:10:57 +0000330 else:
331 # take one shot at running the query
332 self.cur.execute(sql, values)
333 if commit:
334 self.con.commit()
mbligh96b9a5a2007-11-24 19:32:20 +0000335
mbligh2bd48872007-09-20 18:32:25 +0000336
jadmanskib591fba2008-09-10 16:19:22 +0000337 def insert(self, table, data, commit=None):
jadmanski0afbb632008-06-06 21:10:57 +0000338 """\
339 'insert into table (keys) values (%s ... %s)', values
mbligh96cf0512008-04-17 15:25:38 +0000340
jadmanski0afbb632008-06-06 21:10:57 +0000341 data:
342 dictionary of fields and data
Michael Tang5f74ffd2016-10-31 10:34:53 -0700343
344 @param table: The name of the table.
345 @param data: The insert data.
346 @param commit: If commit the transaction .
jadmanski0afbb632008-06-06 21:10:57 +0000347 """
348 fields = data.keys()
349 refs = ['%s' for field in fields]
350 values = [data[field] for field in fields]
showardc1a98d12010-01-15 00:22:22 +0000351 cmd = ('insert into %s (%s) values (%s)' %
352 (table, ','.join(self._quote(field) for field in fields),
353 ','.join(refs)))
jadmanski0afbb632008-06-06 21:10:57 +0000354 self.dprint('%s %s' % (cmd, values))
mblighe9cf9d42007-08-31 08:56:00 +0000355
jadmanski0afbb632008-06-06 21:10:57 +0000356 self._exec_sql_with_commit(cmd, values, commit)
mblighe9cf9d42007-08-31 08:56:00 +0000357
mbligh048e1c92007-10-07 00:10:33 +0000358
jadmanski0afbb632008-06-06 21:10:57 +0000359 def delete(self, table, where, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700360 """Delete entries.
361
362 @param table: The name of the table.
363 @param where: The where clause.
364 @param commit: If commit the transaction .
365 """
jadmanski0afbb632008-06-06 21:10:57 +0000366 cmd = ['delete from', table]
mblighd876f452008-12-03 15:09:17 +0000367 if commit is None:
jadmanski0afbb632008-06-06 21:10:57 +0000368 commit = self.autocommit
showardc1a98d12010-01-15 00:22:22 +0000369 where_clause, values = self._where_clause(where)
370 cmd.append(where_clause)
jadmanski0afbb632008-06-06 21:10:57 +0000371 sql = ' '.join(cmd)
372 self.dprint('%s %s' % (sql, values))
mbligh048e1c92007-10-07 00:10:33 +0000373
jadmanski0afbb632008-06-06 21:10:57 +0000374 self._exec_sql_with_commit(sql, values, commit)
mbligh048e1c92007-10-07 00:10:33 +0000375
mbligh7a41a862007-11-30 17:44:24 +0000376
jadmanski0afbb632008-06-06 21:10:57 +0000377 def update(self, table, data, where, commit = None):
378 """\
379 'update table set data values (%s ... %s) where ...'
mbligh2aaeb672007-10-01 14:54:18 +0000380
jadmanski0afbb632008-06-06 21:10:57 +0000381 data:
382 dictionary of fields and data
Michael Tang5f74ffd2016-10-31 10:34:53 -0700383
384 @param table: The name of the table.
385 @param data: The sql parameter values.
386 @param where: The where clause.
387 @param commit: If commit the transaction .
jadmanski0afbb632008-06-06 21:10:57 +0000388 """
mblighd876f452008-12-03 15:09:17 +0000389 if commit is None:
jadmanski0afbb632008-06-06 21:10:57 +0000390 commit = self.autocommit
391 cmd = 'update %s ' % table
392 fields = data.keys()
showardc1a98d12010-01-15 00:22:22 +0000393 data_refs = [self._quote(field) + '=%s' for field in fields]
jadmanski0afbb632008-06-06 21:10:57 +0000394 data_values = [data[field] for field in fields]
jadmanski74eebf32008-07-15 20:04:42 +0000395 cmd += ' set ' + ', '.join(data_refs)
mbligh2aaeb672007-10-01 14:54:18 +0000396
showardc1a98d12010-01-15 00:22:22 +0000397 where_clause, where_values = self._where_clause(where)
398 cmd += where_clause
mbligh2aaeb672007-10-01 14:54:18 +0000399
jadmanski0afbb632008-06-06 21:10:57 +0000400 values = data_values + where_values
jadmanski74eebf32008-07-15 20:04:42 +0000401 self.dprint('%s %s' % (cmd, values))
mbligh2aaeb672007-10-01 14:54:18 +0000402
jadmanski0afbb632008-06-06 21:10:57 +0000403 self._exec_sql_with_commit(cmd, values, commit)
mblighe9cf9d42007-08-31 08:56:00 +0000404
405
jadmanski0afbb632008-06-06 21:10:57 +0000406 def delete_job(self, tag, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700407 """Delete a tko job.
408
409 @param tag: The job tag.
410 @param commit: If commit the transaction .
411 """
jadmanski0afbb632008-06-06 21:10:57 +0000412 job_idx = self.find_job(tag)
413 for test_idx in self.find_tests(job_idx):
414 where = {'test_idx' : test_idx}
showardeab66ce2009-12-23 00:03:56 +0000415 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700416 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000417 self.delete('tko_iteration_attributes', where)
418 self.delete('tko_test_attributes', where)
419 self.delete('tko_test_labels_tests', {'test_id': test_idx})
jadmanski0afbb632008-06-06 21:10:57 +0000420 where = {'job_idx' : job_idx}
showardeab66ce2009-12-23 00:03:56 +0000421 self.delete('tko_tests', where)
422 self.delete('tko_jobs', where)
apw7a7316b2008-02-21 17:42:05 +0000423
apw7a7316b2008-02-21 17:42:05 +0000424
Dan Shi70647ca2015-07-16 22:52:35 -0700425 def insert_job(self, tag, job, parent_job_id=None, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700426 """Insert a tko job.
427
428 @param tag: The job tag.
429 @param job: The job object.
430 @param parent_job_id: The parent job id.
431 @param commit: If commit the transaction .
432 """
jadmanski0afbb632008-06-06 21:10:57 +0000433 job.machine_idx = self.lookup_machine(job.machine)
434 if not job.machine_idx:
showard71b94312009-08-20 23:40:02 +0000435 job.machine_idx = self.insert_machine(job, commit=commit)
Dan Shid77fc1b2015-10-13 17:34:50 -0700436 elif job.machine:
437 # Only try to update tko_machines record if machine is set. This
438 # prevents unnecessary db writes for suite jobs.
showard71b94312009-08-20 23:40:02 +0000439 self.update_machine_information(job, commit=commit)
440
jamesrena12b8a02010-06-16 23:28:23 +0000441 afe_job_id = utils.get_afe_job_id(tag)
showardc1c1caf2009-09-08 16:26:50 +0000442
showard0fec8a02009-12-04 01:19:54 +0000443 data = {'tag':tag,
444 'label': job.label,
445 'username': job.user,
446 'machine_idx': job.machine_idx,
447 'queued_time': job.queued_time,
448 'started_time': job.started_time,
449 'finished_time': job.finished_time,
Dan Shi70647ca2015-07-16 22:52:35 -0700450 'afe_job_id': afe_job_id,
451 'afe_parent_job_id': parent_job_id}
Michael Tang5f74ffd2016-10-31 10:34:53 -0700452 job.afe_job_id = afe_job_id
453 if parent_job_id:
454 job.afe_parent_job_id = str(parent_job_id)
Dan Shi70647ca2015-07-16 22:52:35 -0700455 if job.label:
456 label_info = site_utils.parse_job_name(job.label)
457 if label_info:
458 data['build'] = label_info.get('build', None)
Michael Tang5f74ffd2016-10-31 10:34:53 -0700459 job.build_version = data['build_version'] = label_info.get(
460 'build_version', None)
461 job.board = data['board'] = label_info.get('board', None)
462 job.suite = data['suite'] = label_info.get('suite', None)
463
464 # TODO(ntang): check job.index directly.
showard0fec8a02009-12-04 01:19:54 +0000465 is_update = hasattr(job, 'index')
466 if is_update:
showardeab66ce2009-12-23 00:03:56 +0000467 self.update('tko_jobs', data, {'job_idx': job.index}, commit=commit)
showard0fec8a02009-12-04 01:19:54 +0000468 else:
showardeab66ce2009-12-23 00:03:56 +0000469 self.insert('tko_jobs', data, commit=commit)
showard0fec8a02009-12-04 01:19:54 +0000470 job.index = self.get_last_autonumber_value()
showardc1a98d12010-01-15 00:22:22 +0000471 self.update_job_keyvals(job, commit=commit)
jadmanski0afbb632008-06-06 21:10:57 +0000472 for test in job.tests:
473 self.insert_test(job, test, commit=commit)
apw7a7316b2008-02-21 17:42:05 +0000474
mbligh237bed32007-09-05 13:05:57 +0000475
showardc1a98d12010-01-15 00:22:22 +0000476 def update_job_keyvals(self, job, commit=None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700477 """Updates the job key values.
478
479 @param job: The job object.
480 @param commit: If commit the transaction .
481 """
showardc1a98d12010-01-15 00:22:22 +0000482 for key, value in job.keyval_dict.iteritems():
483 where = {'job_id': job.index, 'key': key}
484 data = dict(where, value=value)
485 exists = self.select('id', 'tko_job_keyvals', where=where)
486
487 if exists:
488 self.update('tko_job_keyvals', data, where=where, commit=commit)
489 else:
490 self.insert('tko_job_keyvals', data, commit=commit)
491
492
jadmanski0afbb632008-06-06 21:10:57 +0000493 def insert_test(self, job, test, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700494 """Inserts a job test.
495
496 @param job: The job object.
497 @param test: The test object.
498 @param commit: If commit the transaction .
499 """
jadmanski0afbb632008-06-06 21:10:57 +0000500 kver = self.insert_kernel(test.kernel, commit=commit)
501 data = {'job_idx':job.index, 'test':test.testname,
502 'subdir':test.subdir, 'kernel_idx':kver,
503 'status':self.status_idx[test.status],
504 'reason':test.reason, 'machine_idx':job.machine_idx,
505 'started_time': test.started_time,
506 'finished_time':test.finished_time}
jadmanski9b6babf2009-04-21 17:57:40 +0000507 is_update = hasattr(test, "test_idx")
508 if is_update:
jadmanski74eebf32008-07-15 20:04:42 +0000509 test_idx = test.test_idx
showardeab66ce2009-12-23 00:03:56 +0000510 self.update('tko_tests', data,
511 {'test_idx': test_idx}, commit=commit)
jadmanskib591fba2008-09-10 16:19:22 +0000512 where = {'test_idx': test_idx}
showardeab66ce2009-12-23 00:03:56 +0000513 self.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700514 self.delete('tko_iteration_perf_value', where)
showardeab66ce2009-12-23 00:03:56 +0000515 self.delete('tko_iteration_attributes', where)
showard0fec8a02009-12-04 01:19:54 +0000516 where['user_created'] = 0
showardeab66ce2009-12-23 00:03:56 +0000517 self.delete('tko_test_attributes', where)
jadmanski74eebf32008-07-15 20:04:42 +0000518 else:
showardeab66ce2009-12-23 00:03:56 +0000519 self.insert('tko_tests', data, commit=commit)
jadmanski74eebf32008-07-15 20:04:42 +0000520 test_idx = test.test_idx = self.get_last_autonumber_value()
521 data = {'test_idx': test_idx}
mbligh237bed32007-09-05 13:05:57 +0000522
jadmanski0afbb632008-06-06 21:10:57 +0000523 for i in test.iterations:
524 data['iteration'] = i.index
525 for key, value in i.attr_keyval.iteritems():
526 data['attribute'] = key
527 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000528 self.insert('tko_iteration_attributes', data,
jadmanski0afbb632008-06-06 21:10:57 +0000529 commit=commit)
530 for key, value in i.perf_keyval.iteritems():
531 data['attribute'] = key
532 data['value'] = value
showardeab66ce2009-12-23 00:03:56 +0000533 self.insert('tko_iteration_result', data,
mbligh432bad42007-10-09 19:56:07 +0000534 commit=commit)
mbligh056d0d32006-10-08 22:31:10 +0000535
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700536 data = {'test_idx': test_idx}
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700537
jadmanski0afbb632008-06-06 21:10:57 +0000538 for key, value in test.attributes.iteritems():
539 data = {'test_idx': test_idx, 'attribute': key,
540 'value': value}
showardeab66ce2009-12-23 00:03:56 +0000541 self.insert('tko_test_attributes', data, commit=commit)
mbligh2bd48872007-09-20 18:32:25 +0000542
jadmanski9b6babf2009-04-21 17:57:40 +0000543 if not is_update:
544 for label_index in test.labels:
545 data = {'test_id': test_idx, 'testlabel_id': label_index}
showardeab66ce2009-12-23 00:03:56 +0000546 self.insert('tko_test_labels_tests', data, commit=commit)
jadmanski9b6babf2009-04-21 17:57:40 +0000547
mbligh056d0d32006-10-08 22:31:10 +0000548
jadmanski0afbb632008-06-06 21:10:57 +0000549 def read_machine_map(self):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700550 """Reads the machine map."""
showard71b94312009-08-20 23:40:02 +0000551 if self.machine_group or not self.machine_map:
552 return
jadmanski0afbb632008-06-06 21:10:57 +0000553 for line in open(self.machine_map, 'r').readlines():
554 (machine, group) = line.split()
555 self.machine_group[machine] = group
mbligh96b9a5a2007-11-24 19:32:20 +0000556
557
showard71b94312009-08-20 23:40:02 +0000558 def machine_info_dict(self, job):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700559 """Reads the machine information of a job.
560
561 @param job: The job object.
562
563 @return: The machine info dictionary.
564 """
jadmanski0afbb632008-06-06 21:10:57 +0000565 hostname = job.machine
showard71b94312009-08-20 23:40:02 +0000566 group = job.machine_group
567 owner = job.machine_owner
jadmanski0afbb632008-06-06 21:10:57 +0000568
569 if not group:
showard71b94312009-08-20 23:40:02 +0000570 self.read_machine_map()
jadmanski0afbb632008-06-06 21:10:57 +0000571 group = self.machine_group.get(hostname, hostname)
showard71b94312009-08-20 23:40:02 +0000572 if group == hostname and owner:
573 group = owner + '/' + hostname
jadmanski0afbb632008-06-06 21:10:57 +0000574
showard71b94312009-08-20 23:40:02 +0000575 return {'hostname': hostname, 'machine_group': group, 'owner': owner}
576
577
578 def insert_machine(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700579 """Inserts the job machine.
580
581 @param job: The job object.
582 @param commit: If commit the transaction .
583 """
showard71b94312009-08-20 23:40:02 +0000584 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000585 self.insert('tko_machines', machine_info, commit=commit)
jadmanski0afbb632008-06-06 21:10:57 +0000586 return self.get_last_autonumber_value()
587
588
showard71b94312009-08-20 23:40:02 +0000589 def update_machine_information(self, job, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700590 """Updates the job machine information.
591
592 @param job: The job object.
593 @param commit: If commit the transaction .
594 """
showard71b94312009-08-20 23:40:02 +0000595 machine_info = self.machine_info_dict(job)
showardeab66ce2009-12-23 00:03:56 +0000596 self.update('tko_machines', machine_info,
showard71b94312009-08-20 23:40:02 +0000597 where={'hostname': machine_info['hostname']},
598 commit=commit)
599
600
jadmanski0afbb632008-06-06 21:10:57 +0000601 def lookup_machine(self, hostname):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700602 """Look up the machine information.
603
604 @param hostname: The hostname as string.
605 """
jadmanski0afbb632008-06-06 21:10:57 +0000606 where = { 'hostname' : hostname }
showardeab66ce2009-12-23 00:03:56 +0000607 rows = self.select('machine_idx', 'tko_machines', where)
jadmanski0afbb632008-06-06 21:10:57 +0000608 if rows:
609 return rows[0][0]
610 else:
611 return None
612
613
614 def lookup_kernel(self, kernel):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700615 """Look up the kernel.
616
617 @param kernel: The kernel object.
618 """
showardeab66ce2009-12-23 00:03:56 +0000619 rows = self.select('kernel_idx', 'tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000620 {'kernel_hash':kernel.kernel_hash})
621 if rows:
622 return rows[0][0]
623 else:
624 return None
625
626
627 def insert_kernel(self, kernel, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700628 """Insert a kernel.
629
630 @param kernel: The kernel object.
631 @param commit: If commit the transaction .
632 """
jadmanski0afbb632008-06-06 21:10:57 +0000633 kver = self.lookup_kernel(kernel)
634 if kver:
635 return kver
636
637 # If this kernel has any significant patches, append their hash
638 # as diferentiator.
639 printable = kernel.base
640 patch_count = 0
641 for patch in kernel.patches:
642 match = re.match(r'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$',
643 patch.reference)
644 if not match:
645 patch_count += 1
646
showardeab66ce2009-12-23 00:03:56 +0000647 self.insert('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000648 {'base':kernel.base,
649 'kernel_hash':kernel.kernel_hash,
650 'printable':printable},
651 commit=commit)
652 kver = self.get_last_autonumber_value()
653
654 if patch_count > 0:
655 printable += ' p%d' % (kver)
showardeab66ce2009-12-23 00:03:56 +0000656 self.update('tko_kernels',
jadmanski0afbb632008-06-06 21:10:57 +0000657 {'printable':printable},
658 {'kernel_idx':kver})
659
660 for patch in kernel.patches:
661 self.insert_patch(kver, patch, commit=commit)
662 return kver
663
664
665 def insert_patch(self, kver, patch, commit = None):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700666 """Insert a kernel patch.
667
668 @param kver: The kernel version.
669 @param patch: The kernel patch object.
670 @param commit: If commit the transaction .
671 """
jadmanski0afbb632008-06-06 21:10:57 +0000672 print patch.reference
673 name = os.path.basename(patch.reference)[:80]
showardeab66ce2009-12-23 00:03:56 +0000674 self.insert('tko_patches',
jadmanski0afbb632008-06-06 21:10:57 +0000675 {'kernel_idx': kver,
676 'name':name,
677 'url':patch.reference,
678 'hash':patch.hash},
679 commit=commit)
680
681
jadmanski74eebf32008-07-15 20:04:42 +0000682 def find_test(self, job_idx, testname, subdir):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700683 """Find a test by name.
684
685 @param job_idx: The job index.
686 @param testname: The test name.
687 @param subdir: The test sub directory under the job directory.
688 """
jadmanski74eebf32008-07-15 20:04:42 +0000689 where = {'job_idx': job_idx , 'test': testname, 'subdir': subdir}
showardeab66ce2009-12-23 00:03:56 +0000690 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000691 if rows:
692 return rows[0][0]
693 else:
694 return None
695
696
697 def find_tests(self, job_idx):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700698 """Find all tests by job index.
699
700 @param job_idx: The job index.
701 @return: A list of tests.
702 """
jadmanski0afbb632008-06-06 21:10:57 +0000703 where = { 'job_idx':job_idx }
showardeab66ce2009-12-23 00:03:56 +0000704 rows = self.select('test_idx', 'tko_tests', where)
jadmanski0afbb632008-06-06 21:10:57 +0000705 if rows:
706 return [row[0] for row in rows]
707 else:
708 return []
709
710
711 def find_job(self, tag):
Michael Tang5f74ffd2016-10-31 10:34:53 -0700712 """Find a job by tag.
713
714 @param tag: The job tag name.
715 @return: The job object or None.
716 """
showardeab66ce2009-12-23 00:03:56 +0000717 rows = self.select('job_idx', 'tko_jobs', {'tag': tag})
jadmanski0afbb632008-06-06 21:10:57 +0000718 if rows:
719 return rows[0][0]
720 else:
721 return None
mblighaf25f062007-12-03 17:48:35 +0000722
723
mbligh96cf0512008-04-17 15:25:38 +0000724def _get_db_type():
jadmanski0afbb632008-06-06 21:10:57 +0000725 """Get the database type name to use from the global config."""
Jakob Juelich934f0dc2014-10-14 18:21:13 -0700726 get_value = global_config.global_config.get_config_value_with_fallback
727 return "db_" + get_value("AUTOTEST_WEB", "global_db_type", "db_type",
728 default="mysql")
mblighaf25f062007-12-03 17:48:35 +0000729
mbligh96cf0512008-04-17 15:25:38 +0000730
731def _get_error_class(class_name):
jadmanski0afbb632008-06-06 21:10:57 +0000732 """Retrieves the appropriate error class by name from the database
733 module."""
734 db_module = __import__("autotest_lib.tko." + _get_db_type(),
735 globals(), locals(), ["driver"])
736 return getattr(db_module.driver, class_name)
mbligh96cf0512008-04-17 15:25:38 +0000737
738
739def db(*args, **dargs):
jadmanski0afbb632008-06-06 21:10:57 +0000740 """Creates an instance of the database class with the arguments
741 provided in args and dargs, using the database type specified by
Michael Tang5f74ffd2016-10-31 10:34:53 -0700742 the global configuration (defaulting to mysql).
743
744 @param args: The db_type arguments.
745 @param dargs: The db_type named arguments.
746
747 @return: An db object.
748 """
jadmanski0afbb632008-06-06 21:10:57 +0000749 db_type = _get_db_type()
750 db_module = __import__("autotest_lib.tko." + db_type, globals(),
751 locals(), [db_type])
752 db = getattr(db_module, db_type)(*args, **dargs)
753 return db