blob: a322149f94ad9047afee342228a9c2d92416658f [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
MK Ryu9c5fbbe2015-02-11 15:46:22 -080036
Moises Osorio2dc7a102014-12-02 18:24:02 -080037from django.db.models import Count
showardcafd16e2009-05-29 18:37:49 +000038import common
Simran Basib6ec8ae2014-04-23 12:05:08 -070039from autotest_lib.client.common_lib import priorities
Gabe Black1e1c41b2015-02-04 23:55:15 -080040from autotest_lib.client.common_lib.cros.graphite import autotest_stats
showard6d7b2ff2009-06-10 00:16:47 +000041from autotest_lib.frontend.afe import control_file, rpc_utils
J. Richard Barnetteb5164d62015-04-13 12:59:31 -070042from autotest_lib.frontend.afe import models, model_logic, model_attributes
Simran Basib6ec8ae2014-04-23 12:05:08 -070043from autotest_lib.frontend.afe import site_rpc_interface
Moises Osorio2dc7a102014-12-02 18:24:02 -080044from autotest_lib.frontend.tko import models as tko_models
Jiaxi Luoaac54572014-06-04 13:57:02 -070045from autotest_lib.frontend.tko import rpc_interface as tko_rpc_interface
J. Richard Barnetteb5164d62015-04-13 12:59:31 -070046from autotest_lib.server import frontend
Simran Basi71206ef2014-08-13 13:51:18 -070047from autotest_lib.server import utils
Dan Shid215dbe2015-06-18 16:14:59 -070048from autotest_lib.server.cros import provision
Jiaxi Luo90190c92014-06-18 12:35:57 -070049from autotest_lib.server.cros.dynamic_suite import tools
J. Richard Barnette39255fa2015-04-14 17:23:41 -070050from autotest_lib.site_utils import status_history
mblighe8819cd2008-02-15 16:48:40 +000051
Moises Osorio2dc7a102014-12-02 18:24:02 -080052
Gabe Black1e1c41b2015-02-04 23:55:15 -080053_timer = autotest_stats.Timer('rpc_interface')
Moises Osorio2dc7a102014-12-02 18:24:02 -080054
Eric Lid23bc192011-02-09 14:38:57 -080055def get_parameterized_autoupdate_image_url(job):
56 """Get the parameterized autoupdate image url from a parameterized job."""
57 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
58 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
beeps8bb1f7d2013-08-05 01:30:09 -070059 name='image')
Eric Lid23bc192011-02-09 14:38:57 -080060 para_set = job.parameterized_job.parameterizedjobparameter_set
61 job_test_para = para_set.get(test_parameter=image_parameter)
62 return job_test_para.parameter_value
63
64
mblighe8819cd2008-02-15 16:48:40 +000065# labels
66
mblighe8819cd2008-02-15 16:48:40 +000067def modify_label(id, **data):
MK Ryu8c554cf2015-06-12 11:45:50 -070068 """Modify a label.
69
70 @param id: id or name of a label. More often a label name.
71 @param data: New data for a label.
72 """
73 label_model = models.Label.smart_get(id)
74
75 # Master forwards the RPC to shards
76 if not utils.is_shard():
77 rpc_utils.fanout_rpc(label_model.host_set.all(), 'modify_label', False,
78 id=id, **data)
79
80 label_model.update_object(data)
mblighe8819cd2008-02-15 16:48:40 +000081
82
83def delete_label(id):
MK Ryu8c554cf2015-06-12 11:45:50 -070084 """Delete a label.
85
86 @param id: id or name of a label. More often a label name.
87 """
88 label_model = models.Label.smart_get(id)
89
90 # Master forwards the RPC to shards
91 if not utils.is_shard():
92 rpc_utils.fanout_rpc(label_model.host_set.all(), 'delete_label', False,
93 id=id)
94
95 label_model.delete()
mblighe8819cd2008-02-15 16:48:40 +000096
Prashanth Balasubramanian744898f2015-01-13 05:04:16 -080097
MK Ryu9c5fbbe2015-02-11 15:46:22 -080098def add_label(name, ignore_exception_if_exists=False, **kwargs):
MK Ryucf027c62015-03-04 12:00:50 -080099 """Adds a new label of a given name.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800100
101 @param name: label name.
102 @param ignore_exception_if_exists: If True and the exception was
103 thrown due to the duplicated label name when adding a label,
104 then suppress the exception. Default is False.
105 @param kwargs: keyword args that store more info about a label
106 other than the name.
107 @return: int/long id of a new label.
108 """
109 # models.Label.add_object() throws model_logic.ValidationError
110 # when it is given a label name that already exists.
111 # However, ValidationError can be thrown with different errors,
112 # and those errors should be thrown up to the call chain.
113 try:
114 label = models.Label.add_object(name=name, **kwargs)
115 except:
116 exc_info = sys.exc_info()
117 if ignore_exception_if_exists:
118 label = rpc_utils.get_label(name)
119 # If the exception is raised not because of duplicated
120 # "name", then raise the original exception.
121 if label is None:
122 raise exc_info[0], exc_info[1], exc_info[2]
123 else:
124 raise exc_info[0], exc_info[1], exc_info[2]
125 return label.id
126
127
128def add_label_to_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800129 """Adds a label of the given id to the given hosts only in local DB.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800130
131 @param id: id or name of a label. More often a label name.
132 @param hosts: The hostnames of hosts that need the label.
133
134 @raises models.Label.DoesNotExist: If the label with id doesn't exist.
135 """
136 label = models.Label.smart_get(id)
137 host_objs = models.Host.smart_get_bulk(hosts)
138 if label.platform:
139 models.Host.check_no_platform(host_objs)
140 label.host_set.add(*host_objs)
141
142
MK Ryufbb002c2015-06-08 14:13:16 -0700143@rpc_utils.route_rpc_to_master
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800144def label_add_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800145 """Adds a label with the given id to the given hosts.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800146
147 This method should be run only on master not shards.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800148 The given label will be created if it doesn't exist, provided the `id`
149 supplied is a label name not an int/long id.
150
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800151 @param id: id or name of a label. More often a label name.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800152 @param hosts: A list of hostnames or ids. More often hostnames.
153
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800154 @raises ValueError: If the id specified is an int/long (label id)
155 while the label does not exist.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800156 """
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800157 try:
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800158 label = models.Label.smart_get(id)
159 except models.Label.DoesNotExist:
160 # This matches the type checks in smart_get, which is a hack
161 # in and off itself. The aim here is to create any non-existent
162 # label, which we cannot do if the 'id' specified isn't a label name.
163 if isinstance(id, basestring):
164 label = models.Label.smart_get(add_label(id))
165 else:
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800166 raise ValueError('Label id (%s) does not exist. Please specify '
167 'the argument, id, as a string (label name).'
168 % id)
MK Ryucf027c62015-03-04 12:00:50 -0800169
170 host_objs = models.Host.smart_get_bulk(hosts)
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800171 # Make sure the label exists on the shard with the same id
172 # as it is on the master.
MK Ryucf027c62015-03-04 12:00:50 -0800173 # It is possible that the label is already in a shard because
174 # we are adding a new label only to shards of hosts that the label
175 # is going to be attached.
176 # For example, we add a label L1 to a host in shard S1.
177 # Master and S1 will have L1 but other shards won't.
178 # Later, when we add the same label L1 to hosts in shards S1 and S2,
179 # S1 already has the label but S2 doesn't.
180 # S2 should have the new label without any problem.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800181 # We ignore exception in such a case.
182 rpc_utils.fanout_rpc(
MK Ryue019aae2015-07-07 12:46:07 -0700183 host_objs, 'add_label', include_hostnames=False,
184 name=label.name, ignore_exception_if_exists=True,
185 id=label.id, platform=label.platform)
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800186 rpc_utils.fanout_rpc(host_objs, 'add_label_to_hosts', id=id)
showardbbabf502008-06-06 00:02:02 +0000187
MK Ryu26f0c932015-05-28 18:14:33 -0700188 add_label_to_hosts(id, hosts)
189
showardbbabf502008-06-06 00:02:02 +0000190
MK Ryucf027c62015-03-04 12:00:50 -0800191def remove_label_from_hosts(id, hosts):
192 """Removes a label of the given id from the given hosts only in local DB.
193
194 @param id: id or name of a label.
195 @param hosts: The hostnames of hosts that need to remove the label from.
196 """
showardbe3ec042008-11-12 18:16:07 +0000197 host_objs = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000198 models.Label.smart_get(id).host_set.remove(*host_objs)
showardbbabf502008-06-06 00:02:02 +0000199
200
MK Ryufbb002c2015-06-08 14:13:16 -0700201@rpc_utils.route_rpc_to_master
MK Ryucf027c62015-03-04 12:00:50 -0800202def label_remove_hosts(id, hosts):
203 """Removes a label of the given id from the given hosts.
204
205 This method should be run only on master not shards.
206
207 @param id: id or name of a label.
208 @param hosts: A list of hostnames or ids. More often hostnames.
209 """
MK Ryucf027c62015-03-04 12:00:50 -0800210 host_objs = models.Host.smart_get_bulk(hosts)
211 rpc_utils.fanout_rpc(host_objs, 'remove_label_from_hosts', id=id)
212
MK Ryu26f0c932015-05-28 18:14:33 -0700213 remove_label_from_hosts(id, hosts)
214
MK Ryucf027c62015-03-04 12:00:50 -0800215
Jiaxi Luo31874592014-06-11 10:36:35 -0700216def get_labels(exclude_filters=(), **filter_data):
showardc92da832009-04-07 18:14:34 +0000217 """\
Jiaxi Luo31874592014-06-11 10:36:35 -0700218 @param exclude_filters: A sequence of dictionaries of filters.
219
showardc92da832009-04-07 18:14:34 +0000220 @returns A sequence of nested dictionaries of label information.
221 """
Jiaxi Luo31874592014-06-11 10:36:35 -0700222 labels = models.Label.query_objects(filter_data)
223 for exclude_filter in exclude_filters:
224 labels = labels.exclude(**exclude_filter)
225 return rpc_utils.prepare_rows_as_nested_dicts(labels, ('atomic_group',))
showardc92da832009-04-07 18:14:34 +0000226
227
228# atomic groups
229
showarde9450c92009-06-30 01:58:52 +0000230def add_atomic_group(name, max_number_of_machines=None, description=None):
showardc92da832009-04-07 18:14:34 +0000231 return models.AtomicGroup.add_object(
232 name=name, max_number_of_machines=max_number_of_machines,
233 description=description).id
234
235
236def modify_atomic_group(id, **data):
237 models.AtomicGroup.smart_get(id).update_object(data)
238
239
240def delete_atomic_group(id):
241 models.AtomicGroup.smart_get(id).delete()
242
243
244def atomic_group_add_labels(id, labels):
245 label_objs = models.Label.smart_get_bulk(labels)
246 models.AtomicGroup.smart_get(id).label_set.add(*label_objs)
247
248
249def atomic_group_remove_labels(id, labels):
250 label_objs = models.Label.smart_get_bulk(labels)
251 models.AtomicGroup.smart_get(id).label_set.remove(*label_objs)
252
253
254def get_atomic_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000255 return rpc_utils.prepare_for_serialization(
showardc92da832009-04-07 18:14:34 +0000256 models.AtomicGroup.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000257
258
259# hosts
260
Matthew Sartori68186332015-04-27 17:19:53 -0700261def add_host(hostname, status=None, locked=None, lock_reason='', protection=None):
262 if locked and not lock_reason:
263 raise model_logic.ValidationError(
264 {'locked': 'Please provide a reason for locking when adding host.'})
265
jadmanski0afbb632008-06-06 21:10:57 +0000266 return models.Host.add_object(hostname=hostname, status=status,
Matthew Sartori68186332015-04-27 17:19:53 -0700267 locked=locked, lock_reason=lock_reason,
268 protection=protection).id
mblighe8819cd2008-02-15 16:48:40 +0000269
270
MK Ryu33889612015-09-04 14:32:35 -0700271@rpc_utils.route_rpc_to_master
272def modify_host(id, **kwargs):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700273 """Modify local attributes of a host.
274
275 If this is called on the master, but the host is assigned to a shard, this
MK Ryu33889612015-09-04 14:32:35 -0700276 will call `modify_host_local` RPC to the responsible shard. This means if
277 a host is being locked using this function, this change will also propagate
278 to shards.
279 When this is called on a shard, the shard just routes the RPC to the master
280 and does nothing.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700281
282 @param id: id of the host to modify.
MK Ryu33889612015-09-04 14:32:35 -0700283 @param kwargs: key=value pairs of values to set on the host.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700284 """
MK Ryu33889612015-09-04 14:32:35 -0700285 rpc_utils.check_modify_host(kwargs)
showardce7c0922009-09-11 18:39:24 +0000286 host = models.Host.smart_get(id)
MK Ryu33889612015-09-04 14:32:35 -0700287 rpc_utils.check_modify_host_locking(host, kwargs)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700288
MK Ryu33889612015-09-04 14:32:35 -0700289 rpc_utils.fanout_rpc([host], 'modify_host_local',
290 include_hostnames=False, id=id, **kwargs)
291 host.update_object(kwargs)
mblighe8819cd2008-02-15 16:48:40 +0000292
293
MK Ryu33889612015-09-04 14:32:35 -0700294def modify_host_local(id, **kwargs):
295 """Modify host attributes in local DB.
296
297 @param id: Host id.
298 @param kwargs: key=value pairs of values to set on the host.
299 """
300 models.Host.smart_get(id).update_object(kwargs)
301
302
303@rpc_utils.route_rpc_to_master
showard276f9442009-05-20 00:33:16 +0000304def modify_hosts(host_filter_data, update_data):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700305 """Modify local attributes of multiple hosts.
306
307 If this is called on the master, but one of the hosts in that match the
MK Ryu33889612015-09-04 14:32:35 -0700308 filters is assigned to a shard, this will call `modify_hosts_local` RPC
309 to the responsible shard.
310 When this is called on a shard, the shard just routes the RPC to the master
311 and does nothing.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700312
313 The filters are always applied on the master, not on the shards. This means
314 if the states of a host differ on the master and a shard, the state on the
315 master will be used. I.e. this means:
316 A host was synced to Shard 1. On Shard 1 the status of the host was set to
317 'Repair Failed'.
318 - A call to modify_hosts with host_filter_data={'status': 'Ready'} will
319 update the host (both on the shard and on the master), because the state
320 of the host as the master knows it is still 'Ready'.
321 - A call to modify_hosts with host_filter_data={'status': 'Repair failed'
322 will not update the host, because the filter doesn't apply on the master.
323
showardbe0d8692009-08-20 23:42:44 +0000324 @param host_filter_data: Filters out which hosts to modify.
325 @param update_data: A dictionary with the changes to make to the hosts.
showard276f9442009-05-20 00:33:16 +0000326 """
showardbe0d8692009-08-20 23:42:44 +0000327 rpc_utils.check_modify_host(update_data)
showard276f9442009-05-20 00:33:16 +0000328 hosts = models.Host.query_objects(host_filter_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700329
330 affected_shard_hostnames = set()
331 affected_host_ids = []
332
Alex Miller9658a952013-05-14 16:40:02 -0700333 # Check all hosts before changing data for exception safety.
334 for host in hosts:
335 rpc_utils.check_modify_host_locking(host, update_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700336 if host.shard:
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800337 affected_shard_hostnames.add(host.shard.rpc_hostname())
Jakob Juelich50e91f72014-10-01 12:43:23 -0700338 affected_host_ids.append(host.id)
339
MK Ryu33889612015-09-04 14:32:35 -0700340 # Caution: Changing the filter from the original here. See docstring.
341 rpc_utils.run_rpc_on_multiple_hostnames(
342 'modify_hosts_local', affected_shard_hostnames,
Jakob Juelich50e91f72014-10-01 12:43:23 -0700343 host_filter_data={'id__in': affected_host_ids},
344 update_data=update_data)
345
showard276f9442009-05-20 00:33:16 +0000346 for host in hosts:
347 host.update_object(update_data)
348
349
MK Ryu33889612015-09-04 14:32:35 -0700350def modify_hosts_local(host_filter_data, update_data):
351 """Modify attributes of hosts in local DB.
352
353 @param host_filter_data: Filters out which hosts to modify.
354 @param update_data: A dictionary with the changes to make to the hosts.
355 """
356 for host in models.Host.query_objects(host_filter_data):
357 host.update_object(update_data)
358
359
MK Ryufbb002c2015-06-08 14:13:16 -0700360def add_labels_to_host(id, labels):
361 """Adds labels to a given host only in local DB.
showardcafd16e2009-05-29 18:37:49 +0000362
MK Ryufbb002c2015-06-08 14:13:16 -0700363 @param id: id or hostname for a host.
364 @param labels: ids or names for labels.
365 """
366 label_objs = models.Label.smart_get_bulk(labels)
367 models.Host.smart_get(id).labels.add(*label_objs)
368
369
370@rpc_utils.route_rpc_to_master
371def host_add_labels(id, labels):
372 """Adds labels to a given host.
373
374 @param id: id or hostname for a host.
375 @param labels: ids or names for labels.
376
377 @raises ValidationError: If adding more than one platform label.
378 """
379 label_objs = models.Label.smart_get_bulk(labels)
380 platforms = [label.name for label in label_objs if label.platform]
showardcafd16e2009-05-29 18:37:49 +0000381 if len(platforms) > 1:
382 raise model_logic.ValidationError(
383 {'labels': 'Adding more than one platform label: %s' %
384 ', '.join(platforms)})
MK Ryufbb002c2015-06-08 14:13:16 -0700385
386 host_obj = models.Host.smart_get(id)
showardcafd16e2009-05-29 18:37:49 +0000387 if len(platforms) == 1:
MK Ryufbb002c2015-06-08 14:13:16 -0700388 models.Host.check_no_platform([host_obj])
389
390 rpc_utils.fanout_rpc([host_obj], 'add_labels_to_host', False,
391 id=id, labels=labels)
392 add_labels_to_host(id, labels)
mblighe8819cd2008-02-15 16:48:40 +0000393
394
MK Ryufbb002c2015-06-08 14:13:16 -0700395def remove_labels_from_host(id, labels):
396 """Removes labels from a given host only in local DB.
397
398 @param id: id or hostname for a host.
399 @param labels: ids or names for labels.
400 """
401 label_objs = models.Label.smart_get_bulk(labels)
402 models.Host.smart_get(id).labels.remove(*label_objs)
403
404
405@rpc_utils.route_rpc_to_master
mblighe8819cd2008-02-15 16:48:40 +0000406def host_remove_labels(id, labels):
MK Ryufbb002c2015-06-08 14:13:16 -0700407 """Removes labels from a given host.
408
409 @param id: id or hostname for a host.
410 @param labels: ids or names for labels.
411 """
412 host_obj = models.Host.smart_get(id)
413 rpc_utils.fanout_rpc([host_obj], 'remove_labels_from_host', False,
414 id=id, labels=labels)
415 remove_labels_from_host(id, labels)
mblighe8819cd2008-02-15 16:48:40 +0000416
417
MK Ryuacf35922014-10-03 14:56:49 -0700418def get_host_attribute(attribute, **host_filter_data):
419 """
420 @param attribute: string name of attribute
421 @param host_filter_data: filter data to apply to Hosts to choose hosts to
422 act upon
423 """
424 hosts = rpc_utils.get_host_query((), False, False, True, host_filter_data)
425 hosts = list(hosts)
426 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
427 'attribute_list')
428 host_attr_dicts = []
429 for host_obj in hosts:
430 for attr_obj in host_obj.attribute_list:
431 if attr_obj.attribute == attribute:
432 host_attr_dicts.append(attr_obj.get_object_dict())
433 return rpc_utils.prepare_for_serialization(host_attr_dicts)
434
435
showard0957a842009-05-11 19:25:08 +0000436def set_host_attribute(attribute, value, **host_filter_data):
437 """
MK Ryu26f0c932015-05-28 18:14:33 -0700438 @param attribute: string name of attribute
439 @param value: string, or None to delete an attribute
440 @param host_filter_data: filter data to apply to Hosts to choose hosts to
441 act upon
showard0957a842009-05-11 19:25:08 +0000442 """
443 assert host_filter_data # disallow accidental actions on all hosts
444 hosts = models.Host.query_objects(host_filter_data)
445 models.AclGroup.check_for_acl_violation_hosts(hosts)
446
MK Ryu26f0c932015-05-28 18:14:33 -0700447 # Master forwards this RPC to shards.
448 if not utils.is_shard():
449 rpc_utils.fanout_rpc(hosts, 'set_host_attribute', False,
450 attribute=attribute, value=value, **host_filter_data)
451
showard0957a842009-05-11 19:25:08 +0000452 for host in hosts:
showardf8b19042009-05-12 17:22:49 +0000453 host.set_or_delete_attribute(attribute, value)
showard0957a842009-05-11 19:25:08 +0000454
455
Jakob Juelich50e91f72014-10-01 12:43:23 -0700456@rpc_utils.forward_single_host_rpc_to_shard
mblighe8819cd2008-02-15 16:48:40 +0000457def delete_host(id):
jadmanski0afbb632008-06-06 21:10:57 +0000458 models.Host.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000459
460
showard87cc38f2009-08-20 23:37:04 +0000461def get_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000462 exclude_atomic_group_hosts=False, valid_only=True, **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000463 """
464 @param multiple_labels: match hosts in all of the labels given. Should
465 be a list of label names.
466 @param exclude_only_if_needed_labels: Exclude hosts with at least one
467 "only_if_needed" label applied.
468 @param exclude_atomic_group_hosts: Exclude hosts that have one or more
469 atomic group labels associated with them.
jadmanski0afbb632008-06-06 21:10:57 +0000470 """
showard43a3d262008-11-12 18:17:05 +0000471 hosts = rpc_utils.get_host_query(multiple_labels,
472 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000473 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000474 valid_only, filter_data)
showard0957a842009-05-11 19:25:08 +0000475 hosts = list(hosts)
476 models.Host.objects.populate_relationships(hosts, models.Label,
477 'label_list')
478 models.Host.objects.populate_relationships(hosts, models.AclGroup,
479 'acl_list')
480 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
481 'attribute_list')
showard43a3d262008-11-12 18:17:05 +0000482 host_dicts = []
483 for host_obj in hosts:
484 host_dict = host_obj.get_object_dict()
showard0957a842009-05-11 19:25:08 +0000485 host_dict['labels'] = [label.name for label in host_obj.label_list]
showard909c9142009-07-07 20:54:42 +0000486 host_dict['platform'], host_dict['atomic_group'] = (rpc_utils.
487 find_platform_and_atomic_group(host_obj))
showard0957a842009-05-11 19:25:08 +0000488 host_dict['acls'] = [acl.name for acl in host_obj.acl_list]
489 host_dict['attributes'] = dict((attribute.attribute, attribute.value)
490 for attribute in host_obj.attribute_list)
showard43a3d262008-11-12 18:17:05 +0000491 host_dicts.append(host_dict)
492 return rpc_utils.prepare_for_serialization(host_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000493
494
showard87cc38f2009-08-20 23:37:04 +0000495def get_num_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000496 exclude_atomic_group_hosts=False, valid_only=True,
497 **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000498 """
499 Same parameters as get_hosts().
500
501 @returns The number of matching hosts.
502 """
showard43a3d262008-11-12 18:17:05 +0000503 hosts = rpc_utils.get_host_query(multiple_labels,
504 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000505 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000506 valid_only, filter_data)
showard43a3d262008-11-12 18:17:05 +0000507 return hosts.count()
showard1385b162008-03-13 15:59:40 +0000508
mblighe8819cd2008-02-15 16:48:40 +0000509
510# tests
511
showard909c7a62008-07-15 21:52:38 +0000512def add_test(name, test_type, path, author=None, dependencies=None,
showard3d9899a2008-07-31 02:11:58 +0000513 experimental=True, run_verify=None, test_class=None,
showard909c7a62008-07-15 21:52:38 +0000514 test_time=None, test_category=None, description=None,
515 sync_count=1):
jadmanski0afbb632008-06-06 21:10:57 +0000516 return models.Test.add_object(name=name, test_type=test_type, path=path,
showard909c7a62008-07-15 21:52:38 +0000517 author=author, dependencies=dependencies,
518 experimental=experimental,
519 run_verify=run_verify, test_time=test_time,
520 test_category=test_category,
521 sync_count=sync_count,
jadmanski0afbb632008-06-06 21:10:57 +0000522 test_class=test_class,
523 description=description).id
mblighe8819cd2008-02-15 16:48:40 +0000524
525
526def modify_test(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000527 models.Test.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000528
529
530def delete_test(id):
jadmanski0afbb632008-06-06 21:10:57 +0000531 models.Test.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000532
533
534def get_tests(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000535 return rpc_utils.prepare_for_serialization(
536 models.Test.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000537
538
Moises Osorio2dc7a102014-12-02 18:24:02 -0800539@_timer.decorate
540def get_tests_status_counts_by_job_name_label(job_name_prefix, label_name):
541 """Gets the counts of all passed and failed tests from the matching jobs.
542
543 @param job_name_prefix: Name prefix of the jobs to get the summary from, e.g.,
544 'butterfly-release/R40-6457.21.0/bvt-cq/'.
545 @param label_name: Label that must be set in the jobs, e.g.,
546 'cros-version:butterfly-release/R40-6457.21.0'.
547
548 @returns A summary of the counts of all the passed and failed tests.
549 """
550 job_ids = list(models.Job.objects.filter(
551 name__startswith=job_name_prefix,
552 dependency_labels__name=label_name).values_list(
553 'pk', flat=True))
554 summary = {'passed': 0, 'failed': 0}
555 if not job_ids:
556 return summary
557
558 counts = (tko_models.TestView.objects.filter(
559 afe_job_id__in=job_ids).exclude(
560 test_name='SERVER_JOB').exclude(
561 test_name__startswith='CLIENT_JOB').values(
562 'status').annotate(
563 count=Count('status')))
564 for status in counts:
565 if status['status'] == 'GOOD':
566 summary['passed'] += status['count']
567 else:
568 summary['failed'] += status['count']
569 return summary
570
571
showard2b9a88b2008-06-13 20:55:03 +0000572# profilers
573
574def add_profiler(name, description=None):
575 return models.Profiler.add_object(name=name, description=description).id
576
577
578def modify_profiler(id, **data):
579 models.Profiler.smart_get(id).update_object(data)
580
581
582def delete_profiler(id):
583 models.Profiler.smart_get(id).delete()
584
585
586def get_profilers(**filter_data):
587 return rpc_utils.prepare_for_serialization(
588 models.Profiler.list_objects(filter_data))
589
590
mblighe8819cd2008-02-15 16:48:40 +0000591# users
592
593def add_user(login, access_level=None):
jadmanski0afbb632008-06-06 21:10:57 +0000594 return models.User.add_object(login=login, access_level=access_level).id
mblighe8819cd2008-02-15 16:48:40 +0000595
596
597def modify_user(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000598 models.User.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000599
600
601def delete_user(id):
jadmanski0afbb632008-06-06 21:10:57 +0000602 models.User.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000603
604
605def get_users(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000606 return rpc_utils.prepare_for_serialization(
607 models.User.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000608
609
610# acl groups
611
612def add_acl_group(name, description=None):
showard04f2cd82008-07-25 20:53:31 +0000613 group = models.AclGroup.add_object(name=name, description=description)
showard64a95952010-01-13 21:27:16 +0000614 group.users.add(models.User.current_user())
showard04f2cd82008-07-25 20:53:31 +0000615 return group.id
mblighe8819cd2008-02-15 16:48:40 +0000616
617
618def modify_acl_group(id, **data):
showard04f2cd82008-07-25 20:53:31 +0000619 group = models.AclGroup.smart_get(id)
620 group.check_for_acl_violation_acl_group()
621 group.update_object(data)
622 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000623
624
625def acl_group_add_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000626 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000627 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000628 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000629 group.users.add(*users)
mblighe8819cd2008-02-15 16:48:40 +0000630
631
632def acl_group_remove_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000633 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000634 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000635 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000636 group.users.remove(*users)
showard04f2cd82008-07-25 20:53:31 +0000637 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000638
639
640def acl_group_add_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000641 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000642 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000643 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000644 group.hosts.add(*hosts)
showard08f981b2008-06-24 21:59:03 +0000645 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000646
647
648def acl_group_remove_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000649 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000650 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000651 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000652 group.hosts.remove(*hosts)
showard08f981b2008-06-24 21:59:03 +0000653 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000654
655
656def delete_acl_group(id):
jadmanski0afbb632008-06-06 21:10:57 +0000657 models.AclGroup.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000658
659
660def get_acl_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000661 acl_groups = models.AclGroup.list_objects(filter_data)
662 for acl_group in acl_groups:
663 acl_group_obj = models.AclGroup.objects.get(id=acl_group['id'])
664 acl_group['users'] = [user.login
665 for user in acl_group_obj.users.all()]
666 acl_group['hosts'] = [host.hostname
667 for host in acl_group_obj.hosts.all()]
668 return rpc_utils.prepare_for_serialization(acl_groups)
mblighe8819cd2008-02-15 16:48:40 +0000669
670
671# jobs
672
mbligh120351e2009-01-24 01:40:45 +0000673def generate_control_file(tests=(), kernel=None, label=None, profilers=(),
showard91f85102009-10-12 20:34:52 +0000674 client_control_file='', use_container=False,
Matthew Sartori10438092015-06-24 14:30:18 -0700675 profile_only=None, upload_kernel_config=False,
676 db_tests=True):
jadmanski0afbb632008-06-06 21:10:57 +0000677 """
mbligh120351e2009-01-24 01:40:45 +0000678 Generates a client-side control file to load a kernel and run tests.
679
Matthew Sartori10438092015-06-24 14:30:18 -0700680 @param tests List of tests to run. See db_tests for more information.
mbligha3c58d22009-08-24 22:01:51 +0000681 @param kernel A list of kernel info dictionaries configuring which kernels
682 to boot for this job and other options for them
mbligh120351e2009-01-24 01:40:45 +0000683 @param label Name of label to grab kernel config from.
684 @param profilers List of profilers to activate during the job.
685 @param client_control_file The contents of a client-side control file to
686 run at the end of all tests. If this is supplied, all tests must be
687 client side.
688 TODO: in the future we should support server control files directly
689 to wrap with a kernel. That'll require changing the parameter
690 name and adding a boolean to indicate if it is a client or server
691 control file.
692 @param use_container unused argument today. TODO: Enable containers
693 on the host during a client side test.
showard91f85102009-10-12 20:34:52 +0000694 @param profile_only A boolean that indicates what default profile_only
695 mode to use in the control file. Passing None will generate a
696 control file that does not explcitly set the default mode at all.
showard232b7ae2009-11-10 00:46:48 +0000697 @param upload_kernel_config: if enabled it will generate server control
698 file code that uploads the kernel config file to the client and
699 tells the client of the new (local) path when compiling the kernel;
700 the tests must be server side tests
Matthew Sartori10438092015-06-24 14:30:18 -0700701 @param db_tests: if True, the test object can be found in the database
702 backing the test model. In this case, tests is a tuple
703 of test IDs which are used to retrieve the test objects
704 from the database. If False, tests is a tuple of test
705 dictionaries stored client-side in the AFE.
mbligh120351e2009-01-24 01:40:45 +0000706
707 @returns a dict with the following keys:
708 control_file: str, The control file text.
709 is_server: bool, is the control file a server-side control file?
710 synch_count: How many machines the job uses per autoserv execution.
711 synch_count == 1 means the job is asynchronous.
712 dependencies: A list of the names of labels on which the job depends.
713 """
showardd86debe2009-06-10 17:37:56 +0000714 if not tests and not client_control_file:
showard2bab8f42008-11-12 18:15:22 +0000715 return dict(control_file='', is_server=False, synch_count=1,
showard989f25d2008-10-01 11:38:11 +0000716 dependencies=[])
mblighe8819cd2008-02-15 16:48:40 +0000717
showard989f25d2008-10-01 11:38:11 +0000718 cf_info, test_objects, profiler_objects, label = (
showard2b9a88b2008-06-13 20:55:03 +0000719 rpc_utils.prepare_generate_control_file(tests, kernel, label,
Matthew Sartori10438092015-06-24 14:30:18 -0700720 profilers, db_tests))
showard989f25d2008-10-01 11:38:11 +0000721 cf_info['control_file'] = control_file.generate_control(
mbligha3c58d22009-08-24 22:01:51 +0000722 tests=test_objects, kernels=kernel, platform=label,
mbligh120351e2009-01-24 01:40:45 +0000723 profilers=profiler_objects, is_server=cf_info['is_server'],
showard232b7ae2009-11-10 00:46:48 +0000724 client_control_file=client_control_file, profile_only=profile_only,
725 upload_kernel_config=upload_kernel_config)
showard989f25d2008-10-01 11:38:11 +0000726 return cf_info
mblighe8819cd2008-02-15 16:48:40 +0000727
728
jamesren4a41e012010-07-16 22:33:48 +0000729def create_parameterized_job(name, priority, test, parameters, kernel=None,
730 label=None, profilers=(), profiler_parameters=None,
731 use_container=False, profile_only=None,
732 upload_kernel_config=False, hosts=(),
733 meta_hosts=(), one_time_hosts=(),
734 atomic_group_name=None, synch_count=None,
735 is_template=False, timeout=None,
Simran Basi7e605742013-11-12 13:43:36 -0800736 timeout_mins=None, max_runtime_mins=None,
737 run_verify=False, email_list='', dependencies=(),
738 reboot_before=None, reboot_after=None,
739 parse_failed_repair=None, hostless=False,
Dan Shiec1d47d2015-02-13 11:38:13 -0800740 keyvals=None, drone_set=None, run_reset=True,
Dan Shi2a5297b2015-07-23 17:03:29 -0700741 require_ssp=None):
jamesren4a41e012010-07-16 22:33:48 +0000742 """
743 Creates and enqueues a parameterized job.
744
745 Most parameters a combination of the parameters for generate_control_file()
746 and create_job(), with the exception of:
747
748 @param test name or ID of the test to run
749 @param parameters a map of parameter name ->
750 tuple of (param value, param type)
751 @param profiler_parameters a dictionary of parameters for the profilers:
752 key: profiler name
753 value: dict of param name -> tuple of
754 (param value,
755 param type)
756 """
757 # Save the values of the passed arguments here. What we're going to do with
758 # them is pass them all to rpc_utils.get_create_job_common_args(), which
759 # will extract the subset of these arguments that apply for
760 # rpc_utils.create_job_common(), which we then pass in to that function.
761 args = locals()
762
763 # Set up the parameterized job configs
764 test_obj = models.Test.smart_get(test)
Aviv Keshet3dd8beb2013-05-13 17:36:04 -0700765 control_type = test_obj.test_type
jamesren4a41e012010-07-16 22:33:48 +0000766
767 try:
768 label = models.Label.smart_get(label)
769 except models.Label.DoesNotExist:
770 label = None
771
772 kernel_objs = models.Kernel.create_kernels(kernel)
773 profiler_objs = [models.Profiler.smart_get(profiler)
774 for profiler in profilers]
775
776 parameterized_job = models.ParameterizedJob.objects.create(
777 test=test_obj, label=label, use_container=use_container,
778 profile_only=profile_only,
779 upload_kernel_config=upload_kernel_config)
780 parameterized_job.kernels.add(*kernel_objs)
781
782 for profiler in profiler_objs:
783 parameterized_profiler = models.ParameterizedJobProfiler.objects.create(
784 parameterized_job=parameterized_job,
785 profiler=profiler)
786 profiler_params = profiler_parameters.get(profiler.name, {})
787 for name, (value, param_type) in profiler_params.iteritems():
788 models.ParameterizedJobProfilerParameter.objects.create(
789 parameterized_job_profiler=parameterized_profiler,
790 parameter_name=name,
791 parameter_value=value,
792 parameter_type=param_type)
793
794 try:
795 for parameter in test_obj.testparameter_set.all():
796 if parameter.name in parameters:
797 param_value, param_type = parameters.pop(parameter.name)
798 parameterized_job.parameterizedjobparameter_set.create(
799 test_parameter=parameter, parameter_value=param_value,
800 parameter_type=param_type)
801
802 if parameters:
803 raise Exception('Extra parameters remain: %r' % parameters)
804
805 return rpc_utils.create_job_common(
806 parameterized_job=parameterized_job.id,
807 control_type=control_type,
808 **rpc_utils.get_create_job_common_args(args))
809 except:
810 parameterized_job.delete()
811 raise
812
813
Simran Basib6ec8ae2014-04-23 12:05:08 -0700814def create_job_page_handler(name, priority, control_file, control_type,
Dan Shid215dbe2015-06-18 16:14:59 -0700815 image=None, hostless=False, firmware_rw_build=None,
816 firmware_ro_build=None, test_source_build=None,
817 **kwargs):
Simran Basib6ec8ae2014-04-23 12:05:08 -0700818 """\
819 Create and enqueue a job.
820
821 @param name name of this job
822 @param priority Integer priority of this job. Higher is more important.
823 @param control_file String contents of the control file.
824 @param control_type Type of control file, Client or Server.
Dan Shid215dbe2015-06-18 16:14:59 -0700825 @param image: ChromeOS build to be installed in the dut. Default to None.
826 @param firmware_rw_build: Firmware build to update RW firmware. Default to
827 None, i.e., RW firmware will not be updated.
828 @param firmware_ro_build: Firmware build to update RO firmware. Default to
829 None, i.e., RO firmware will not be updated.
830 @param test_source_build: Build to be used to retrieve test code. Default
831 to None.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700832 @param kwargs extra args that will be required by create_suite_job or
833 create_job.
834
835 @returns The created Job id number.
836 """
837 control_file = rpc_utils.encode_ascii(control_file)
Jiaxi Luodd67beb2014-07-18 16:28:31 -0700838 if not control_file:
839 raise model_logic.ValidationError({
840 'control_file' : "Control file cannot be empty"})
Simran Basib6ec8ae2014-04-23 12:05:08 -0700841
842 if image and hostless:
Dan Shid215dbe2015-06-18 16:14:59 -0700843 builds = {}
844 builds[provision.CROS_VERSION_PREFIX] = image
845 if firmware_rw_build:
Dan Shi0723bf52015-06-24 10:52:38 -0700846 builds[provision.FW_RW_VERSION_PREFIX] = firmware_rw_build
Dan Shid215dbe2015-06-18 16:14:59 -0700847 if firmware_ro_build:
848 builds[provision.FW_RO_VERSION_PREFIX] = firmware_ro_build
Simran Basib6ec8ae2014-04-23 12:05:08 -0700849 return site_rpc_interface.create_suite_job(
850 name=name, control_file=control_file, priority=priority,
Dan Shid215dbe2015-06-18 16:14:59 -0700851 builds=builds, test_source_build=test_source_build, **kwargs)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700852 return create_job(name, priority, control_file, control_type, image=image,
853 hostless=hostless, **kwargs)
854
855
MK Ryue301eb72015-06-25 12:51:02 -0700856@rpc_utils.route_rpc_to_master
showard12f3e322009-05-13 21:27:42 +0000857def create_job(name, priority, control_file, control_type,
858 hosts=(), meta_hosts=(), one_time_hosts=(),
859 atomic_group_name=None, synch_count=None, is_template=False,
Simran Basi7e605742013-11-12 13:43:36 -0800860 timeout=None, timeout_mins=None, max_runtime_mins=None,
861 run_verify=False, email_list='', dependencies=(),
862 reboot_before=None, reboot_after=None, parse_failed_repair=None,
863 hostless=False, keyvals=None, drone_set=None, image=None,
Dan Shiec1d47d2015-02-13 11:38:13 -0800864 parent_job_id=None, test_retry=0, run_reset=True,
865 require_ssp=None, args=(), **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +0000866 """\
867 Create and enqueue a job.
mblighe8819cd2008-02-15 16:48:40 +0000868
showarda1e74b32009-05-12 17:32:04 +0000869 @param name name of this job
Alex Miller7d658cf2013-09-04 16:00:35 -0700870 @param priority Integer priority of this job. Higher is more important.
showarda1e74b32009-05-12 17:32:04 +0000871 @param control_file String contents of the control file.
872 @param control_type Type of control file, Client or Server.
873 @param synch_count How many machines the job uses per autoserv execution.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700874 synch_count == 1 means the job is asynchronous. If an atomic group is
875 given this value is treated as a minimum.
showarda1e74b32009-05-12 17:32:04 +0000876 @param is_template If true then create a template job.
877 @param timeout Hours after this call returns until the job times out.
Simran Basi7e605742013-11-12 13:43:36 -0800878 @param timeout_mins Minutes after this call returns until the job times
Jiaxi Luo90190c92014-06-18 12:35:57 -0700879 out.
Simran Basi34217022012-11-06 13:43:15 -0800880 @param max_runtime_mins Minutes from job starting time until job times out
showarda1e74b32009-05-12 17:32:04 +0000881 @param run_verify Should the host be verified before running the test?
882 @param email_list String containing emails to mail when the job is done
883 @param dependencies List of label names on which this job depends
884 @param reboot_before Never, If dirty, or Always
885 @param reboot_after Never, If all tests passed, or Always
886 @param parse_failed_repair if true, results of failed repairs launched by
Jiaxi Luo90190c92014-06-18 12:35:57 -0700887 this job will be parsed as part of the job.
showarda9545c02009-12-18 22:44:26 +0000888 @param hostless if true, create a hostless job
showardc1a98d12010-01-15 00:22:22 +0000889 @param keyvals dict of keyvals to associate with the job
showarda1e74b32009-05-12 17:32:04 +0000890 @param hosts List of hosts to run job on.
891 @param meta_hosts List where each entry is a label name, and for each entry
Jiaxi Luo90190c92014-06-18 12:35:57 -0700892 one host will be chosen from that label to run the job on.
showarda1e74b32009-05-12 17:32:04 +0000893 @param one_time_hosts List of hosts not in the database to run the job on.
894 @param atomic_group_name The name of an atomic group to schedule the job on.
jamesren76fcf192010-04-21 20:39:50 +0000895 @param drone_set The name of the drone set to run this test on.
Paul Pendlebury5a8c6ad2011-02-01 07:20:17 -0800896 @param image OS image to install before running job.
Aviv Keshet0b9cfc92013-02-05 11:36:02 -0800897 @param parent_job_id id of a job considered to be parent of created job.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700898 @param test_retry Number of times to retry test if the test did not
Jiaxi Luo90190c92014-06-18 12:35:57 -0700899 complete successfully. (optional, default: 0)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700900 @param run_reset Should the host be reset before running the test?
Dan Shiec1d47d2015-02-13 11:38:13 -0800901 @param require_ssp Set to True to require server-side packaging to run the
902 test. If it's set to None, drone will still try to run
903 the server side with server-side packaging. If the
904 autotest-server package doesn't exist for the build or
905 image is not set, drone will run the test without server-
906 side packaging. Default is None.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700907 @param args A list of args to be injected into control file.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700908 @param kwargs extra keyword args. NOT USED.
showardc92da832009-04-07 18:14:34 +0000909
910 @returns The created Job id number.
jadmanski0afbb632008-06-06 21:10:57 +0000911 """
Jiaxi Luo90190c92014-06-18 12:35:57 -0700912 if args:
913 control_file = tools.inject_vars({'args': args}, control_file)
914
Simran Basiab5a1bf2014-05-28 15:39:44 -0700915 if image is None:
916 return rpc_utils.create_job_common(
917 **rpc_utils.get_create_job_common_args(locals()))
918
919 # When image is supplied use a known parameterized test already in the
920 # database to pass the OS image path from the front end, through the
921 # scheduler, and finally to autoserv as the --image parameter.
922
923 # The test autoupdate_ParameterizedJob is in afe_autotests and used to
924 # instantiate a Test object and from there a ParameterizedJob.
925 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
926 known_parameterized_job = models.ParameterizedJob.objects.create(
927 test=known_test_obj)
928
929 # autoupdate_ParameterizedJob has a single parameter, the image parameter,
930 # stored in the table afe_test_parameters. We retrieve and set this
931 # instance of the parameter to the OS image path.
932 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
933 name='image')
934 known_parameterized_job.parameterizedjobparameter_set.create(
935 test_parameter=image_parameter, parameter_value=image,
936 parameter_type='string')
937
Dan Shid215dbe2015-06-18 16:14:59 -0700938 # TODO(crbug.com/502638): save firmware build etc to parameterized_job.
939
Simran Basiab5a1bf2014-05-28 15:39:44 -0700940 # By passing a parameterized_job to create_job_common the job entry in
941 # the afe_jobs table will have the field parameterized_job_id set.
942 # The scheduler uses this id in the afe_parameterized_jobs table to
943 # match this job to our known test, and then with the
944 # afe_parameterized_job_parameters table to get the actual image path.
jamesren4a41e012010-07-16 22:33:48 +0000945 return rpc_utils.create_job_common(
Simran Basiab5a1bf2014-05-28 15:39:44 -0700946 parameterized_job=known_parameterized_job.id,
jamesren4a41e012010-07-16 22:33:48 +0000947 **rpc_utils.get_create_job_common_args(locals()))
mblighe8819cd2008-02-15 16:48:40 +0000948
949
showard9dbdcda2008-10-14 17:34:36 +0000950def abort_host_queue_entries(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000951 """\
showard9dbdcda2008-10-14 17:34:36 +0000952 Abort a set of host queue entries.
Fang Deng63b0e452014-12-19 14:38:15 -0800953
954 @return: A list of dictionaries, each contains information
955 about an aborted HQE.
jadmanski0afbb632008-06-06 21:10:57 +0000956 """
showard9dbdcda2008-10-14 17:34:36 +0000957 query = models.HostQueueEntry.query_objects(filter_data)
beepsfaecbce2013-10-29 11:35:10 -0700958
959 # Dont allow aborts on:
960 # 1. Jobs that have already completed (whether or not they were aborted)
961 # 2. Jobs that we have already been aborted (but may not have completed)
962 query = query.filter(complete=False).filter(aborted=False)
showarddc817512008-11-12 18:16:41 +0000963 models.AclGroup.check_abort_permissions(query)
showard9dbdcda2008-10-14 17:34:36 +0000964 host_queue_entries = list(query.select_related())
showard2bab8f42008-11-12 18:15:22 +0000965 rpc_utils.check_abort_synchronous_jobs(host_queue_entries)
mblighe8819cd2008-02-15 16:48:40 +0000966
Simran Basic1b26762013-06-26 14:23:21 -0700967 models.HostQueueEntry.abort_host_queue_entries(host_queue_entries)
Fang Deng63b0e452014-12-19 14:38:15 -0800968 hqe_info = [{'HostQueueEntry': hqe.id, 'Job': hqe.job_id,
969 'Job name': hqe.job.name} for hqe in host_queue_entries]
970 return hqe_info
showard9d821ab2008-07-11 16:54:29 +0000971
972
beeps8bb1f7d2013-08-05 01:30:09 -0700973def abort_special_tasks(**filter_data):
974 """\
975 Abort the special task, or tasks, specified in the filter.
976 """
977 query = models.SpecialTask.query_objects(filter_data)
978 special_tasks = query.filter(is_active=True)
979 for task in special_tasks:
980 task.abort()
981
982
Simran Basi73dae552013-02-25 14:57:46 -0800983def _call_special_tasks_on_hosts(task, hosts):
984 """\
985 Schedules a set of hosts for a special task.
986
987 @returns A list of hostnames that a special task was created for.
988 """
989 models.AclGroup.check_for_acl_violation_hosts(hosts)
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -0800990 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800991 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -0800992 raise ValueError('The following hosts are on shards, please '
993 'follow the link to the shards and create jobs '
994 'there instead. %s.' % shard_host_map)
Simran Basi73dae552013-02-25 14:57:46 -0800995 for host in hosts:
996 models.SpecialTask.schedule_special_task(host, task)
997 return list(sorted(host.hostname for host in hosts))
998
999
MK Ryu5aa25042015-07-28 16:08:04 -07001000def _forward_special_tasks_on_hosts(task, rpc, **filter_data):
1001 """Forward special tasks to corresponding shards.
mbligh4e545a52009-12-19 05:30:39 +00001002
MK Ryu5aa25042015-07-28 16:08:04 -07001003 For master, when special tasks are fired on hosts that are sharded,
1004 forward the RPC to corresponding shards.
1005
1006 For shard, create special task records in local DB.
1007
1008 @param task: Enum value of frontend.afe.models.SpecialTask.Task
1009 @param rpc: RPC name to forward.
1010 @param filter_data: Filter keywords to be used for DB query.
1011
1012 @return: A list of hostnames that a special task was created for.
showard1ff7b2e2009-05-15 23:17:18 +00001013 """
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001014 hosts = models.Host.query_objects(filter_data)
1015 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts, rpc_hostnames=True)
1016
1017 # Filter out hosts on a shard from those on the master, forward
1018 # rpcs to the shard with an additional hostname__in filter, and
1019 # create a local SpecialTask for each remaining host.
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -08001020 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001021 hosts = [h for h in hosts if h.shard is None]
1022 for shard, hostnames in shard_host_map.iteritems():
1023
1024 # The main client of this module is the frontend website, and
1025 # it invokes it with an 'id' or an 'id__in' filter. Regardless,
1026 # the 'hostname' filter should narrow down the list of hosts on
1027 # each shard even though we supply all the ids in filter_data.
1028 # This method uses hostname instead of id because it fits better
MK Ryu5aa25042015-07-28 16:08:04 -07001029 # with the overall architecture of redirection functions in
1030 # rpc_utils.
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001031 shard_filter = filter_data.copy()
1032 shard_filter['hostname__in'] = hostnames
1033 rpc_utils.run_rpc_on_multiple_hostnames(
MK Ryu5aa25042015-07-28 16:08:04 -07001034 rpc, [shard], **shard_filter)
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001035
1036 # There is a race condition here if someone assigns a shard to one of these
1037 # hosts before we create the task. The host will stay on the master if:
1038 # 1. The host is not Ready
1039 # 2. The host is Ready but has a task
1040 # But if the host is Ready and doesn't have a task yet, it will get sent
1041 # to the shard as we're creating a task here.
1042
1043 # Given that we only rarely verify Ready hosts it isn't worth putting this
1044 # entire method in a transaction. The worst case scenario is that we have
MK Ryu5aa25042015-07-28 16:08:04 -07001045 # a verify running on a Ready host while the shard is using it, if the
1046 # verify fails no subsequent tasks will be created against the host on the
1047 # master, and verifies are safe enough that this is OK.
1048 return _call_special_tasks_on_hosts(task, hosts)
1049
1050
1051def reverify_hosts(**filter_data):
1052 """\
1053 Schedules a set of hosts for verify.
1054
1055 @returns A list of hostnames that a verify task was created for.
1056 """
1057 return _forward_special_tasks_on_hosts(
1058 models.SpecialTask.Task.VERIFY, 'reverify_hosts', **filter_data)
Simran Basi73dae552013-02-25 14:57:46 -08001059
1060
1061def repair_hosts(**filter_data):
1062 """\
1063 Schedules a set of hosts for repair.
1064
1065 @returns A list of hostnames that a repair task was created for.
1066 """
MK Ryu5aa25042015-07-28 16:08:04 -07001067 return _forward_special_tasks_on_hosts(
1068 models.SpecialTask.Task.REPAIR, 'repair_hosts', **filter_data)
showard1ff7b2e2009-05-15 23:17:18 +00001069
1070
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001071def get_jobs(not_yet_run=False, running=False, finished=False,
1072 suite=False, sub=False, standalone=False, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001073 """\
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001074 Extra status filter args for get_jobs:
jadmanski0afbb632008-06-06 21:10:57 +00001075 -not_yet_run: Include only jobs that have not yet started running.
1076 -running: Include only jobs that have start running but for which not
1077 all hosts have completed.
1078 -finished: Include only jobs for which all hosts have completed (or
1079 aborted).
1080 At most one of these three fields should be specified.
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001081
1082 Extra type filter args for get_jobs:
1083 -suite: Include only jobs with child jobs.
1084 -sub: Include only jobs with a parent job.
1085 -standalone: Inlcude only jobs with no child or parent jobs.
1086 At most one of these three fields should be specified.
jadmanski0afbb632008-06-06 21:10:57 +00001087 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001088 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
1089 running,
1090 finished)
1091 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
1092 suite,
1093 sub,
1094 standalone)
showard0957a842009-05-11 19:25:08 +00001095 job_dicts = []
1096 jobs = list(models.Job.query_objects(filter_data))
1097 models.Job.objects.populate_relationships(jobs, models.Label,
1098 'dependencies')
showardc1a98d12010-01-15 00:22:22 +00001099 models.Job.objects.populate_relationships(jobs, models.JobKeyval, 'keyvals')
showard0957a842009-05-11 19:25:08 +00001100 for job in jobs:
1101 job_dict = job.get_object_dict()
1102 job_dict['dependencies'] = ','.join(label.name
1103 for label in job.dependencies)
showardc1a98d12010-01-15 00:22:22 +00001104 job_dict['keyvals'] = dict((keyval.key, keyval.value)
1105 for keyval in job.keyvals)
Eric Lid23bc192011-02-09 14:38:57 -08001106 if job.parameterized_job:
1107 job_dict['image'] = get_parameterized_autoupdate_image_url(job)
showard0957a842009-05-11 19:25:08 +00001108 job_dicts.append(job_dict)
1109 return rpc_utils.prepare_for_serialization(job_dicts)
mblighe8819cd2008-02-15 16:48:40 +00001110
1111
1112def get_num_jobs(not_yet_run=False, running=False, finished=False,
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001113 suite=False, sub=False, standalone=False,
jadmanski0afbb632008-06-06 21:10:57 +00001114 **filter_data):
1115 """\
1116 See get_jobs() for documentation of extra filter parameters.
1117 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001118 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
1119 running,
1120 finished)
1121 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
1122 suite,
1123 sub,
1124 standalone)
jadmanski0afbb632008-06-06 21:10:57 +00001125 return models.Job.query_count(filter_data)
mblighe8819cd2008-02-15 16:48:40 +00001126
1127
mblighe8819cd2008-02-15 16:48:40 +00001128def get_jobs_summary(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001129 """\
Jiaxi Luoaac54572014-06-04 13:57:02 -07001130 Like get_jobs(), but adds 'status_counts' and 'result_counts' field.
1131
1132 'status_counts' filed is a dictionary mapping status strings to the number
1133 of hosts currently with that status, i.e. {'Queued' : 4, 'Running' : 2}.
1134
1135 'result_counts' field is piped to tko's rpc_interface and has the return
1136 format specified under get_group_counts.
jadmanski0afbb632008-06-06 21:10:57 +00001137 """
1138 jobs = get_jobs(**filter_data)
1139 ids = [job['id'] for job in jobs]
1140 all_status_counts = models.Job.objects.get_status_counts(ids)
1141 for job in jobs:
1142 job['status_counts'] = all_status_counts[job['id']]
Jiaxi Luoaac54572014-06-04 13:57:02 -07001143 job['result_counts'] = tko_rpc_interface.get_status_counts(
1144 ['afe_job_id', 'afe_job_id'],
1145 header_groups=[['afe_job_id'], ['afe_job_id']],
1146 **{'afe_job_id': job['id']})
jadmanski0afbb632008-06-06 21:10:57 +00001147 return rpc_utils.prepare_for_serialization(jobs)
mblighe8819cd2008-02-15 16:48:40 +00001148
1149
showarda965cef2009-05-15 23:17:41 +00001150def get_info_for_clone(id, preserve_metahosts, queue_entry_filter_data=None):
showarda8709c52008-07-03 19:44:54 +00001151 """\
1152 Retrieves all the information needed to clone a job.
1153 """
showarda8709c52008-07-03 19:44:54 +00001154 job = models.Job.objects.get(id=id)
showard29f7cd22009-04-29 21:16:24 +00001155 job_info = rpc_utils.get_job_info(job,
showarda965cef2009-05-15 23:17:41 +00001156 preserve_metahosts,
1157 queue_entry_filter_data)
showard945072f2008-09-03 20:34:59 +00001158
showardd9992fe2008-07-31 02:15:03 +00001159 host_dicts = []
showard29f7cd22009-04-29 21:16:24 +00001160 for host in job_info['hosts']:
1161 host_dict = get_hosts(id=host.id)[0]
1162 other_labels = host_dict['labels']
1163 if host_dict['platform']:
1164 other_labels.remove(host_dict['platform'])
1165 host_dict['other_labels'] = ', '.join(other_labels)
showardd9992fe2008-07-31 02:15:03 +00001166 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001167
showard29f7cd22009-04-29 21:16:24 +00001168 for host in job_info['one_time_hosts']:
1169 host_dict = dict(hostname=host.hostname,
1170 id=host.id,
1171 platform='(one-time host)',
1172 locked_text='')
1173 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001174
showard4d077562009-05-08 18:24:36 +00001175 # convert keys from Label objects to strings (names of labels)
showard29f7cd22009-04-29 21:16:24 +00001176 meta_host_counts = dict((meta_host.name, count) for meta_host, count
showard4d077562009-05-08 18:24:36 +00001177 in job_info['meta_host_counts'].iteritems())
showard29f7cd22009-04-29 21:16:24 +00001178
1179 info = dict(job=job.get_object_dict(),
1180 meta_host_counts=meta_host_counts,
1181 hosts=host_dicts)
1182 info['job']['dependencies'] = job_info['dependencies']
1183 if job_info['atomic_group']:
1184 info['atomic_group_name'] = (job_info['atomic_group']).name
1185 else:
1186 info['atomic_group_name'] = None
jamesren2275ef12010-04-12 18:25:06 +00001187 info['hostless'] = job_info['hostless']
jamesren76fcf192010-04-21 20:39:50 +00001188 info['drone_set'] = job.drone_set and job.drone_set.name
showarda8709c52008-07-03 19:44:54 +00001189
Eric Lid23bc192011-02-09 14:38:57 -08001190 if job.parameterized_job:
1191 info['job']['image'] = get_parameterized_autoupdate_image_url(job)
1192
showarda8709c52008-07-03 19:44:54 +00001193 return rpc_utils.prepare_for_serialization(info)
1194
1195
showard34dc5fa2008-04-24 20:58:40 +00001196# host queue entries
1197
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001198def get_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001199 """\
showardc92da832009-04-07 18:14:34 +00001200 @returns A sequence of nested dictionaries of host and job information.
jadmanski0afbb632008-06-06 21:10:57 +00001201 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001202 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1203 'started_on__lte',
1204 start_time,
1205 end_time,
1206 **filter_data)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001207 return rpc_utils.prepare_rows_as_nested_dicts(
1208 models.HostQueueEntry.query_objects(filter_data),
1209 ('host', 'atomic_group', 'job'))
showard34dc5fa2008-04-24 20:58:40 +00001210
1211
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001212def get_num_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001213 """\
1214 Get the number of host queue entries associated with this job.
1215 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001216 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1217 'started_on__lte',
1218 start_time,
1219 end_time,
1220 **filter_data)
jadmanski0afbb632008-06-06 21:10:57 +00001221 return models.HostQueueEntry.query_count(filter_data)
showard34dc5fa2008-04-24 20:58:40 +00001222
1223
showard1e935f12008-07-11 00:11:36 +00001224def get_hqe_percentage_complete(**filter_data):
1225 """
showardc92da832009-04-07 18:14:34 +00001226 Computes the fraction of host queue entries matching the given filter data
showard1e935f12008-07-11 00:11:36 +00001227 that are complete.
1228 """
1229 query = models.HostQueueEntry.query_objects(filter_data)
1230 complete_count = query.filter(complete=True).count()
1231 total_count = query.count()
1232 if total_count == 0:
1233 return 1
1234 return float(complete_count) / total_count
1235
1236
showard1a5a4082009-07-28 20:01:37 +00001237# special tasks
1238
1239def get_special_tasks(**filter_data):
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001240 """Get special task entries from the local database.
1241
1242 Query the special tasks table for tasks matching the given
1243 `filter_data`, and return a list of the results. No attempt is
1244 made to forward the call to shards; the buck will stop here.
1245 The caller is expected to know the target shard for such reasons
1246 as:
1247 * The caller is a service (such as gs_offloader) configured
1248 to operate on behalf of one specific shard, and no other.
1249 * The caller has a host as a parameter, and knows that this is
1250 the shard assigned to that host.
1251
1252 @param filter_data Filter keywords to pass to the underlying
1253 database query.
1254
1255 """
J. Richard Barnettefdfcd662015-04-13 17:20:29 -07001256 return rpc_utils.prepare_rows_as_nested_dicts(
1257 models.SpecialTask.query_objects(filter_data),
1258 ('host', 'queue_entry'))
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001259
1260
1261def get_host_special_tasks(host_id, **filter_data):
1262 """Get special task entries for a given host.
1263
1264 Query the special tasks table for tasks that ran on the host
1265 given by `host_id` and matching the given `filter_data`.
1266 Return a list of the results. If the host is assigned to a
1267 shard, forward this call to that shard.
1268
1269 @param host_id Id in the database of the target host.
1270 @param filter_data Filter keywords to pass to the underlying
1271 database query.
1272
1273 """
MK Ryu0c1a37d2015-04-30 12:00:55 -07001274 # Retrieve host data even if the host is in an invalid state.
1275 host = models.Host.smart_get(host_id, False)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001276 if not host.shard:
J. Richard Barnettefdfcd662015-04-13 17:20:29 -07001277 return get_special_tasks(host_id=host_id, **filter_data)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001278 else:
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001279 # The return values from AFE methods are post-processed
1280 # objects that aren't JSON-serializable. So, we have to
1281 # call AFE.run() to get the raw, serializable output from
1282 # the shard.
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001283 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1284 return shard_afe.run('get_special_tasks',
1285 host_id=host_id, **filter_data)
showard1a5a4082009-07-28 20:01:37 +00001286
1287
MK Ryu0c1a37d2015-04-30 12:00:55 -07001288def get_num_special_tasks(**kwargs):
1289 """Get the number of special task entries from the local database.
1290
1291 Query the special tasks table for tasks matching the given 'kwargs',
1292 and return the number of the results. No attempt is made to forward
1293 the call to shards; the buck will stop here.
1294
1295 @param kwargs Filter keywords to pass to the underlying database query.
1296
1297 """
1298 return models.SpecialTask.query_count(kwargs)
1299
1300
1301def get_host_num_special_tasks(host, **kwargs):
1302 """Get special task entries for a given host.
1303
1304 Query the special tasks table for tasks that ran on the host
1305 given by 'host' and matching the given 'kwargs'.
1306 Return a list of the results. If the host is assigned to a
1307 shard, forward this call to that shard.
1308
1309 @param host id or name of a host. More often a hostname.
1310 @param kwargs Filter keywords to pass to the underlying database query.
1311
1312 """
1313 # Retrieve host data even if the host is in an invalid state.
1314 host_model = models.Host.smart_get(host, False)
1315 if not host_model.shard:
1316 return get_num_special_tasks(host=host, **kwargs)
1317 else:
1318 shard_afe = frontend.AFE(server=host_model.shard.rpc_hostname())
1319 return shard_afe.run('get_num_special_tasks', host=host, **kwargs)
1320
1321
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001322def get_status_task(host_id, end_time):
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001323 """Get the "status task" for a host from the local shard.
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001324
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001325 Returns a single special task representing the given host's
1326 "status task". The status task is a completed special task that
1327 identifies whether the corresponding host was working or broken
1328 when it completed. A successful task indicates a working host;
1329 a failed task indicates broken.
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001330
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001331 This call will not be forward to a shard; the receiving server
1332 must be the shard that owns the host.
1333
1334 @param host_id Id in the database of the target host.
1335 @param end_time Time reference for the host's status.
1336
1337 @return A single task; its status (successful or not)
1338 corresponds to the status of the host (working or
1339 broken) at the given time. If no task is found, return
1340 `None`.
1341
1342 """
1343 tasklist = rpc_utils.prepare_rows_as_nested_dicts(
1344 status_history.get_status_task(host_id, end_time),
1345 ('host', 'queue_entry'))
1346 return tasklist[0] if tasklist else None
1347
1348
1349def get_host_status_task(host_id, end_time):
1350 """Get the "status task" for a host from its owning shard.
1351
1352 Finds the given host's owning shard, and forwards to it a call
1353 to `get_status_task()` (see above).
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001354
1355 @param host_id Id in the database of the target host.
1356 @param end_time Time reference for the host's status.
1357
1358 @return A single task; its status (successful or not)
1359 corresponds to the status of the host (working or
1360 broken) at the given time. If no task is found, return
1361 `None`.
1362
1363 """
1364 host = models.Host.smart_get(host_id)
1365 if not host.shard:
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001366 return get_status_task(host_id, end_time)
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001367 else:
1368 # The return values from AFE methods are post-processed
1369 # objects that aren't JSON-serializable. So, we have to
1370 # call AFE.run() to get the raw, serializable output from
1371 # the shard.
1372 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1373 return shard_afe.run('get_status_task',
1374 host_id=host_id, end_time=end_time)
1375
1376
J. Richard Barnette8abbfd62015-06-23 12:46:54 -07001377def get_host_diagnosis_interval(host_id, end_time, success):
1378 """Find a "diagnosis interval" for a given host.
1379
1380 A "diagnosis interval" identifies a start and end time where
1381 the host went from "working" to "broken", or vice versa. The
1382 interval's starting time is the starting time of the last status
1383 task with the old status; the end time is the finish time of the
1384 first status task with the new status.
1385
1386 This routine finds the most recent diagnosis interval for the
1387 given host prior to `end_time`, with a starting status matching
1388 `success`. If `success` is true, the interval will start with a
1389 successful status task; if false the interval will start with a
1390 failed status task.
1391
1392 @param host_id Id in the database of the target host.
1393 @param end_time Time reference for the diagnosis interval.
1394 @param success Whether the diagnosis interval should start
1395 with a successful or failed status task.
1396
1397 @return A list of two strings. The first is the timestamp for
1398 the beginning of the interval; the second is the
1399 timestamp for the end. If the host has never changed
1400 state, the list is empty.
1401
1402 """
1403 host = models.Host.smart_get(host_id)
J. Richard Barnette78f281a2015-06-29 13:24:51 -07001404 if not host.shard or utils.is_shard():
J. Richard Barnette8abbfd62015-06-23 12:46:54 -07001405 return status_history.get_diagnosis_interval(
1406 host_id, end_time, success)
1407 else:
1408 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1409 return shard_afe.get_host_diagnosis_interval(
1410 host_id, end_time, success)
1411
1412
showardc0ac3a72009-07-08 21:14:45 +00001413# support for host detail view
1414
MK Ryu0c1a37d2015-04-30 12:00:55 -07001415def get_host_queue_entries_and_special_tasks(host, query_start=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001416 query_limit=None, start_time=None,
1417 end_time=None):
showardc0ac3a72009-07-08 21:14:45 +00001418 """
1419 @returns an interleaved list of HostQueueEntries and SpecialTasks,
1420 in approximate run order. each dict contains keys for type, host,
1421 job, status, started_on, execution_path, and ID.
1422 """
1423 total_limit = None
1424 if query_limit is not None:
1425 total_limit = query_start + query_limit
MK Ryu0c1a37d2015-04-30 12:00:55 -07001426 filter_data_common = {'host': host,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001427 'query_limit': total_limit,
1428 'sort_by': ['-id']}
showardc0ac3a72009-07-08 21:14:45 +00001429
MK Ryu0c1a37d2015-04-30 12:00:55 -07001430 filter_data_special_tasks = rpc_utils.inject_times_to_filter(
1431 'time_started__gte', 'time_started__lte', start_time, end_time,
1432 **filter_data_common)
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001433
MK Ryu0c1a37d2015-04-30 12:00:55 -07001434 queue_entries = get_host_queue_entries(
1435 start_time, end_time, **filter_data_common)
1436 special_tasks = get_host_special_tasks(host, **filter_data_special_tasks)
showardc0ac3a72009-07-08 21:14:45 +00001437
1438 interleaved_entries = rpc_utils.interleave_entries(queue_entries,
1439 special_tasks)
1440 if query_start is not None:
1441 interleaved_entries = interleaved_entries[query_start:]
1442 if query_limit is not None:
1443 interleaved_entries = interleaved_entries[:query_limit]
MK Ryu0c1a37d2015-04-30 12:00:55 -07001444 return rpc_utils.prepare_host_queue_entries_and_special_tasks(
1445 interleaved_entries, queue_entries)
showardc0ac3a72009-07-08 21:14:45 +00001446
1447
MK Ryu0c1a37d2015-04-30 12:00:55 -07001448def get_num_host_queue_entries_and_special_tasks(host, start_time=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001449 end_time=None):
MK Ryu0c1a37d2015-04-30 12:00:55 -07001450 filter_data_common = {'host': host}
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001451
1452 filter_data_queue_entries, filter_data_special_tasks = (
1453 rpc_utils.inject_times_to_hqe_special_tasks_filters(
1454 filter_data_common, start_time, end_time))
1455
1456 return (models.HostQueueEntry.query_count(filter_data_queue_entries)
MK Ryu0c1a37d2015-04-30 12:00:55 -07001457 + get_host_num_special_tasks(**filter_data_special_tasks))
showardc0ac3a72009-07-08 21:14:45 +00001458
1459
showard29f7cd22009-04-29 21:16:24 +00001460# recurring run
1461
1462def get_recurring(**filter_data):
1463 return rpc_utils.prepare_rows_as_nested_dicts(
1464 models.RecurringRun.query_objects(filter_data),
1465 ('job', 'owner'))
1466
1467
1468def get_num_recurring(**filter_data):
1469 return models.RecurringRun.query_count(filter_data)
1470
1471
1472def delete_recurring_runs(**filter_data):
1473 to_delete = models.RecurringRun.query_objects(filter_data)
1474 to_delete.delete()
1475
1476
1477def create_recurring_run(job_id, start_date, loop_period, loop_count):
showard64a95952010-01-13 21:27:16 +00001478 owner = models.User.current_user().login
showard29f7cd22009-04-29 21:16:24 +00001479 job = models.Job.objects.get(id=job_id)
1480 return job.create_recurring_job(start_date=start_date,
1481 loop_period=loop_period,
1482 loop_count=loop_count,
1483 owner=owner)
1484
1485
mblighe8819cd2008-02-15 16:48:40 +00001486# other
1487
showarde0b63622008-08-04 20:58:47 +00001488def echo(data=""):
1489 """\
1490 Returns a passed in string. For doing a basic test to see if RPC calls
1491 can successfully be made.
1492 """
1493 return data
1494
1495
showardb7a52fd2009-04-27 20:10:56 +00001496def get_motd():
1497 """\
1498 Returns the message of the day as a string.
1499 """
1500 return rpc_utils.get_motd()
1501
1502
mblighe8819cd2008-02-15 16:48:40 +00001503def get_static_data():
jadmanski0afbb632008-06-06 21:10:57 +00001504 """\
1505 Returns a dictionary containing a bunch of data that shouldn't change
1506 often and is otherwise inaccessible. This includes:
showardc92da832009-04-07 18:14:34 +00001507
1508 priorities: List of job priority choices.
1509 default_priority: Default priority value for new jobs.
1510 users: Sorted list of all users.
Jiaxi Luo31874592014-06-11 10:36:35 -07001511 labels: Sorted list of labels not start with 'cros-version' and
1512 'fw-version'.
showardc92da832009-04-07 18:14:34 +00001513 atomic_groups: Sorted list of all atomic groups.
1514 tests: Sorted list of all tests.
1515 profilers: Sorted list of all profilers.
1516 current_user: Logged-in username.
1517 host_statuses: Sorted list of possible Host statuses.
1518 job_statuses: Sorted list of possible HostQueueEntry statuses.
Simran Basi7e605742013-11-12 13:43:36 -08001519 job_timeout_default: The default job timeout length in minutes.
showarda1e74b32009-05-12 17:32:04 +00001520 parse_failed_repair_default: Default value for the parse_failed_repair job
Jiaxi Luo31874592014-06-11 10:36:35 -07001521 option.
showardc92da832009-04-07 18:14:34 +00001522 reboot_before_options: A list of valid RebootBefore string enums.
1523 reboot_after_options: A list of valid RebootAfter string enums.
1524 motd: Server's message of the day.
1525 status_dictionary: A mapping from one word job status names to a more
1526 informative description.
jadmanski0afbb632008-06-06 21:10:57 +00001527 """
showard21baa452008-10-21 00:08:39 +00001528
1529 job_fields = models.Job.get_field_dict()
jamesren76fcf192010-04-21 20:39:50 +00001530 default_drone_set_name = models.DroneSet.default_drone_set_name()
1531 drone_sets = ([default_drone_set_name] +
1532 sorted(drone_set.name for drone_set in
1533 models.DroneSet.objects.exclude(
1534 name=default_drone_set_name)))
showard21baa452008-10-21 00:08:39 +00001535
jadmanski0afbb632008-06-06 21:10:57 +00001536 result = {}
Alex Miller7d658cf2013-09-04 16:00:35 -07001537 result['priorities'] = priorities.Priority.choices()
1538 default_priority = priorities.Priority.DEFAULT
1539 result['default_priority'] = 'Default'
1540 result['max_schedulable_priority'] = priorities.Priority.DEFAULT
jadmanski0afbb632008-06-06 21:10:57 +00001541 result['users'] = get_users(sort_by=['login'])
Jiaxi Luo31874592014-06-11 10:36:35 -07001542
1543 label_exclude_filters = [{'name__startswith': 'cros-version'},
Dan Shi65351d62015-08-03 12:03:23 -07001544 {'name__startswith': 'fw-version'},
1545 {'name__startswith': 'fwrw-version'},
1546 {'name__startswith': 'fwro-version'}]
Jiaxi Luo31874592014-06-11 10:36:35 -07001547 result['labels'] = get_labels(
1548 label_exclude_filters,
1549 sort_by=['-platform', 'name'])
1550
showardc92da832009-04-07 18:14:34 +00001551 result['atomic_groups'] = get_atomic_groups(sort_by=['name'])
jadmanski0afbb632008-06-06 21:10:57 +00001552 result['tests'] = get_tests(sort_by=['name'])
showard2b9a88b2008-06-13 20:55:03 +00001553 result['profilers'] = get_profilers(sort_by=['name'])
showard0fc38302008-10-23 00:44:07 +00001554 result['current_user'] = rpc_utils.prepare_for_serialization(
showard64a95952010-01-13 21:27:16 +00001555 models.User.current_user().get_object_dict())
showard2b9a88b2008-06-13 20:55:03 +00001556 result['host_statuses'] = sorted(models.Host.Status.names)
mbligh5a198b92008-12-11 19:33:29 +00001557 result['job_statuses'] = sorted(models.HostQueueEntry.Status.names)
Simran Basi7e605742013-11-12 13:43:36 -08001558 result['job_timeout_mins_default'] = models.Job.DEFAULT_TIMEOUT_MINS
Simran Basi34217022012-11-06 13:43:15 -08001559 result['job_max_runtime_mins_default'] = (
1560 models.Job.DEFAULT_MAX_RUNTIME_MINS)
showarda1e74b32009-05-12 17:32:04 +00001561 result['parse_failed_repair_default'] = bool(
1562 models.Job.DEFAULT_PARSE_FAILED_REPAIR)
jamesrendd855242010-03-02 22:23:44 +00001563 result['reboot_before_options'] = model_attributes.RebootBefore.names
1564 result['reboot_after_options'] = model_attributes.RebootAfter.names
showard8fbae652009-01-20 23:23:10 +00001565 result['motd'] = rpc_utils.get_motd()
jamesren76fcf192010-04-21 20:39:50 +00001566 result['drone_sets_enabled'] = models.DroneSet.drone_sets_enabled()
1567 result['drone_sets'] = drone_sets
jamesren4a41e012010-07-16 22:33:48 +00001568 result['parameterized_jobs'] = models.Job.parameterized_jobs_enabled()
showard8ac29b42008-07-17 17:01:55 +00001569
showardd3dc1992009-04-22 21:01:40 +00001570 result['status_dictionary'] = {"Aborted": "Aborted",
showard8ac29b42008-07-17 17:01:55 +00001571 "Verifying": "Verifying Host",
Alex Millerdfff2fd2013-05-28 13:05:06 -07001572 "Provisioning": "Provisioning Host",
showard8ac29b42008-07-17 17:01:55 +00001573 "Pending": "Waiting on other hosts",
1574 "Running": "Running autoserv",
1575 "Completed": "Autoserv completed",
1576 "Failed": "Failed to complete",
showardd823b362008-07-24 16:35:46 +00001577 "Queued": "Queued",
showard5deb6772008-11-04 21:54:33 +00001578 "Starting": "Next in host's queue",
1579 "Stopped": "Other host(s) failed verify",
showardd3dc1992009-04-22 21:01:40 +00001580 "Parsing": "Awaiting parse of final results",
showard29f7cd22009-04-29 21:16:24 +00001581 "Gathering": "Gathering log files",
showard8cc058f2009-09-08 16:26:33 +00001582 "Template": "Template job for recurring run",
mbligh4608b002010-01-05 18:22:35 +00001583 "Waiting": "Waiting for scheduler action",
Dan Shi07e09af2013-04-12 09:31:29 -07001584 "Archiving": "Archiving results",
1585 "Resetting": "Resetting hosts"}
Jiaxi Luo421608e2014-07-07 14:38:00 -07001586
1587 result['wmatrix_url'] = rpc_utils.get_wmatrix_url()
Simran Basi71206ef2014-08-13 13:51:18 -07001588 result['is_moblab'] = bool(utils.is_moblab())
Jiaxi Luo421608e2014-07-07 14:38:00 -07001589
jadmanski0afbb632008-06-06 21:10:57 +00001590 return result
showard29f7cd22009-04-29 21:16:24 +00001591
1592
1593def get_server_time():
1594 return datetime.datetime.now().strftime("%Y-%m-%d %H:%M")