blob: 6e2611d0ed15ee04badce5e631ab4717f9e46580 [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
jamesrendd855242010-03-02 22:23:44 +000041from autotest_lib.frontend.afe import models, model_logic, model_attributes
showard6d7b2ff2009-06-10 00:16:47 +000042from autotest_lib.frontend.afe import control_file, rpc_utils
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
Simran Basi71206ef2014-08-13 13:51:18 -070046from autotest_lib.server import utils
Jiaxi Luo90190c92014-06-18 12:35:57 -070047from autotest_lib.server.cros.dynamic_suite import tools
mblighe8819cd2008-02-15 16:48:40 +000048
Moises Osorio2dc7a102014-12-02 18:24:02 -080049
Gabe Black1e1c41b2015-02-04 23:55:15 -080050_timer = autotest_stats.Timer('rpc_interface')
Moises Osorio2dc7a102014-12-02 18:24:02 -080051
Eric Lid23bc192011-02-09 14:38:57 -080052def get_parameterized_autoupdate_image_url(job):
53 """Get the parameterized autoupdate image url from a parameterized job."""
54 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
55 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
beeps8bb1f7d2013-08-05 01:30:09 -070056 name='image')
Eric Lid23bc192011-02-09 14:38:57 -080057 para_set = job.parameterized_job.parameterizedjobparameter_set
58 job_test_para = para_set.get(test_parameter=image_parameter)
59 return job_test_para.parameter_value
60
61
mblighe8819cd2008-02-15 16:48:40 +000062# labels
63
mblighe8819cd2008-02-15 16:48:40 +000064def modify_label(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +000065 models.Label.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +000066
67
68def delete_label(id):
jadmanski0afbb632008-06-06 21:10:57 +000069 models.Label.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +000070
Prashanth Balasubramanian744898f2015-01-13 05:04:16 -080071
MK Ryu9c5fbbe2015-02-11 15:46:22 -080072def add_label(name, ignore_exception_if_exists=False, **kwargs):
MK Ryucf027c62015-03-04 12:00:50 -080073 """Adds a new label of a given name.
MK Ryu9c5fbbe2015-02-11 15:46:22 -080074
75 @param name: label name.
76 @param ignore_exception_if_exists: If True and the exception was
77 thrown due to the duplicated label name when adding a label,
78 then suppress the exception. Default is False.
79 @param kwargs: keyword args that store more info about a label
80 other than the name.
81 @return: int/long id of a new label.
82 """
83 # models.Label.add_object() throws model_logic.ValidationError
84 # when it is given a label name that already exists.
85 # However, ValidationError can be thrown with different errors,
86 # and those errors should be thrown up to the call chain.
87 try:
88 label = models.Label.add_object(name=name, **kwargs)
89 except:
90 exc_info = sys.exc_info()
91 if ignore_exception_if_exists:
92 label = rpc_utils.get_label(name)
93 # If the exception is raised not because of duplicated
94 # "name", then raise the original exception.
95 if label is None:
96 raise exc_info[0], exc_info[1], exc_info[2]
97 else:
98 raise exc_info[0], exc_info[1], exc_info[2]
99 return label.id
100
101
102def add_label_to_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800103 """Adds a label of the given id to the given hosts only in local DB.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800104
105 @param id: id or name of a label. More often a label name.
106 @param hosts: The hostnames of hosts that need the label.
107
108 @raises models.Label.DoesNotExist: If the label with id doesn't exist.
109 """
110 label = models.Label.smart_get(id)
111 host_objs = models.Host.smart_get_bulk(hosts)
112 if label.platform:
113 models.Host.check_no_platform(host_objs)
114 label.host_set.add(*host_objs)
115
116
117def label_add_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800118 """Adds a label with the given id to the given hosts.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800119
120 This method should be run only on master not shards.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800121 The given label will be created if it doesn't exist, provided the `id`
122 supplied is a label name not an int/long id.
123
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800124 @param id: id or name of a label. More often a label name.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800125 @param hosts: A list of hostnames or ids. More often hostnames.
126
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800127 @raises ValueError: If the id specified is an int/long (label id)
128 while the label does not exist.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800129 """
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800130 # This RPC call should be accepted only by master.
131 if utils.is_shard():
132 rpc_utils.route_rpc_to_master('label_add_hosts', id=id, hosts=hosts)
133 return
134
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800135 try:
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800136 label = models.Label.smart_get(id)
137 except models.Label.DoesNotExist:
138 # This matches the type checks in smart_get, which is a hack
139 # in and off itself. The aim here is to create any non-existent
140 # label, which we cannot do if the 'id' specified isn't a label name.
141 if isinstance(id, basestring):
142 label = models.Label.smart_get(add_label(id))
143 else:
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800144 raise ValueError('Label id (%s) does not exist. Please specify '
145 'the argument, id, as a string (label name).'
146 % id)
147 add_label_to_hosts(id, hosts)
MK Ryucf027c62015-03-04 12:00:50 -0800148
149 host_objs = models.Host.smart_get_bulk(hosts)
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800150 # Make sure the label exists on the shard with the same id
151 # as it is on the master.
MK Ryucf027c62015-03-04 12:00:50 -0800152 # It is possible that the label is already in a shard because
153 # we are adding a new label only to shards of hosts that the label
154 # is going to be attached.
155 # For example, we add a label L1 to a host in shard S1.
156 # Master and S1 will have L1 but other shards won't.
157 # Later, when we add the same label L1 to hosts in shards S1 and S2,
158 # S1 already has the label but S2 doesn't.
159 # S2 should have the new label without any problem.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800160 # We ignore exception in such a case.
161 rpc_utils.fanout_rpc(
162 host_objs, 'add_label', name=label.name, id=label.id,
163 include_hostnames=False, ignore_exception_if_exists=True)
164 rpc_utils.fanout_rpc(host_objs, 'add_label_to_hosts', id=id)
showardbbabf502008-06-06 00:02:02 +0000165
166
MK Ryucf027c62015-03-04 12:00:50 -0800167def remove_label_from_hosts(id, hosts):
168 """Removes a label of the given id from the given hosts only in local DB.
169
170 @param id: id or name of a label.
171 @param hosts: The hostnames of hosts that need to remove the label from.
172 """
showardbe3ec042008-11-12 18:16:07 +0000173 host_objs = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000174 models.Label.smart_get(id).host_set.remove(*host_objs)
showardbbabf502008-06-06 00:02:02 +0000175
176
MK Ryucf027c62015-03-04 12:00:50 -0800177def label_remove_hosts(id, hosts):
178 """Removes a label of the given id from the given hosts.
179
180 This method should be run only on master not shards.
181
182 @param id: id or name of a label.
183 @param hosts: A list of hostnames or ids. More often hostnames.
184 """
185 # This RPC call should be accepted only by master.
186 if utils.is_shard():
187 rpc_utils.route_rpc_to_master('label_remove_hosts', id=id, hosts=hosts)
188 return
189
190 remove_label_from_hosts(id, hosts)
191 host_objs = models.Host.smart_get_bulk(hosts)
192 rpc_utils.fanout_rpc(host_objs, 'remove_label_from_hosts', id=id)
193
194
Jiaxi Luo31874592014-06-11 10:36:35 -0700195def get_labels(exclude_filters=(), **filter_data):
showardc92da832009-04-07 18:14:34 +0000196 """\
Jiaxi Luo31874592014-06-11 10:36:35 -0700197 @param exclude_filters: A sequence of dictionaries of filters.
198
showardc92da832009-04-07 18:14:34 +0000199 @returns A sequence of nested dictionaries of label information.
200 """
Jiaxi Luo31874592014-06-11 10:36:35 -0700201 labels = models.Label.query_objects(filter_data)
202 for exclude_filter in exclude_filters:
203 labels = labels.exclude(**exclude_filter)
204 return rpc_utils.prepare_rows_as_nested_dicts(labels, ('atomic_group',))
showardc92da832009-04-07 18:14:34 +0000205
206
207# atomic groups
208
showarde9450c92009-06-30 01:58:52 +0000209def add_atomic_group(name, max_number_of_machines=None, description=None):
showardc92da832009-04-07 18:14:34 +0000210 return models.AtomicGroup.add_object(
211 name=name, max_number_of_machines=max_number_of_machines,
212 description=description).id
213
214
215def modify_atomic_group(id, **data):
216 models.AtomicGroup.smart_get(id).update_object(data)
217
218
219def delete_atomic_group(id):
220 models.AtomicGroup.smart_get(id).delete()
221
222
223def atomic_group_add_labels(id, labels):
224 label_objs = models.Label.smart_get_bulk(labels)
225 models.AtomicGroup.smart_get(id).label_set.add(*label_objs)
226
227
228def atomic_group_remove_labels(id, labels):
229 label_objs = models.Label.smart_get_bulk(labels)
230 models.AtomicGroup.smart_get(id).label_set.remove(*label_objs)
231
232
233def get_atomic_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000234 return rpc_utils.prepare_for_serialization(
showardc92da832009-04-07 18:14:34 +0000235 models.AtomicGroup.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000236
237
238# hosts
239
showarddf062562008-07-03 19:56:37 +0000240def add_host(hostname, status=None, locked=None, protection=None):
jadmanski0afbb632008-06-06 21:10:57 +0000241 return models.Host.add_object(hostname=hostname, status=status,
showarddf062562008-07-03 19:56:37 +0000242 locked=locked, protection=protection).id
mblighe8819cd2008-02-15 16:48:40 +0000243
244
Jakob Juelich50e91f72014-10-01 12:43:23 -0700245@rpc_utils.forward_single_host_rpc_to_shard
mblighe8819cd2008-02-15 16:48:40 +0000246def modify_host(id, **data):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700247 """Modify local attributes of a host.
248
249 If this is called on the master, but the host is assigned to a shard, this
250 will also forward the call to the responsible shard. This means i.e. if a
251 host is being locked using this function, this change will also propagate to
252 shards.
253
254 @param id: id of the host to modify.
255 @param **data: key=value pairs of values to set on the host.
256 """
showardbe0d8692009-08-20 23:42:44 +0000257 rpc_utils.check_modify_host(data)
showardce7c0922009-09-11 18:39:24 +0000258 host = models.Host.smart_get(id)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700259
showardce7c0922009-09-11 18:39:24 +0000260 rpc_utils.check_modify_host_locking(host, data)
261 host.update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000262
263
showard276f9442009-05-20 00:33:16 +0000264def modify_hosts(host_filter_data, update_data):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700265 """Modify local attributes of multiple hosts.
266
267 If this is called on the master, but one of the hosts in that match the
268 filters is assigned to a shard, this will also forward the call to the
269 responsible shard.
270
271 The filters are always applied on the master, not on the shards. This means
272 if the states of a host differ on the master and a shard, the state on the
273 master will be used. I.e. this means:
274 A host was synced to Shard 1. On Shard 1 the status of the host was set to
275 'Repair Failed'.
276 - A call to modify_hosts with host_filter_data={'status': 'Ready'} will
277 update the host (both on the shard and on the master), because the state
278 of the host as the master knows it is still 'Ready'.
279 - A call to modify_hosts with host_filter_data={'status': 'Repair failed'
280 will not update the host, because the filter doesn't apply on the master.
281
showardbe0d8692009-08-20 23:42:44 +0000282 @param host_filter_data: Filters out which hosts to modify.
283 @param update_data: A dictionary with the changes to make to the hosts.
showard276f9442009-05-20 00:33:16 +0000284 """
showardbe0d8692009-08-20 23:42:44 +0000285 rpc_utils.check_modify_host(update_data)
showard276f9442009-05-20 00:33:16 +0000286 hosts = models.Host.query_objects(host_filter_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700287
288 affected_shard_hostnames = set()
289 affected_host_ids = []
290
Alex Miller9658a952013-05-14 16:40:02 -0700291 # Check all hosts before changing data for exception safety.
292 for host in hosts:
293 rpc_utils.check_modify_host_locking(host, update_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700294 if host.shard:
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800295 affected_shard_hostnames.add(host.shard.rpc_hostname())
Jakob Juelich50e91f72014-10-01 12:43:23 -0700296 affected_host_ids.append(host.id)
297
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800298 if not utils.is_shard():
Jakob Juelich50e91f72014-10-01 12:43:23 -0700299 # Caution: Changing the filter from the original here. See docstring.
300 rpc_utils.run_rpc_on_multiple_hostnames(
301 'modify_hosts', affected_shard_hostnames,
302 host_filter_data={'id__in': affected_host_ids},
303 update_data=update_data)
304
showard276f9442009-05-20 00:33:16 +0000305 for host in hosts:
306 host.update_object(update_data)
307
308
mblighe8819cd2008-02-15 16:48:40 +0000309def host_add_labels(id, labels):
showardbe3ec042008-11-12 18:16:07 +0000310 labels = models.Label.smart_get_bulk(labels)
showardcafd16e2009-05-29 18:37:49 +0000311 host = models.Host.smart_get(id)
312
313 platforms = [label.name for label in labels if label.platform]
314 if len(platforms) > 1:
315 raise model_logic.ValidationError(
316 {'labels': 'Adding more than one platform label: %s' %
317 ', '.join(platforms)})
318 if len(platforms) == 1:
319 models.Host.check_no_platform([host])
320 host.labels.add(*labels)
mblighe8819cd2008-02-15 16:48:40 +0000321
322
323def host_remove_labels(id, labels):
showardbe3ec042008-11-12 18:16:07 +0000324 labels = models.Label.smart_get_bulk(labels)
jadmanski0afbb632008-06-06 21:10:57 +0000325 models.Host.smart_get(id).labels.remove(*labels)
mblighe8819cd2008-02-15 16:48:40 +0000326
327
MK Ryuacf35922014-10-03 14:56:49 -0700328def get_host_attribute(attribute, **host_filter_data):
329 """
330 @param attribute: string name of attribute
331 @param host_filter_data: filter data to apply to Hosts to choose hosts to
332 act upon
333 """
334 hosts = rpc_utils.get_host_query((), False, False, True, host_filter_data)
335 hosts = list(hosts)
336 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
337 'attribute_list')
338 host_attr_dicts = []
339 for host_obj in hosts:
340 for attr_obj in host_obj.attribute_list:
341 if attr_obj.attribute == attribute:
342 host_attr_dicts.append(attr_obj.get_object_dict())
343 return rpc_utils.prepare_for_serialization(host_attr_dicts)
344
345
showard0957a842009-05-11 19:25:08 +0000346def set_host_attribute(attribute, value, **host_filter_data):
347 """
348 @param attribute string name of attribute
349 @param value string, or None to delete an attribute
350 @param host_filter_data filter data to apply to Hosts to choose hosts to act
351 upon
352 """
353 assert host_filter_data # disallow accidental actions on all hosts
354 hosts = models.Host.query_objects(host_filter_data)
355 models.AclGroup.check_for_acl_violation_hosts(hosts)
356
357 for host in hosts:
showardf8b19042009-05-12 17:22:49 +0000358 host.set_or_delete_attribute(attribute, value)
showard0957a842009-05-11 19:25:08 +0000359
360
Jakob Juelich50e91f72014-10-01 12:43:23 -0700361@rpc_utils.forward_single_host_rpc_to_shard
mblighe8819cd2008-02-15 16:48:40 +0000362def delete_host(id):
jadmanski0afbb632008-06-06 21:10:57 +0000363 models.Host.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000364
365
showard87cc38f2009-08-20 23:37:04 +0000366def get_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000367 exclude_atomic_group_hosts=False, valid_only=True, **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000368 """
369 @param multiple_labels: match hosts in all of the labels given. Should
370 be a list of label names.
371 @param exclude_only_if_needed_labels: Exclude hosts with at least one
372 "only_if_needed" label applied.
373 @param exclude_atomic_group_hosts: Exclude hosts that have one or more
374 atomic group labels associated with them.
jadmanski0afbb632008-06-06 21:10:57 +0000375 """
showard43a3d262008-11-12 18:17:05 +0000376 hosts = rpc_utils.get_host_query(multiple_labels,
377 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000378 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000379 valid_only, filter_data)
showard0957a842009-05-11 19:25:08 +0000380 hosts = list(hosts)
381 models.Host.objects.populate_relationships(hosts, models.Label,
382 'label_list')
383 models.Host.objects.populate_relationships(hosts, models.AclGroup,
384 'acl_list')
385 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
386 'attribute_list')
showard43a3d262008-11-12 18:17:05 +0000387 host_dicts = []
388 for host_obj in hosts:
389 host_dict = host_obj.get_object_dict()
showard0957a842009-05-11 19:25:08 +0000390 host_dict['labels'] = [label.name for label in host_obj.label_list]
showard909c9142009-07-07 20:54:42 +0000391 host_dict['platform'], host_dict['atomic_group'] = (rpc_utils.
392 find_platform_and_atomic_group(host_obj))
showard0957a842009-05-11 19:25:08 +0000393 host_dict['acls'] = [acl.name for acl in host_obj.acl_list]
394 host_dict['attributes'] = dict((attribute.attribute, attribute.value)
395 for attribute in host_obj.attribute_list)
showard43a3d262008-11-12 18:17:05 +0000396 host_dicts.append(host_dict)
397 return rpc_utils.prepare_for_serialization(host_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000398
399
showard87cc38f2009-08-20 23:37:04 +0000400def get_num_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000401 exclude_atomic_group_hosts=False, valid_only=True,
402 **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000403 """
404 Same parameters as get_hosts().
405
406 @returns The number of matching hosts.
407 """
showard43a3d262008-11-12 18:17:05 +0000408 hosts = rpc_utils.get_host_query(multiple_labels,
409 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000410 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000411 valid_only, filter_data)
showard43a3d262008-11-12 18:17:05 +0000412 return hosts.count()
showard1385b162008-03-13 15:59:40 +0000413
mblighe8819cd2008-02-15 16:48:40 +0000414
415# tests
416
showard909c7a62008-07-15 21:52:38 +0000417def add_test(name, test_type, path, author=None, dependencies=None,
showard3d9899a2008-07-31 02:11:58 +0000418 experimental=True, run_verify=None, test_class=None,
showard909c7a62008-07-15 21:52:38 +0000419 test_time=None, test_category=None, description=None,
420 sync_count=1):
jadmanski0afbb632008-06-06 21:10:57 +0000421 return models.Test.add_object(name=name, test_type=test_type, path=path,
showard909c7a62008-07-15 21:52:38 +0000422 author=author, dependencies=dependencies,
423 experimental=experimental,
424 run_verify=run_verify, test_time=test_time,
425 test_category=test_category,
426 sync_count=sync_count,
jadmanski0afbb632008-06-06 21:10:57 +0000427 test_class=test_class,
428 description=description).id
mblighe8819cd2008-02-15 16:48:40 +0000429
430
431def modify_test(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000432 models.Test.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000433
434
435def delete_test(id):
jadmanski0afbb632008-06-06 21:10:57 +0000436 models.Test.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000437
438
439def get_tests(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000440 return rpc_utils.prepare_for_serialization(
441 models.Test.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000442
443
Moises Osorio2dc7a102014-12-02 18:24:02 -0800444@_timer.decorate
445def get_tests_status_counts_by_job_name_label(job_name_prefix, label_name):
446 """Gets the counts of all passed and failed tests from the matching jobs.
447
448 @param job_name_prefix: Name prefix of the jobs to get the summary from, e.g.,
449 'butterfly-release/R40-6457.21.0/bvt-cq/'.
450 @param label_name: Label that must be set in the jobs, e.g.,
451 'cros-version:butterfly-release/R40-6457.21.0'.
452
453 @returns A summary of the counts of all the passed and failed tests.
454 """
455 job_ids = list(models.Job.objects.filter(
456 name__startswith=job_name_prefix,
457 dependency_labels__name=label_name).values_list(
458 'pk', flat=True))
459 summary = {'passed': 0, 'failed': 0}
460 if not job_ids:
461 return summary
462
463 counts = (tko_models.TestView.objects.filter(
464 afe_job_id__in=job_ids).exclude(
465 test_name='SERVER_JOB').exclude(
466 test_name__startswith='CLIENT_JOB').values(
467 'status').annotate(
468 count=Count('status')))
469 for status in counts:
470 if status['status'] == 'GOOD':
471 summary['passed'] += status['count']
472 else:
473 summary['failed'] += status['count']
474 return summary
475
476
showard2b9a88b2008-06-13 20:55:03 +0000477# profilers
478
479def add_profiler(name, description=None):
480 return models.Profiler.add_object(name=name, description=description).id
481
482
483def modify_profiler(id, **data):
484 models.Profiler.smart_get(id).update_object(data)
485
486
487def delete_profiler(id):
488 models.Profiler.smart_get(id).delete()
489
490
491def get_profilers(**filter_data):
492 return rpc_utils.prepare_for_serialization(
493 models.Profiler.list_objects(filter_data))
494
495
mblighe8819cd2008-02-15 16:48:40 +0000496# users
497
498def add_user(login, access_level=None):
jadmanski0afbb632008-06-06 21:10:57 +0000499 return models.User.add_object(login=login, access_level=access_level).id
mblighe8819cd2008-02-15 16:48:40 +0000500
501
502def modify_user(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000503 models.User.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000504
505
506def delete_user(id):
jadmanski0afbb632008-06-06 21:10:57 +0000507 models.User.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000508
509
510def get_users(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000511 return rpc_utils.prepare_for_serialization(
512 models.User.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000513
514
515# acl groups
516
517def add_acl_group(name, description=None):
showard04f2cd82008-07-25 20:53:31 +0000518 group = models.AclGroup.add_object(name=name, description=description)
showard64a95952010-01-13 21:27:16 +0000519 group.users.add(models.User.current_user())
showard04f2cd82008-07-25 20:53:31 +0000520 return group.id
mblighe8819cd2008-02-15 16:48:40 +0000521
522
523def modify_acl_group(id, **data):
showard04f2cd82008-07-25 20:53:31 +0000524 group = models.AclGroup.smart_get(id)
525 group.check_for_acl_violation_acl_group()
526 group.update_object(data)
527 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000528
529
530def acl_group_add_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000531 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000532 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000533 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000534 group.users.add(*users)
mblighe8819cd2008-02-15 16:48:40 +0000535
536
537def acl_group_remove_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000538 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000539 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000540 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000541 group.users.remove(*users)
showard04f2cd82008-07-25 20:53:31 +0000542 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000543
544
545def acl_group_add_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000546 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000547 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000548 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000549 group.hosts.add(*hosts)
showard08f981b2008-06-24 21:59:03 +0000550 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000551
552
553def acl_group_remove_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000554 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000555 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000556 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000557 group.hosts.remove(*hosts)
showard08f981b2008-06-24 21:59:03 +0000558 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000559
560
561def delete_acl_group(id):
jadmanski0afbb632008-06-06 21:10:57 +0000562 models.AclGroup.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000563
564
565def get_acl_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000566 acl_groups = models.AclGroup.list_objects(filter_data)
567 for acl_group in acl_groups:
568 acl_group_obj = models.AclGroup.objects.get(id=acl_group['id'])
569 acl_group['users'] = [user.login
570 for user in acl_group_obj.users.all()]
571 acl_group['hosts'] = [host.hostname
572 for host in acl_group_obj.hosts.all()]
573 return rpc_utils.prepare_for_serialization(acl_groups)
mblighe8819cd2008-02-15 16:48:40 +0000574
575
576# jobs
577
mbligh120351e2009-01-24 01:40:45 +0000578def generate_control_file(tests=(), kernel=None, label=None, profilers=(),
showard91f85102009-10-12 20:34:52 +0000579 client_control_file='', use_container=False,
showard232b7ae2009-11-10 00:46:48 +0000580 profile_only=None, upload_kernel_config=False):
jadmanski0afbb632008-06-06 21:10:57 +0000581 """
mbligh120351e2009-01-24 01:40:45 +0000582 Generates a client-side control file to load a kernel and run tests.
583
584 @param tests List of tests to run.
mbligha3c58d22009-08-24 22:01:51 +0000585 @param kernel A list of kernel info dictionaries configuring which kernels
586 to boot for this job and other options for them
mbligh120351e2009-01-24 01:40:45 +0000587 @param label Name of label to grab kernel config from.
588 @param profilers List of profilers to activate during the job.
589 @param client_control_file The contents of a client-side control file to
590 run at the end of all tests. If this is supplied, all tests must be
591 client side.
592 TODO: in the future we should support server control files directly
593 to wrap with a kernel. That'll require changing the parameter
594 name and adding a boolean to indicate if it is a client or server
595 control file.
596 @param use_container unused argument today. TODO: Enable containers
597 on the host during a client side test.
showard91f85102009-10-12 20:34:52 +0000598 @param profile_only A boolean that indicates what default profile_only
599 mode to use in the control file. Passing None will generate a
600 control file that does not explcitly set the default mode at all.
showard232b7ae2009-11-10 00:46:48 +0000601 @param upload_kernel_config: if enabled it will generate server control
602 file code that uploads the kernel config file to the client and
603 tells the client of the new (local) path when compiling the kernel;
604 the tests must be server side tests
mbligh120351e2009-01-24 01:40:45 +0000605
606 @returns a dict with the following keys:
607 control_file: str, The control file text.
608 is_server: bool, is the control file a server-side control file?
609 synch_count: How many machines the job uses per autoserv execution.
610 synch_count == 1 means the job is asynchronous.
611 dependencies: A list of the names of labels on which the job depends.
612 """
showardd86debe2009-06-10 17:37:56 +0000613 if not tests and not client_control_file:
showard2bab8f42008-11-12 18:15:22 +0000614 return dict(control_file='', is_server=False, synch_count=1,
showard989f25d2008-10-01 11:38:11 +0000615 dependencies=[])
mblighe8819cd2008-02-15 16:48:40 +0000616
showard989f25d2008-10-01 11:38:11 +0000617 cf_info, test_objects, profiler_objects, label = (
showard2b9a88b2008-06-13 20:55:03 +0000618 rpc_utils.prepare_generate_control_file(tests, kernel, label,
619 profilers))
showard989f25d2008-10-01 11:38:11 +0000620 cf_info['control_file'] = control_file.generate_control(
mbligha3c58d22009-08-24 22:01:51 +0000621 tests=test_objects, kernels=kernel, platform=label,
mbligh120351e2009-01-24 01:40:45 +0000622 profilers=profiler_objects, is_server=cf_info['is_server'],
showard232b7ae2009-11-10 00:46:48 +0000623 client_control_file=client_control_file, profile_only=profile_only,
624 upload_kernel_config=upload_kernel_config)
showard989f25d2008-10-01 11:38:11 +0000625 return cf_info
mblighe8819cd2008-02-15 16:48:40 +0000626
627
jamesren4a41e012010-07-16 22:33:48 +0000628def create_parameterized_job(name, priority, test, parameters, kernel=None,
629 label=None, profilers=(), profiler_parameters=None,
630 use_container=False, profile_only=None,
631 upload_kernel_config=False, hosts=(),
632 meta_hosts=(), one_time_hosts=(),
633 atomic_group_name=None, synch_count=None,
634 is_template=False, timeout=None,
Simran Basi7e605742013-11-12 13:43:36 -0800635 timeout_mins=None, max_runtime_mins=None,
636 run_verify=False, email_list='', dependencies=(),
637 reboot_before=None, reboot_after=None,
638 parse_failed_repair=None, hostless=False,
Dan Shiec1d47d2015-02-13 11:38:13 -0800639 keyvals=None, drone_set=None, run_reset=True,
640 require_ssq=None):
jamesren4a41e012010-07-16 22:33:48 +0000641 """
642 Creates and enqueues a parameterized job.
643
644 Most parameters a combination of the parameters for generate_control_file()
645 and create_job(), with the exception of:
646
647 @param test name or ID of the test to run
648 @param parameters a map of parameter name ->
649 tuple of (param value, param type)
650 @param profiler_parameters a dictionary of parameters for the profilers:
651 key: profiler name
652 value: dict of param name -> tuple of
653 (param value,
654 param type)
655 """
656 # Save the values of the passed arguments here. What we're going to do with
657 # them is pass them all to rpc_utils.get_create_job_common_args(), which
658 # will extract the subset of these arguments that apply for
659 # rpc_utils.create_job_common(), which we then pass in to that function.
660 args = locals()
661
662 # Set up the parameterized job configs
663 test_obj = models.Test.smart_get(test)
Aviv Keshet3dd8beb2013-05-13 17:36:04 -0700664 control_type = test_obj.test_type
jamesren4a41e012010-07-16 22:33:48 +0000665
666 try:
667 label = models.Label.smart_get(label)
668 except models.Label.DoesNotExist:
669 label = None
670
671 kernel_objs = models.Kernel.create_kernels(kernel)
672 profiler_objs = [models.Profiler.smart_get(profiler)
673 for profiler in profilers]
674
675 parameterized_job = models.ParameterizedJob.objects.create(
676 test=test_obj, label=label, use_container=use_container,
677 profile_only=profile_only,
678 upload_kernel_config=upload_kernel_config)
679 parameterized_job.kernels.add(*kernel_objs)
680
681 for profiler in profiler_objs:
682 parameterized_profiler = models.ParameterizedJobProfiler.objects.create(
683 parameterized_job=parameterized_job,
684 profiler=profiler)
685 profiler_params = profiler_parameters.get(profiler.name, {})
686 for name, (value, param_type) in profiler_params.iteritems():
687 models.ParameterizedJobProfilerParameter.objects.create(
688 parameterized_job_profiler=parameterized_profiler,
689 parameter_name=name,
690 parameter_value=value,
691 parameter_type=param_type)
692
693 try:
694 for parameter in test_obj.testparameter_set.all():
695 if parameter.name in parameters:
696 param_value, param_type = parameters.pop(parameter.name)
697 parameterized_job.parameterizedjobparameter_set.create(
698 test_parameter=parameter, parameter_value=param_value,
699 parameter_type=param_type)
700
701 if parameters:
702 raise Exception('Extra parameters remain: %r' % parameters)
703
704 return rpc_utils.create_job_common(
705 parameterized_job=parameterized_job.id,
706 control_type=control_type,
707 **rpc_utils.get_create_job_common_args(args))
708 except:
709 parameterized_job.delete()
710 raise
711
712
Simran Basib6ec8ae2014-04-23 12:05:08 -0700713def create_job_page_handler(name, priority, control_file, control_type,
714 image=None, hostless=False, **kwargs):
715 """\
716 Create and enqueue a job.
717
718 @param name name of this job
719 @param priority Integer priority of this job. Higher is more important.
720 @param control_file String contents of the control file.
721 @param control_type Type of control file, Client or Server.
722 @param kwargs extra args that will be required by create_suite_job or
723 create_job.
724
725 @returns The created Job id number.
726 """
727 control_file = rpc_utils.encode_ascii(control_file)
Jiaxi Luodd67beb2014-07-18 16:28:31 -0700728 if not control_file:
729 raise model_logic.ValidationError({
730 'control_file' : "Control file cannot be empty"})
Simran Basib6ec8ae2014-04-23 12:05:08 -0700731
732 if image and hostless:
733 return site_rpc_interface.create_suite_job(
734 name=name, control_file=control_file, priority=priority,
735 build=image, **kwargs)
736 return create_job(name, priority, control_file, control_type, image=image,
737 hostless=hostless, **kwargs)
738
739
showard12f3e322009-05-13 21:27:42 +0000740def create_job(name, priority, control_file, control_type,
741 hosts=(), meta_hosts=(), one_time_hosts=(),
742 atomic_group_name=None, synch_count=None, is_template=False,
Simran Basi7e605742013-11-12 13:43:36 -0800743 timeout=None, timeout_mins=None, max_runtime_mins=None,
744 run_verify=False, email_list='', dependencies=(),
745 reboot_before=None, reboot_after=None, parse_failed_repair=None,
746 hostless=False, keyvals=None, drone_set=None, image=None,
Dan Shiec1d47d2015-02-13 11:38:13 -0800747 parent_job_id=None, test_retry=0, run_reset=True,
748 require_ssp=None, args=(), **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +0000749 """\
750 Create and enqueue a job.
mblighe8819cd2008-02-15 16:48:40 +0000751
showarda1e74b32009-05-12 17:32:04 +0000752 @param name name of this job
Alex Miller7d658cf2013-09-04 16:00:35 -0700753 @param priority Integer priority of this job. Higher is more important.
showarda1e74b32009-05-12 17:32:04 +0000754 @param control_file String contents of the control file.
755 @param control_type Type of control file, Client or Server.
756 @param synch_count How many machines the job uses per autoserv execution.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700757 synch_count == 1 means the job is asynchronous. If an atomic group is
758 given this value is treated as a minimum.
showarda1e74b32009-05-12 17:32:04 +0000759 @param is_template If true then create a template job.
760 @param timeout Hours after this call returns until the job times out.
Simran Basi7e605742013-11-12 13:43:36 -0800761 @param timeout_mins Minutes after this call returns until the job times
Jiaxi Luo90190c92014-06-18 12:35:57 -0700762 out.
Simran Basi34217022012-11-06 13:43:15 -0800763 @param max_runtime_mins Minutes from job starting time until job times out
showarda1e74b32009-05-12 17:32:04 +0000764 @param run_verify Should the host be verified before running the test?
765 @param email_list String containing emails to mail when the job is done
766 @param dependencies List of label names on which this job depends
767 @param reboot_before Never, If dirty, or Always
768 @param reboot_after Never, If all tests passed, or Always
769 @param parse_failed_repair if true, results of failed repairs launched by
Jiaxi Luo90190c92014-06-18 12:35:57 -0700770 this job will be parsed as part of the job.
showarda9545c02009-12-18 22:44:26 +0000771 @param hostless if true, create a hostless job
showardc1a98d12010-01-15 00:22:22 +0000772 @param keyvals dict of keyvals to associate with the job
showarda1e74b32009-05-12 17:32:04 +0000773 @param hosts List of hosts to run job on.
774 @param meta_hosts List where each entry is a label name, and for each entry
Jiaxi Luo90190c92014-06-18 12:35:57 -0700775 one host will be chosen from that label to run the job on.
showarda1e74b32009-05-12 17:32:04 +0000776 @param one_time_hosts List of hosts not in the database to run the job on.
777 @param atomic_group_name The name of an atomic group to schedule the job on.
jamesren76fcf192010-04-21 20:39:50 +0000778 @param drone_set The name of the drone set to run this test on.
Paul Pendlebury5a8c6ad2011-02-01 07:20:17 -0800779 @param image OS image to install before running job.
Aviv Keshet0b9cfc92013-02-05 11:36:02 -0800780 @param parent_job_id id of a job considered to be parent of created job.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700781 @param test_retry Number of times to retry test if the test did not
Jiaxi Luo90190c92014-06-18 12:35:57 -0700782 complete successfully. (optional, default: 0)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700783 @param run_reset Should the host be reset before running the test?
Dan Shiec1d47d2015-02-13 11:38:13 -0800784 @param require_ssp Set to True to require server-side packaging to run the
785 test. If it's set to None, drone will still try to run
786 the server side with server-side packaging. If the
787 autotest-server package doesn't exist for the build or
788 image is not set, drone will run the test without server-
789 side packaging. Default is None.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700790 @param args A list of args to be injected into control file.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700791 @param kwargs extra keyword args. NOT USED.
showardc92da832009-04-07 18:14:34 +0000792
793 @returns The created Job id number.
jadmanski0afbb632008-06-06 21:10:57 +0000794 """
Jiaxi Luo90190c92014-06-18 12:35:57 -0700795 if args:
796 control_file = tools.inject_vars({'args': args}, control_file)
797
Simran Basiab5a1bf2014-05-28 15:39:44 -0700798 if image is None:
799 return rpc_utils.create_job_common(
800 **rpc_utils.get_create_job_common_args(locals()))
801
802 # When image is supplied use a known parameterized test already in the
803 # database to pass the OS image path from the front end, through the
804 # scheduler, and finally to autoserv as the --image parameter.
805
806 # The test autoupdate_ParameterizedJob is in afe_autotests and used to
807 # instantiate a Test object and from there a ParameterizedJob.
808 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
809 known_parameterized_job = models.ParameterizedJob.objects.create(
810 test=known_test_obj)
811
812 # autoupdate_ParameterizedJob has a single parameter, the image parameter,
813 # stored in the table afe_test_parameters. We retrieve and set this
814 # instance of the parameter to the OS image path.
815 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
816 name='image')
817 known_parameterized_job.parameterizedjobparameter_set.create(
818 test_parameter=image_parameter, parameter_value=image,
819 parameter_type='string')
820
821 # By passing a parameterized_job to create_job_common the job entry in
822 # the afe_jobs table will have the field parameterized_job_id set.
823 # The scheduler uses this id in the afe_parameterized_jobs table to
824 # match this job to our known test, and then with the
825 # afe_parameterized_job_parameters table to get the actual image path.
jamesren4a41e012010-07-16 22:33:48 +0000826 return rpc_utils.create_job_common(
Simran Basiab5a1bf2014-05-28 15:39:44 -0700827 parameterized_job=known_parameterized_job.id,
jamesren4a41e012010-07-16 22:33:48 +0000828 **rpc_utils.get_create_job_common_args(locals()))
mblighe8819cd2008-02-15 16:48:40 +0000829
830
showard9dbdcda2008-10-14 17:34:36 +0000831def abort_host_queue_entries(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000832 """\
showard9dbdcda2008-10-14 17:34:36 +0000833 Abort a set of host queue entries.
Fang Deng63b0e452014-12-19 14:38:15 -0800834
835 @return: A list of dictionaries, each contains information
836 about an aborted HQE.
jadmanski0afbb632008-06-06 21:10:57 +0000837 """
showard9dbdcda2008-10-14 17:34:36 +0000838 query = models.HostQueueEntry.query_objects(filter_data)
beepsfaecbce2013-10-29 11:35:10 -0700839
840 # Dont allow aborts on:
841 # 1. Jobs that have already completed (whether or not they were aborted)
842 # 2. Jobs that we have already been aborted (but may not have completed)
843 query = query.filter(complete=False).filter(aborted=False)
showarddc817512008-11-12 18:16:41 +0000844 models.AclGroup.check_abort_permissions(query)
showard9dbdcda2008-10-14 17:34:36 +0000845 host_queue_entries = list(query.select_related())
showard2bab8f42008-11-12 18:15:22 +0000846 rpc_utils.check_abort_synchronous_jobs(host_queue_entries)
mblighe8819cd2008-02-15 16:48:40 +0000847
Simran Basic1b26762013-06-26 14:23:21 -0700848 models.HostQueueEntry.abort_host_queue_entries(host_queue_entries)
Fang Deng63b0e452014-12-19 14:38:15 -0800849 hqe_info = [{'HostQueueEntry': hqe.id, 'Job': hqe.job_id,
850 'Job name': hqe.job.name} for hqe in host_queue_entries]
851 return hqe_info
showard9d821ab2008-07-11 16:54:29 +0000852
853
beeps8bb1f7d2013-08-05 01:30:09 -0700854def abort_special_tasks(**filter_data):
855 """\
856 Abort the special task, or tasks, specified in the filter.
857 """
858 query = models.SpecialTask.query_objects(filter_data)
859 special_tasks = query.filter(is_active=True)
860 for task in special_tasks:
861 task.abort()
862
863
Simran Basi73dae552013-02-25 14:57:46 -0800864def _call_special_tasks_on_hosts(task, hosts):
865 """\
866 Schedules a set of hosts for a special task.
867
868 @returns A list of hostnames that a special task was created for.
869 """
870 models.AclGroup.check_for_acl_violation_hosts(hosts)
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -0800871 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800872 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -0800873 raise ValueError('The following hosts are on shards, please '
874 'follow the link to the shards and create jobs '
875 'there instead. %s.' % shard_host_map)
Simran Basi73dae552013-02-25 14:57:46 -0800876 for host in hosts:
877 models.SpecialTask.schedule_special_task(host, task)
878 return list(sorted(host.hostname for host in hosts))
879
880
showard1ff7b2e2009-05-15 23:17:18 +0000881def reverify_hosts(**filter_data):
882 """\
883 Schedules a set of hosts for verify.
mbligh4e545a52009-12-19 05:30:39 +0000884
885 @returns A list of hostnames that a verify task was created for.
showard1ff7b2e2009-05-15 23:17:18 +0000886 """
Prashanth Balasubramanian40981232014-12-16 19:01:58 -0800887 hosts = models.Host.query_objects(filter_data)
888 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts, rpc_hostnames=True)
889
890 # Filter out hosts on a shard from those on the master, forward
891 # rpcs to the shard with an additional hostname__in filter, and
892 # create a local SpecialTask for each remaining host.
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800893 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian40981232014-12-16 19:01:58 -0800894 hosts = [h for h in hosts if h.shard is None]
895 for shard, hostnames in shard_host_map.iteritems():
896
897 # The main client of this module is the frontend website, and
898 # it invokes it with an 'id' or an 'id__in' filter. Regardless,
899 # the 'hostname' filter should narrow down the list of hosts on
900 # each shard even though we supply all the ids in filter_data.
901 # This method uses hostname instead of id because it fits better
902 # with the overall architecture of redirection functions in rpc_utils.
903 shard_filter = filter_data.copy()
904 shard_filter['hostname__in'] = hostnames
905 rpc_utils.run_rpc_on_multiple_hostnames(
906 'reverify_hosts', [shard], **shard_filter)
907
908 # There is a race condition here if someone assigns a shard to one of these
909 # hosts before we create the task. The host will stay on the master if:
910 # 1. The host is not Ready
911 # 2. The host is Ready but has a task
912 # But if the host is Ready and doesn't have a task yet, it will get sent
913 # to the shard as we're creating a task here.
914
915 # Given that we only rarely verify Ready hosts it isn't worth putting this
916 # entire method in a transaction. The worst case scenario is that we have
917 # a verify running on a Ready host while the shard is using it, if the verify
918 # fails no subsequent tasks will be created against the host on the master,
919 # and verifies are safe enough that this is OK.
920 return _call_special_tasks_on_hosts(models.SpecialTask.Task.VERIFY, hosts)
Simran Basi73dae552013-02-25 14:57:46 -0800921
922
923def repair_hosts(**filter_data):
924 """\
925 Schedules a set of hosts for repair.
926
927 @returns A list of hostnames that a repair task was created for.
928 """
929 return _call_special_tasks_on_hosts(models.SpecialTask.Task.REPAIR,
930 models.Host.query_objects(filter_data))
showard1ff7b2e2009-05-15 23:17:18 +0000931
932
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700933def get_jobs(not_yet_run=False, running=False, finished=False,
934 suite=False, sub=False, standalone=False, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000935 """\
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700936 Extra status filter args for get_jobs:
jadmanski0afbb632008-06-06 21:10:57 +0000937 -not_yet_run: Include only jobs that have not yet started running.
938 -running: Include only jobs that have start running but for which not
939 all hosts have completed.
940 -finished: Include only jobs for which all hosts have completed (or
941 aborted).
942 At most one of these three fields should be specified.
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700943
944 Extra type filter args for get_jobs:
945 -suite: Include only jobs with child jobs.
946 -sub: Include only jobs with a parent job.
947 -standalone: Inlcude only jobs with no child or parent jobs.
948 At most one of these three fields should be specified.
jadmanski0afbb632008-06-06 21:10:57 +0000949 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700950 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
951 running,
952 finished)
953 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
954 suite,
955 sub,
956 standalone)
showard0957a842009-05-11 19:25:08 +0000957 job_dicts = []
958 jobs = list(models.Job.query_objects(filter_data))
959 models.Job.objects.populate_relationships(jobs, models.Label,
960 'dependencies')
showardc1a98d12010-01-15 00:22:22 +0000961 models.Job.objects.populate_relationships(jobs, models.JobKeyval, 'keyvals')
showard0957a842009-05-11 19:25:08 +0000962 for job in jobs:
963 job_dict = job.get_object_dict()
964 job_dict['dependencies'] = ','.join(label.name
965 for label in job.dependencies)
showardc1a98d12010-01-15 00:22:22 +0000966 job_dict['keyvals'] = dict((keyval.key, keyval.value)
967 for keyval in job.keyvals)
Eric Lid23bc192011-02-09 14:38:57 -0800968 if job.parameterized_job:
969 job_dict['image'] = get_parameterized_autoupdate_image_url(job)
showard0957a842009-05-11 19:25:08 +0000970 job_dicts.append(job_dict)
971 return rpc_utils.prepare_for_serialization(job_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000972
973
974def get_num_jobs(not_yet_run=False, running=False, finished=False,
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700975 suite=False, sub=False, standalone=False,
jadmanski0afbb632008-06-06 21:10:57 +0000976 **filter_data):
977 """\
978 See get_jobs() for documentation of extra filter parameters.
979 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700980 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
981 running,
982 finished)
983 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
984 suite,
985 sub,
986 standalone)
jadmanski0afbb632008-06-06 21:10:57 +0000987 return models.Job.query_count(filter_data)
mblighe8819cd2008-02-15 16:48:40 +0000988
989
mblighe8819cd2008-02-15 16:48:40 +0000990def get_jobs_summary(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000991 """\
Jiaxi Luoaac54572014-06-04 13:57:02 -0700992 Like get_jobs(), but adds 'status_counts' and 'result_counts' field.
993
994 'status_counts' filed is a dictionary mapping status strings to the number
995 of hosts currently with that status, i.e. {'Queued' : 4, 'Running' : 2}.
996
997 'result_counts' field is piped to tko's rpc_interface and has the return
998 format specified under get_group_counts.
jadmanski0afbb632008-06-06 21:10:57 +0000999 """
1000 jobs = get_jobs(**filter_data)
1001 ids = [job['id'] for job in jobs]
1002 all_status_counts = models.Job.objects.get_status_counts(ids)
1003 for job in jobs:
1004 job['status_counts'] = all_status_counts[job['id']]
Jiaxi Luoaac54572014-06-04 13:57:02 -07001005 job['result_counts'] = tko_rpc_interface.get_status_counts(
1006 ['afe_job_id', 'afe_job_id'],
1007 header_groups=[['afe_job_id'], ['afe_job_id']],
1008 **{'afe_job_id': job['id']})
jadmanski0afbb632008-06-06 21:10:57 +00001009 return rpc_utils.prepare_for_serialization(jobs)
mblighe8819cd2008-02-15 16:48:40 +00001010
1011
showarda965cef2009-05-15 23:17:41 +00001012def get_info_for_clone(id, preserve_metahosts, queue_entry_filter_data=None):
showarda8709c52008-07-03 19:44:54 +00001013 """\
1014 Retrieves all the information needed to clone a job.
1015 """
showarda8709c52008-07-03 19:44:54 +00001016 job = models.Job.objects.get(id=id)
showard29f7cd22009-04-29 21:16:24 +00001017 job_info = rpc_utils.get_job_info(job,
showarda965cef2009-05-15 23:17:41 +00001018 preserve_metahosts,
1019 queue_entry_filter_data)
showard945072f2008-09-03 20:34:59 +00001020
showardd9992fe2008-07-31 02:15:03 +00001021 host_dicts = []
showard29f7cd22009-04-29 21:16:24 +00001022 for host in job_info['hosts']:
1023 host_dict = get_hosts(id=host.id)[0]
1024 other_labels = host_dict['labels']
1025 if host_dict['platform']:
1026 other_labels.remove(host_dict['platform'])
1027 host_dict['other_labels'] = ', '.join(other_labels)
showardd9992fe2008-07-31 02:15:03 +00001028 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001029
showard29f7cd22009-04-29 21:16:24 +00001030 for host in job_info['one_time_hosts']:
1031 host_dict = dict(hostname=host.hostname,
1032 id=host.id,
1033 platform='(one-time host)',
1034 locked_text='')
1035 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001036
showard4d077562009-05-08 18:24:36 +00001037 # convert keys from Label objects to strings (names of labels)
showard29f7cd22009-04-29 21:16:24 +00001038 meta_host_counts = dict((meta_host.name, count) for meta_host, count
showard4d077562009-05-08 18:24:36 +00001039 in job_info['meta_host_counts'].iteritems())
showard29f7cd22009-04-29 21:16:24 +00001040
1041 info = dict(job=job.get_object_dict(),
1042 meta_host_counts=meta_host_counts,
1043 hosts=host_dicts)
1044 info['job']['dependencies'] = job_info['dependencies']
1045 if job_info['atomic_group']:
1046 info['atomic_group_name'] = (job_info['atomic_group']).name
1047 else:
1048 info['atomic_group_name'] = None
jamesren2275ef12010-04-12 18:25:06 +00001049 info['hostless'] = job_info['hostless']
jamesren76fcf192010-04-21 20:39:50 +00001050 info['drone_set'] = job.drone_set and job.drone_set.name
showarda8709c52008-07-03 19:44:54 +00001051
Eric Lid23bc192011-02-09 14:38:57 -08001052 if job.parameterized_job:
1053 info['job']['image'] = get_parameterized_autoupdate_image_url(job)
1054
showarda8709c52008-07-03 19:44:54 +00001055 return rpc_utils.prepare_for_serialization(info)
1056
1057
showard34dc5fa2008-04-24 20:58:40 +00001058# host queue entries
1059
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001060def get_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001061 """\
showardc92da832009-04-07 18:14:34 +00001062 @returns A sequence of nested dictionaries of host and job information.
jadmanski0afbb632008-06-06 21:10:57 +00001063 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001064 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1065 'started_on__lte',
1066 start_time,
1067 end_time,
1068 **filter_data)
MK Ryu2d107562015-02-24 17:45:02 -08001069 return rpc_utils.get_serialized_local_host_queue_entries(**filter_data)
showard34dc5fa2008-04-24 20:58:40 +00001070
1071
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001072def get_num_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001073 """\
1074 Get the number of host queue entries associated with this job.
1075 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001076 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1077 'started_on__lte',
1078 start_time,
1079 end_time,
1080 **filter_data)
jadmanski0afbb632008-06-06 21:10:57 +00001081 return models.HostQueueEntry.query_count(filter_data)
showard34dc5fa2008-04-24 20:58:40 +00001082
1083
showard1e935f12008-07-11 00:11:36 +00001084def get_hqe_percentage_complete(**filter_data):
1085 """
showardc92da832009-04-07 18:14:34 +00001086 Computes the fraction of host queue entries matching the given filter data
showard1e935f12008-07-11 00:11:36 +00001087 that are complete.
1088 """
1089 query = models.HostQueueEntry.query_objects(filter_data)
1090 complete_count = query.filter(complete=True).count()
1091 total_count = query.count()
1092 if total_count == 0:
1093 return 1
1094 return float(complete_count) / total_count
1095
1096
showard1a5a4082009-07-28 20:01:37 +00001097# special tasks
1098
1099def get_special_tasks(**filter_data):
MK Ryu2d107562015-02-24 17:45:02 -08001100 # Task id is not universally unique, the id passed in would only be
1101 # applicable to local db.
1102 if 'id' in filter_data or 'id__in' in filter_data:
1103 return rpc_utils.get_serialized_local_special_tasks(**filter_data)
1104 else:
1105 return rpc_utils.get_data(rpc_utils.get_serialized_local_special_tasks,
1106 'get_special_tasks', **filter_data)
showard1a5a4082009-07-28 20:01:37 +00001107
1108
showardc0ac3a72009-07-08 21:14:45 +00001109# support for host detail view
1110
Jiaxi Luo79ce6422014-06-13 17:08:09 -07001111def get_host_queue_entries_and_special_tasks(host_id, query_start=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001112 query_limit=None, start_time=None,
1113 end_time=None):
showardc0ac3a72009-07-08 21:14:45 +00001114 """
1115 @returns an interleaved list of HostQueueEntries and SpecialTasks,
1116 in approximate run order. each dict contains keys for type, host,
1117 job, status, started_on, execution_path, and ID.
1118 """
1119 total_limit = None
1120 if query_limit is not None:
1121 total_limit = query_start + query_limit
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001122 filter_data_common = {'host': host_id,
1123 'query_limit': total_limit,
1124 'sort_by': ['-id']}
showardc0ac3a72009-07-08 21:14:45 +00001125
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001126 filter_data_queue_entries, filter_data_special_tasks = (
1127 rpc_utils.inject_times_to_hqe_special_tasks_filters(
1128 filter_data_common, start_time, end_time))
1129
1130 queue_entries = list(models.HostQueueEntry.query_objects(
1131 filter_data_queue_entries))
1132 special_tasks = list(models.SpecialTask.query_objects(
1133 filter_data_special_tasks))
showardc0ac3a72009-07-08 21:14:45 +00001134
1135 interleaved_entries = rpc_utils.interleave_entries(queue_entries,
1136 special_tasks)
1137 if query_start is not None:
1138 interleaved_entries = interleaved_entries[query_start:]
1139 if query_limit is not None:
1140 interleaved_entries = interleaved_entries[:query_limit]
1141 return rpc_utils.prepare_for_serialization(interleaved_entries)
1142
1143
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001144def get_num_host_queue_entries_and_special_tasks(host_id, start_time=None,
1145 end_time=None):
1146 filter_data_common = {'host': host_id}
1147
1148 filter_data_queue_entries, filter_data_special_tasks = (
1149 rpc_utils.inject_times_to_hqe_special_tasks_filters(
1150 filter_data_common, start_time, end_time))
1151
1152 return (models.HostQueueEntry.query_count(filter_data_queue_entries)
1153 + models.SpecialTask.query_count(filter_data_special_tasks))
showardc0ac3a72009-07-08 21:14:45 +00001154
1155
showard29f7cd22009-04-29 21:16:24 +00001156# recurring run
1157
1158def get_recurring(**filter_data):
1159 return rpc_utils.prepare_rows_as_nested_dicts(
1160 models.RecurringRun.query_objects(filter_data),
1161 ('job', 'owner'))
1162
1163
1164def get_num_recurring(**filter_data):
1165 return models.RecurringRun.query_count(filter_data)
1166
1167
1168def delete_recurring_runs(**filter_data):
1169 to_delete = models.RecurringRun.query_objects(filter_data)
1170 to_delete.delete()
1171
1172
1173def create_recurring_run(job_id, start_date, loop_period, loop_count):
showard64a95952010-01-13 21:27:16 +00001174 owner = models.User.current_user().login
showard29f7cd22009-04-29 21:16:24 +00001175 job = models.Job.objects.get(id=job_id)
1176 return job.create_recurring_job(start_date=start_date,
1177 loop_period=loop_period,
1178 loop_count=loop_count,
1179 owner=owner)
1180
1181
mblighe8819cd2008-02-15 16:48:40 +00001182# other
1183
showarde0b63622008-08-04 20:58:47 +00001184def echo(data=""):
1185 """\
1186 Returns a passed in string. For doing a basic test to see if RPC calls
1187 can successfully be made.
1188 """
1189 return data
1190
1191
showardb7a52fd2009-04-27 20:10:56 +00001192def get_motd():
1193 """\
1194 Returns the message of the day as a string.
1195 """
1196 return rpc_utils.get_motd()
1197
1198
mblighe8819cd2008-02-15 16:48:40 +00001199def get_static_data():
jadmanski0afbb632008-06-06 21:10:57 +00001200 """\
1201 Returns a dictionary containing a bunch of data that shouldn't change
1202 often and is otherwise inaccessible. This includes:
showardc92da832009-04-07 18:14:34 +00001203
1204 priorities: List of job priority choices.
1205 default_priority: Default priority value for new jobs.
1206 users: Sorted list of all users.
Jiaxi Luo31874592014-06-11 10:36:35 -07001207 labels: Sorted list of labels not start with 'cros-version' and
1208 'fw-version'.
showardc92da832009-04-07 18:14:34 +00001209 atomic_groups: Sorted list of all atomic groups.
1210 tests: Sorted list of all tests.
1211 profilers: Sorted list of all profilers.
1212 current_user: Logged-in username.
1213 host_statuses: Sorted list of possible Host statuses.
1214 job_statuses: Sorted list of possible HostQueueEntry statuses.
Simran Basi7e605742013-11-12 13:43:36 -08001215 job_timeout_default: The default job timeout length in minutes.
showarda1e74b32009-05-12 17:32:04 +00001216 parse_failed_repair_default: Default value for the parse_failed_repair job
Jiaxi Luo31874592014-06-11 10:36:35 -07001217 option.
showardc92da832009-04-07 18:14:34 +00001218 reboot_before_options: A list of valid RebootBefore string enums.
1219 reboot_after_options: A list of valid RebootAfter string enums.
1220 motd: Server's message of the day.
1221 status_dictionary: A mapping from one word job status names to a more
1222 informative description.
jadmanski0afbb632008-06-06 21:10:57 +00001223 """
showard21baa452008-10-21 00:08:39 +00001224
1225 job_fields = models.Job.get_field_dict()
jamesren76fcf192010-04-21 20:39:50 +00001226 default_drone_set_name = models.DroneSet.default_drone_set_name()
1227 drone_sets = ([default_drone_set_name] +
1228 sorted(drone_set.name for drone_set in
1229 models.DroneSet.objects.exclude(
1230 name=default_drone_set_name)))
showard21baa452008-10-21 00:08:39 +00001231
jadmanski0afbb632008-06-06 21:10:57 +00001232 result = {}
Alex Miller7d658cf2013-09-04 16:00:35 -07001233 result['priorities'] = priorities.Priority.choices()
1234 default_priority = priorities.Priority.DEFAULT
1235 result['default_priority'] = 'Default'
1236 result['max_schedulable_priority'] = priorities.Priority.DEFAULT
jadmanski0afbb632008-06-06 21:10:57 +00001237 result['users'] = get_users(sort_by=['login'])
Jiaxi Luo31874592014-06-11 10:36:35 -07001238
1239 label_exclude_filters = [{'name__startswith': 'cros-version'},
1240 {'name__startswith': 'fw-version'}]
1241 result['labels'] = get_labels(
1242 label_exclude_filters,
1243 sort_by=['-platform', 'name'])
1244
showardc92da832009-04-07 18:14:34 +00001245 result['atomic_groups'] = get_atomic_groups(sort_by=['name'])
jadmanski0afbb632008-06-06 21:10:57 +00001246 result['tests'] = get_tests(sort_by=['name'])
showard2b9a88b2008-06-13 20:55:03 +00001247 result['profilers'] = get_profilers(sort_by=['name'])
showard0fc38302008-10-23 00:44:07 +00001248 result['current_user'] = rpc_utils.prepare_for_serialization(
showard64a95952010-01-13 21:27:16 +00001249 models.User.current_user().get_object_dict())
showard2b9a88b2008-06-13 20:55:03 +00001250 result['host_statuses'] = sorted(models.Host.Status.names)
mbligh5a198b92008-12-11 19:33:29 +00001251 result['job_statuses'] = sorted(models.HostQueueEntry.Status.names)
Simran Basi7e605742013-11-12 13:43:36 -08001252 result['job_timeout_mins_default'] = models.Job.DEFAULT_TIMEOUT_MINS
Simran Basi34217022012-11-06 13:43:15 -08001253 result['job_max_runtime_mins_default'] = (
1254 models.Job.DEFAULT_MAX_RUNTIME_MINS)
showarda1e74b32009-05-12 17:32:04 +00001255 result['parse_failed_repair_default'] = bool(
1256 models.Job.DEFAULT_PARSE_FAILED_REPAIR)
jamesrendd855242010-03-02 22:23:44 +00001257 result['reboot_before_options'] = model_attributes.RebootBefore.names
1258 result['reboot_after_options'] = model_attributes.RebootAfter.names
showard8fbae652009-01-20 23:23:10 +00001259 result['motd'] = rpc_utils.get_motd()
jamesren76fcf192010-04-21 20:39:50 +00001260 result['drone_sets_enabled'] = models.DroneSet.drone_sets_enabled()
1261 result['drone_sets'] = drone_sets
jamesren4a41e012010-07-16 22:33:48 +00001262 result['parameterized_jobs'] = models.Job.parameterized_jobs_enabled()
showard8ac29b42008-07-17 17:01:55 +00001263
showardd3dc1992009-04-22 21:01:40 +00001264 result['status_dictionary'] = {"Aborted": "Aborted",
showard8ac29b42008-07-17 17:01:55 +00001265 "Verifying": "Verifying Host",
Alex Millerdfff2fd2013-05-28 13:05:06 -07001266 "Provisioning": "Provisioning Host",
showard8ac29b42008-07-17 17:01:55 +00001267 "Pending": "Waiting on other hosts",
1268 "Running": "Running autoserv",
1269 "Completed": "Autoserv completed",
1270 "Failed": "Failed to complete",
showardd823b362008-07-24 16:35:46 +00001271 "Queued": "Queued",
showard5deb6772008-11-04 21:54:33 +00001272 "Starting": "Next in host's queue",
1273 "Stopped": "Other host(s) failed verify",
showardd3dc1992009-04-22 21:01:40 +00001274 "Parsing": "Awaiting parse of final results",
showard29f7cd22009-04-29 21:16:24 +00001275 "Gathering": "Gathering log files",
showard8cc058f2009-09-08 16:26:33 +00001276 "Template": "Template job for recurring run",
mbligh4608b002010-01-05 18:22:35 +00001277 "Waiting": "Waiting for scheduler action",
Dan Shi07e09af2013-04-12 09:31:29 -07001278 "Archiving": "Archiving results",
1279 "Resetting": "Resetting hosts"}
Jiaxi Luo421608e2014-07-07 14:38:00 -07001280
1281 result['wmatrix_url'] = rpc_utils.get_wmatrix_url()
Simran Basi71206ef2014-08-13 13:51:18 -07001282 result['is_moblab'] = bool(utils.is_moblab())
Jiaxi Luo421608e2014-07-07 14:38:00 -07001283
jadmanski0afbb632008-06-06 21:10:57 +00001284 return result
showard29f7cd22009-04-29 21:16:24 +00001285
1286
1287def get_server_time():
1288 return datetime.datetime.now().strftime("%Y-%m-%d %H:%M")