blob: 1592088498f4ac7879b8e465a86eb52dcc57ae55 [file] [log] [blame]
Aviv Keshet0b9cfc92013-02-05 11:36:02 -08001# pylint: disable-msg=C0111
2
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
MK Ryu9c5fbbe2015-02-11 15:46:22 -080034import sys
showard29f7cd22009-04-29 21:16:24 +000035import datetime
Shuqian Zhao4c0d2902016-01-12 17:03:15 -080036import logging
MK Ryu9c5fbbe2015-02-11 15:46:22 -080037
Aviv Keshetb07a09d2016-04-01 16:01:19 -070038from django.db import connections
Moises Osorio2dc7a102014-12-02 18:24:02 -080039from django.db.models import Count
showardcafd16e2009-05-29 18:37:49 +000040import common
Simran Basib6ec8ae2014-04-23 12:05:08 -070041from autotest_lib.client.common_lib import priorities
Simran Basi6157e8e2015-12-07 18:22:34 -080042from autotest_lib.client.common_lib.cros import dev_server
Gabe Black1e1c41b2015-02-04 23:55:15 -080043from autotest_lib.client.common_lib.cros.graphite import autotest_stats
showard6d7b2ff2009-06-10 00:16:47 +000044from autotest_lib.frontend.afe import control_file, rpc_utils
J. Richard Barnetteb5164d62015-04-13 12:59:31 -070045from autotest_lib.frontend.afe import models, model_logic, model_attributes
Simran Basib6ec8ae2014-04-23 12:05:08 -070046from autotest_lib.frontend.afe import site_rpc_interface
Moises Osorio2dc7a102014-12-02 18:24:02 -080047from autotest_lib.frontend.tko import models as tko_models
Jiaxi Luoaac54572014-06-04 13:57:02 -070048from autotest_lib.frontend.tko import rpc_interface as tko_rpc_interface
J. Richard Barnetteb5164d62015-04-13 12:59:31 -070049from autotest_lib.server import frontend
Simran Basi71206ef2014-08-13 13:51:18 -070050from autotest_lib.server import utils
Dan Shid215dbe2015-06-18 16:14:59 -070051from autotest_lib.server.cros import provision
Jiaxi Luo90190c92014-06-18 12:35:57 -070052from autotest_lib.server.cros.dynamic_suite import tools
J. Richard Barnette39255fa2015-04-14 17:23:41 -070053from autotest_lib.site_utils import status_history
mblighe8819cd2008-02-15 16:48:40 +000054
Moises Osorio2dc7a102014-12-02 18:24:02 -080055
Gabe Black1e1c41b2015-02-04 23:55:15 -080056_timer = autotest_stats.Timer('rpc_interface')
Moises Osorio2dc7a102014-12-02 18:24:02 -080057
Eric Lid23bc192011-02-09 14:38:57 -080058def get_parameterized_autoupdate_image_url(job):
59 """Get the parameterized autoupdate image url from a parameterized job."""
60 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
61 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
beeps8bb1f7d2013-08-05 01:30:09 -070062 name='image')
Eric Lid23bc192011-02-09 14:38:57 -080063 para_set = job.parameterized_job.parameterizedjobparameter_set
64 job_test_para = para_set.get(test_parameter=image_parameter)
65 return job_test_para.parameter_value
66
67
mblighe8819cd2008-02-15 16:48:40 +000068# labels
69
mblighe8819cd2008-02-15 16:48:40 +000070def modify_label(id, **data):
MK Ryu8c554cf2015-06-12 11:45:50 -070071 """Modify a label.
72
73 @param id: id or name of a label. More often a label name.
74 @param data: New data for a label.
75 """
76 label_model = models.Label.smart_get(id)
MK Ryu8e2c2d02016-01-06 15:24:38 -080077 label_model.update_object(data)
MK Ryu8c554cf2015-06-12 11:45:50 -070078
79 # Master forwards the RPC to shards
80 if not utils.is_shard():
81 rpc_utils.fanout_rpc(label_model.host_set.all(), 'modify_label', False,
82 id=id, **data)
83
mblighe8819cd2008-02-15 16:48:40 +000084
85def delete_label(id):
MK Ryu8c554cf2015-06-12 11:45:50 -070086 """Delete a label.
87
88 @param id: id or name of a label. More often a label name.
89 """
90 label_model = models.Label.smart_get(id)
MK Ryu8e2c2d02016-01-06 15:24:38 -080091 # Hosts that have the label to be deleted. Save this info before
92 # the label is deleted to use it later.
93 hosts = []
94 for h in label_model.host_set.all():
95 hosts.append(models.Host.smart_get(h.id))
96 label_model.delete()
MK Ryu8c554cf2015-06-12 11:45:50 -070097
98 # Master forwards the RPC to shards
99 if not utils.is_shard():
MK Ryu8e2c2d02016-01-06 15:24:38 -0800100 rpc_utils.fanout_rpc(hosts, 'delete_label', False, id=id)
mblighe8819cd2008-02-15 16:48:40 +0000101
Prashanth Balasubramanian744898f2015-01-13 05:04:16 -0800102
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800103def add_label(name, ignore_exception_if_exists=False, **kwargs):
MK Ryucf027c62015-03-04 12:00:50 -0800104 """Adds a new label of a given name.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800105
106 @param name: label name.
107 @param ignore_exception_if_exists: If True and the exception was
108 thrown due to the duplicated label name when adding a label,
109 then suppress the exception. Default is False.
110 @param kwargs: keyword args that store more info about a label
111 other than the name.
112 @return: int/long id of a new label.
113 """
114 # models.Label.add_object() throws model_logic.ValidationError
115 # when it is given a label name that already exists.
116 # However, ValidationError can be thrown with different errors,
117 # and those errors should be thrown up to the call chain.
118 try:
119 label = models.Label.add_object(name=name, **kwargs)
120 except:
121 exc_info = sys.exc_info()
122 if ignore_exception_if_exists:
123 label = rpc_utils.get_label(name)
124 # If the exception is raised not because of duplicated
125 # "name", then raise the original exception.
126 if label is None:
127 raise exc_info[0], exc_info[1], exc_info[2]
128 else:
129 raise exc_info[0], exc_info[1], exc_info[2]
130 return label.id
131
132
133def add_label_to_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800134 """Adds a label of the given id to the given hosts only in local DB.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800135
136 @param id: id or name of a label. More often a label name.
137 @param hosts: The hostnames of hosts that need the label.
138
139 @raises models.Label.DoesNotExist: If the label with id doesn't exist.
140 """
141 label = models.Label.smart_get(id)
142 host_objs = models.Host.smart_get_bulk(hosts)
143 if label.platform:
144 models.Host.check_no_platform(host_objs)
145 label.host_set.add(*host_objs)
146
147
MK Ryufbb002c2015-06-08 14:13:16 -0700148@rpc_utils.route_rpc_to_master
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800149def label_add_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800150 """Adds a label with the given id to the given hosts.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800151
152 This method should be run only on master not shards.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800153 The given label will be created if it doesn't exist, provided the `id`
154 supplied is a label name not an int/long id.
155
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800156 @param id: id or name of a label. More often a label name.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800157 @param hosts: A list of hostnames or ids. More often hostnames.
158
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800159 @raises ValueError: If the id specified is an int/long (label id)
160 while the label does not exist.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800161 """
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800162 try:
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800163 label = models.Label.smart_get(id)
164 except models.Label.DoesNotExist:
165 # This matches the type checks in smart_get, which is a hack
166 # in and off itself. The aim here is to create any non-existent
167 # label, which we cannot do if the 'id' specified isn't a label name.
168 if isinstance(id, basestring):
169 label = models.Label.smart_get(add_label(id))
170 else:
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800171 raise ValueError('Label id (%s) does not exist. Please specify '
172 'the argument, id, as a string (label name).'
173 % id)
MK Ryu8e2c2d02016-01-06 15:24:38 -0800174 add_label_to_hosts(id, hosts)
MK Ryucf027c62015-03-04 12:00:50 -0800175
176 host_objs = models.Host.smart_get_bulk(hosts)
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800177 # Make sure the label exists on the shard with the same id
178 # as it is on the master.
MK Ryucf027c62015-03-04 12:00:50 -0800179 # It is possible that the label is already in a shard because
180 # we are adding a new label only to shards of hosts that the label
181 # is going to be attached.
182 # For example, we add a label L1 to a host in shard S1.
183 # Master and S1 will have L1 but other shards won't.
184 # Later, when we add the same label L1 to hosts in shards S1 and S2,
185 # S1 already has the label but S2 doesn't.
186 # S2 should have the new label without any problem.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800187 # We ignore exception in such a case.
188 rpc_utils.fanout_rpc(
MK Ryue019aae2015-07-07 12:46:07 -0700189 host_objs, 'add_label', include_hostnames=False,
190 name=label.name, ignore_exception_if_exists=True,
191 id=label.id, platform=label.platform)
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800192 rpc_utils.fanout_rpc(host_objs, 'add_label_to_hosts', id=id)
showardbbabf502008-06-06 00:02:02 +0000193
194
MK Ryucf027c62015-03-04 12:00:50 -0800195def remove_label_from_hosts(id, hosts):
196 """Removes a label of the given id from the given hosts only in local DB.
197
198 @param id: id or name of a label.
199 @param hosts: The hostnames of hosts that need to remove the label from.
200 """
showardbe3ec042008-11-12 18:16:07 +0000201 host_objs = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000202 models.Label.smart_get(id).host_set.remove(*host_objs)
showardbbabf502008-06-06 00:02:02 +0000203
204
MK Ryufbb002c2015-06-08 14:13:16 -0700205@rpc_utils.route_rpc_to_master
MK Ryucf027c62015-03-04 12:00:50 -0800206def label_remove_hosts(id, hosts):
207 """Removes a label of the given id from the given hosts.
208
209 This method should be run only on master not shards.
210
211 @param id: id or name of a label.
212 @param hosts: A list of hostnames or ids. More often hostnames.
213 """
MK Ryucf027c62015-03-04 12:00:50 -0800214 host_objs = models.Host.smart_get_bulk(hosts)
MK Ryu26f0c932015-05-28 18:14:33 -0700215 remove_label_from_hosts(id, hosts)
216
MK Ryu8e2c2d02016-01-06 15:24:38 -0800217 rpc_utils.fanout_rpc(host_objs, 'remove_label_from_hosts', id=id)
218
MK Ryucf027c62015-03-04 12:00:50 -0800219
Jiaxi Luo31874592014-06-11 10:36:35 -0700220def get_labels(exclude_filters=(), **filter_data):
showardc92da832009-04-07 18:14:34 +0000221 """\
Jiaxi Luo31874592014-06-11 10:36:35 -0700222 @param exclude_filters: A sequence of dictionaries of filters.
223
showardc92da832009-04-07 18:14:34 +0000224 @returns A sequence of nested dictionaries of label information.
225 """
Jiaxi Luo31874592014-06-11 10:36:35 -0700226 labels = models.Label.query_objects(filter_data)
227 for exclude_filter in exclude_filters:
228 labels = labels.exclude(**exclude_filter)
229 return rpc_utils.prepare_rows_as_nested_dicts(labels, ('atomic_group',))
showardc92da832009-04-07 18:14:34 +0000230
231
232# atomic groups
233
showarde9450c92009-06-30 01:58:52 +0000234def add_atomic_group(name, max_number_of_machines=None, description=None):
showardc92da832009-04-07 18:14:34 +0000235 return models.AtomicGroup.add_object(
236 name=name, max_number_of_machines=max_number_of_machines,
237 description=description).id
238
239
240def modify_atomic_group(id, **data):
241 models.AtomicGroup.smart_get(id).update_object(data)
242
243
244def delete_atomic_group(id):
245 models.AtomicGroup.smart_get(id).delete()
246
247
248def atomic_group_add_labels(id, labels):
249 label_objs = models.Label.smart_get_bulk(labels)
250 models.AtomicGroup.smart_get(id).label_set.add(*label_objs)
251
252
253def atomic_group_remove_labels(id, labels):
254 label_objs = models.Label.smart_get_bulk(labels)
255 models.AtomicGroup.smart_get(id).label_set.remove(*label_objs)
256
257
258def get_atomic_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000259 return rpc_utils.prepare_for_serialization(
showardc92da832009-04-07 18:14:34 +0000260 models.AtomicGroup.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000261
262
263# hosts
264
Matthew Sartori68186332015-04-27 17:19:53 -0700265def add_host(hostname, status=None, locked=None, lock_reason='', protection=None):
266 if locked and not lock_reason:
267 raise model_logic.ValidationError(
268 {'locked': 'Please provide a reason for locking when adding host.'})
269
jadmanski0afbb632008-06-06 21:10:57 +0000270 return models.Host.add_object(hostname=hostname, status=status,
Matthew Sartori68186332015-04-27 17:19:53 -0700271 locked=locked, lock_reason=lock_reason,
272 protection=protection).id
mblighe8819cd2008-02-15 16:48:40 +0000273
274
MK Ryu33889612015-09-04 14:32:35 -0700275@rpc_utils.route_rpc_to_master
276def modify_host(id, **kwargs):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700277 """Modify local attributes of a host.
278
279 If this is called on the master, but the host is assigned to a shard, this
MK Ryu33889612015-09-04 14:32:35 -0700280 will call `modify_host_local` RPC to the responsible shard. This means if
281 a host is being locked using this function, this change will also propagate
282 to shards.
283 When this is called on a shard, the shard just routes the RPC to the master
284 and does nothing.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700285
286 @param id: id of the host to modify.
MK Ryu33889612015-09-04 14:32:35 -0700287 @param kwargs: key=value pairs of values to set on the host.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700288 """
MK Ryu33889612015-09-04 14:32:35 -0700289 rpc_utils.check_modify_host(kwargs)
showardce7c0922009-09-11 18:39:24 +0000290 host = models.Host.smart_get(id)
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800291 try:
292 rpc_utils.check_modify_host_locking(host, kwargs)
293 except model_logic.ValidationError as e:
294 if not kwargs.get('force_modify_locking', False):
295 raise
296 logging.exception('The following exception will be ignored and lock '
297 'modification will be enforced. %s', e)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700298
MK Ryud53e1492015-12-15 12:09:03 -0800299 # This is required to make `lock_time` for a host be exactly same
300 # between the master and a shard.
301 if kwargs.get('locked', None) and 'lock_time' not in kwargs:
302 kwargs['lock_time'] = datetime.datetime.now()
MK Ryu8e2c2d02016-01-06 15:24:38 -0800303 host.update_object(kwargs)
MK Ryud53e1492015-12-15 12:09:03 -0800304
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800305 # force_modifying_locking is not an internal field in database, remove.
306 kwargs.pop('force_modify_locking', None)
MK Ryu33889612015-09-04 14:32:35 -0700307 rpc_utils.fanout_rpc([host], 'modify_host_local',
308 include_hostnames=False, id=id, **kwargs)
mblighe8819cd2008-02-15 16:48:40 +0000309
310
MK Ryu33889612015-09-04 14:32:35 -0700311def modify_host_local(id, **kwargs):
312 """Modify host attributes in local DB.
313
314 @param id: Host id.
315 @param kwargs: key=value pairs of values to set on the host.
316 """
317 models.Host.smart_get(id).update_object(kwargs)
318
319
320@rpc_utils.route_rpc_to_master
showard276f9442009-05-20 00:33:16 +0000321def modify_hosts(host_filter_data, update_data):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700322 """Modify local attributes of multiple hosts.
323
324 If this is called on the master, but one of the hosts in that match the
MK Ryu33889612015-09-04 14:32:35 -0700325 filters is assigned to a shard, this will call `modify_hosts_local` RPC
326 to the responsible shard.
327 When this is called on a shard, the shard just routes the RPC to the master
328 and does nothing.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700329
330 The filters are always applied on the master, not on the shards. This means
331 if the states of a host differ on the master and a shard, the state on the
332 master will be used. I.e. this means:
333 A host was synced to Shard 1. On Shard 1 the status of the host was set to
334 'Repair Failed'.
335 - A call to modify_hosts with host_filter_data={'status': 'Ready'} will
336 update the host (both on the shard and on the master), because the state
337 of the host as the master knows it is still 'Ready'.
338 - A call to modify_hosts with host_filter_data={'status': 'Repair failed'
339 will not update the host, because the filter doesn't apply on the master.
340
showardbe0d8692009-08-20 23:42:44 +0000341 @param host_filter_data: Filters out which hosts to modify.
342 @param update_data: A dictionary with the changes to make to the hosts.
showard276f9442009-05-20 00:33:16 +0000343 """
MK Ryu93161712015-12-21 10:41:32 -0800344 update_data = update_data.copy()
showardbe0d8692009-08-20 23:42:44 +0000345 rpc_utils.check_modify_host(update_data)
showard276f9442009-05-20 00:33:16 +0000346 hosts = models.Host.query_objects(host_filter_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700347
348 affected_shard_hostnames = set()
349 affected_host_ids = []
350
Alex Miller9658a952013-05-14 16:40:02 -0700351 # Check all hosts before changing data for exception safety.
352 for host in hosts:
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800353 try:
354 rpc_utils.check_modify_host_locking(host, update_data)
355 except model_logic.ValidationError as e:
356 if not update_data.get('force_modify_locking', False):
357 raise
358 logging.exception('The following exception will be ignored and '
359 'lock modification will be enforced. %s', e)
360
Jakob Juelich50e91f72014-10-01 12:43:23 -0700361 if host.shard:
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800362 affected_shard_hostnames.add(host.shard.rpc_hostname())
Jakob Juelich50e91f72014-10-01 12:43:23 -0700363 affected_host_ids.append(host.id)
364
MK Ryud53e1492015-12-15 12:09:03 -0800365 # This is required to make `lock_time` for a host be exactly same
366 # between the master and a shard.
367 if update_data.get('locked', None) and 'lock_time' not in update_data:
368 update_data['lock_time'] = datetime.datetime.now()
MK Ryu8e2c2d02016-01-06 15:24:38 -0800369 for host in hosts:
370 host.update_object(update_data)
MK Ryud53e1492015-12-15 12:09:03 -0800371
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800372 update_data.pop('force_modify_locking', None)
MK Ryu33889612015-09-04 14:32:35 -0700373 # Caution: Changing the filter from the original here. See docstring.
374 rpc_utils.run_rpc_on_multiple_hostnames(
375 'modify_hosts_local', affected_shard_hostnames,
Jakob Juelich50e91f72014-10-01 12:43:23 -0700376 host_filter_data={'id__in': affected_host_ids},
377 update_data=update_data)
378
showard276f9442009-05-20 00:33:16 +0000379
MK Ryu33889612015-09-04 14:32:35 -0700380def modify_hosts_local(host_filter_data, update_data):
381 """Modify attributes of hosts in local DB.
382
383 @param host_filter_data: Filters out which hosts to modify.
384 @param update_data: A dictionary with the changes to make to the hosts.
385 """
386 for host in models.Host.query_objects(host_filter_data):
387 host.update_object(update_data)
388
389
MK Ryufbb002c2015-06-08 14:13:16 -0700390def add_labels_to_host(id, labels):
391 """Adds labels to a given host only in local DB.
showardcafd16e2009-05-29 18:37:49 +0000392
MK Ryufbb002c2015-06-08 14:13:16 -0700393 @param id: id or hostname for a host.
394 @param labels: ids or names for labels.
395 """
396 label_objs = models.Label.smart_get_bulk(labels)
397 models.Host.smart_get(id).labels.add(*label_objs)
398
399
400@rpc_utils.route_rpc_to_master
401def host_add_labels(id, labels):
402 """Adds labels to a given host.
403
404 @param id: id or hostname for a host.
405 @param labels: ids or names for labels.
406
407 @raises ValidationError: If adding more than one platform label.
408 """
409 label_objs = models.Label.smart_get_bulk(labels)
410 platforms = [label.name for label in label_objs if label.platform]
showardcafd16e2009-05-29 18:37:49 +0000411 if len(platforms) > 1:
412 raise model_logic.ValidationError(
413 {'labels': 'Adding more than one platform label: %s' %
414 ', '.join(platforms)})
MK Ryufbb002c2015-06-08 14:13:16 -0700415
416 host_obj = models.Host.smart_get(id)
showardcafd16e2009-05-29 18:37:49 +0000417 if len(platforms) == 1:
MK Ryufbb002c2015-06-08 14:13:16 -0700418 models.Host.check_no_platform([host_obj])
MK Ryu8e2c2d02016-01-06 15:24:38 -0800419 add_labels_to_host(id, labels)
MK Ryufbb002c2015-06-08 14:13:16 -0700420
421 rpc_utils.fanout_rpc([host_obj], 'add_labels_to_host', False,
422 id=id, labels=labels)
mblighe8819cd2008-02-15 16:48:40 +0000423
424
MK Ryufbb002c2015-06-08 14:13:16 -0700425def remove_labels_from_host(id, labels):
426 """Removes labels from a given host only in local DB.
427
428 @param id: id or hostname for a host.
429 @param labels: ids or names for labels.
430 """
431 label_objs = models.Label.smart_get_bulk(labels)
432 models.Host.smart_get(id).labels.remove(*label_objs)
433
434
435@rpc_utils.route_rpc_to_master
mblighe8819cd2008-02-15 16:48:40 +0000436def host_remove_labels(id, labels):
MK Ryufbb002c2015-06-08 14:13:16 -0700437 """Removes labels from a given host.
438
439 @param id: id or hostname for a host.
440 @param labels: ids or names for labels.
441 """
MK Ryu8e2c2d02016-01-06 15:24:38 -0800442 remove_labels_from_host(id, labels)
443
MK Ryufbb002c2015-06-08 14:13:16 -0700444 host_obj = models.Host.smart_get(id)
445 rpc_utils.fanout_rpc([host_obj], 'remove_labels_from_host', False,
446 id=id, labels=labels)
mblighe8819cd2008-02-15 16:48:40 +0000447
448
MK Ryuacf35922014-10-03 14:56:49 -0700449def get_host_attribute(attribute, **host_filter_data):
450 """
451 @param attribute: string name of attribute
452 @param host_filter_data: filter data to apply to Hosts to choose hosts to
453 act upon
454 """
455 hosts = rpc_utils.get_host_query((), False, False, True, host_filter_data)
456 hosts = list(hosts)
457 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
458 'attribute_list')
459 host_attr_dicts = []
460 for host_obj in hosts:
461 for attr_obj in host_obj.attribute_list:
462 if attr_obj.attribute == attribute:
463 host_attr_dicts.append(attr_obj.get_object_dict())
464 return rpc_utils.prepare_for_serialization(host_attr_dicts)
465
466
showard0957a842009-05-11 19:25:08 +0000467def set_host_attribute(attribute, value, **host_filter_data):
468 """
MK Ryu26f0c932015-05-28 18:14:33 -0700469 @param attribute: string name of attribute
470 @param value: string, or None to delete an attribute
471 @param host_filter_data: filter data to apply to Hosts to choose hosts to
472 act upon
showard0957a842009-05-11 19:25:08 +0000473 """
474 assert host_filter_data # disallow accidental actions on all hosts
475 hosts = models.Host.query_objects(host_filter_data)
476 models.AclGroup.check_for_acl_violation_hosts(hosts)
MK Ryu8e2c2d02016-01-06 15:24:38 -0800477 for host in hosts:
478 host.set_or_delete_attribute(attribute, value)
showard0957a842009-05-11 19:25:08 +0000479
MK Ryu26f0c932015-05-28 18:14:33 -0700480 # Master forwards this RPC to shards.
481 if not utils.is_shard():
482 rpc_utils.fanout_rpc(hosts, 'set_host_attribute', False,
483 attribute=attribute, value=value, **host_filter_data)
484
showard0957a842009-05-11 19:25:08 +0000485
Jakob Juelich50e91f72014-10-01 12:43:23 -0700486@rpc_utils.forward_single_host_rpc_to_shard
mblighe8819cd2008-02-15 16:48:40 +0000487def delete_host(id):
jadmanski0afbb632008-06-06 21:10:57 +0000488 models.Host.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000489
490
showard87cc38f2009-08-20 23:37:04 +0000491def get_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
Dan Shi37df54d2015-12-14 11:16:28 -0800492 exclude_atomic_group_hosts=False, valid_only=True,
493 include_current_job=False, **filter_data):
494 """Get a list of dictionaries which contains the information of hosts.
495
showard87cc38f2009-08-20 23:37:04 +0000496 @param multiple_labels: match hosts in all of the labels given. Should
497 be a list of label names.
498 @param exclude_only_if_needed_labels: Exclude hosts with at least one
499 "only_if_needed" label applied.
500 @param exclude_atomic_group_hosts: Exclude hosts that have one or more
501 atomic group labels associated with them.
Dan Shi37df54d2015-12-14 11:16:28 -0800502 @param include_current_job: Set to True to include ids of currently running
503 job and special task.
jadmanski0afbb632008-06-06 21:10:57 +0000504 """
showard43a3d262008-11-12 18:17:05 +0000505 hosts = rpc_utils.get_host_query(multiple_labels,
506 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000507 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000508 valid_only, filter_data)
showard0957a842009-05-11 19:25:08 +0000509 hosts = list(hosts)
510 models.Host.objects.populate_relationships(hosts, models.Label,
511 'label_list')
512 models.Host.objects.populate_relationships(hosts, models.AclGroup,
513 'acl_list')
514 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
515 'attribute_list')
showard43a3d262008-11-12 18:17:05 +0000516 host_dicts = []
517 for host_obj in hosts:
518 host_dict = host_obj.get_object_dict()
showard0957a842009-05-11 19:25:08 +0000519 host_dict['labels'] = [label.name for label in host_obj.label_list]
showard909c9142009-07-07 20:54:42 +0000520 host_dict['platform'], host_dict['atomic_group'] = (rpc_utils.
521 find_platform_and_atomic_group(host_obj))
showard0957a842009-05-11 19:25:08 +0000522 host_dict['acls'] = [acl.name for acl in host_obj.acl_list]
523 host_dict['attributes'] = dict((attribute.attribute, attribute.value)
524 for attribute in host_obj.attribute_list)
Dan Shi37df54d2015-12-14 11:16:28 -0800525 if include_current_job:
526 host_dict['current_job'] = None
527 host_dict['current_special_task'] = None
528 entries = models.HostQueueEntry.objects.filter(
529 host_id=host_dict['id'], active=True, complete=False)
530 if entries:
531 host_dict['current_job'] = (
532 entries[0].get_object_dict()['job'])
533 tasks = models.SpecialTask.objects.filter(
534 host_id=host_dict['id'], is_active=True, is_complete=False)
535 if tasks:
536 host_dict['current_special_task'] = (
537 '%d-%s' % (tasks[0].get_object_dict()['id'],
538 tasks[0].get_object_dict()['task'].lower()))
showard43a3d262008-11-12 18:17:05 +0000539 host_dicts.append(host_dict)
540 return rpc_utils.prepare_for_serialization(host_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000541
542
showard87cc38f2009-08-20 23:37:04 +0000543def get_num_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000544 exclude_atomic_group_hosts=False, valid_only=True,
545 **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000546 """
547 Same parameters as get_hosts().
548
549 @returns The number of matching hosts.
550 """
showard43a3d262008-11-12 18:17:05 +0000551 hosts = rpc_utils.get_host_query(multiple_labels,
552 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000553 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000554 valid_only, filter_data)
showard43a3d262008-11-12 18:17:05 +0000555 return hosts.count()
showard1385b162008-03-13 15:59:40 +0000556
mblighe8819cd2008-02-15 16:48:40 +0000557
558# tests
559
showard909c7a62008-07-15 21:52:38 +0000560def add_test(name, test_type, path, author=None, dependencies=None,
showard3d9899a2008-07-31 02:11:58 +0000561 experimental=True, run_verify=None, test_class=None,
showard909c7a62008-07-15 21:52:38 +0000562 test_time=None, test_category=None, description=None,
563 sync_count=1):
jadmanski0afbb632008-06-06 21:10:57 +0000564 return models.Test.add_object(name=name, test_type=test_type, path=path,
showard909c7a62008-07-15 21:52:38 +0000565 author=author, dependencies=dependencies,
566 experimental=experimental,
567 run_verify=run_verify, test_time=test_time,
568 test_category=test_category,
569 sync_count=sync_count,
jadmanski0afbb632008-06-06 21:10:57 +0000570 test_class=test_class,
571 description=description).id
mblighe8819cd2008-02-15 16:48:40 +0000572
573
574def modify_test(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000575 models.Test.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000576
577
578def delete_test(id):
jadmanski0afbb632008-06-06 21:10:57 +0000579 models.Test.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000580
581
582def get_tests(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000583 return rpc_utils.prepare_for_serialization(
584 models.Test.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000585
586
Moises Osorio2dc7a102014-12-02 18:24:02 -0800587@_timer.decorate
588def get_tests_status_counts_by_job_name_label(job_name_prefix, label_name):
589 """Gets the counts of all passed and failed tests from the matching jobs.
590
591 @param job_name_prefix: Name prefix of the jobs to get the summary from, e.g.,
592 'butterfly-release/R40-6457.21.0/bvt-cq/'.
593 @param label_name: Label that must be set in the jobs, e.g.,
594 'cros-version:butterfly-release/R40-6457.21.0'.
595
596 @returns A summary of the counts of all the passed and failed tests.
597 """
598 job_ids = list(models.Job.objects.filter(
599 name__startswith=job_name_prefix,
600 dependency_labels__name=label_name).values_list(
601 'pk', flat=True))
602 summary = {'passed': 0, 'failed': 0}
603 if not job_ids:
604 return summary
605
606 counts = (tko_models.TestView.objects.filter(
607 afe_job_id__in=job_ids).exclude(
608 test_name='SERVER_JOB').exclude(
609 test_name__startswith='CLIENT_JOB').values(
610 'status').annotate(
611 count=Count('status')))
612 for status in counts:
613 if status['status'] == 'GOOD':
614 summary['passed'] += status['count']
615 else:
616 summary['failed'] += status['count']
617 return summary
618
619
showard2b9a88b2008-06-13 20:55:03 +0000620# profilers
621
622def add_profiler(name, description=None):
623 return models.Profiler.add_object(name=name, description=description).id
624
625
626def modify_profiler(id, **data):
627 models.Profiler.smart_get(id).update_object(data)
628
629
630def delete_profiler(id):
631 models.Profiler.smart_get(id).delete()
632
633
634def get_profilers(**filter_data):
635 return rpc_utils.prepare_for_serialization(
636 models.Profiler.list_objects(filter_data))
637
638
mblighe8819cd2008-02-15 16:48:40 +0000639# users
640
641def add_user(login, access_level=None):
jadmanski0afbb632008-06-06 21:10:57 +0000642 return models.User.add_object(login=login, access_level=access_level).id
mblighe8819cd2008-02-15 16:48:40 +0000643
644
645def modify_user(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000646 models.User.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000647
648
649def delete_user(id):
jadmanski0afbb632008-06-06 21:10:57 +0000650 models.User.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000651
652
653def get_users(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000654 return rpc_utils.prepare_for_serialization(
655 models.User.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000656
657
658# acl groups
659
660def add_acl_group(name, description=None):
showard04f2cd82008-07-25 20:53:31 +0000661 group = models.AclGroup.add_object(name=name, description=description)
showard64a95952010-01-13 21:27:16 +0000662 group.users.add(models.User.current_user())
showard04f2cd82008-07-25 20:53:31 +0000663 return group.id
mblighe8819cd2008-02-15 16:48:40 +0000664
665
666def modify_acl_group(id, **data):
showard04f2cd82008-07-25 20:53:31 +0000667 group = models.AclGroup.smart_get(id)
668 group.check_for_acl_violation_acl_group()
669 group.update_object(data)
670 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000671
672
673def acl_group_add_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000674 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000675 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000676 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000677 group.users.add(*users)
mblighe8819cd2008-02-15 16:48:40 +0000678
679
680def acl_group_remove_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000681 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000682 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000683 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000684 group.users.remove(*users)
showard04f2cd82008-07-25 20:53:31 +0000685 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000686
687
688def acl_group_add_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000689 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000690 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000691 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000692 group.hosts.add(*hosts)
showard08f981b2008-06-24 21:59:03 +0000693 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000694
695
696def acl_group_remove_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000697 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000698 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000699 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000700 group.hosts.remove(*hosts)
showard08f981b2008-06-24 21:59:03 +0000701 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000702
703
704def delete_acl_group(id):
jadmanski0afbb632008-06-06 21:10:57 +0000705 models.AclGroup.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000706
707
708def get_acl_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000709 acl_groups = models.AclGroup.list_objects(filter_data)
710 for acl_group in acl_groups:
711 acl_group_obj = models.AclGroup.objects.get(id=acl_group['id'])
712 acl_group['users'] = [user.login
713 for user in acl_group_obj.users.all()]
714 acl_group['hosts'] = [host.hostname
715 for host in acl_group_obj.hosts.all()]
716 return rpc_utils.prepare_for_serialization(acl_groups)
mblighe8819cd2008-02-15 16:48:40 +0000717
718
719# jobs
720
mbligh120351e2009-01-24 01:40:45 +0000721def generate_control_file(tests=(), kernel=None, label=None, profilers=(),
showard91f85102009-10-12 20:34:52 +0000722 client_control_file='', use_container=False,
Matthew Sartori10438092015-06-24 14:30:18 -0700723 profile_only=None, upload_kernel_config=False,
724 db_tests=True):
jadmanski0afbb632008-06-06 21:10:57 +0000725 """
mbligh120351e2009-01-24 01:40:45 +0000726 Generates a client-side control file to load a kernel and run tests.
727
Matthew Sartori10438092015-06-24 14:30:18 -0700728 @param tests List of tests to run. See db_tests for more information.
mbligha3c58d22009-08-24 22:01:51 +0000729 @param kernel A list of kernel info dictionaries configuring which kernels
730 to boot for this job and other options for them
mbligh120351e2009-01-24 01:40:45 +0000731 @param label Name of label to grab kernel config from.
732 @param profilers List of profilers to activate during the job.
733 @param client_control_file The contents of a client-side control file to
734 run at the end of all tests. If this is supplied, all tests must be
735 client side.
736 TODO: in the future we should support server control files directly
737 to wrap with a kernel. That'll require changing the parameter
738 name and adding a boolean to indicate if it is a client or server
739 control file.
740 @param use_container unused argument today. TODO: Enable containers
741 on the host during a client side test.
showard91f85102009-10-12 20:34:52 +0000742 @param profile_only A boolean that indicates what default profile_only
743 mode to use in the control file. Passing None will generate a
744 control file that does not explcitly set the default mode at all.
showard232b7ae2009-11-10 00:46:48 +0000745 @param upload_kernel_config: if enabled it will generate server control
746 file code that uploads the kernel config file to the client and
747 tells the client of the new (local) path when compiling the kernel;
748 the tests must be server side tests
Matthew Sartori10438092015-06-24 14:30:18 -0700749 @param db_tests: if True, the test object can be found in the database
750 backing the test model. In this case, tests is a tuple
751 of test IDs which are used to retrieve the test objects
752 from the database. If False, tests is a tuple of test
753 dictionaries stored client-side in the AFE.
mbligh120351e2009-01-24 01:40:45 +0000754
755 @returns a dict with the following keys:
756 control_file: str, The control file text.
757 is_server: bool, is the control file a server-side control file?
758 synch_count: How many machines the job uses per autoserv execution.
759 synch_count == 1 means the job is asynchronous.
760 dependencies: A list of the names of labels on which the job depends.
761 """
showardd86debe2009-06-10 17:37:56 +0000762 if not tests and not client_control_file:
showard2bab8f42008-11-12 18:15:22 +0000763 return dict(control_file='', is_server=False, synch_count=1,
showard989f25d2008-10-01 11:38:11 +0000764 dependencies=[])
mblighe8819cd2008-02-15 16:48:40 +0000765
showard989f25d2008-10-01 11:38:11 +0000766 cf_info, test_objects, profiler_objects, label = (
showard2b9a88b2008-06-13 20:55:03 +0000767 rpc_utils.prepare_generate_control_file(tests, kernel, label,
Matthew Sartori10438092015-06-24 14:30:18 -0700768 profilers, db_tests))
showard989f25d2008-10-01 11:38:11 +0000769 cf_info['control_file'] = control_file.generate_control(
mbligha3c58d22009-08-24 22:01:51 +0000770 tests=test_objects, kernels=kernel, platform=label,
mbligh120351e2009-01-24 01:40:45 +0000771 profilers=profiler_objects, is_server=cf_info['is_server'],
showard232b7ae2009-11-10 00:46:48 +0000772 client_control_file=client_control_file, profile_only=profile_only,
773 upload_kernel_config=upload_kernel_config)
showard989f25d2008-10-01 11:38:11 +0000774 return cf_info
mblighe8819cd2008-02-15 16:48:40 +0000775
776
jamesren4a41e012010-07-16 22:33:48 +0000777def create_parameterized_job(name, priority, test, parameters, kernel=None,
778 label=None, profilers=(), profiler_parameters=None,
779 use_container=False, profile_only=None,
780 upload_kernel_config=False, hosts=(),
781 meta_hosts=(), one_time_hosts=(),
782 atomic_group_name=None, synch_count=None,
783 is_template=False, timeout=None,
Simran Basi7e605742013-11-12 13:43:36 -0800784 timeout_mins=None, max_runtime_mins=None,
785 run_verify=False, email_list='', dependencies=(),
786 reboot_before=None, reboot_after=None,
787 parse_failed_repair=None, hostless=False,
Dan Shiec1d47d2015-02-13 11:38:13 -0800788 keyvals=None, drone_set=None, run_reset=True,
Dan Shi2a5297b2015-07-23 17:03:29 -0700789 require_ssp=None):
jamesren4a41e012010-07-16 22:33:48 +0000790 """
791 Creates and enqueues a parameterized job.
792
793 Most parameters a combination of the parameters for generate_control_file()
794 and create_job(), with the exception of:
795
796 @param test name or ID of the test to run
797 @param parameters a map of parameter name ->
798 tuple of (param value, param type)
799 @param profiler_parameters a dictionary of parameters for the profilers:
800 key: profiler name
801 value: dict of param name -> tuple of
802 (param value,
803 param type)
804 """
805 # Save the values of the passed arguments here. What we're going to do with
806 # them is pass them all to rpc_utils.get_create_job_common_args(), which
807 # will extract the subset of these arguments that apply for
808 # rpc_utils.create_job_common(), which we then pass in to that function.
809 args = locals()
810
811 # Set up the parameterized job configs
812 test_obj = models.Test.smart_get(test)
Aviv Keshet3dd8beb2013-05-13 17:36:04 -0700813 control_type = test_obj.test_type
jamesren4a41e012010-07-16 22:33:48 +0000814
815 try:
816 label = models.Label.smart_get(label)
817 except models.Label.DoesNotExist:
818 label = None
819
820 kernel_objs = models.Kernel.create_kernels(kernel)
821 profiler_objs = [models.Profiler.smart_get(profiler)
822 for profiler in profilers]
823
824 parameterized_job = models.ParameterizedJob.objects.create(
825 test=test_obj, label=label, use_container=use_container,
826 profile_only=profile_only,
827 upload_kernel_config=upload_kernel_config)
828 parameterized_job.kernels.add(*kernel_objs)
829
830 for profiler in profiler_objs:
831 parameterized_profiler = models.ParameterizedJobProfiler.objects.create(
832 parameterized_job=parameterized_job,
833 profiler=profiler)
834 profiler_params = profiler_parameters.get(profiler.name, {})
835 for name, (value, param_type) in profiler_params.iteritems():
836 models.ParameterizedJobProfilerParameter.objects.create(
837 parameterized_job_profiler=parameterized_profiler,
838 parameter_name=name,
839 parameter_value=value,
840 parameter_type=param_type)
841
842 try:
843 for parameter in test_obj.testparameter_set.all():
844 if parameter.name in parameters:
845 param_value, param_type = parameters.pop(parameter.name)
846 parameterized_job.parameterizedjobparameter_set.create(
847 test_parameter=parameter, parameter_value=param_value,
848 parameter_type=param_type)
849
850 if parameters:
851 raise Exception('Extra parameters remain: %r' % parameters)
852
853 return rpc_utils.create_job_common(
854 parameterized_job=parameterized_job.id,
855 control_type=control_type,
856 **rpc_utils.get_create_job_common_args(args))
857 except:
858 parameterized_job.delete()
859 raise
860
861
Simran Basib6ec8ae2014-04-23 12:05:08 -0700862def create_job_page_handler(name, priority, control_file, control_type,
Dan Shid215dbe2015-06-18 16:14:59 -0700863 image=None, hostless=False, firmware_rw_build=None,
864 firmware_ro_build=None, test_source_build=None,
865 **kwargs):
Simran Basib6ec8ae2014-04-23 12:05:08 -0700866 """\
867 Create and enqueue a job.
868
869 @param name name of this job
870 @param priority Integer priority of this job. Higher is more important.
871 @param control_file String contents of the control file.
872 @param control_type Type of control file, Client or Server.
Dan Shid215dbe2015-06-18 16:14:59 -0700873 @param image: ChromeOS build to be installed in the dut. Default to None.
874 @param firmware_rw_build: Firmware build to update RW firmware. Default to
875 None, i.e., RW firmware will not be updated.
876 @param firmware_ro_build: Firmware build to update RO firmware. Default to
877 None, i.e., RO firmware will not be updated.
878 @param test_source_build: Build to be used to retrieve test code. Default
879 to None.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700880 @param kwargs extra args that will be required by create_suite_job or
881 create_job.
882
883 @returns The created Job id number.
884 """
885 control_file = rpc_utils.encode_ascii(control_file)
Jiaxi Luodd67beb2014-07-18 16:28:31 -0700886 if not control_file:
887 raise model_logic.ValidationError({
888 'control_file' : "Control file cannot be empty"})
Simran Basib6ec8ae2014-04-23 12:05:08 -0700889
890 if image and hostless:
Dan Shid215dbe2015-06-18 16:14:59 -0700891 builds = {}
892 builds[provision.CROS_VERSION_PREFIX] = image
893 if firmware_rw_build:
Dan Shi0723bf52015-06-24 10:52:38 -0700894 builds[provision.FW_RW_VERSION_PREFIX] = firmware_rw_build
Dan Shid215dbe2015-06-18 16:14:59 -0700895 if firmware_ro_build:
896 builds[provision.FW_RO_VERSION_PREFIX] = firmware_ro_build
Simran Basib6ec8ae2014-04-23 12:05:08 -0700897 return site_rpc_interface.create_suite_job(
898 name=name, control_file=control_file, priority=priority,
Dan Shid215dbe2015-06-18 16:14:59 -0700899 builds=builds, test_source_build=test_source_build, **kwargs)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700900 return create_job(name, priority, control_file, control_type, image=image,
901 hostless=hostless, **kwargs)
902
903
MK Ryue301eb72015-06-25 12:51:02 -0700904@rpc_utils.route_rpc_to_master
showard12f3e322009-05-13 21:27:42 +0000905def create_job(name, priority, control_file, control_type,
906 hosts=(), meta_hosts=(), one_time_hosts=(),
907 atomic_group_name=None, synch_count=None, is_template=False,
Simran Basi7e605742013-11-12 13:43:36 -0800908 timeout=None, timeout_mins=None, max_runtime_mins=None,
909 run_verify=False, email_list='', dependencies=(),
910 reboot_before=None, reboot_after=None, parse_failed_repair=None,
911 hostless=False, keyvals=None, drone_set=None, image=None,
Dan Shiec1d47d2015-02-13 11:38:13 -0800912 parent_job_id=None, test_retry=0, run_reset=True,
913 require_ssp=None, args=(), **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +0000914 """\
915 Create and enqueue a job.
mblighe8819cd2008-02-15 16:48:40 +0000916
showarda1e74b32009-05-12 17:32:04 +0000917 @param name name of this job
Alex Miller7d658cf2013-09-04 16:00:35 -0700918 @param priority Integer priority of this job. Higher is more important.
showarda1e74b32009-05-12 17:32:04 +0000919 @param control_file String contents of the control file.
920 @param control_type Type of control file, Client or Server.
921 @param synch_count How many machines the job uses per autoserv execution.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700922 synch_count == 1 means the job is asynchronous. If an atomic group is
923 given this value is treated as a minimum.
showarda1e74b32009-05-12 17:32:04 +0000924 @param is_template If true then create a template job.
925 @param timeout Hours after this call returns until the job times out.
Simran Basi7e605742013-11-12 13:43:36 -0800926 @param timeout_mins Minutes after this call returns until the job times
Jiaxi Luo90190c92014-06-18 12:35:57 -0700927 out.
Simran Basi34217022012-11-06 13:43:15 -0800928 @param max_runtime_mins Minutes from job starting time until job times out
showarda1e74b32009-05-12 17:32:04 +0000929 @param run_verify Should the host be verified before running the test?
930 @param email_list String containing emails to mail when the job is done
931 @param dependencies List of label names on which this job depends
932 @param reboot_before Never, If dirty, or Always
933 @param reboot_after Never, If all tests passed, or Always
934 @param parse_failed_repair if true, results of failed repairs launched by
Jiaxi Luo90190c92014-06-18 12:35:57 -0700935 this job will be parsed as part of the job.
showarda9545c02009-12-18 22:44:26 +0000936 @param hostless if true, create a hostless job
showardc1a98d12010-01-15 00:22:22 +0000937 @param keyvals dict of keyvals to associate with the job
showarda1e74b32009-05-12 17:32:04 +0000938 @param hosts List of hosts to run job on.
939 @param meta_hosts List where each entry is a label name, and for each entry
Jiaxi Luo90190c92014-06-18 12:35:57 -0700940 one host will be chosen from that label to run the job on.
showarda1e74b32009-05-12 17:32:04 +0000941 @param one_time_hosts List of hosts not in the database to run the job on.
942 @param atomic_group_name The name of an atomic group to schedule the job on.
jamesren76fcf192010-04-21 20:39:50 +0000943 @param drone_set The name of the drone set to run this test on.
Paul Pendlebury5a8c6ad2011-02-01 07:20:17 -0800944 @param image OS image to install before running job.
Aviv Keshet0b9cfc92013-02-05 11:36:02 -0800945 @param parent_job_id id of a job considered to be parent of created job.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700946 @param test_retry Number of times to retry test if the test did not
Jiaxi Luo90190c92014-06-18 12:35:57 -0700947 complete successfully. (optional, default: 0)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700948 @param run_reset Should the host be reset before running the test?
Dan Shiec1d47d2015-02-13 11:38:13 -0800949 @param require_ssp Set to True to require server-side packaging to run the
950 test. If it's set to None, drone will still try to run
951 the server side with server-side packaging. If the
952 autotest-server package doesn't exist for the build or
953 image is not set, drone will run the test without server-
954 side packaging. Default is None.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700955 @param args A list of args to be injected into control file.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700956 @param kwargs extra keyword args. NOT USED.
showardc92da832009-04-07 18:14:34 +0000957
958 @returns The created Job id number.
jadmanski0afbb632008-06-06 21:10:57 +0000959 """
Jiaxi Luo90190c92014-06-18 12:35:57 -0700960 if args:
961 control_file = tools.inject_vars({'args': args}, control_file)
962
Simran Basiab5a1bf2014-05-28 15:39:44 -0700963 if image is None:
964 return rpc_utils.create_job_common(
965 **rpc_utils.get_create_job_common_args(locals()))
966
Simran Basi6157e8e2015-12-07 18:22:34 -0800967 # Translate the image name, in case its a relative build name.
968 ds = dev_server.ImageServer.resolve(image)
969 image = ds.translate(image)
970
Simran Basiab5a1bf2014-05-28 15:39:44 -0700971 # When image is supplied use a known parameterized test already in the
972 # database to pass the OS image path from the front end, through the
973 # scheduler, and finally to autoserv as the --image parameter.
974
975 # The test autoupdate_ParameterizedJob is in afe_autotests and used to
976 # instantiate a Test object and from there a ParameterizedJob.
977 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
978 known_parameterized_job = models.ParameterizedJob.objects.create(
979 test=known_test_obj)
980
981 # autoupdate_ParameterizedJob has a single parameter, the image parameter,
982 # stored in the table afe_test_parameters. We retrieve and set this
983 # instance of the parameter to the OS image path.
984 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
985 name='image')
986 known_parameterized_job.parameterizedjobparameter_set.create(
987 test_parameter=image_parameter, parameter_value=image,
988 parameter_type='string')
989
Dan Shid215dbe2015-06-18 16:14:59 -0700990 # TODO(crbug.com/502638): save firmware build etc to parameterized_job.
991
Simran Basiab5a1bf2014-05-28 15:39:44 -0700992 # By passing a parameterized_job to create_job_common the job entry in
993 # the afe_jobs table will have the field parameterized_job_id set.
994 # The scheduler uses this id in the afe_parameterized_jobs table to
995 # match this job to our known test, and then with the
996 # afe_parameterized_job_parameters table to get the actual image path.
jamesren4a41e012010-07-16 22:33:48 +0000997 return rpc_utils.create_job_common(
Simran Basiab5a1bf2014-05-28 15:39:44 -0700998 parameterized_job=known_parameterized_job.id,
jamesren4a41e012010-07-16 22:33:48 +0000999 **rpc_utils.get_create_job_common_args(locals()))
mblighe8819cd2008-02-15 16:48:40 +00001000
1001
showard9dbdcda2008-10-14 17:34:36 +00001002def abort_host_queue_entries(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001003 """\
showard9dbdcda2008-10-14 17:34:36 +00001004 Abort a set of host queue entries.
Fang Deng63b0e452014-12-19 14:38:15 -08001005
1006 @return: A list of dictionaries, each contains information
1007 about an aborted HQE.
jadmanski0afbb632008-06-06 21:10:57 +00001008 """
showard9dbdcda2008-10-14 17:34:36 +00001009 query = models.HostQueueEntry.query_objects(filter_data)
beepsfaecbce2013-10-29 11:35:10 -07001010
1011 # Dont allow aborts on:
1012 # 1. Jobs that have already completed (whether or not they were aborted)
1013 # 2. Jobs that we have already been aborted (but may not have completed)
1014 query = query.filter(complete=False).filter(aborted=False)
showarddc817512008-11-12 18:16:41 +00001015 models.AclGroup.check_abort_permissions(query)
showard9dbdcda2008-10-14 17:34:36 +00001016 host_queue_entries = list(query.select_related())
showard2bab8f42008-11-12 18:15:22 +00001017 rpc_utils.check_abort_synchronous_jobs(host_queue_entries)
mblighe8819cd2008-02-15 16:48:40 +00001018
Simran Basic1b26762013-06-26 14:23:21 -07001019 models.HostQueueEntry.abort_host_queue_entries(host_queue_entries)
Fang Deng63b0e452014-12-19 14:38:15 -08001020 hqe_info = [{'HostQueueEntry': hqe.id, 'Job': hqe.job_id,
1021 'Job name': hqe.job.name} for hqe in host_queue_entries]
1022 return hqe_info
showard9d821ab2008-07-11 16:54:29 +00001023
1024
beeps8bb1f7d2013-08-05 01:30:09 -07001025def abort_special_tasks(**filter_data):
1026 """\
1027 Abort the special task, or tasks, specified in the filter.
1028 """
1029 query = models.SpecialTask.query_objects(filter_data)
1030 special_tasks = query.filter(is_active=True)
1031 for task in special_tasks:
1032 task.abort()
1033
1034
Simran Basi73dae552013-02-25 14:57:46 -08001035def _call_special_tasks_on_hosts(task, hosts):
1036 """\
1037 Schedules a set of hosts for a special task.
1038
1039 @returns A list of hostnames that a special task was created for.
1040 """
1041 models.AclGroup.check_for_acl_violation_hosts(hosts)
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -08001042 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -08001043 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -08001044 raise ValueError('The following hosts are on shards, please '
1045 'follow the link to the shards and create jobs '
1046 'there instead. %s.' % shard_host_map)
Simran Basi73dae552013-02-25 14:57:46 -08001047 for host in hosts:
1048 models.SpecialTask.schedule_special_task(host, task)
1049 return list(sorted(host.hostname for host in hosts))
1050
1051
MK Ryu5aa25042015-07-28 16:08:04 -07001052def _forward_special_tasks_on_hosts(task, rpc, **filter_data):
1053 """Forward special tasks to corresponding shards.
mbligh4e545a52009-12-19 05:30:39 +00001054
MK Ryu5aa25042015-07-28 16:08:04 -07001055 For master, when special tasks are fired on hosts that are sharded,
1056 forward the RPC to corresponding shards.
1057
1058 For shard, create special task records in local DB.
1059
1060 @param task: Enum value of frontend.afe.models.SpecialTask.Task
1061 @param rpc: RPC name to forward.
1062 @param filter_data: Filter keywords to be used for DB query.
1063
1064 @return: A list of hostnames that a special task was created for.
showard1ff7b2e2009-05-15 23:17:18 +00001065 """
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001066 hosts = models.Host.query_objects(filter_data)
1067 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts, rpc_hostnames=True)
1068
1069 # Filter out hosts on a shard from those on the master, forward
1070 # rpcs to the shard with an additional hostname__in filter, and
1071 # create a local SpecialTask for each remaining host.
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -08001072 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001073 hosts = [h for h in hosts if h.shard is None]
1074 for shard, hostnames in shard_host_map.iteritems():
1075
1076 # The main client of this module is the frontend website, and
1077 # it invokes it with an 'id' or an 'id__in' filter. Regardless,
1078 # the 'hostname' filter should narrow down the list of hosts on
1079 # each shard even though we supply all the ids in filter_data.
1080 # This method uses hostname instead of id because it fits better
MK Ryu5aa25042015-07-28 16:08:04 -07001081 # with the overall architecture of redirection functions in
1082 # rpc_utils.
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001083 shard_filter = filter_data.copy()
1084 shard_filter['hostname__in'] = hostnames
1085 rpc_utils.run_rpc_on_multiple_hostnames(
MK Ryu5aa25042015-07-28 16:08:04 -07001086 rpc, [shard], **shard_filter)
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001087
1088 # There is a race condition here if someone assigns a shard to one of these
1089 # hosts before we create the task. The host will stay on the master if:
1090 # 1. The host is not Ready
1091 # 2. The host is Ready but has a task
1092 # But if the host is Ready and doesn't have a task yet, it will get sent
1093 # to the shard as we're creating a task here.
1094
1095 # Given that we only rarely verify Ready hosts it isn't worth putting this
1096 # entire method in a transaction. The worst case scenario is that we have
MK Ryu5aa25042015-07-28 16:08:04 -07001097 # a verify running on a Ready host while the shard is using it, if the
1098 # verify fails no subsequent tasks will be created against the host on the
1099 # master, and verifies are safe enough that this is OK.
1100 return _call_special_tasks_on_hosts(task, hosts)
1101
1102
1103def reverify_hosts(**filter_data):
1104 """\
1105 Schedules a set of hosts for verify.
1106
1107 @returns A list of hostnames that a verify task was created for.
1108 """
1109 return _forward_special_tasks_on_hosts(
1110 models.SpecialTask.Task.VERIFY, 'reverify_hosts', **filter_data)
Simran Basi73dae552013-02-25 14:57:46 -08001111
1112
1113def repair_hosts(**filter_data):
1114 """\
1115 Schedules a set of hosts for repair.
1116
1117 @returns A list of hostnames that a repair task was created for.
1118 """
MK Ryu5aa25042015-07-28 16:08:04 -07001119 return _forward_special_tasks_on_hosts(
1120 models.SpecialTask.Task.REPAIR, 'repair_hosts', **filter_data)
showard1ff7b2e2009-05-15 23:17:18 +00001121
1122
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001123def get_jobs(not_yet_run=False, running=False, finished=False,
1124 suite=False, sub=False, standalone=False, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001125 """\
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001126 Extra status filter args for get_jobs:
jadmanski0afbb632008-06-06 21:10:57 +00001127 -not_yet_run: Include only jobs that have not yet started running.
1128 -running: Include only jobs that have start running but for which not
1129 all hosts have completed.
1130 -finished: Include only jobs for which all hosts have completed (or
1131 aborted).
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001132
1133 Extra type filter args for get_jobs:
1134 -suite: Include only jobs with child jobs.
1135 -sub: Include only jobs with a parent job.
1136 -standalone: Inlcude only jobs with no child or parent jobs.
1137 At most one of these three fields should be specified.
jadmanski0afbb632008-06-06 21:10:57 +00001138 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001139 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
1140 running,
1141 finished)
1142 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
1143 suite,
1144 sub,
1145 standalone)
showard0957a842009-05-11 19:25:08 +00001146 job_dicts = []
1147 jobs = list(models.Job.query_objects(filter_data))
1148 models.Job.objects.populate_relationships(jobs, models.Label,
1149 'dependencies')
showardc1a98d12010-01-15 00:22:22 +00001150 models.Job.objects.populate_relationships(jobs, models.JobKeyval, 'keyvals')
showard0957a842009-05-11 19:25:08 +00001151 for job in jobs:
1152 job_dict = job.get_object_dict()
1153 job_dict['dependencies'] = ','.join(label.name
1154 for label in job.dependencies)
showardc1a98d12010-01-15 00:22:22 +00001155 job_dict['keyvals'] = dict((keyval.key, keyval.value)
1156 for keyval in job.keyvals)
Eric Lid23bc192011-02-09 14:38:57 -08001157 if job.parameterized_job:
1158 job_dict['image'] = get_parameterized_autoupdate_image_url(job)
showard0957a842009-05-11 19:25:08 +00001159 job_dicts.append(job_dict)
1160 return rpc_utils.prepare_for_serialization(job_dicts)
mblighe8819cd2008-02-15 16:48:40 +00001161
1162
1163def get_num_jobs(not_yet_run=False, running=False, finished=False,
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001164 suite=False, sub=False, standalone=False,
Aviv Keshetb07a09d2016-04-01 16:01:19 -07001165 max_search_count=9999,
jadmanski0afbb632008-06-06 21:10:57 +00001166 **filter_data):
Aviv Keshetb07a09d2016-04-01 16:01:19 -07001167 """See get_jobs() for documentation of extra filter parameters.
1168
1169 This method returns the number of jobs that match the filter, or 9999
1170 whichever is less.
1171
1172 @params: max_search_count: Default 9999. If None, return accurate job count.
1173 Limit the maximum number of jobs we inspect, since this query examines the
1174 afe_host_queue_entries table as well and can be quite expensive if there
1175 are a lot of jobs. See crbug.com/599267
jadmanski0afbb632008-06-06 21:10:57 +00001176 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001177 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
1178 running,
1179 finished)
1180 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
1181 suite,
1182 sub,
1183 standalone)
Aviv Keshetb07a09d2016-04-01 16:01:19 -07001184 if max_search_count is None:
1185 return models.Job.query_count(filter_data)
mblighe8819cd2008-02-15 16:48:40 +00001186
Aviv Keshetb07a09d2016-04-01 16:01:19 -07001187 # We need to quote the string as django may not quote it properly.
1188 for key, val in filter_data.iteritems():
1189 if isinstance(val, str):
1190 filter_data[key] = repr(val)
1191 inner_query = str(models.Job.query_objects(filter_data)[:max_search_count].query)
1192 full_query = 'SELECT COUNT(*) FROM (%s) t' % inner_query
1193 cursor = connections['default'].cursor()
1194 cursor.execute(full_query)
1195 row = cursor.fetchone()
1196 return row[0]
mblighe8819cd2008-02-15 16:48:40 +00001197
mblighe8819cd2008-02-15 16:48:40 +00001198def get_jobs_summary(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001199 """\
Jiaxi Luoaac54572014-06-04 13:57:02 -07001200 Like get_jobs(), but adds 'status_counts' and 'result_counts' field.
1201
1202 'status_counts' filed is a dictionary mapping status strings to the number
1203 of hosts currently with that status, i.e. {'Queued' : 4, 'Running' : 2}.
1204
1205 'result_counts' field is piped to tko's rpc_interface and has the return
1206 format specified under get_group_counts.
jadmanski0afbb632008-06-06 21:10:57 +00001207 """
1208 jobs = get_jobs(**filter_data)
1209 ids = [job['id'] for job in jobs]
1210 all_status_counts = models.Job.objects.get_status_counts(ids)
1211 for job in jobs:
1212 job['status_counts'] = all_status_counts[job['id']]
Jiaxi Luoaac54572014-06-04 13:57:02 -07001213 job['result_counts'] = tko_rpc_interface.get_status_counts(
1214 ['afe_job_id', 'afe_job_id'],
1215 header_groups=[['afe_job_id'], ['afe_job_id']],
1216 **{'afe_job_id': job['id']})
jadmanski0afbb632008-06-06 21:10:57 +00001217 return rpc_utils.prepare_for_serialization(jobs)
mblighe8819cd2008-02-15 16:48:40 +00001218
1219
showarda965cef2009-05-15 23:17:41 +00001220def get_info_for_clone(id, preserve_metahosts, queue_entry_filter_data=None):
showarda8709c52008-07-03 19:44:54 +00001221 """\
1222 Retrieves all the information needed to clone a job.
1223 """
showarda8709c52008-07-03 19:44:54 +00001224 job = models.Job.objects.get(id=id)
showard29f7cd22009-04-29 21:16:24 +00001225 job_info = rpc_utils.get_job_info(job,
showarda965cef2009-05-15 23:17:41 +00001226 preserve_metahosts,
1227 queue_entry_filter_data)
showard945072f2008-09-03 20:34:59 +00001228
showardd9992fe2008-07-31 02:15:03 +00001229 host_dicts = []
showard29f7cd22009-04-29 21:16:24 +00001230 for host in job_info['hosts']:
1231 host_dict = get_hosts(id=host.id)[0]
1232 other_labels = host_dict['labels']
1233 if host_dict['platform']:
1234 other_labels.remove(host_dict['platform'])
1235 host_dict['other_labels'] = ', '.join(other_labels)
showardd9992fe2008-07-31 02:15:03 +00001236 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001237
showard29f7cd22009-04-29 21:16:24 +00001238 for host in job_info['one_time_hosts']:
1239 host_dict = dict(hostname=host.hostname,
1240 id=host.id,
1241 platform='(one-time host)',
1242 locked_text='')
1243 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001244
showard4d077562009-05-08 18:24:36 +00001245 # convert keys from Label objects to strings (names of labels)
showard29f7cd22009-04-29 21:16:24 +00001246 meta_host_counts = dict((meta_host.name, count) for meta_host, count
showard4d077562009-05-08 18:24:36 +00001247 in job_info['meta_host_counts'].iteritems())
showard29f7cd22009-04-29 21:16:24 +00001248
1249 info = dict(job=job.get_object_dict(),
1250 meta_host_counts=meta_host_counts,
1251 hosts=host_dicts)
1252 info['job']['dependencies'] = job_info['dependencies']
1253 if job_info['atomic_group']:
1254 info['atomic_group_name'] = (job_info['atomic_group']).name
1255 else:
1256 info['atomic_group_name'] = None
jamesren2275ef12010-04-12 18:25:06 +00001257 info['hostless'] = job_info['hostless']
jamesren76fcf192010-04-21 20:39:50 +00001258 info['drone_set'] = job.drone_set and job.drone_set.name
showarda8709c52008-07-03 19:44:54 +00001259
Eric Lid23bc192011-02-09 14:38:57 -08001260 if job.parameterized_job:
1261 info['job']['image'] = get_parameterized_autoupdate_image_url(job)
1262
showarda8709c52008-07-03 19:44:54 +00001263 return rpc_utils.prepare_for_serialization(info)
1264
1265
showard34dc5fa2008-04-24 20:58:40 +00001266# host queue entries
1267
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001268def get_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001269 """\
showardc92da832009-04-07 18:14:34 +00001270 @returns A sequence of nested dictionaries of host and job information.
jadmanski0afbb632008-06-06 21:10:57 +00001271 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001272 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1273 'started_on__lte',
1274 start_time,
1275 end_time,
1276 **filter_data)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001277 return rpc_utils.prepare_rows_as_nested_dicts(
1278 models.HostQueueEntry.query_objects(filter_data),
1279 ('host', 'atomic_group', 'job'))
showard34dc5fa2008-04-24 20:58:40 +00001280
1281
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001282def get_num_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001283 """\
1284 Get the number of host queue entries associated with this job.
1285 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001286 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1287 'started_on__lte',
1288 start_time,
1289 end_time,
1290 **filter_data)
jadmanski0afbb632008-06-06 21:10:57 +00001291 return models.HostQueueEntry.query_count(filter_data)
showard34dc5fa2008-04-24 20:58:40 +00001292
1293
showard1e935f12008-07-11 00:11:36 +00001294def get_hqe_percentage_complete(**filter_data):
1295 """
showardc92da832009-04-07 18:14:34 +00001296 Computes the fraction of host queue entries matching the given filter data
showard1e935f12008-07-11 00:11:36 +00001297 that are complete.
1298 """
1299 query = models.HostQueueEntry.query_objects(filter_data)
1300 complete_count = query.filter(complete=True).count()
1301 total_count = query.count()
1302 if total_count == 0:
1303 return 1
1304 return float(complete_count) / total_count
1305
1306
showard1a5a4082009-07-28 20:01:37 +00001307# special tasks
1308
1309def get_special_tasks(**filter_data):
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001310 """Get special task entries from the local database.
1311
1312 Query the special tasks table for tasks matching the given
1313 `filter_data`, and return a list of the results. No attempt is
1314 made to forward the call to shards; the buck will stop here.
1315 The caller is expected to know the target shard for such reasons
1316 as:
1317 * The caller is a service (such as gs_offloader) configured
1318 to operate on behalf of one specific shard, and no other.
1319 * The caller has a host as a parameter, and knows that this is
1320 the shard assigned to that host.
1321
1322 @param filter_data Filter keywords to pass to the underlying
1323 database query.
1324
1325 """
J. Richard Barnettefdfcd662015-04-13 17:20:29 -07001326 return rpc_utils.prepare_rows_as_nested_dicts(
1327 models.SpecialTask.query_objects(filter_data),
1328 ('host', 'queue_entry'))
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001329
1330
1331def get_host_special_tasks(host_id, **filter_data):
1332 """Get special task entries for a given host.
1333
1334 Query the special tasks table for tasks that ran on the host
1335 given by `host_id` and matching the given `filter_data`.
1336 Return a list of the results. If the host is assigned to a
1337 shard, forward this call to that shard.
1338
1339 @param host_id Id in the database of the target host.
1340 @param filter_data Filter keywords to pass to the underlying
1341 database query.
1342
1343 """
MK Ryu0c1a37d2015-04-30 12:00:55 -07001344 # Retrieve host data even if the host is in an invalid state.
1345 host = models.Host.smart_get(host_id, False)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001346 if not host.shard:
J. Richard Barnettefdfcd662015-04-13 17:20:29 -07001347 return get_special_tasks(host_id=host_id, **filter_data)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001348 else:
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001349 # The return values from AFE methods are post-processed
1350 # objects that aren't JSON-serializable. So, we have to
1351 # call AFE.run() to get the raw, serializable output from
1352 # the shard.
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001353 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1354 return shard_afe.run('get_special_tasks',
1355 host_id=host_id, **filter_data)
showard1a5a4082009-07-28 20:01:37 +00001356
1357
MK Ryu0c1a37d2015-04-30 12:00:55 -07001358def get_num_special_tasks(**kwargs):
1359 """Get the number of special task entries from the local database.
1360
1361 Query the special tasks table for tasks matching the given 'kwargs',
1362 and return the number of the results. No attempt is made to forward
1363 the call to shards; the buck will stop here.
1364
1365 @param kwargs Filter keywords to pass to the underlying database query.
1366
1367 """
1368 return models.SpecialTask.query_count(kwargs)
1369
1370
1371def get_host_num_special_tasks(host, **kwargs):
1372 """Get special task entries for a given host.
1373
1374 Query the special tasks table for tasks that ran on the host
1375 given by 'host' and matching the given 'kwargs'.
1376 Return a list of the results. If the host is assigned to a
1377 shard, forward this call to that shard.
1378
1379 @param host id or name of a host. More often a hostname.
1380 @param kwargs Filter keywords to pass to the underlying database query.
1381
1382 """
1383 # Retrieve host data even if the host is in an invalid state.
1384 host_model = models.Host.smart_get(host, False)
1385 if not host_model.shard:
1386 return get_num_special_tasks(host=host, **kwargs)
1387 else:
1388 shard_afe = frontend.AFE(server=host_model.shard.rpc_hostname())
1389 return shard_afe.run('get_num_special_tasks', host=host, **kwargs)
1390
1391
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001392def get_status_task(host_id, end_time):
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001393 """Get the "status task" for a host from the local shard.
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001394
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001395 Returns a single special task representing the given host's
1396 "status task". The status task is a completed special task that
1397 identifies whether the corresponding host was working or broken
1398 when it completed. A successful task indicates a working host;
1399 a failed task indicates broken.
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001400
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001401 This call will not be forward to a shard; the receiving server
1402 must be the shard that owns the host.
1403
1404 @param host_id Id in the database of the target host.
1405 @param end_time Time reference for the host's status.
1406
1407 @return A single task; its status (successful or not)
1408 corresponds to the status of the host (working or
1409 broken) at the given time. If no task is found, return
1410 `None`.
1411
1412 """
1413 tasklist = rpc_utils.prepare_rows_as_nested_dicts(
1414 status_history.get_status_task(host_id, end_time),
1415 ('host', 'queue_entry'))
1416 return tasklist[0] if tasklist else None
1417
1418
1419def get_host_status_task(host_id, end_time):
1420 """Get the "status task" for a host from its owning shard.
1421
1422 Finds the given host's owning shard, and forwards to it a call
1423 to `get_status_task()` (see above).
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001424
1425 @param host_id Id in the database of the target host.
1426 @param end_time Time reference for the host's status.
1427
1428 @return A single task; its status (successful or not)
1429 corresponds to the status of the host (working or
1430 broken) at the given time. If no task is found, return
1431 `None`.
1432
1433 """
1434 host = models.Host.smart_get(host_id)
1435 if not host.shard:
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001436 return get_status_task(host_id, end_time)
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001437 else:
1438 # The return values from AFE methods are post-processed
1439 # objects that aren't JSON-serializable. So, we have to
1440 # call AFE.run() to get the raw, serializable output from
1441 # the shard.
1442 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1443 return shard_afe.run('get_status_task',
1444 host_id=host_id, end_time=end_time)
1445
1446
J. Richard Barnette8abbfd62015-06-23 12:46:54 -07001447def get_host_diagnosis_interval(host_id, end_time, success):
1448 """Find a "diagnosis interval" for a given host.
1449
1450 A "diagnosis interval" identifies a start and end time where
1451 the host went from "working" to "broken", or vice versa. The
1452 interval's starting time is the starting time of the last status
1453 task with the old status; the end time is the finish time of the
1454 first status task with the new status.
1455
1456 This routine finds the most recent diagnosis interval for the
1457 given host prior to `end_time`, with a starting status matching
1458 `success`. If `success` is true, the interval will start with a
1459 successful status task; if false the interval will start with a
1460 failed status task.
1461
1462 @param host_id Id in the database of the target host.
1463 @param end_time Time reference for the diagnosis interval.
1464 @param success Whether the diagnosis interval should start
1465 with a successful or failed status task.
1466
1467 @return A list of two strings. The first is the timestamp for
1468 the beginning of the interval; the second is the
1469 timestamp for the end. If the host has never changed
1470 state, the list is empty.
1471
1472 """
1473 host = models.Host.smart_get(host_id)
J. Richard Barnette78f281a2015-06-29 13:24:51 -07001474 if not host.shard or utils.is_shard():
J. Richard Barnette8abbfd62015-06-23 12:46:54 -07001475 return status_history.get_diagnosis_interval(
1476 host_id, end_time, success)
1477 else:
1478 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1479 return shard_afe.get_host_diagnosis_interval(
1480 host_id, end_time, success)
1481
1482
showardc0ac3a72009-07-08 21:14:45 +00001483# support for host detail view
1484
MK Ryu0c1a37d2015-04-30 12:00:55 -07001485def get_host_queue_entries_and_special_tasks(host, query_start=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001486 query_limit=None, start_time=None,
1487 end_time=None):
showardc0ac3a72009-07-08 21:14:45 +00001488 """
1489 @returns an interleaved list of HostQueueEntries and SpecialTasks,
1490 in approximate run order. each dict contains keys for type, host,
1491 job, status, started_on, execution_path, and ID.
1492 """
1493 total_limit = None
1494 if query_limit is not None:
1495 total_limit = query_start + query_limit
MK Ryu0c1a37d2015-04-30 12:00:55 -07001496 filter_data_common = {'host': host,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001497 'query_limit': total_limit,
1498 'sort_by': ['-id']}
showardc0ac3a72009-07-08 21:14:45 +00001499
MK Ryu0c1a37d2015-04-30 12:00:55 -07001500 filter_data_special_tasks = rpc_utils.inject_times_to_filter(
1501 'time_started__gte', 'time_started__lte', start_time, end_time,
1502 **filter_data_common)
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001503
MK Ryu0c1a37d2015-04-30 12:00:55 -07001504 queue_entries = get_host_queue_entries(
1505 start_time, end_time, **filter_data_common)
1506 special_tasks = get_host_special_tasks(host, **filter_data_special_tasks)
showardc0ac3a72009-07-08 21:14:45 +00001507
1508 interleaved_entries = rpc_utils.interleave_entries(queue_entries,
1509 special_tasks)
1510 if query_start is not None:
1511 interleaved_entries = interleaved_entries[query_start:]
1512 if query_limit is not None:
1513 interleaved_entries = interleaved_entries[:query_limit]
MK Ryu0c1a37d2015-04-30 12:00:55 -07001514 return rpc_utils.prepare_host_queue_entries_and_special_tasks(
1515 interleaved_entries, queue_entries)
showardc0ac3a72009-07-08 21:14:45 +00001516
1517
MK Ryu0c1a37d2015-04-30 12:00:55 -07001518def get_num_host_queue_entries_and_special_tasks(host, start_time=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001519 end_time=None):
MK Ryu0c1a37d2015-04-30 12:00:55 -07001520 filter_data_common = {'host': host}
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001521
1522 filter_data_queue_entries, filter_data_special_tasks = (
1523 rpc_utils.inject_times_to_hqe_special_tasks_filters(
1524 filter_data_common, start_time, end_time))
1525
1526 return (models.HostQueueEntry.query_count(filter_data_queue_entries)
MK Ryu0c1a37d2015-04-30 12:00:55 -07001527 + get_host_num_special_tasks(**filter_data_special_tasks))
showardc0ac3a72009-07-08 21:14:45 +00001528
1529
showard29f7cd22009-04-29 21:16:24 +00001530# recurring run
1531
1532def get_recurring(**filter_data):
1533 return rpc_utils.prepare_rows_as_nested_dicts(
1534 models.RecurringRun.query_objects(filter_data),
1535 ('job', 'owner'))
1536
1537
1538def get_num_recurring(**filter_data):
1539 return models.RecurringRun.query_count(filter_data)
1540
1541
1542def delete_recurring_runs(**filter_data):
1543 to_delete = models.RecurringRun.query_objects(filter_data)
1544 to_delete.delete()
1545
1546
1547def create_recurring_run(job_id, start_date, loop_period, loop_count):
showard64a95952010-01-13 21:27:16 +00001548 owner = models.User.current_user().login
showard29f7cd22009-04-29 21:16:24 +00001549 job = models.Job.objects.get(id=job_id)
1550 return job.create_recurring_job(start_date=start_date,
1551 loop_period=loop_period,
1552 loop_count=loop_count,
1553 owner=owner)
1554
1555
mblighe8819cd2008-02-15 16:48:40 +00001556# other
1557
showarde0b63622008-08-04 20:58:47 +00001558def echo(data=""):
1559 """\
1560 Returns a passed in string. For doing a basic test to see if RPC calls
1561 can successfully be made.
1562 """
1563 return data
1564
1565
showardb7a52fd2009-04-27 20:10:56 +00001566def get_motd():
1567 """\
1568 Returns the message of the day as a string.
1569 """
1570 return rpc_utils.get_motd()
1571
1572
mblighe8819cd2008-02-15 16:48:40 +00001573def get_static_data():
jadmanski0afbb632008-06-06 21:10:57 +00001574 """\
1575 Returns a dictionary containing a bunch of data that shouldn't change
1576 often and is otherwise inaccessible. This includes:
showardc92da832009-04-07 18:14:34 +00001577
1578 priorities: List of job priority choices.
1579 default_priority: Default priority value for new jobs.
1580 users: Sorted list of all users.
Jiaxi Luo31874592014-06-11 10:36:35 -07001581 labels: Sorted list of labels not start with 'cros-version' and
1582 'fw-version'.
showardc92da832009-04-07 18:14:34 +00001583 atomic_groups: Sorted list of all atomic groups.
1584 tests: Sorted list of all tests.
1585 profilers: Sorted list of all profilers.
1586 current_user: Logged-in username.
1587 host_statuses: Sorted list of possible Host statuses.
1588 job_statuses: Sorted list of possible HostQueueEntry statuses.
Simran Basi7e605742013-11-12 13:43:36 -08001589 job_timeout_default: The default job timeout length in minutes.
showarda1e74b32009-05-12 17:32:04 +00001590 parse_failed_repair_default: Default value for the parse_failed_repair job
Jiaxi Luo31874592014-06-11 10:36:35 -07001591 option.
showardc92da832009-04-07 18:14:34 +00001592 reboot_before_options: A list of valid RebootBefore string enums.
1593 reboot_after_options: A list of valid RebootAfter string enums.
1594 motd: Server's message of the day.
1595 status_dictionary: A mapping from one word job status names to a more
1596 informative description.
jadmanski0afbb632008-06-06 21:10:57 +00001597 """
showard21baa452008-10-21 00:08:39 +00001598
1599 job_fields = models.Job.get_field_dict()
jamesren76fcf192010-04-21 20:39:50 +00001600 default_drone_set_name = models.DroneSet.default_drone_set_name()
1601 drone_sets = ([default_drone_set_name] +
1602 sorted(drone_set.name for drone_set in
1603 models.DroneSet.objects.exclude(
1604 name=default_drone_set_name)))
showard21baa452008-10-21 00:08:39 +00001605
jadmanski0afbb632008-06-06 21:10:57 +00001606 result = {}
Alex Miller7d658cf2013-09-04 16:00:35 -07001607 result['priorities'] = priorities.Priority.choices()
1608 default_priority = priorities.Priority.DEFAULT
1609 result['default_priority'] = 'Default'
1610 result['max_schedulable_priority'] = priorities.Priority.DEFAULT
jadmanski0afbb632008-06-06 21:10:57 +00001611 result['users'] = get_users(sort_by=['login'])
Jiaxi Luo31874592014-06-11 10:36:35 -07001612
1613 label_exclude_filters = [{'name__startswith': 'cros-version'},
Dan Shi65351d62015-08-03 12:03:23 -07001614 {'name__startswith': 'fw-version'},
1615 {'name__startswith': 'fwrw-version'},
Dan Shi27516972016-03-16 14:03:41 -07001616 {'name__startswith': 'fwro-version'},
1617 {'name__startswith': 'ab-version'},
1618 {'name__startswith': 'testbed-version'}]
Jiaxi Luo31874592014-06-11 10:36:35 -07001619 result['labels'] = get_labels(
1620 label_exclude_filters,
1621 sort_by=['-platform', 'name'])
1622
showardc92da832009-04-07 18:14:34 +00001623 result['atomic_groups'] = get_atomic_groups(sort_by=['name'])
jadmanski0afbb632008-06-06 21:10:57 +00001624 result['tests'] = get_tests(sort_by=['name'])
showard2b9a88b2008-06-13 20:55:03 +00001625 result['profilers'] = get_profilers(sort_by=['name'])
showard0fc38302008-10-23 00:44:07 +00001626 result['current_user'] = rpc_utils.prepare_for_serialization(
showard64a95952010-01-13 21:27:16 +00001627 models.User.current_user().get_object_dict())
showard2b9a88b2008-06-13 20:55:03 +00001628 result['host_statuses'] = sorted(models.Host.Status.names)
mbligh5a198b92008-12-11 19:33:29 +00001629 result['job_statuses'] = sorted(models.HostQueueEntry.Status.names)
Simran Basi7e605742013-11-12 13:43:36 -08001630 result['job_timeout_mins_default'] = models.Job.DEFAULT_TIMEOUT_MINS
Simran Basi34217022012-11-06 13:43:15 -08001631 result['job_max_runtime_mins_default'] = (
1632 models.Job.DEFAULT_MAX_RUNTIME_MINS)
showarda1e74b32009-05-12 17:32:04 +00001633 result['parse_failed_repair_default'] = bool(
1634 models.Job.DEFAULT_PARSE_FAILED_REPAIR)
jamesrendd855242010-03-02 22:23:44 +00001635 result['reboot_before_options'] = model_attributes.RebootBefore.names
1636 result['reboot_after_options'] = model_attributes.RebootAfter.names
showard8fbae652009-01-20 23:23:10 +00001637 result['motd'] = rpc_utils.get_motd()
jamesren76fcf192010-04-21 20:39:50 +00001638 result['drone_sets_enabled'] = models.DroneSet.drone_sets_enabled()
1639 result['drone_sets'] = drone_sets
jamesren4a41e012010-07-16 22:33:48 +00001640 result['parameterized_jobs'] = models.Job.parameterized_jobs_enabled()
showard8ac29b42008-07-17 17:01:55 +00001641
showardd3dc1992009-04-22 21:01:40 +00001642 result['status_dictionary'] = {"Aborted": "Aborted",
showard8ac29b42008-07-17 17:01:55 +00001643 "Verifying": "Verifying Host",
Alex Millerdfff2fd2013-05-28 13:05:06 -07001644 "Provisioning": "Provisioning Host",
showard8ac29b42008-07-17 17:01:55 +00001645 "Pending": "Waiting on other hosts",
1646 "Running": "Running autoserv",
1647 "Completed": "Autoserv completed",
1648 "Failed": "Failed to complete",
showardd823b362008-07-24 16:35:46 +00001649 "Queued": "Queued",
showard5deb6772008-11-04 21:54:33 +00001650 "Starting": "Next in host's queue",
1651 "Stopped": "Other host(s) failed verify",
showardd3dc1992009-04-22 21:01:40 +00001652 "Parsing": "Awaiting parse of final results",
showard29f7cd22009-04-29 21:16:24 +00001653 "Gathering": "Gathering log files",
showard8cc058f2009-09-08 16:26:33 +00001654 "Template": "Template job for recurring run",
mbligh4608b002010-01-05 18:22:35 +00001655 "Waiting": "Waiting for scheduler action",
Dan Shi07e09af2013-04-12 09:31:29 -07001656 "Archiving": "Archiving results",
1657 "Resetting": "Resetting hosts"}
Jiaxi Luo421608e2014-07-07 14:38:00 -07001658
1659 result['wmatrix_url'] = rpc_utils.get_wmatrix_url()
Simran Basi71206ef2014-08-13 13:51:18 -07001660 result['is_moblab'] = bool(utils.is_moblab())
Jiaxi Luo421608e2014-07-07 14:38:00 -07001661
jadmanski0afbb632008-06-06 21:10:57 +00001662 return result
showard29f7cd22009-04-29 21:16:24 +00001663
1664
1665def get_server_time():
1666 return datetime.datetime.now().strftime("%Y-%m-%d %H:%M")