blob: d7c433ca9e2176c6f99fabe888c739fafb33a69f [file] [log] [blame]
Shuqian Zhao54a5b672016-05-11 22:12:17 +00001# pylint: disable-msg=C0111
Aviv Keshet0b9cfc92013-02-05 11:36:02 -08002
mblighe8819cd2008-02-15 16:48:40 +00003"""\
4Functions to expose over the RPC interface.
5
6For all modify* and delete* functions that ask for an 'id' parameter to
7identify the object to operate on, the id may be either
8 * the database row ID
9 * the name of the object (label name, hostname, user login, etc.)
10 * a dictionary containing uniquely identifying field (this option should seldom
11 be used)
12
13When specifying foreign key fields (i.e. adding hosts to a label, or adding
14users to an ACL group), the given value may be either the database row ID or the
15name of the object.
16
17All get* functions return lists of dictionaries. Each dictionary represents one
18object and maps field names to values.
19
20Some examples:
21modify_host(2, hostname='myhost') # modify hostname of host with database ID 2
22modify_host('ipaj2', hostname='myhost') # modify hostname of host 'ipaj2'
23modify_test('sleeptest', test_type='Client', params=', seconds=60')
24delete_acl_group(1) # delete by ID
25delete_acl_group('Everyone') # delete by name
26acl_group_add_users('Everyone', ['mbligh', 'showard'])
27get_jobs(owner='showard', status='Queued')
28
mbligh93c80e62009-02-03 17:48:30 +000029See doctests/001_rpc_test.txt for (lots) more examples.
mblighe8819cd2008-02-15 16:48:40 +000030"""
31
32__author__ = 'showard@google.com (Steve Howard)'
33
Michael Tang6dc174e2016-05-31 23:13:42 -070034import ast
MK Ryu9c5fbbe2015-02-11 15:46:22 -080035import sys
showard29f7cd22009-04-29 21:16:24 +000036import datetime
Shuqian Zhao4c0d2902016-01-12 17:03:15 -080037import logging
MK Ryu9c5fbbe2015-02-11 15:46:22 -080038
Moises Osorio2dc7a102014-12-02 18:24:02 -080039from django.db.models import Count
showardcafd16e2009-05-29 18:37:49 +000040import common
Michael Tang6dc174e2016-05-31 23:13:42 -070041from autotest_lib.client.common_lib import control_data
Simran Basib6ec8ae2014-04-23 12:05:08 -070042from autotest_lib.client.common_lib import priorities
Simran Basi6157e8e2015-12-07 18:22:34 -080043from autotest_lib.client.common_lib.cros import dev_server
Gabe Black1e1c41b2015-02-04 23:55:15 -080044from autotest_lib.client.common_lib.cros.graphite import autotest_stats
showard6d7b2ff2009-06-10 00:16:47 +000045from autotest_lib.frontend.afe import control_file, rpc_utils
J. Richard Barnetteb5164d62015-04-13 12:59:31 -070046from autotest_lib.frontend.afe import models, model_logic, model_attributes
Simran Basib6ec8ae2014-04-23 12:05:08 -070047from autotest_lib.frontend.afe import site_rpc_interface
Moises Osorio2dc7a102014-12-02 18:24:02 -080048from autotest_lib.frontend.tko import models as tko_models
Jiaxi Luoaac54572014-06-04 13:57:02 -070049from autotest_lib.frontend.tko import rpc_interface as tko_rpc_interface
J. Richard Barnetteb5164d62015-04-13 12:59:31 -070050from autotest_lib.server import frontend
Simran Basi71206ef2014-08-13 13:51:18 -070051from autotest_lib.server import utils
Dan Shid215dbe2015-06-18 16:14:59 -070052from autotest_lib.server.cros import provision
Jiaxi Luo90190c92014-06-18 12:35:57 -070053from autotest_lib.server.cros.dynamic_suite import tools
Aviv Keshet7ee95862016-08-30 15:18:27 -070054from autotest_lib.server.lib import status_history
mblighe8819cd2008-02-15 16:48:40 +000055
Moises Osorio2dc7a102014-12-02 18:24:02 -080056
Gabe Black1e1c41b2015-02-04 23:55:15 -080057_timer = autotest_stats.Timer('rpc_interface')
Moises Osorio2dc7a102014-12-02 18:24:02 -080058
Eric Lid23bc192011-02-09 14:38:57 -080059def get_parameterized_autoupdate_image_url(job):
60 """Get the parameterized autoupdate image url from a parameterized job."""
61 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
62 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
beeps8bb1f7d2013-08-05 01:30:09 -070063 name='image')
Eric Lid23bc192011-02-09 14:38:57 -080064 para_set = job.parameterized_job.parameterizedjobparameter_set
65 job_test_para = para_set.get(test_parameter=image_parameter)
66 return job_test_para.parameter_value
67
68
mblighe8819cd2008-02-15 16:48:40 +000069# labels
70
mblighe8819cd2008-02-15 16:48:40 +000071def modify_label(id, **data):
MK Ryu8c554cf2015-06-12 11:45:50 -070072 """Modify a label.
73
74 @param id: id or name of a label. More often a label name.
75 @param data: New data for a label.
76 """
77 label_model = models.Label.smart_get(id)
MK Ryu8e2c2d02016-01-06 15:24:38 -080078 label_model.update_object(data)
MK Ryu8c554cf2015-06-12 11:45:50 -070079
80 # Master forwards the RPC to shards
81 if not utils.is_shard():
82 rpc_utils.fanout_rpc(label_model.host_set.all(), 'modify_label', False,
83 id=id, **data)
84
mblighe8819cd2008-02-15 16:48:40 +000085
86def delete_label(id):
MK Ryu8c554cf2015-06-12 11:45:50 -070087 """Delete a label.
88
89 @param id: id or name of a label. More often a label name.
90 """
91 label_model = models.Label.smart_get(id)
MK Ryu8e2c2d02016-01-06 15:24:38 -080092 # Hosts that have the label to be deleted. Save this info before
93 # the label is deleted to use it later.
94 hosts = []
95 for h in label_model.host_set.all():
96 hosts.append(models.Host.smart_get(h.id))
97 label_model.delete()
MK Ryu8c554cf2015-06-12 11:45:50 -070098
99 # Master forwards the RPC to shards
100 if not utils.is_shard():
MK Ryu8e2c2d02016-01-06 15:24:38 -0800101 rpc_utils.fanout_rpc(hosts, 'delete_label', False, id=id)
mblighe8819cd2008-02-15 16:48:40 +0000102
Prashanth Balasubramanian744898f2015-01-13 05:04:16 -0800103
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800104def add_label(name, ignore_exception_if_exists=False, **kwargs):
MK Ryucf027c62015-03-04 12:00:50 -0800105 """Adds a new label of a given name.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800106
107 @param name: label name.
108 @param ignore_exception_if_exists: If True and the exception was
109 thrown due to the duplicated label name when adding a label,
110 then suppress the exception. Default is False.
111 @param kwargs: keyword args that store more info about a label
112 other than the name.
113 @return: int/long id of a new label.
114 """
115 # models.Label.add_object() throws model_logic.ValidationError
116 # when it is given a label name that already exists.
117 # However, ValidationError can be thrown with different errors,
118 # and those errors should be thrown up to the call chain.
119 try:
120 label = models.Label.add_object(name=name, **kwargs)
121 except:
122 exc_info = sys.exc_info()
123 if ignore_exception_if_exists:
124 label = rpc_utils.get_label(name)
125 # If the exception is raised not because of duplicated
126 # "name", then raise the original exception.
127 if label is None:
128 raise exc_info[0], exc_info[1], exc_info[2]
129 else:
130 raise exc_info[0], exc_info[1], exc_info[2]
131 return label.id
132
133
134def add_label_to_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800135 """Adds a label of the given id to the given hosts only in local DB.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800136
137 @param id: id or name of a label. More often a label name.
138 @param hosts: The hostnames of hosts that need the label.
139
140 @raises models.Label.DoesNotExist: If the label with id doesn't exist.
141 """
142 label = models.Label.smart_get(id)
143 host_objs = models.Host.smart_get_bulk(hosts)
144 if label.platform:
145 models.Host.check_no_platform(host_objs)
146 label.host_set.add(*host_objs)
147
148
Kevin Chengbdfc57d2016-04-14 13:46:58 -0700149def _create_label_everywhere(id, hosts):
150 """
151 Yet another method to create labels.
152
153 ALERT! This method should be run only on master not shards!
154 DO NOT RUN THIS ON A SHARD!!! Deputies will hate you if you do!!!
155
156 This method exists primarily to serve label_add_hosts() and
157 host_add_labels(). Basically it pulls out the label check/add logic
158 from label_add_hosts() into this nice method that not only creates
159 the label but also tells the shards that service the hosts to also
160 create the label.
161
162 @param id: id or name of a label. More often a label name.
163 @param hosts: A list of hostnames or ids. More often hostnames.
164 """
165 try:
166 label = models.Label.smart_get(id)
167 except models.Label.DoesNotExist:
168 # This matches the type checks in smart_get, which is a hack
169 # in and off itself. The aim here is to create any non-existent
170 # label, which we cannot do if the 'id' specified isn't a label name.
171 if isinstance(id, basestring):
172 label = models.Label.smart_get(add_label(id))
173 else:
174 raise ValueError('Label id (%s) does not exist. Please specify '
175 'the argument, id, as a string (label name).'
176 % id)
177
178 # Make sure the label exists on the shard with the same id
179 # as it is on the master.
180 # It is possible that the label is already in a shard because
181 # we are adding a new label only to shards of hosts that the label
182 # is going to be attached.
183 # For example, we add a label L1 to a host in shard S1.
184 # Master and S1 will have L1 but other shards won't.
185 # Later, when we add the same label L1 to hosts in shards S1 and S2,
186 # S1 already has the label but S2 doesn't.
187 # S2 should have the new label without any problem.
188 # We ignore exception in such a case.
189 host_objs = models.Host.smart_get_bulk(hosts)
190 rpc_utils.fanout_rpc(
191 host_objs, 'add_label', include_hostnames=False,
192 name=label.name, ignore_exception_if_exists=True,
193 id=label.id, platform=label.platform)
194
195
MK Ryufbb002c2015-06-08 14:13:16 -0700196@rpc_utils.route_rpc_to_master
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800197def label_add_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800198 """Adds a label with the given id to the given hosts.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800199
200 This method should be run only on master not shards.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800201 The given label will be created if it doesn't exist, provided the `id`
202 supplied is a label name not an int/long id.
203
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800204 @param id: id or name of a label. More often a label name.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800205 @param hosts: A list of hostnames or ids. More often hostnames.
206
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800207 @raises ValueError: If the id specified is an int/long (label id)
208 while the label does not exist.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800209 """
Kevin Chengbdfc57d2016-04-14 13:46:58 -0700210 # Create the label.
211 _create_label_everywhere(id, hosts)
212
213 # Add it to the master.
MK Ryu8e2c2d02016-01-06 15:24:38 -0800214 add_label_to_hosts(id, hosts)
MK Ryucf027c62015-03-04 12:00:50 -0800215
Kevin Chengbdfc57d2016-04-14 13:46:58 -0700216 # Add it to the shards.
MK Ryucf027c62015-03-04 12:00:50 -0800217 host_objs = models.Host.smart_get_bulk(hosts)
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800218 rpc_utils.fanout_rpc(host_objs, 'add_label_to_hosts', id=id)
showardbbabf502008-06-06 00:02:02 +0000219
220
MK Ryucf027c62015-03-04 12:00:50 -0800221def remove_label_from_hosts(id, hosts):
222 """Removes a label of the given id from the given hosts only in local DB.
223
224 @param id: id or name of a label.
225 @param hosts: The hostnames of hosts that need to remove the label from.
226 """
showardbe3ec042008-11-12 18:16:07 +0000227 host_objs = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000228 models.Label.smart_get(id).host_set.remove(*host_objs)
showardbbabf502008-06-06 00:02:02 +0000229
230
MK Ryufbb002c2015-06-08 14:13:16 -0700231@rpc_utils.route_rpc_to_master
MK Ryucf027c62015-03-04 12:00:50 -0800232def label_remove_hosts(id, hosts):
233 """Removes a label of the given id from the given hosts.
234
235 This method should be run only on master not shards.
236
237 @param id: id or name of a label.
238 @param hosts: A list of hostnames or ids. More often hostnames.
239 """
MK Ryucf027c62015-03-04 12:00:50 -0800240 host_objs = models.Host.smart_get_bulk(hosts)
MK Ryu26f0c932015-05-28 18:14:33 -0700241 remove_label_from_hosts(id, hosts)
242
MK Ryu8e2c2d02016-01-06 15:24:38 -0800243 rpc_utils.fanout_rpc(host_objs, 'remove_label_from_hosts', id=id)
244
MK Ryucf027c62015-03-04 12:00:50 -0800245
Jiaxi Luo31874592014-06-11 10:36:35 -0700246def get_labels(exclude_filters=(), **filter_data):
showardc92da832009-04-07 18:14:34 +0000247 """\
Jiaxi Luo31874592014-06-11 10:36:35 -0700248 @param exclude_filters: A sequence of dictionaries of filters.
249
showardc92da832009-04-07 18:14:34 +0000250 @returns A sequence of nested dictionaries of label information.
251 """
Jiaxi Luo31874592014-06-11 10:36:35 -0700252 labels = models.Label.query_objects(filter_data)
253 for exclude_filter in exclude_filters:
254 labels = labels.exclude(**exclude_filter)
255 return rpc_utils.prepare_rows_as_nested_dicts(labels, ('atomic_group',))
showardc92da832009-04-07 18:14:34 +0000256
257
258# atomic groups
259
showarde9450c92009-06-30 01:58:52 +0000260def add_atomic_group(name, max_number_of_machines=None, description=None):
showardc92da832009-04-07 18:14:34 +0000261 return models.AtomicGroup.add_object(
262 name=name, max_number_of_machines=max_number_of_machines,
263 description=description).id
264
265
266def modify_atomic_group(id, **data):
267 models.AtomicGroup.smart_get(id).update_object(data)
268
269
270def delete_atomic_group(id):
271 models.AtomicGroup.smart_get(id).delete()
272
273
274def atomic_group_add_labels(id, labels):
275 label_objs = models.Label.smart_get_bulk(labels)
276 models.AtomicGroup.smart_get(id).label_set.add(*label_objs)
277
278
279def atomic_group_remove_labels(id, labels):
280 label_objs = models.Label.smart_get_bulk(labels)
281 models.AtomicGroup.smart_get(id).label_set.remove(*label_objs)
282
283
284def get_atomic_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000285 return rpc_utils.prepare_for_serialization(
showardc92da832009-04-07 18:14:34 +0000286 models.AtomicGroup.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000287
288
289# hosts
290
Matthew Sartori68186332015-04-27 17:19:53 -0700291def add_host(hostname, status=None, locked=None, lock_reason='', protection=None):
292 if locked and not lock_reason:
293 raise model_logic.ValidationError(
294 {'locked': 'Please provide a reason for locking when adding host.'})
295
jadmanski0afbb632008-06-06 21:10:57 +0000296 return models.Host.add_object(hostname=hostname, status=status,
Matthew Sartori68186332015-04-27 17:19:53 -0700297 locked=locked, lock_reason=lock_reason,
298 protection=protection).id
mblighe8819cd2008-02-15 16:48:40 +0000299
300
MK Ryu33889612015-09-04 14:32:35 -0700301@rpc_utils.route_rpc_to_master
302def modify_host(id, **kwargs):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700303 """Modify local attributes of a host.
304
305 If this is called on the master, but the host is assigned to a shard, this
MK Ryu33889612015-09-04 14:32:35 -0700306 will call `modify_host_local` RPC to the responsible shard. This means if
307 a host is being locked using this function, this change will also propagate
308 to shards.
309 When this is called on a shard, the shard just routes the RPC to the master
310 and does nothing.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700311
312 @param id: id of the host to modify.
MK Ryu33889612015-09-04 14:32:35 -0700313 @param kwargs: key=value pairs of values to set on the host.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700314 """
MK Ryu33889612015-09-04 14:32:35 -0700315 rpc_utils.check_modify_host(kwargs)
showardce7c0922009-09-11 18:39:24 +0000316 host = models.Host.smart_get(id)
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800317 try:
318 rpc_utils.check_modify_host_locking(host, kwargs)
319 except model_logic.ValidationError as e:
320 if not kwargs.get('force_modify_locking', False):
321 raise
322 logging.exception('The following exception will be ignored and lock '
323 'modification will be enforced. %s', e)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700324
MK Ryud53e1492015-12-15 12:09:03 -0800325 # This is required to make `lock_time` for a host be exactly same
326 # between the master and a shard.
327 if kwargs.get('locked', None) and 'lock_time' not in kwargs:
328 kwargs['lock_time'] = datetime.datetime.now()
MK Ryu8e2c2d02016-01-06 15:24:38 -0800329 host.update_object(kwargs)
MK Ryud53e1492015-12-15 12:09:03 -0800330
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800331 # force_modifying_locking is not an internal field in database, remove.
332 kwargs.pop('force_modify_locking', None)
MK Ryu33889612015-09-04 14:32:35 -0700333 rpc_utils.fanout_rpc([host], 'modify_host_local',
334 include_hostnames=False, id=id, **kwargs)
mblighe8819cd2008-02-15 16:48:40 +0000335
336
MK Ryu33889612015-09-04 14:32:35 -0700337def modify_host_local(id, **kwargs):
338 """Modify host attributes in local DB.
339
340 @param id: Host id.
341 @param kwargs: key=value pairs of values to set on the host.
342 """
343 models.Host.smart_get(id).update_object(kwargs)
344
345
346@rpc_utils.route_rpc_to_master
showard276f9442009-05-20 00:33:16 +0000347def modify_hosts(host_filter_data, update_data):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700348 """Modify local attributes of multiple hosts.
349
350 If this is called on the master, but one of the hosts in that match the
MK Ryu33889612015-09-04 14:32:35 -0700351 filters is assigned to a shard, this will call `modify_hosts_local` RPC
352 to the responsible shard.
353 When this is called on a shard, the shard just routes the RPC to the master
354 and does nothing.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700355
356 The filters are always applied on the master, not on the shards. This means
357 if the states of a host differ on the master and a shard, the state on the
358 master will be used. I.e. this means:
359 A host was synced to Shard 1. On Shard 1 the status of the host was set to
360 'Repair Failed'.
361 - A call to modify_hosts with host_filter_data={'status': 'Ready'} will
362 update the host (both on the shard and on the master), because the state
363 of the host as the master knows it is still 'Ready'.
364 - A call to modify_hosts with host_filter_data={'status': 'Repair failed'
365 will not update the host, because the filter doesn't apply on the master.
366
showardbe0d8692009-08-20 23:42:44 +0000367 @param host_filter_data: Filters out which hosts to modify.
368 @param update_data: A dictionary with the changes to make to the hosts.
showard276f9442009-05-20 00:33:16 +0000369 """
MK Ryu93161712015-12-21 10:41:32 -0800370 update_data = update_data.copy()
showardbe0d8692009-08-20 23:42:44 +0000371 rpc_utils.check_modify_host(update_data)
showard276f9442009-05-20 00:33:16 +0000372 hosts = models.Host.query_objects(host_filter_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700373
374 affected_shard_hostnames = set()
375 affected_host_ids = []
376
Alex Miller9658a952013-05-14 16:40:02 -0700377 # Check all hosts before changing data for exception safety.
378 for host in hosts:
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800379 try:
380 rpc_utils.check_modify_host_locking(host, update_data)
381 except model_logic.ValidationError as e:
382 if not update_data.get('force_modify_locking', False):
383 raise
384 logging.exception('The following exception will be ignored and '
385 'lock modification will be enforced. %s', e)
386
Jakob Juelich50e91f72014-10-01 12:43:23 -0700387 if host.shard:
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800388 affected_shard_hostnames.add(host.shard.rpc_hostname())
Jakob Juelich50e91f72014-10-01 12:43:23 -0700389 affected_host_ids.append(host.id)
390
MK Ryud53e1492015-12-15 12:09:03 -0800391 # This is required to make `lock_time` for a host be exactly same
392 # between the master and a shard.
393 if update_data.get('locked', None) and 'lock_time' not in update_data:
394 update_data['lock_time'] = datetime.datetime.now()
MK Ryu8e2c2d02016-01-06 15:24:38 -0800395 for host in hosts:
396 host.update_object(update_data)
MK Ryud53e1492015-12-15 12:09:03 -0800397
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800398 update_data.pop('force_modify_locking', None)
MK Ryu33889612015-09-04 14:32:35 -0700399 # Caution: Changing the filter from the original here. See docstring.
400 rpc_utils.run_rpc_on_multiple_hostnames(
401 'modify_hosts_local', affected_shard_hostnames,
Jakob Juelich50e91f72014-10-01 12:43:23 -0700402 host_filter_data={'id__in': affected_host_ids},
403 update_data=update_data)
404
showard276f9442009-05-20 00:33:16 +0000405
MK Ryu33889612015-09-04 14:32:35 -0700406def modify_hosts_local(host_filter_data, update_data):
407 """Modify attributes of hosts in local DB.
408
409 @param host_filter_data: Filters out which hosts to modify.
410 @param update_data: A dictionary with the changes to make to the hosts.
411 """
412 for host in models.Host.query_objects(host_filter_data):
413 host.update_object(update_data)
414
415
MK Ryufbb002c2015-06-08 14:13:16 -0700416def add_labels_to_host(id, labels):
417 """Adds labels to a given host only in local DB.
showardcafd16e2009-05-29 18:37:49 +0000418
MK Ryufbb002c2015-06-08 14:13:16 -0700419 @param id: id or hostname for a host.
420 @param labels: ids or names for labels.
421 """
422 label_objs = models.Label.smart_get_bulk(labels)
423 models.Host.smart_get(id).labels.add(*label_objs)
424
425
426@rpc_utils.route_rpc_to_master
427def host_add_labels(id, labels):
428 """Adds labels to a given host.
429
430 @param id: id or hostname for a host.
431 @param labels: ids or names for labels.
432
433 @raises ValidationError: If adding more than one platform label.
434 """
Kevin Chengbdfc57d2016-04-14 13:46:58 -0700435 # Create the labels on the master/shards.
436 for label in labels:
437 _create_label_everywhere(label, [id])
438
MK Ryufbb002c2015-06-08 14:13:16 -0700439 label_objs = models.Label.smart_get_bulk(labels)
440 platforms = [label.name for label in label_objs if label.platform]
showardcafd16e2009-05-29 18:37:49 +0000441 if len(platforms) > 1:
442 raise model_logic.ValidationError(
443 {'labels': 'Adding more than one platform label: %s' %
444 ', '.join(platforms)})
MK Ryufbb002c2015-06-08 14:13:16 -0700445
446 host_obj = models.Host.smart_get(id)
showardcafd16e2009-05-29 18:37:49 +0000447 if len(platforms) == 1:
MK Ryufbb002c2015-06-08 14:13:16 -0700448 models.Host.check_no_platform([host_obj])
MK Ryu8e2c2d02016-01-06 15:24:38 -0800449 add_labels_to_host(id, labels)
MK Ryufbb002c2015-06-08 14:13:16 -0700450
451 rpc_utils.fanout_rpc([host_obj], 'add_labels_to_host', False,
452 id=id, labels=labels)
mblighe8819cd2008-02-15 16:48:40 +0000453
454
MK Ryufbb002c2015-06-08 14:13:16 -0700455def remove_labels_from_host(id, labels):
456 """Removes labels from a given host only in local DB.
457
458 @param id: id or hostname for a host.
459 @param labels: ids or names for labels.
460 """
461 label_objs = models.Label.smart_get_bulk(labels)
462 models.Host.smart_get(id).labels.remove(*label_objs)
463
464
465@rpc_utils.route_rpc_to_master
mblighe8819cd2008-02-15 16:48:40 +0000466def host_remove_labels(id, labels):
MK Ryufbb002c2015-06-08 14:13:16 -0700467 """Removes labels from a given host.
468
469 @param id: id or hostname for a host.
470 @param labels: ids or names for labels.
471 """
MK Ryu8e2c2d02016-01-06 15:24:38 -0800472 remove_labels_from_host(id, labels)
473
MK Ryufbb002c2015-06-08 14:13:16 -0700474 host_obj = models.Host.smart_get(id)
475 rpc_utils.fanout_rpc([host_obj], 'remove_labels_from_host', False,
476 id=id, labels=labels)
mblighe8819cd2008-02-15 16:48:40 +0000477
478
MK Ryuacf35922014-10-03 14:56:49 -0700479def get_host_attribute(attribute, **host_filter_data):
480 """
481 @param attribute: string name of attribute
482 @param host_filter_data: filter data to apply to Hosts to choose hosts to
483 act upon
484 """
485 hosts = rpc_utils.get_host_query((), False, False, True, host_filter_data)
486 hosts = list(hosts)
487 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
488 'attribute_list')
489 host_attr_dicts = []
490 for host_obj in hosts:
491 for attr_obj in host_obj.attribute_list:
492 if attr_obj.attribute == attribute:
493 host_attr_dicts.append(attr_obj.get_object_dict())
494 return rpc_utils.prepare_for_serialization(host_attr_dicts)
495
496
showard0957a842009-05-11 19:25:08 +0000497def set_host_attribute(attribute, value, **host_filter_data):
498 """
MK Ryu26f0c932015-05-28 18:14:33 -0700499 @param attribute: string name of attribute
500 @param value: string, or None to delete an attribute
501 @param host_filter_data: filter data to apply to Hosts to choose hosts to
502 act upon
showard0957a842009-05-11 19:25:08 +0000503 """
504 assert host_filter_data # disallow accidental actions on all hosts
505 hosts = models.Host.query_objects(host_filter_data)
506 models.AclGroup.check_for_acl_violation_hosts(hosts)
MK Ryu8e2c2d02016-01-06 15:24:38 -0800507 for host in hosts:
508 host.set_or_delete_attribute(attribute, value)
showard0957a842009-05-11 19:25:08 +0000509
MK Ryu26f0c932015-05-28 18:14:33 -0700510 # Master forwards this RPC to shards.
511 if not utils.is_shard():
512 rpc_utils.fanout_rpc(hosts, 'set_host_attribute', False,
513 attribute=attribute, value=value, **host_filter_data)
514
showard0957a842009-05-11 19:25:08 +0000515
Jakob Juelich50e91f72014-10-01 12:43:23 -0700516@rpc_utils.forward_single_host_rpc_to_shard
mblighe8819cd2008-02-15 16:48:40 +0000517def delete_host(id):
jadmanski0afbb632008-06-06 21:10:57 +0000518 models.Host.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000519
520
showard87cc38f2009-08-20 23:37:04 +0000521def get_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
Dan Shi37df54d2015-12-14 11:16:28 -0800522 exclude_atomic_group_hosts=False, valid_only=True,
523 include_current_job=False, **filter_data):
524 """Get a list of dictionaries which contains the information of hosts.
525
showard87cc38f2009-08-20 23:37:04 +0000526 @param multiple_labels: match hosts in all of the labels given. Should
527 be a list of label names.
528 @param exclude_only_if_needed_labels: Exclude hosts with at least one
529 "only_if_needed" label applied.
530 @param exclude_atomic_group_hosts: Exclude hosts that have one or more
531 atomic group labels associated with them.
Dan Shi37df54d2015-12-14 11:16:28 -0800532 @param include_current_job: Set to True to include ids of currently running
533 job and special task.
jadmanski0afbb632008-06-06 21:10:57 +0000534 """
showard43a3d262008-11-12 18:17:05 +0000535 hosts = rpc_utils.get_host_query(multiple_labels,
536 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000537 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000538 valid_only, filter_data)
showard0957a842009-05-11 19:25:08 +0000539 hosts = list(hosts)
540 models.Host.objects.populate_relationships(hosts, models.Label,
541 'label_list')
542 models.Host.objects.populate_relationships(hosts, models.AclGroup,
543 'acl_list')
544 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
545 'attribute_list')
showard43a3d262008-11-12 18:17:05 +0000546 host_dicts = []
547 for host_obj in hosts:
548 host_dict = host_obj.get_object_dict()
showard0957a842009-05-11 19:25:08 +0000549 host_dict['labels'] = [label.name for label in host_obj.label_list]
showard909c9142009-07-07 20:54:42 +0000550 host_dict['platform'], host_dict['atomic_group'] = (rpc_utils.
551 find_platform_and_atomic_group(host_obj))
showard0957a842009-05-11 19:25:08 +0000552 host_dict['acls'] = [acl.name for acl in host_obj.acl_list]
553 host_dict['attributes'] = dict((attribute.attribute, attribute.value)
554 for attribute in host_obj.attribute_list)
Dan Shi37df54d2015-12-14 11:16:28 -0800555 if include_current_job:
556 host_dict['current_job'] = None
557 host_dict['current_special_task'] = None
558 entries = models.HostQueueEntry.objects.filter(
559 host_id=host_dict['id'], active=True, complete=False)
560 if entries:
561 host_dict['current_job'] = (
562 entries[0].get_object_dict()['job'])
563 tasks = models.SpecialTask.objects.filter(
564 host_id=host_dict['id'], is_active=True, is_complete=False)
565 if tasks:
566 host_dict['current_special_task'] = (
567 '%d-%s' % (tasks[0].get_object_dict()['id'],
568 tasks[0].get_object_dict()['task'].lower()))
showard43a3d262008-11-12 18:17:05 +0000569 host_dicts.append(host_dict)
570 return rpc_utils.prepare_for_serialization(host_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000571
572
showard87cc38f2009-08-20 23:37:04 +0000573def get_num_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000574 exclude_atomic_group_hosts=False, valid_only=True,
575 **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000576 """
577 Same parameters as get_hosts().
578
579 @returns The number of matching hosts.
580 """
showard43a3d262008-11-12 18:17:05 +0000581 hosts = rpc_utils.get_host_query(multiple_labels,
582 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000583 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000584 valid_only, filter_data)
showard43a3d262008-11-12 18:17:05 +0000585 return hosts.count()
showard1385b162008-03-13 15:59:40 +0000586
mblighe8819cd2008-02-15 16:48:40 +0000587
588# tests
589
showard909c7a62008-07-15 21:52:38 +0000590def add_test(name, test_type, path, author=None, dependencies=None,
showard3d9899a2008-07-31 02:11:58 +0000591 experimental=True, run_verify=None, test_class=None,
showard909c7a62008-07-15 21:52:38 +0000592 test_time=None, test_category=None, description=None,
593 sync_count=1):
jadmanski0afbb632008-06-06 21:10:57 +0000594 return models.Test.add_object(name=name, test_type=test_type, path=path,
showard909c7a62008-07-15 21:52:38 +0000595 author=author, dependencies=dependencies,
596 experimental=experimental,
597 run_verify=run_verify, test_time=test_time,
598 test_category=test_category,
599 sync_count=sync_count,
jadmanski0afbb632008-06-06 21:10:57 +0000600 test_class=test_class,
601 description=description).id
mblighe8819cd2008-02-15 16:48:40 +0000602
603
604def modify_test(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000605 models.Test.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000606
607
608def delete_test(id):
jadmanski0afbb632008-06-06 21:10:57 +0000609 models.Test.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000610
611
612def get_tests(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000613 return rpc_utils.prepare_for_serialization(
614 models.Test.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000615
616
Moises Osorio2dc7a102014-12-02 18:24:02 -0800617@_timer.decorate
618def get_tests_status_counts_by_job_name_label(job_name_prefix, label_name):
619 """Gets the counts of all passed and failed tests from the matching jobs.
620
621 @param job_name_prefix: Name prefix of the jobs to get the summary from, e.g.,
622 'butterfly-release/R40-6457.21.0/bvt-cq/'.
623 @param label_name: Label that must be set in the jobs, e.g.,
624 'cros-version:butterfly-release/R40-6457.21.0'.
625
626 @returns A summary of the counts of all the passed and failed tests.
627 """
628 job_ids = list(models.Job.objects.filter(
629 name__startswith=job_name_prefix,
630 dependency_labels__name=label_name).values_list(
631 'pk', flat=True))
632 summary = {'passed': 0, 'failed': 0}
633 if not job_ids:
634 return summary
635
636 counts = (tko_models.TestView.objects.filter(
637 afe_job_id__in=job_ids).exclude(
638 test_name='SERVER_JOB').exclude(
639 test_name__startswith='CLIENT_JOB').values(
640 'status').annotate(
641 count=Count('status')))
642 for status in counts:
643 if status['status'] == 'GOOD':
644 summary['passed'] += status['count']
645 else:
646 summary['failed'] += status['count']
647 return summary
648
649
showard2b9a88b2008-06-13 20:55:03 +0000650# profilers
651
652def add_profiler(name, description=None):
653 return models.Profiler.add_object(name=name, description=description).id
654
655
656def modify_profiler(id, **data):
657 models.Profiler.smart_get(id).update_object(data)
658
659
660def delete_profiler(id):
661 models.Profiler.smart_get(id).delete()
662
663
664def get_profilers(**filter_data):
665 return rpc_utils.prepare_for_serialization(
666 models.Profiler.list_objects(filter_data))
667
668
mblighe8819cd2008-02-15 16:48:40 +0000669# users
670
671def add_user(login, access_level=None):
jadmanski0afbb632008-06-06 21:10:57 +0000672 return models.User.add_object(login=login, access_level=access_level).id
mblighe8819cd2008-02-15 16:48:40 +0000673
674
675def modify_user(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000676 models.User.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000677
678
679def delete_user(id):
jadmanski0afbb632008-06-06 21:10:57 +0000680 models.User.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000681
682
683def get_users(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000684 return rpc_utils.prepare_for_serialization(
685 models.User.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000686
687
688# acl groups
689
690def add_acl_group(name, description=None):
showard04f2cd82008-07-25 20:53:31 +0000691 group = models.AclGroup.add_object(name=name, description=description)
showard64a95952010-01-13 21:27:16 +0000692 group.users.add(models.User.current_user())
showard04f2cd82008-07-25 20:53:31 +0000693 return group.id
mblighe8819cd2008-02-15 16:48:40 +0000694
695
696def modify_acl_group(id, **data):
showard04f2cd82008-07-25 20:53:31 +0000697 group = models.AclGroup.smart_get(id)
698 group.check_for_acl_violation_acl_group()
699 group.update_object(data)
700 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000701
702
703def acl_group_add_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000704 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000705 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000706 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000707 group.users.add(*users)
mblighe8819cd2008-02-15 16:48:40 +0000708
709
710def acl_group_remove_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000711 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000712 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000713 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000714 group.users.remove(*users)
showard04f2cd82008-07-25 20:53:31 +0000715 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000716
717
718def acl_group_add_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000719 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000720 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000721 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000722 group.hosts.add(*hosts)
showard08f981b2008-06-24 21:59:03 +0000723 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000724
725
726def acl_group_remove_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000727 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000728 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000729 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000730 group.hosts.remove(*hosts)
showard08f981b2008-06-24 21:59:03 +0000731 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000732
733
734def delete_acl_group(id):
jadmanski0afbb632008-06-06 21:10:57 +0000735 models.AclGroup.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000736
737
738def get_acl_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000739 acl_groups = models.AclGroup.list_objects(filter_data)
740 for acl_group in acl_groups:
741 acl_group_obj = models.AclGroup.objects.get(id=acl_group['id'])
742 acl_group['users'] = [user.login
743 for user in acl_group_obj.users.all()]
744 acl_group['hosts'] = [host.hostname
745 for host in acl_group_obj.hosts.all()]
746 return rpc_utils.prepare_for_serialization(acl_groups)
mblighe8819cd2008-02-15 16:48:40 +0000747
748
749# jobs
750
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700751def generate_control_file(tests=(), profilers=(),
showard91f85102009-10-12 20:34:52 +0000752 client_control_file='', use_container=False,
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700753 profile_only=None, db_tests=True,
754 test_source_build=None):
jadmanski0afbb632008-06-06 21:10:57 +0000755 """
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700756 Generates a client-side control file to run tests.
mbligh120351e2009-01-24 01:40:45 +0000757
Matthew Sartori10438092015-06-24 14:30:18 -0700758 @param tests List of tests to run. See db_tests for more information.
mbligh120351e2009-01-24 01:40:45 +0000759 @param profilers List of profilers to activate during the job.
760 @param client_control_file The contents of a client-side control file to
761 run at the end of all tests. If this is supplied, all tests must be
762 client side.
763 TODO: in the future we should support server control files directly
764 to wrap with a kernel. That'll require changing the parameter
765 name and adding a boolean to indicate if it is a client or server
766 control file.
767 @param use_container unused argument today. TODO: Enable containers
768 on the host during a client side test.
showard91f85102009-10-12 20:34:52 +0000769 @param profile_only A boolean that indicates what default profile_only
770 mode to use in the control file. Passing None will generate a
771 control file that does not explcitly set the default mode at all.
Matthew Sartori10438092015-06-24 14:30:18 -0700772 @param db_tests: if True, the test object can be found in the database
773 backing the test model. In this case, tests is a tuple
774 of test IDs which are used to retrieve the test objects
775 from the database. If False, tests is a tuple of test
776 dictionaries stored client-side in the AFE.
Michael Tang84a2ecf2016-06-07 15:10:53 -0700777 @param test_source_build: Build to be used to retrieve test code. Default
778 to None.
mbligh120351e2009-01-24 01:40:45 +0000779
780 @returns a dict with the following keys:
781 control_file: str, The control file text.
782 is_server: bool, is the control file a server-side control file?
783 synch_count: How many machines the job uses per autoserv execution.
784 synch_count == 1 means the job is asynchronous.
785 dependencies: A list of the names of labels on which the job depends.
786 """
showardd86debe2009-06-10 17:37:56 +0000787 if not tests and not client_control_file:
showard2bab8f42008-11-12 18:15:22 +0000788 return dict(control_file='', is_server=False, synch_count=1,
showard989f25d2008-10-01 11:38:11 +0000789 dependencies=[])
mblighe8819cd2008-02-15 16:48:40 +0000790
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700791 cf_info, test_objects, profiler_objects = (
792 rpc_utils.prepare_generate_control_file(tests, profilers,
793 db_tests))
showard989f25d2008-10-01 11:38:11 +0000794 cf_info['control_file'] = control_file.generate_control(
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700795 tests=test_objects, profilers=profiler_objects,
796 is_server=cf_info['is_server'],
showard232b7ae2009-11-10 00:46:48 +0000797 client_control_file=client_control_file, profile_only=profile_only,
Michael Tang84a2ecf2016-06-07 15:10:53 -0700798 test_source_build=test_source_build)
showard989f25d2008-10-01 11:38:11 +0000799 return cf_info
mblighe8819cd2008-02-15 16:48:40 +0000800
801
Shuqian Zhao54a5b672016-05-11 22:12:17 +0000802def create_parameterized_job(name, priority, test, parameters, kernel=None,
803 label=None, profilers=(), profiler_parameters=None,
804 use_container=False, profile_only=None,
805 upload_kernel_config=False, hosts=(),
806 meta_hosts=(), one_time_hosts=(),
807 atomic_group_name=None, synch_count=None,
808 is_template=False, timeout=None,
809 timeout_mins=None, max_runtime_mins=None,
810 run_verify=False, email_list='', dependencies=(),
811 reboot_before=None, reboot_after=None,
812 parse_failed_repair=None, hostless=False,
813 keyvals=None, drone_set=None, run_reset=True,
814 require_ssp=None):
815 """
816 Creates and enqueues a parameterized job.
817
818 Most parameters a combination of the parameters for generate_control_file()
819 and create_job(), with the exception of:
820
821 @param test name or ID of the test to run
822 @param parameters a map of parameter name ->
823 tuple of (param value, param type)
824 @param profiler_parameters a dictionary of parameters for the profilers:
825 key: profiler name
826 value: dict of param name -> tuple of
827 (param value,
828 param type)
829 """
830 # Save the values of the passed arguments here. What we're going to do with
831 # them is pass them all to rpc_utils.get_create_job_common_args(), which
832 # will extract the subset of these arguments that apply for
833 # rpc_utils.create_job_common(), which we then pass in to that function.
834 args = locals()
835
836 # Set up the parameterized job configs
837 test_obj = models.Test.smart_get(test)
838 control_type = test_obj.test_type
839
840 try:
841 label = models.Label.smart_get(label)
842 except models.Label.DoesNotExist:
843 label = None
844
845 kernel_objs = models.Kernel.create_kernels(kernel)
846 profiler_objs = [models.Profiler.smart_get(profiler)
847 for profiler in profilers]
848
849 parameterized_job = models.ParameterizedJob.objects.create(
850 test=test_obj, label=label, use_container=use_container,
851 profile_only=profile_only,
852 upload_kernel_config=upload_kernel_config)
853 parameterized_job.kernels.add(*kernel_objs)
854
855 for profiler in profiler_objs:
856 parameterized_profiler = models.ParameterizedJobProfiler.objects.create(
857 parameterized_job=parameterized_job,
858 profiler=profiler)
859 profiler_params = profiler_parameters.get(profiler.name, {})
860 for name, (value, param_type) in profiler_params.iteritems():
861 models.ParameterizedJobProfilerParameter.objects.create(
862 parameterized_job_profiler=parameterized_profiler,
863 parameter_name=name,
864 parameter_value=value,
865 parameter_type=param_type)
866
867 try:
868 for parameter in test_obj.testparameter_set.all():
869 if parameter.name in parameters:
870 param_value, param_type = parameters.pop(parameter.name)
871 parameterized_job.parameterizedjobparameter_set.create(
872 test_parameter=parameter, parameter_value=param_value,
873 parameter_type=param_type)
874
875 if parameters:
876 raise Exception('Extra parameters remain: %r' % parameters)
877
878 return rpc_utils.create_job_common(
879 parameterized_job=parameterized_job.id,
880 control_type=control_type,
881 **rpc_utils.get_create_job_common_args(args))
882 except:
883 parameterized_job.delete()
884 raise
885
886
Simran Basib6ec8ae2014-04-23 12:05:08 -0700887def create_job_page_handler(name, priority, control_file, control_type,
Dan Shid215dbe2015-06-18 16:14:59 -0700888 image=None, hostless=False, firmware_rw_build=None,
889 firmware_ro_build=None, test_source_build=None,
Michael Tang84a2ecf2016-06-07 15:10:53 -0700890 is_cloning=False, **kwargs):
Simran Basib6ec8ae2014-04-23 12:05:08 -0700891 """\
892 Create and enqueue a job.
893
894 @param name name of this job
895 @param priority Integer priority of this job. Higher is more important.
896 @param control_file String contents of the control file.
897 @param control_type Type of control file, Client or Server.
Dan Shid215dbe2015-06-18 16:14:59 -0700898 @param image: ChromeOS build to be installed in the dut. Default to None.
899 @param firmware_rw_build: Firmware build to update RW firmware. Default to
900 None, i.e., RW firmware will not be updated.
901 @param firmware_ro_build: Firmware build to update RO firmware. Default to
902 None, i.e., RO firmware will not be updated.
903 @param test_source_build: Build to be used to retrieve test code. Default
904 to None.
Michael Tang6dc174e2016-05-31 23:13:42 -0700905 @param is_cloning: True if creating a cloning job.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700906 @param kwargs extra args that will be required by create_suite_job or
907 create_job.
908
909 @returns The created Job id number.
910 """
Michael Tang6dc174e2016-05-31 23:13:42 -0700911 if is_cloning:
912 logging.info('Start to clone a new job')
Shuqian Zhao61f5d312016-08-05 17:15:23 -0700913 # When cloning a job, hosts and meta_hosts should not exist together,
914 # which would cause host-scheduler to schedule two hqe jobs to one host
915 # at the same time, and crash itself. Clear meta_hosts for this case.
916 if kwargs.get('hosts') and kwargs.get('meta_hosts'):
917 kwargs['meta_hosts'] = []
Michael Tang6dc174e2016-05-31 23:13:42 -0700918 else:
919 logging.info('Start to create a new job')
Simran Basib6ec8ae2014-04-23 12:05:08 -0700920 control_file = rpc_utils.encode_ascii(control_file)
Jiaxi Luodd67beb2014-07-18 16:28:31 -0700921 if not control_file:
922 raise model_logic.ValidationError({
923 'control_file' : "Control file cannot be empty"})
Simran Basib6ec8ae2014-04-23 12:05:08 -0700924
925 if image and hostless:
Dan Shid215dbe2015-06-18 16:14:59 -0700926 builds = {}
927 builds[provision.CROS_VERSION_PREFIX] = image
928 if firmware_rw_build:
Dan Shi0723bf52015-06-24 10:52:38 -0700929 builds[provision.FW_RW_VERSION_PREFIX] = firmware_rw_build
Dan Shid215dbe2015-06-18 16:14:59 -0700930 if firmware_ro_build:
931 builds[provision.FW_RO_VERSION_PREFIX] = firmware_ro_build
Simran Basib6ec8ae2014-04-23 12:05:08 -0700932 return site_rpc_interface.create_suite_job(
933 name=name, control_file=control_file, priority=priority,
Michael Tang6dc174e2016-05-31 23:13:42 -0700934 builds=builds, test_source_build=test_source_build,
935 is_cloning=is_cloning, **kwargs)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700936 return create_job(name, priority, control_file, control_type, image=image,
Michael Tang6dc174e2016-05-31 23:13:42 -0700937 hostless=hostless, is_cloning=is_cloning, **kwargs)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700938
939
MK Ryue301eb72015-06-25 12:51:02 -0700940@rpc_utils.route_rpc_to_master
showard12f3e322009-05-13 21:27:42 +0000941def create_job(name, priority, control_file, control_type,
942 hosts=(), meta_hosts=(), one_time_hosts=(),
943 atomic_group_name=None, synch_count=None, is_template=False,
Simran Basi7e605742013-11-12 13:43:36 -0800944 timeout=None, timeout_mins=None, max_runtime_mins=None,
945 run_verify=False, email_list='', dependencies=(),
946 reboot_before=None, reboot_after=None, parse_failed_repair=None,
947 hostless=False, keyvals=None, drone_set=None, image=None,
Dan Shiec1d47d2015-02-13 11:38:13 -0800948 parent_job_id=None, test_retry=0, run_reset=True,
Michael Tang6dc174e2016-05-31 23:13:42 -0700949 require_ssp=None, args=(), is_cloning=False, **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +0000950 """\
951 Create and enqueue a job.
mblighe8819cd2008-02-15 16:48:40 +0000952
showarda1e74b32009-05-12 17:32:04 +0000953 @param name name of this job
Alex Miller7d658cf2013-09-04 16:00:35 -0700954 @param priority Integer priority of this job. Higher is more important.
showarda1e74b32009-05-12 17:32:04 +0000955 @param control_file String contents of the control file.
956 @param control_type Type of control file, Client or Server.
957 @param synch_count How many machines the job uses per autoserv execution.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700958 synch_count == 1 means the job is asynchronous. If an atomic group is
959 given this value is treated as a minimum.
showarda1e74b32009-05-12 17:32:04 +0000960 @param is_template If true then create a template job.
961 @param timeout Hours after this call returns until the job times out.
Simran Basi7e605742013-11-12 13:43:36 -0800962 @param timeout_mins Minutes after this call returns until the job times
Jiaxi Luo90190c92014-06-18 12:35:57 -0700963 out.
Simran Basi34217022012-11-06 13:43:15 -0800964 @param max_runtime_mins Minutes from job starting time until job times out
showarda1e74b32009-05-12 17:32:04 +0000965 @param run_verify Should the host be verified before running the test?
966 @param email_list String containing emails to mail when the job is done
967 @param dependencies List of label names on which this job depends
968 @param reboot_before Never, If dirty, or Always
969 @param reboot_after Never, If all tests passed, or Always
970 @param parse_failed_repair if true, results of failed repairs launched by
Jiaxi Luo90190c92014-06-18 12:35:57 -0700971 this job will be parsed as part of the job.
showarda9545c02009-12-18 22:44:26 +0000972 @param hostless if true, create a hostless job
showardc1a98d12010-01-15 00:22:22 +0000973 @param keyvals dict of keyvals to associate with the job
showarda1e74b32009-05-12 17:32:04 +0000974 @param hosts List of hosts to run job on.
975 @param meta_hosts List where each entry is a label name, and for each entry
Jiaxi Luo90190c92014-06-18 12:35:57 -0700976 one host will be chosen from that label to run the job on.
showarda1e74b32009-05-12 17:32:04 +0000977 @param one_time_hosts List of hosts not in the database to run the job on.
978 @param atomic_group_name The name of an atomic group to schedule the job on.
jamesren76fcf192010-04-21 20:39:50 +0000979 @param drone_set The name of the drone set to run this test on.
Paul Pendlebury5a8c6ad2011-02-01 07:20:17 -0800980 @param image OS image to install before running job.
Aviv Keshet0b9cfc92013-02-05 11:36:02 -0800981 @param parent_job_id id of a job considered to be parent of created job.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700982 @param test_retry Number of times to retry test if the test did not
Jiaxi Luo90190c92014-06-18 12:35:57 -0700983 complete successfully. (optional, default: 0)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700984 @param run_reset Should the host be reset before running the test?
Dan Shiec1d47d2015-02-13 11:38:13 -0800985 @param require_ssp Set to True to require server-side packaging to run the
986 test. If it's set to None, drone will still try to run
987 the server side with server-side packaging. If the
988 autotest-server package doesn't exist for the build or
989 image is not set, drone will run the test without server-
990 side packaging. Default is None.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700991 @param args A list of args to be injected into control file.
Michael Tang6dc174e2016-05-31 23:13:42 -0700992 @param is_cloning: True if creating a cloning job.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700993 @param kwargs extra keyword args. NOT USED.
showardc92da832009-04-07 18:14:34 +0000994
995 @returns The created Job id number.
jadmanski0afbb632008-06-06 21:10:57 +0000996 """
Jiaxi Luo90190c92014-06-18 12:35:57 -0700997 if args:
998 control_file = tools.inject_vars({'args': args}, control_file)
999
Simran Basiab5a1bf2014-05-28 15:39:44 -07001000 if image is None:
1001 return rpc_utils.create_job_common(
1002 **rpc_utils.get_create_job_common_args(locals()))
1003
Simran Basi6157e8e2015-12-07 18:22:34 -08001004 # Translate the image name, in case its a relative build name.
1005 ds = dev_server.ImageServer.resolve(image)
1006 image = ds.translate(image)
1007
Simran Basiab5a1bf2014-05-28 15:39:44 -07001008 # When image is supplied use a known parameterized test already in the
1009 # database to pass the OS image path from the front end, through the
1010 # scheduler, and finally to autoserv as the --image parameter.
1011
1012 # The test autoupdate_ParameterizedJob is in afe_autotests and used to
1013 # instantiate a Test object and from there a ParameterizedJob.
1014 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
1015 known_parameterized_job = models.ParameterizedJob.objects.create(
1016 test=known_test_obj)
1017
1018 # autoupdate_ParameterizedJob has a single parameter, the image parameter,
1019 # stored in the table afe_test_parameters. We retrieve and set this
1020 # instance of the parameter to the OS image path.
1021 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
1022 name='image')
1023 known_parameterized_job.parameterizedjobparameter_set.create(
1024 test_parameter=image_parameter, parameter_value=image,
1025 parameter_type='string')
1026
Dan Shid215dbe2015-06-18 16:14:59 -07001027 # TODO(crbug.com/502638): save firmware build etc to parameterized_job.
1028
Simran Basiab5a1bf2014-05-28 15:39:44 -07001029 # By passing a parameterized_job to create_job_common the job entry in
1030 # the afe_jobs table will have the field parameterized_job_id set.
1031 # The scheduler uses this id in the afe_parameterized_jobs table to
1032 # match this job to our known test, and then with the
1033 # afe_parameterized_job_parameters table to get the actual image path.
jamesren4a41e012010-07-16 22:33:48 +00001034 return rpc_utils.create_job_common(
Simran Basiab5a1bf2014-05-28 15:39:44 -07001035 parameterized_job=known_parameterized_job.id,
jamesren4a41e012010-07-16 22:33:48 +00001036 **rpc_utils.get_create_job_common_args(locals()))
mblighe8819cd2008-02-15 16:48:40 +00001037
1038
showard9dbdcda2008-10-14 17:34:36 +00001039def abort_host_queue_entries(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001040 """\
showard9dbdcda2008-10-14 17:34:36 +00001041 Abort a set of host queue entries.
Fang Deng63b0e452014-12-19 14:38:15 -08001042
1043 @return: A list of dictionaries, each contains information
1044 about an aborted HQE.
jadmanski0afbb632008-06-06 21:10:57 +00001045 """
showard9dbdcda2008-10-14 17:34:36 +00001046 query = models.HostQueueEntry.query_objects(filter_data)
beepsfaecbce2013-10-29 11:35:10 -07001047
1048 # Dont allow aborts on:
1049 # 1. Jobs that have already completed (whether or not they were aborted)
1050 # 2. Jobs that we have already been aborted (but may not have completed)
1051 query = query.filter(complete=False).filter(aborted=False)
showarddc817512008-11-12 18:16:41 +00001052 models.AclGroup.check_abort_permissions(query)
showard9dbdcda2008-10-14 17:34:36 +00001053 host_queue_entries = list(query.select_related())
showard2bab8f42008-11-12 18:15:22 +00001054 rpc_utils.check_abort_synchronous_jobs(host_queue_entries)
mblighe8819cd2008-02-15 16:48:40 +00001055
Simran Basic1b26762013-06-26 14:23:21 -07001056 models.HostQueueEntry.abort_host_queue_entries(host_queue_entries)
Fang Deng63b0e452014-12-19 14:38:15 -08001057 hqe_info = [{'HostQueueEntry': hqe.id, 'Job': hqe.job_id,
1058 'Job name': hqe.job.name} for hqe in host_queue_entries]
1059 return hqe_info
showard9d821ab2008-07-11 16:54:29 +00001060
1061
beeps8bb1f7d2013-08-05 01:30:09 -07001062def abort_special_tasks(**filter_data):
1063 """\
1064 Abort the special task, or tasks, specified in the filter.
1065 """
1066 query = models.SpecialTask.query_objects(filter_data)
1067 special_tasks = query.filter(is_active=True)
1068 for task in special_tasks:
1069 task.abort()
1070
1071
Simran Basi73dae552013-02-25 14:57:46 -08001072def _call_special_tasks_on_hosts(task, hosts):
1073 """\
1074 Schedules a set of hosts for a special task.
1075
1076 @returns A list of hostnames that a special task was created for.
1077 """
1078 models.AclGroup.check_for_acl_violation_hosts(hosts)
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -08001079 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -08001080 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -08001081 raise ValueError('The following hosts are on shards, please '
1082 'follow the link to the shards and create jobs '
1083 'there instead. %s.' % shard_host_map)
Simran Basi73dae552013-02-25 14:57:46 -08001084 for host in hosts:
1085 models.SpecialTask.schedule_special_task(host, task)
1086 return list(sorted(host.hostname for host in hosts))
1087
1088
MK Ryu5aa25042015-07-28 16:08:04 -07001089def _forward_special_tasks_on_hosts(task, rpc, **filter_data):
1090 """Forward special tasks to corresponding shards.
mbligh4e545a52009-12-19 05:30:39 +00001091
MK Ryu5aa25042015-07-28 16:08:04 -07001092 For master, when special tasks are fired on hosts that are sharded,
1093 forward the RPC to corresponding shards.
1094
1095 For shard, create special task records in local DB.
1096
1097 @param task: Enum value of frontend.afe.models.SpecialTask.Task
1098 @param rpc: RPC name to forward.
1099 @param filter_data: Filter keywords to be used for DB query.
1100
1101 @return: A list of hostnames that a special task was created for.
showard1ff7b2e2009-05-15 23:17:18 +00001102 """
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001103 hosts = models.Host.query_objects(filter_data)
1104 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts, rpc_hostnames=True)
1105
1106 # Filter out hosts on a shard from those on the master, forward
1107 # rpcs to the shard with an additional hostname__in filter, and
1108 # create a local SpecialTask for each remaining host.
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -08001109 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001110 hosts = [h for h in hosts if h.shard is None]
1111 for shard, hostnames in shard_host_map.iteritems():
1112
1113 # The main client of this module is the frontend website, and
1114 # it invokes it with an 'id' or an 'id__in' filter. Regardless,
1115 # the 'hostname' filter should narrow down the list of hosts on
1116 # each shard even though we supply all the ids in filter_data.
1117 # This method uses hostname instead of id because it fits better
MK Ryu5aa25042015-07-28 16:08:04 -07001118 # with the overall architecture of redirection functions in
1119 # rpc_utils.
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001120 shard_filter = filter_data.copy()
1121 shard_filter['hostname__in'] = hostnames
1122 rpc_utils.run_rpc_on_multiple_hostnames(
MK Ryu5aa25042015-07-28 16:08:04 -07001123 rpc, [shard], **shard_filter)
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001124
1125 # There is a race condition here if someone assigns a shard to one of these
1126 # hosts before we create the task. The host will stay on the master if:
1127 # 1. The host is not Ready
1128 # 2. The host is Ready but has a task
1129 # But if the host is Ready and doesn't have a task yet, it will get sent
1130 # to the shard as we're creating a task here.
1131
1132 # Given that we only rarely verify Ready hosts it isn't worth putting this
1133 # entire method in a transaction. The worst case scenario is that we have
MK Ryu5aa25042015-07-28 16:08:04 -07001134 # a verify running on a Ready host while the shard is using it, if the
1135 # verify fails no subsequent tasks will be created against the host on the
1136 # master, and verifies are safe enough that this is OK.
1137 return _call_special_tasks_on_hosts(task, hosts)
1138
1139
1140def reverify_hosts(**filter_data):
1141 """\
1142 Schedules a set of hosts for verify.
1143
1144 @returns A list of hostnames that a verify task was created for.
1145 """
1146 return _forward_special_tasks_on_hosts(
1147 models.SpecialTask.Task.VERIFY, 'reverify_hosts', **filter_data)
Simran Basi73dae552013-02-25 14:57:46 -08001148
1149
1150def repair_hosts(**filter_data):
1151 """\
1152 Schedules a set of hosts for repair.
1153
1154 @returns A list of hostnames that a repair task was created for.
1155 """
MK Ryu5aa25042015-07-28 16:08:04 -07001156 return _forward_special_tasks_on_hosts(
1157 models.SpecialTask.Task.REPAIR, 'repair_hosts', **filter_data)
showard1ff7b2e2009-05-15 23:17:18 +00001158
1159
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001160def get_jobs(not_yet_run=False, running=False, finished=False,
1161 suite=False, sub=False, standalone=False, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001162 """\
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001163 Extra status filter args for get_jobs:
jadmanski0afbb632008-06-06 21:10:57 +00001164 -not_yet_run: Include only jobs that have not yet started running.
1165 -running: Include only jobs that have start running but for which not
1166 all hosts have completed.
1167 -finished: Include only jobs for which all hosts have completed (or
1168 aborted).
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001169
1170 Extra type filter args for get_jobs:
1171 -suite: Include only jobs with child jobs.
1172 -sub: Include only jobs with a parent job.
1173 -standalone: Inlcude only jobs with no child or parent jobs.
1174 At most one of these three fields should be specified.
jadmanski0afbb632008-06-06 21:10:57 +00001175 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001176 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
1177 running,
1178 finished)
1179 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
1180 suite,
1181 sub,
1182 standalone)
showard0957a842009-05-11 19:25:08 +00001183 job_dicts = []
1184 jobs = list(models.Job.query_objects(filter_data))
1185 models.Job.objects.populate_relationships(jobs, models.Label,
1186 'dependencies')
showardc1a98d12010-01-15 00:22:22 +00001187 models.Job.objects.populate_relationships(jobs, models.JobKeyval, 'keyvals')
showard0957a842009-05-11 19:25:08 +00001188 for job in jobs:
1189 job_dict = job.get_object_dict()
1190 job_dict['dependencies'] = ','.join(label.name
1191 for label in job.dependencies)
showardc1a98d12010-01-15 00:22:22 +00001192 job_dict['keyvals'] = dict((keyval.key, keyval.value)
1193 for keyval in job.keyvals)
Eric Lid23bc192011-02-09 14:38:57 -08001194 if job.parameterized_job:
1195 job_dict['image'] = get_parameterized_autoupdate_image_url(job)
showard0957a842009-05-11 19:25:08 +00001196 job_dicts.append(job_dict)
1197 return rpc_utils.prepare_for_serialization(job_dicts)
mblighe8819cd2008-02-15 16:48:40 +00001198
1199
1200def get_num_jobs(not_yet_run=False, running=False, finished=False,
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001201 suite=False, sub=False, standalone=False,
jadmanski0afbb632008-06-06 21:10:57 +00001202 **filter_data):
Aviv Keshet17660a52016-04-06 18:56:43 +00001203 """\
1204 See get_jobs() for documentation of extra filter parameters.
jadmanski0afbb632008-06-06 21:10:57 +00001205 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001206 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
1207 running,
1208 finished)
1209 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
1210 suite,
1211 sub,
1212 standalone)
Aviv Keshet17660a52016-04-06 18:56:43 +00001213 return models.Job.query_count(filter_data)
mblighe8819cd2008-02-15 16:48:40 +00001214
1215
mblighe8819cd2008-02-15 16:48:40 +00001216def get_jobs_summary(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001217 """\
Jiaxi Luoaac54572014-06-04 13:57:02 -07001218 Like get_jobs(), but adds 'status_counts' and 'result_counts' field.
1219
1220 'status_counts' filed is a dictionary mapping status strings to the number
1221 of hosts currently with that status, i.e. {'Queued' : 4, 'Running' : 2}.
1222
1223 'result_counts' field is piped to tko's rpc_interface and has the return
1224 format specified under get_group_counts.
jadmanski0afbb632008-06-06 21:10:57 +00001225 """
1226 jobs = get_jobs(**filter_data)
1227 ids = [job['id'] for job in jobs]
1228 all_status_counts = models.Job.objects.get_status_counts(ids)
1229 for job in jobs:
1230 job['status_counts'] = all_status_counts[job['id']]
Jiaxi Luoaac54572014-06-04 13:57:02 -07001231 job['result_counts'] = tko_rpc_interface.get_status_counts(
1232 ['afe_job_id', 'afe_job_id'],
1233 header_groups=[['afe_job_id'], ['afe_job_id']],
1234 **{'afe_job_id': job['id']})
jadmanski0afbb632008-06-06 21:10:57 +00001235 return rpc_utils.prepare_for_serialization(jobs)
mblighe8819cd2008-02-15 16:48:40 +00001236
1237
showarda965cef2009-05-15 23:17:41 +00001238def get_info_for_clone(id, preserve_metahosts, queue_entry_filter_data=None):
showarda8709c52008-07-03 19:44:54 +00001239 """\
1240 Retrieves all the information needed to clone a job.
1241 """
showarda8709c52008-07-03 19:44:54 +00001242 job = models.Job.objects.get(id=id)
showard29f7cd22009-04-29 21:16:24 +00001243 job_info = rpc_utils.get_job_info(job,
showarda965cef2009-05-15 23:17:41 +00001244 preserve_metahosts,
1245 queue_entry_filter_data)
showard945072f2008-09-03 20:34:59 +00001246
showardd9992fe2008-07-31 02:15:03 +00001247 host_dicts = []
showard29f7cd22009-04-29 21:16:24 +00001248 for host in job_info['hosts']:
1249 host_dict = get_hosts(id=host.id)[0]
1250 other_labels = host_dict['labels']
1251 if host_dict['platform']:
1252 other_labels.remove(host_dict['platform'])
1253 host_dict['other_labels'] = ', '.join(other_labels)
showardd9992fe2008-07-31 02:15:03 +00001254 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001255
showard29f7cd22009-04-29 21:16:24 +00001256 for host in job_info['one_time_hosts']:
1257 host_dict = dict(hostname=host.hostname,
1258 id=host.id,
1259 platform='(one-time host)',
1260 locked_text='')
1261 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001262
showard4d077562009-05-08 18:24:36 +00001263 # convert keys from Label objects to strings (names of labels)
showard29f7cd22009-04-29 21:16:24 +00001264 meta_host_counts = dict((meta_host.name, count) for meta_host, count
showard4d077562009-05-08 18:24:36 +00001265 in job_info['meta_host_counts'].iteritems())
showard29f7cd22009-04-29 21:16:24 +00001266
1267 info = dict(job=job.get_object_dict(),
1268 meta_host_counts=meta_host_counts,
1269 hosts=host_dicts)
1270 info['job']['dependencies'] = job_info['dependencies']
1271 if job_info['atomic_group']:
1272 info['atomic_group_name'] = (job_info['atomic_group']).name
1273 else:
1274 info['atomic_group_name'] = None
jamesren2275ef12010-04-12 18:25:06 +00001275 info['hostless'] = job_info['hostless']
jamesren76fcf192010-04-21 20:39:50 +00001276 info['drone_set'] = job.drone_set and job.drone_set.name
showarda8709c52008-07-03 19:44:54 +00001277
Michael Tang6dc174e2016-05-31 23:13:42 -07001278 image = _get_image_for_job(job, job_info['hostless'])
1279 if image:
1280 info['job']['image'] = image
Eric Lid23bc192011-02-09 14:38:57 -08001281
showarda8709c52008-07-03 19:44:54 +00001282 return rpc_utils.prepare_for_serialization(info)
1283
1284
Michael Tang6dc174e2016-05-31 23:13:42 -07001285def _get_image_for_job(job, hostless):
1286 """ Gets the image used for a job.
1287
1288 Gets the image used for an AFE job. If the job is a parameterized job, get
1289 the image from the job parameter; otherwise, tries to get the image from
1290 the job's keyvals 'build' or 'builds'. As a last resort, if the job is a
1291 hostless job, tries to get the image from its control file attributes
1292 'build' or 'builds'.
1293
1294 TODO(ntang): Needs to handle FAFT with two builds for ro/rw.
1295
1296 @param job An AFE job object.
1297 @param hostless Boolean on of the job is hostless.
1298
1299 @returns The image build used for the job.
1300 """
1301 image = None
1302 if job.parameterized_job:
1303 image = get_parameterized_autoupdate_image_url(job)
1304 else:
1305 keyvals = job.keyval_dict()
Michael Tang84a2ecf2016-06-07 15:10:53 -07001306 image = keyvals.get('build')
Michael Tang6dc174e2016-05-31 23:13:42 -07001307 if not image:
1308 value = keyvals.get('builds')
1309 builds = None
1310 if isinstance(value, dict):
1311 builds = value
1312 elif isinstance(value, basestring):
1313 builds = ast.literal_eval(value)
1314 if builds:
1315 image = builds.get('cros-version')
1316 if not image and hostless and job.control_file:
1317 try:
1318 control_obj = control_data.parse_control_string(
1319 job.control_file)
1320 if hasattr(control_obj, 'build'):
1321 image = getattr(control_obj, 'build')
1322 if not image and hasattr(control_obj, 'builds'):
1323 builds = getattr(control_obj, 'builds')
1324 image = builds.get('cros-version')
1325 except:
1326 logging.warning('Failed to parse control file for job: %s',
1327 job.name)
1328 return image
1329
showard34dc5fa2008-04-24 20:58:40 +00001330
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001331def get_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001332 """\
showardc92da832009-04-07 18:14:34 +00001333 @returns A sequence of nested dictionaries of host and job information.
jadmanski0afbb632008-06-06 21:10:57 +00001334 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001335 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1336 'started_on__lte',
1337 start_time,
1338 end_time,
1339 **filter_data)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001340 return rpc_utils.prepare_rows_as_nested_dicts(
1341 models.HostQueueEntry.query_objects(filter_data),
1342 ('host', 'atomic_group', 'job'))
showard34dc5fa2008-04-24 20:58:40 +00001343
1344
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001345def get_num_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001346 """\
1347 Get the number of host queue entries associated with this job.
1348 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001349 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1350 'started_on__lte',
1351 start_time,
1352 end_time,
1353 **filter_data)
jadmanski0afbb632008-06-06 21:10:57 +00001354 return models.HostQueueEntry.query_count(filter_data)
showard34dc5fa2008-04-24 20:58:40 +00001355
1356
showard1e935f12008-07-11 00:11:36 +00001357def get_hqe_percentage_complete(**filter_data):
1358 """
showardc92da832009-04-07 18:14:34 +00001359 Computes the fraction of host queue entries matching the given filter data
showard1e935f12008-07-11 00:11:36 +00001360 that are complete.
1361 """
1362 query = models.HostQueueEntry.query_objects(filter_data)
1363 complete_count = query.filter(complete=True).count()
1364 total_count = query.count()
1365 if total_count == 0:
1366 return 1
1367 return float(complete_count) / total_count
1368
1369
showard1a5a4082009-07-28 20:01:37 +00001370# special tasks
1371
1372def get_special_tasks(**filter_data):
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001373 """Get special task entries from the local database.
1374
1375 Query the special tasks table for tasks matching the given
1376 `filter_data`, and return a list of the results. No attempt is
1377 made to forward the call to shards; the buck will stop here.
1378 The caller is expected to know the target shard for such reasons
1379 as:
1380 * The caller is a service (such as gs_offloader) configured
1381 to operate on behalf of one specific shard, and no other.
1382 * The caller has a host as a parameter, and knows that this is
1383 the shard assigned to that host.
1384
1385 @param filter_data Filter keywords to pass to the underlying
1386 database query.
1387
1388 """
J. Richard Barnettefdfcd662015-04-13 17:20:29 -07001389 return rpc_utils.prepare_rows_as_nested_dicts(
1390 models.SpecialTask.query_objects(filter_data),
1391 ('host', 'queue_entry'))
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001392
1393
1394def get_host_special_tasks(host_id, **filter_data):
1395 """Get special task entries for a given host.
1396
1397 Query the special tasks table for tasks that ran on the host
1398 given by `host_id` and matching the given `filter_data`.
1399 Return a list of the results. If the host is assigned to a
1400 shard, forward this call to that shard.
1401
1402 @param host_id Id in the database of the target host.
1403 @param filter_data Filter keywords to pass to the underlying
1404 database query.
1405
1406 """
MK Ryu0c1a37d2015-04-30 12:00:55 -07001407 # Retrieve host data even if the host is in an invalid state.
1408 host = models.Host.smart_get(host_id, False)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001409 if not host.shard:
J. Richard Barnettefdfcd662015-04-13 17:20:29 -07001410 return get_special_tasks(host_id=host_id, **filter_data)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001411 else:
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001412 # The return values from AFE methods are post-processed
1413 # objects that aren't JSON-serializable. So, we have to
1414 # call AFE.run() to get the raw, serializable output from
1415 # the shard.
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001416 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1417 return shard_afe.run('get_special_tasks',
1418 host_id=host_id, **filter_data)
showard1a5a4082009-07-28 20:01:37 +00001419
1420
MK Ryu0c1a37d2015-04-30 12:00:55 -07001421def get_num_special_tasks(**kwargs):
1422 """Get the number of special task entries from the local database.
1423
1424 Query the special tasks table for tasks matching the given 'kwargs',
1425 and return the number of the results. No attempt is made to forward
1426 the call to shards; the buck will stop here.
1427
1428 @param kwargs Filter keywords to pass to the underlying database query.
1429
1430 """
1431 return models.SpecialTask.query_count(kwargs)
1432
1433
1434def get_host_num_special_tasks(host, **kwargs):
1435 """Get special task entries for a given host.
1436
1437 Query the special tasks table for tasks that ran on the host
1438 given by 'host' and matching the given 'kwargs'.
1439 Return a list of the results. If the host is assigned to a
1440 shard, forward this call to that shard.
1441
1442 @param host id or name of a host. More often a hostname.
1443 @param kwargs Filter keywords to pass to the underlying database query.
1444
1445 """
1446 # Retrieve host data even if the host is in an invalid state.
1447 host_model = models.Host.smart_get(host, False)
1448 if not host_model.shard:
1449 return get_num_special_tasks(host=host, **kwargs)
1450 else:
1451 shard_afe = frontend.AFE(server=host_model.shard.rpc_hostname())
1452 return shard_afe.run('get_num_special_tasks', host=host, **kwargs)
1453
1454
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001455def get_status_task(host_id, end_time):
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001456 """Get the "status task" for a host from the local shard.
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001457
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001458 Returns a single special task representing the given host's
1459 "status task". The status task is a completed special task that
1460 identifies whether the corresponding host was working or broken
1461 when it completed. A successful task indicates a working host;
1462 a failed task indicates broken.
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001463
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001464 This call will not be forward to a shard; the receiving server
1465 must be the shard that owns the host.
1466
1467 @param host_id Id in the database of the target host.
1468 @param end_time Time reference for the host's status.
1469
1470 @return A single task; its status (successful or not)
1471 corresponds to the status of the host (working or
1472 broken) at the given time. If no task is found, return
1473 `None`.
1474
1475 """
1476 tasklist = rpc_utils.prepare_rows_as_nested_dicts(
1477 status_history.get_status_task(host_id, end_time),
1478 ('host', 'queue_entry'))
1479 return tasklist[0] if tasklist else None
1480
1481
1482def get_host_status_task(host_id, end_time):
1483 """Get the "status task" for a host from its owning shard.
1484
1485 Finds the given host's owning shard, and forwards to it a call
1486 to `get_status_task()` (see above).
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001487
1488 @param host_id Id in the database of the target host.
1489 @param end_time Time reference for the host's status.
1490
1491 @return A single task; its status (successful or not)
1492 corresponds to the status of the host (working or
1493 broken) at the given time. If no task is found, return
1494 `None`.
1495
1496 """
1497 host = models.Host.smart_get(host_id)
1498 if not host.shard:
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001499 return get_status_task(host_id, end_time)
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001500 else:
1501 # The return values from AFE methods are post-processed
1502 # objects that aren't JSON-serializable. So, we have to
1503 # call AFE.run() to get the raw, serializable output from
1504 # the shard.
1505 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1506 return shard_afe.run('get_status_task',
1507 host_id=host_id, end_time=end_time)
1508
1509
J. Richard Barnette8abbfd62015-06-23 12:46:54 -07001510def get_host_diagnosis_interval(host_id, end_time, success):
1511 """Find a "diagnosis interval" for a given host.
1512
1513 A "diagnosis interval" identifies a start and end time where
1514 the host went from "working" to "broken", or vice versa. The
1515 interval's starting time is the starting time of the last status
1516 task with the old status; the end time is the finish time of the
1517 first status task with the new status.
1518
1519 This routine finds the most recent diagnosis interval for the
1520 given host prior to `end_time`, with a starting status matching
1521 `success`. If `success` is true, the interval will start with a
1522 successful status task; if false the interval will start with a
1523 failed status task.
1524
1525 @param host_id Id in the database of the target host.
1526 @param end_time Time reference for the diagnosis interval.
1527 @param success Whether the diagnosis interval should start
1528 with a successful or failed status task.
1529
1530 @return A list of two strings. The first is the timestamp for
1531 the beginning of the interval; the second is the
1532 timestamp for the end. If the host has never changed
1533 state, the list is empty.
1534
1535 """
1536 host = models.Host.smart_get(host_id)
J. Richard Barnette78f281a2015-06-29 13:24:51 -07001537 if not host.shard or utils.is_shard():
J. Richard Barnette8abbfd62015-06-23 12:46:54 -07001538 return status_history.get_diagnosis_interval(
1539 host_id, end_time, success)
1540 else:
1541 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1542 return shard_afe.get_host_diagnosis_interval(
1543 host_id, end_time, success)
1544
1545
showardc0ac3a72009-07-08 21:14:45 +00001546# support for host detail view
1547
MK Ryu0c1a37d2015-04-30 12:00:55 -07001548def get_host_queue_entries_and_special_tasks(host, query_start=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001549 query_limit=None, start_time=None,
1550 end_time=None):
showardc0ac3a72009-07-08 21:14:45 +00001551 """
1552 @returns an interleaved list of HostQueueEntries and SpecialTasks,
1553 in approximate run order. each dict contains keys for type, host,
1554 job, status, started_on, execution_path, and ID.
1555 """
1556 total_limit = None
1557 if query_limit is not None:
1558 total_limit = query_start + query_limit
MK Ryu0c1a37d2015-04-30 12:00:55 -07001559 filter_data_common = {'host': host,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001560 'query_limit': total_limit,
1561 'sort_by': ['-id']}
showardc0ac3a72009-07-08 21:14:45 +00001562
MK Ryu0c1a37d2015-04-30 12:00:55 -07001563 filter_data_special_tasks = rpc_utils.inject_times_to_filter(
1564 'time_started__gte', 'time_started__lte', start_time, end_time,
1565 **filter_data_common)
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001566
MK Ryu0c1a37d2015-04-30 12:00:55 -07001567 queue_entries = get_host_queue_entries(
1568 start_time, end_time, **filter_data_common)
1569 special_tasks = get_host_special_tasks(host, **filter_data_special_tasks)
showardc0ac3a72009-07-08 21:14:45 +00001570
1571 interleaved_entries = rpc_utils.interleave_entries(queue_entries,
1572 special_tasks)
1573 if query_start is not None:
1574 interleaved_entries = interleaved_entries[query_start:]
1575 if query_limit is not None:
1576 interleaved_entries = interleaved_entries[:query_limit]
MK Ryu0c1a37d2015-04-30 12:00:55 -07001577 return rpc_utils.prepare_host_queue_entries_and_special_tasks(
1578 interleaved_entries, queue_entries)
showardc0ac3a72009-07-08 21:14:45 +00001579
1580
MK Ryu0c1a37d2015-04-30 12:00:55 -07001581def get_num_host_queue_entries_and_special_tasks(host, start_time=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001582 end_time=None):
MK Ryu0c1a37d2015-04-30 12:00:55 -07001583 filter_data_common = {'host': host}
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001584
1585 filter_data_queue_entries, filter_data_special_tasks = (
1586 rpc_utils.inject_times_to_hqe_special_tasks_filters(
1587 filter_data_common, start_time, end_time))
1588
1589 return (models.HostQueueEntry.query_count(filter_data_queue_entries)
MK Ryu0c1a37d2015-04-30 12:00:55 -07001590 + get_host_num_special_tasks(**filter_data_special_tasks))
showardc0ac3a72009-07-08 21:14:45 +00001591
1592
showard29f7cd22009-04-29 21:16:24 +00001593# recurring run
1594
1595def get_recurring(**filter_data):
1596 return rpc_utils.prepare_rows_as_nested_dicts(
1597 models.RecurringRun.query_objects(filter_data),
1598 ('job', 'owner'))
1599
1600
1601def get_num_recurring(**filter_data):
1602 return models.RecurringRun.query_count(filter_data)
1603
1604
1605def delete_recurring_runs(**filter_data):
1606 to_delete = models.RecurringRun.query_objects(filter_data)
1607 to_delete.delete()
1608
1609
1610def create_recurring_run(job_id, start_date, loop_period, loop_count):
showard64a95952010-01-13 21:27:16 +00001611 owner = models.User.current_user().login
showard29f7cd22009-04-29 21:16:24 +00001612 job = models.Job.objects.get(id=job_id)
1613 return job.create_recurring_job(start_date=start_date,
1614 loop_period=loop_period,
1615 loop_count=loop_count,
1616 owner=owner)
1617
1618
mblighe8819cd2008-02-15 16:48:40 +00001619# other
1620
showarde0b63622008-08-04 20:58:47 +00001621def echo(data=""):
1622 """\
1623 Returns a passed in string. For doing a basic test to see if RPC calls
1624 can successfully be made.
1625 """
1626 return data
1627
1628
showardb7a52fd2009-04-27 20:10:56 +00001629def get_motd():
1630 """\
1631 Returns the message of the day as a string.
1632 """
1633 return rpc_utils.get_motd()
1634
1635
mblighe8819cd2008-02-15 16:48:40 +00001636def get_static_data():
jadmanski0afbb632008-06-06 21:10:57 +00001637 """\
1638 Returns a dictionary containing a bunch of data that shouldn't change
1639 often and is otherwise inaccessible. This includes:
showardc92da832009-04-07 18:14:34 +00001640
1641 priorities: List of job priority choices.
1642 default_priority: Default priority value for new jobs.
1643 users: Sorted list of all users.
Jiaxi Luo31874592014-06-11 10:36:35 -07001644 labels: Sorted list of labels not start with 'cros-version' and
1645 'fw-version'.
showardc92da832009-04-07 18:14:34 +00001646 atomic_groups: Sorted list of all atomic groups.
1647 tests: Sorted list of all tests.
1648 profilers: Sorted list of all profilers.
1649 current_user: Logged-in username.
1650 host_statuses: Sorted list of possible Host statuses.
1651 job_statuses: Sorted list of possible HostQueueEntry statuses.
Simran Basi7e605742013-11-12 13:43:36 -08001652 job_timeout_default: The default job timeout length in minutes.
showarda1e74b32009-05-12 17:32:04 +00001653 parse_failed_repair_default: Default value for the parse_failed_repair job
Jiaxi Luo31874592014-06-11 10:36:35 -07001654 option.
showardc92da832009-04-07 18:14:34 +00001655 reboot_before_options: A list of valid RebootBefore string enums.
1656 reboot_after_options: A list of valid RebootAfter string enums.
1657 motd: Server's message of the day.
1658 status_dictionary: A mapping from one word job status names to a more
1659 informative description.
jadmanski0afbb632008-06-06 21:10:57 +00001660 """
showard21baa452008-10-21 00:08:39 +00001661
1662 job_fields = models.Job.get_field_dict()
jamesren76fcf192010-04-21 20:39:50 +00001663 default_drone_set_name = models.DroneSet.default_drone_set_name()
1664 drone_sets = ([default_drone_set_name] +
1665 sorted(drone_set.name for drone_set in
1666 models.DroneSet.objects.exclude(
1667 name=default_drone_set_name)))
showard21baa452008-10-21 00:08:39 +00001668
jadmanski0afbb632008-06-06 21:10:57 +00001669 result = {}
Alex Miller7d658cf2013-09-04 16:00:35 -07001670 result['priorities'] = priorities.Priority.choices()
1671 default_priority = priorities.Priority.DEFAULT
1672 result['default_priority'] = 'Default'
1673 result['max_schedulable_priority'] = priorities.Priority.DEFAULT
jadmanski0afbb632008-06-06 21:10:57 +00001674 result['users'] = get_users(sort_by=['login'])
Jiaxi Luo31874592014-06-11 10:36:35 -07001675
1676 label_exclude_filters = [{'name__startswith': 'cros-version'},
Dan Shi65351d62015-08-03 12:03:23 -07001677 {'name__startswith': 'fw-version'},
1678 {'name__startswith': 'fwrw-version'},
Dan Shi27516972016-03-16 14:03:41 -07001679 {'name__startswith': 'fwro-version'},
1680 {'name__startswith': 'ab-version'},
1681 {'name__startswith': 'testbed-version'}]
Jiaxi Luo31874592014-06-11 10:36:35 -07001682 result['labels'] = get_labels(
1683 label_exclude_filters,
1684 sort_by=['-platform', 'name'])
1685
showardc92da832009-04-07 18:14:34 +00001686 result['atomic_groups'] = get_atomic_groups(sort_by=['name'])
jadmanski0afbb632008-06-06 21:10:57 +00001687 result['tests'] = get_tests(sort_by=['name'])
showard2b9a88b2008-06-13 20:55:03 +00001688 result['profilers'] = get_profilers(sort_by=['name'])
showard0fc38302008-10-23 00:44:07 +00001689 result['current_user'] = rpc_utils.prepare_for_serialization(
showard64a95952010-01-13 21:27:16 +00001690 models.User.current_user().get_object_dict())
showard2b9a88b2008-06-13 20:55:03 +00001691 result['host_statuses'] = sorted(models.Host.Status.names)
mbligh5a198b92008-12-11 19:33:29 +00001692 result['job_statuses'] = sorted(models.HostQueueEntry.Status.names)
Simran Basi7e605742013-11-12 13:43:36 -08001693 result['job_timeout_mins_default'] = models.Job.DEFAULT_TIMEOUT_MINS
Simran Basi34217022012-11-06 13:43:15 -08001694 result['job_max_runtime_mins_default'] = (
1695 models.Job.DEFAULT_MAX_RUNTIME_MINS)
showarda1e74b32009-05-12 17:32:04 +00001696 result['parse_failed_repair_default'] = bool(
1697 models.Job.DEFAULT_PARSE_FAILED_REPAIR)
jamesrendd855242010-03-02 22:23:44 +00001698 result['reboot_before_options'] = model_attributes.RebootBefore.names
1699 result['reboot_after_options'] = model_attributes.RebootAfter.names
showard8fbae652009-01-20 23:23:10 +00001700 result['motd'] = rpc_utils.get_motd()
jamesren76fcf192010-04-21 20:39:50 +00001701 result['drone_sets_enabled'] = models.DroneSet.drone_sets_enabled()
1702 result['drone_sets'] = drone_sets
jamesren4a41e012010-07-16 22:33:48 +00001703 result['parameterized_jobs'] = models.Job.parameterized_jobs_enabled()
showard8ac29b42008-07-17 17:01:55 +00001704
showardd3dc1992009-04-22 21:01:40 +00001705 result['status_dictionary'] = {"Aborted": "Aborted",
showard8ac29b42008-07-17 17:01:55 +00001706 "Verifying": "Verifying Host",
Alex Millerdfff2fd2013-05-28 13:05:06 -07001707 "Provisioning": "Provisioning Host",
showard8ac29b42008-07-17 17:01:55 +00001708 "Pending": "Waiting on other hosts",
1709 "Running": "Running autoserv",
1710 "Completed": "Autoserv completed",
1711 "Failed": "Failed to complete",
showardd823b362008-07-24 16:35:46 +00001712 "Queued": "Queued",
showard5deb6772008-11-04 21:54:33 +00001713 "Starting": "Next in host's queue",
1714 "Stopped": "Other host(s) failed verify",
showardd3dc1992009-04-22 21:01:40 +00001715 "Parsing": "Awaiting parse of final results",
showard29f7cd22009-04-29 21:16:24 +00001716 "Gathering": "Gathering log files",
showard8cc058f2009-09-08 16:26:33 +00001717 "Template": "Template job for recurring run",
mbligh4608b002010-01-05 18:22:35 +00001718 "Waiting": "Waiting for scheduler action",
Dan Shi07e09af2013-04-12 09:31:29 -07001719 "Archiving": "Archiving results",
1720 "Resetting": "Resetting hosts"}
Jiaxi Luo421608e2014-07-07 14:38:00 -07001721
1722 result['wmatrix_url'] = rpc_utils.get_wmatrix_url()
Simran Basi71206ef2014-08-13 13:51:18 -07001723 result['is_moblab'] = bool(utils.is_moblab())
Jiaxi Luo421608e2014-07-07 14:38:00 -07001724
jadmanski0afbb632008-06-06 21:10:57 +00001725 return result
showard29f7cd22009-04-29 21:16:24 +00001726
1727
1728def get_server_time():
1729 return datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
Kevin Cheng19521982016-09-22 12:27:23 -07001730
1731
1732def get_hosts_by_attribute(attribute, value):
1733 """
1734 Get the list of valid hosts that share the same host attribute value.
1735
1736 @param attribute: String of the host attribute to check.
1737 @param value: String of the value that is shared between hosts.
1738
1739 @returns List of hostnames that all have the same host attribute and
1740 value.
1741 """
1742 hosts = models.HostAttribute.query_objects({'attribute': attribute,
1743 'value': value})
1744 return [row.host.hostname for row in hosts if row.host.invalid == 0]