blob: 7a058db7d1a5a01f785d6fc9695aef3e47ada658 [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
showard29f7cd22009-04-29 21:16:24 +000034import datetime
Moises Osorio2dc7a102014-12-02 18:24:02 -080035from django.db.models import Count
showardcafd16e2009-05-29 18:37:49 +000036import common
Simran Basib6ec8ae2014-04-23 12:05:08 -070037from autotest_lib.client.common_lib import priorities
Moises Osorio2dc7a102014-12-02 18:24:02 -080038from autotest_lib.client.common_lib.cros.graphite import stats
jamesrendd855242010-03-02 22:23:44 +000039from autotest_lib.frontend.afe import models, model_logic, model_attributes
showard6d7b2ff2009-06-10 00:16:47 +000040from autotest_lib.frontend.afe import control_file, rpc_utils
Simran Basib6ec8ae2014-04-23 12:05:08 -070041from autotest_lib.frontend.afe import site_rpc_interface
Moises Osorio2dc7a102014-12-02 18:24:02 -080042from autotest_lib.frontend.tko import models as tko_models
Jiaxi Luoaac54572014-06-04 13:57:02 -070043from autotest_lib.frontend.tko import rpc_interface as tko_rpc_interface
Simran Basi71206ef2014-08-13 13:51:18 -070044from autotest_lib.server import utils
Jiaxi Luo90190c92014-06-18 12:35:57 -070045from autotest_lib.server.cros.dynamic_suite import tools
mblighe8819cd2008-02-15 16:48:40 +000046
Moises Osorio2dc7a102014-12-02 18:24:02 -080047
48_timer = stats.Timer('rpc_interface')
49
Eric Lid23bc192011-02-09 14:38:57 -080050def get_parameterized_autoupdate_image_url(job):
51 """Get the parameterized autoupdate image url from a parameterized job."""
52 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
53 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
beeps8bb1f7d2013-08-05 01:30:09 -070054 name='image')
Eric Lid23bc192011-02-09 14:38:57 -080055 para_set = job.parameterized_job.parameterizedjobparameter_set
56 job_test_para = para_set.get(test_parameter=image_parameter)
57 return job_test_para.parameter_value
58
59
mblighe8819cd2008-02-15 16:48:40 +000060# labels
61
Mungyung Ryud893df02015-02-04 23:41:38 +000062def add_label(name, kernel_config=None, platform=None, only_if_needed=None):
63 return models.Label.add_object(
64 name=name, kernel_config=kernel_config, platform=platform,
65 only_if_needed=only_if_needed).id
66
67
mblighe8819cd2008-02-15 16:48:40 +000068def modify_label(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +000069 models.Label.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +000070
71
72def delete_label(id):
jadmanski0afbb632008-06-06 21:10:57 +000073 models.Label.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +000074
Mungyung Ryud893df02015-02-04 23:41:38 +000075@rpc_utils.forward_multi_host_rpc_to_shards
Prashanth Balasubramanian744898f2015-01-13 05:04:16 -080076def label_add_hosts(id, hosts):
Mungyung Ryud893df02015-02-04 23:41:38 +000077 """Add the label with the given id to the list of hosts.
Prashanth Balasubramanian744898f2015-01-13 05:04:16 -080078
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -080079 The given label will be created if it doesn't exist, provided the `id`
80 supplied is a label name not an int/long id.
81
Mungyung Ryud893df02015-02-04 23:41:38 +000082 @param id: An id or label name. More often a label name.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -080083 @param hosts: A list of hostnames or ids. More often hostnames.
84
Mungyung Ryud893df02015-02-04 23:41:38 +000085 @raises models.Label.DoesNotExist: If the id specified is an int/long
86 and a label with that id doesn't exist.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -080087 """
showardbe3ec042008-11-12 18:16:07 +000088 host_objs = models.Host.smart_get_bulk(hosts)
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -080089 try:
Mungyung Ryud893df02015-02-04 23:41:38 +000090 # In the rare event that we're given an id and not a label name,
91 # it should already exist.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -080092 label = models.Label.smart_get(id)
93 except models.Label.DoesNotExist:
94 # This matches the type checks in smart_get, which is a hack
95 # in and off itself. The aim here is to create any non-existent
96 # label, which we cannot do if the 'id' specified isn't a label name.
97 if isinstance(id, basestring):
98 label = models.Label.smart_get(add_label(id))
99 else:
Mungyung Ryud893df02015-02-04 23:41:38 +0000100 raise
101
102 if label.platform:
103 models.Host.check_no_platform(host_objs)
104 label.host_set.add(*host_objs)
showardbbabf502008-06-06 00:02:02 +0000105
106
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800107@rpc_utils.forward_multi_host_rpc_to_shards
showardbbabf502008-06-06 00:02:02 +0000108def label_remove_hosts(id, hosts):
showardbe3ec042008-11-12 18:16:07 +0000109 host_objs = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000110 models.Label.smart_get(id).host_set.remove(*host_objs)
showardbbabf502008-06-06 00:02:02 +0000111
112
Jiaxi Luo31874592014-06-11 10:36:35 -0700113def get_labels(exclude_filters=(), **filter_data):
showardc92da832009-04-07 18:14:34 +0000114 """\
Jiaxi Luo31874592014-06-11 10:36:35 -0700115 @param exclude_filters: A sequence of dictionaries of filters.
116
showardc92da832009-04-07 18:14:34 +0000117 @returns A sequence of nested dictionaries of label information.
118 """
Jiaxi Luo31874592014-06-11 10:36:35 -0700119 labels = models.Label.query_objects(filter_data)
120 for exclude_filter in exclude_filters:
121 labels = labels.exclude(**exclude_filter)
122 return rpc_utils.prepare_rows_as_nested_dicts(labels, ('atomic_group',))
showardc92da832009-04-07 18:14:34 +0000123
124
125# atomic groups
126
showarde9450c92009-06-30 01:58:52 +0000127def add_atomic_group(name, max_number_of_machines=None, description=None):
showardc92da832009-04-07 18:14:34 +0000128 return models.AtomicGroup.add_object(
129 name=name, max_number_of_machines=max_number_of_machines,
130 description=description).id
131
132
133def modify_atomic_group(id, **data):
134 models.AtomicGroup.smart_get(id).update_object(data)
135
136
137def delete_atomic_group(id):
138 models.AtomicGroup.smart_get(id).delete()
139
140
141def atomic_group_add_labels(id, labels):
142 label_objs = models.Label.smart_get_bulk(labels)
143 models.AtomicGroup.smart_get(id).label_set.add(*label_objs)
144
145
146def atomic_group_remove_labels(id, labels):
147 label_objs = models.Label.smart_get_bulk(labels)
148 models.AtomicGroup.smart_get(id).label_set.remove(*label_objs)
149
150
151def get_atomic_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000152 return rpc_utils.prepare_for_serialization(
showardc92da832009-04-07 18:14:34 +0000153 models.AtomicGroup.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000154
155
156# hosts
157
showarddf062562008-07-03 19:56:37 +0000158def add_host(hostname, status=None, locked=None, protection=None):
jadmanski0afbb632008-06-06 21:10:57 +0000159 return models.Host.add_object(hostname=hostname, status=status,
showarddf062562008-07-03 19:56:37 +0000160 locked=locked, protection=protection).id
mblighe8819cd2008-02-15 16:48:40 +0000161
162
Jakob Juelich50e91f72014-10-01 12:43:23 -0700163@rpc_utils.forward_single_host_rpc_to_shard
mblighe8819cd2008-02-15 16:48:40 +0000164def modify_host(id, **data):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700165 """Modify local attributes of a host.
166
167 If this is called on the master, but the host is assigned to a shard, this
168 will also forward the call to the responsible shard. This means i.e. if a
169 host is being locked using this function, this change will also propagate to
170 shards.
171
172 @param id: id of the host to modify.
173 @param **data: key=value pairs of values to set on the host.
174 """
showardbe0d8692009-08-20 23:42:44 +0000175 rpc_utils.check_modify_host(data)
showardce7c0922009-09-11 18:39:24 +0000176 host = models.Host.smart_get(id)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700177
showardce7c0922009-09-11 18:39:24 +0000178 rpc_utils.check_modify_host_locking(host, data)
179 host.update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000180
181
showard276f9442009-05-20 00:33:16 +0000182def modify_hosts(host_filter_data, update_data):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700183 """Modify local attributes of multiple hosts.
184
185 If this is called on the master, but one of the hosts in that match the
186 filters is assigned to a shard, this will also forward the call to the
187 responsible shard.
188
189 The filters are always applied on the master, not on the shards. This means
190 if the states of a host differ on the master and a shard, the state on the
191 master will be used. I.e. this means:
192 A host was synced to Shard 1. On Shard 1 the status of the host was set to
193 'Repair Failed'.
194 - A call to modify_hosts with host_filter_data={'status': 'Ready'} will
195 update the host (both on the shard and on the master), because the state
196 of the host as the master knows it is still 'Ready'.
197 - A call to modify_hosts with host_filter_data={'status': 'Repair failed'
198 will not update the host, because the filter doesn't apply on the master.
199
showardbe0d8692009-08-20 23:42:44 +0000200 @param host_filter_data: Filters out which hosts to modify.
201 @param update_data: A dictionary with the changes to make to the hosts.
showard276f9442009-05-20 00:33:16 +0000202 """
showardbe0d8692009-08-20 23:42:44 +0000203 rpc_utils.check_modify_host(update_data)
showard276f9442009-05-20 00:33:16 +0000204 hosts = models.Host.query_objects(host_filter_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700205
206 affected_shard_hostnames = set()
207 affected_host_ids = []
208
Alex Miller9658a952013-05-14 16:40:02 -0700209 # Check all hosts before changing data for exception safety.
210 for host in hosts:
211 rpc_utils.check_modify_host_locking(host, update_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700212 if host.shard:
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800213 affected_shard_hostnames.add(host.shard.rpc_hostname())
Jakob Juelich50e91f72014-10-01 12:43:23 -0700214 affected_host_ids.append(host.id)
215
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800216 if not utils.is_shard():
Jakob Juelich50e91f72014-10-01 12:43:23 -0700217 # Caution: Changing the filter from the original here. See docstring.
218 rpc_utils.run_rpc_on_multiple_hostnames(
219 'modify_hosts', affected_shard_hostnames,
220 host_filter_data={'id__in': affected_host_ids},
221 update_data=update_data)
222
showard276f9442009-05-20 00:33:16 +0000223 for host in hosts:
224 host.update_object(update_data)
225
226
mblighe8819cd2008-02-15 16:48:40 +0000227def host_add_labels(id, labels):
showardbe3ec042008-11-12 18:16:07 +0000228 labels = models.Label.smart_get_bulk(labels)
showardcafd16e2009-05-29 18:37:49 +0000229 host = models.Host.smart_get(id)
230
231 platforms = [label.name for label in labels if label.platform]
232 if len(platforms) > 1:
233 raise model_logic.ValidationError(
234 {'labels': 'Adding more than one platform label: %s' %
235 ', '.join(platforms)})
236 if len(platforms) == 1:
237 models.Host.check_no_platform([host])
238 host.labels.add(*labels)
mblighe8819cd2008-02-15 16:48:40 +0000239
240
241def host_remove_labels(id, labels):
showardbe3ec042008-11-12 18:16:07 +0000242 labels = models.Label.smart_get_bulk(labels)
jadmanski0afbb632008-06-06 21:10:57 +0000243 models.Host.smart_get(id).labels.remove(*labels)
mblighe8819cd2008-02-15 16:48:40 +0000244
245
MK Ryuacf35922014-10-03 14:56:49 -0700246def get_host_attribute(attribute, **host_filter_data):
247 """
248 @param attribute: string name of attribute
249 @param host_filter_data: filter data to apply to Hosts to choose hosts to
250 act upon
251 """
252 hosts = rpc_utils.get_host_query((), False, False, True, host_filter_data)
253 hosts = list(hosts)
254 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
255 'attribute_list')
256 host_attr_dicts = []
257 for host_obj in hosts:
258 for attr_obj in host_obj.attribute_list:
259 if attr_obj.attribute == attribute:
260 host_attr_dicts.append(attr_obj.get_object_dict())
261 return rpc_utils.prepare_for_serialization(host_attr_dicts)
262
263
showard0957a842009-05-11 19:25:08 +0000264def set_host_attribute(attribute, value, **host_filter_data):
265 """
266 @param attribute string name of attribute
267 @param value string, or None to delete an attribute
268 @param host_filter_data filter data to apply to Hosts to choose hosts to act
269 upon
270 """
271 assert host_filter_data # disallow accidental actions on all hosts
272 hosts = models.Host.query_objects(host_filter_data)
273 models.AclGroup.check_for_acl_violation_hosts(hosts)
274
275 for host in hosts:
showardf8b19042009-05-12 17:22:49 +0000276 host.set_or_delete_attribute(attribute, value)
showard0957a842009-05-11 19:25:08 +0000277
278
Jakob Juelich50e91f72014-10-01 12:43:23 -0700279@rpc_utils.forward_single_host_rpc_to_shard
mblighe8819cd2008-02-15 16:48:40 +0000280def delete_host(id):
jadmanski0afbb632008-06-06 21:10:57 +0000281 models.Host.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000282
283
showard87cc38f2009-08-20 23:37:04 +0000284def get_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000285 exclude_atomic_group_hosts=False, valid_only=True, **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000286 """
287 @param multiple_labels: match hosts in all of the labels given. Should
288 be a list of label names.
289 @param exclude_only_if_needed_labels: Exclude hosts with at least one
290 "only_if_needed" label applied.
291 @param exclude_atomic_group_hosts: Exclude hosts that have one or more
292 atomic group labels associated with them.
jadmanski0afbb632008-06-06 21:10:57 +0000293 """
showard43a3d262008-11-12 18:17:05 +0000294 hosts = rpc_utils.get_host_query(multiple_labels,
295 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000296 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000297 valid_only, filter_data)
showard0957a842009-05-11 19:25:08 +0000298 hosts = list(hosts)
299 models.Host.objects.populate_relationships(hosts, models.Label,
300 'label_list')
301 models.Host.objects.populate_relationships(hosts, models.AclGroup,
302 'acl_list')
303 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
304 'attribute_list')
showard43a3d262008-11-12 18:17:05 +0000305 host_dicts = []
306 for host_obj in hosts:
307 host_dict = host_obj.get_object_dict()
showard0957a842009-05-11 19:25:08 +0000308 host_dict['labels'] = [label.name for label in host_obj.label_list]
showard909c9142009-07-07 20:54:42 +0000309 host_dict['platform'], host_dict['atomic_group'] = (rpc_utils.
310 find_platform_and_atomic_group(host_obj))
showard0957a842009-05-11 19:25:08 +0000311 host_dict['acls'] = [acl.name for acl in host_obj.acl_list]
312 host_dict['attributes'] = dict((attribute.attribute, attribute.value)
313 for attribute in host_obj.attribute_list)
showard43a3d262008-11-12 18:17:05 +0000314 host_dicts.append(host_dict)
315 return rpc_utils.prepare_for_serialization(host_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000316
317
showard87cc38f2009-08-20 23:37:04 +0000318def get_num_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000319 exclude_atomic_group_hosts=False, valid_only=True,
320 **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000321 """
322 Same parameters as get_hosts().
323
324 @returns The number of matching hosts.
325 """
showard43a3d262008-11-12 18:17:05 +0000326 hosts = rpc_utils.get_host_query(multiple_labels,
327 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000328 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000329 valid_only, filter_data)
showard43a3d262008-11-12 18:17:05 +0000330 return hosts.count()
showard1385b162008-03-13 15:59:40 +0000331
mblighe8819cd2008-02-15 16:48:40 +0000332
333# tests
334
showard909c7a62008-07-15 21:52:38 +0000335def add_test(name, test_type, path, author=None, dependencies=None,
showard3d9899a2008-07-31 02:11:58 +0000336 experimental=True, run_verify=None, test_class=None,
showard909c7a62008-07-15 21:52:38 +0000337 test_time=None, test_category=None, description=None,
338 sync_count=1):
jadmanski0afbb632008-06-06 21:10:57 +0000339 return models.Test.add_object(name=name, test_type=test_type, path=path,
showard909c7a62008-07-15 21:52:38 +0000340 author=author, dependencies=dependencies,
341 experimental=experimental,
342 run_verify=run_verify, test_time=test_time,
343 test_category=test_category,
344 sync_count=sync_count,
jadmanski0afbb632008-06-06 21:10:57 +0000345 test_class=test_class,
346 description=description).id
mblighe8819cd2008-02-15 16:48:40 +0000347
348
349def modify_test(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000350 models.Test.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000351
352
353def delete_test(id):
jadmanski0afbb632008-06-06 21:10:57 +0000354 models.Test.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000355
356
357def get_tests(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000358 return rpc_utils.prepare_for_serialization(
359 models.Test.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000360
361
Moises Osorio2dc7a102014-12-02 18:24:02 -0800362@_timer.decorate
363def get_tests_status_counts_by_job_name_label(job_name_prefix, label_name):
364 """Gets the counts of all passed and failed tests from the matching jobs.
365
366 @param job_name_prefix: Name prefix of the jobs to get the summary from, e.g.,
367 'butterfly-release/R40-6457.21.0/bvt-cq/'.
368 @param label_name: Label that must be set in the jobs, e.g.,
369 'cros-version:butterfly-release/R40-6457.21.0'.
370
371 @returns A summary of the counts of all the passed and failed tests.
372 """
373 job_ids = list(models.Job.objects.filter(
374 name__startswith=job_name_prefix,
375 dependency_labels__name=label_name).values_list(
376 'pk', flat=True))
377 summary = {'passed': 0, 'failed': 0}
378 if not job_ids:
379 return summary
380
381 counts = (tko_models.TestView.objects.filter(
382 afe_job_id__in=job_ids).exclude(
383 test_name='SERVER_JOB').exclude(
384 test_name__startswith='CLIENT_JOB').values(
385 'status').annotate(
386 count=Count('status')))
387 for status in counts:
388 if status['status'] == 'GOOD':
389 summary['passed'] += status['count']
390 else:
391 summary['failed'] += status['count']
392 return summary
393
394
showard2b9a88b2008-06-13 20:55:03 +0000395# profilers
396
397def add_profiler(name, description=None):
398 return models.Profiler.add_object(name=name, description=description).id
399
400
401def modify_profiler(id, **data):
402 models.Profiler.smart_get(id).update_object(data)
403
404
405def delete_profiler(id):
406 models.Profiler.smart_get(id).delete()
407
408
409def get_profilers(**filter_data):
410 return rpc_utils.prepare_for_serialization(
411 models.Profiler.list_objects(filter_data))
412
413
mblighe8819cd2008-02-15 16:48:40 +0000414# users
415
416def add_user(login, access_level=None):
jadmanski0afbb632008-06-06 21:10:57 +0000417 return models.User.add_object(login=login, access_level=access_level).id
mblighe8819cd2008-02-15 16:48:40 +0000418
419
420def modify_user(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000421 models.User.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000422
423
424def delete_user(id):
jadmanski0afbb632008-06-06 21:10:57 +0000425 models.User.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000426
427
428def get_users(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000429 return rpc_utils.prepare_for_serialization(
430 models.User.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000431
432
433# acl groups
434
435def add_acl_group(name, description=None):
showard04f2cd82008-07-25 20:53:31 +0000436 group = models.AclGroup.add_object(name=name, description=description)
showard64a95952010-01-13 21:27:16 +0000437 group.users.add(models.User.current_user())
showard04f2cd82008-07-25 20:53:31 +0000438 return group.id
mblighe8819cd2008-02-15 16:48:40 +0000439
440
441def modify_acl_group(id, **data):
showard04f2cd82008-07-25 20:53:31 +0000442 group = models.AclGroup.smart_get(id)
443 group.check_for_acl_violation_acl_group()
444 group.update_object(data)
445 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000446
447
448def acl_group_add_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000449 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000450 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000451 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000452 group.users.add(*users)
mblighe8819cd2008-02-15 16:48:40 +0000453
454
455def acl_group_remove_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000456 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000457 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000458 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000459 group.users.remove(*users)
showard04f2cd82008-07-25 20:53:31 +0000460 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000461
462
463def acl_group_add_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000464 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000465 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000466 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000467 group.hosts.add(*hosts)
showard08f981b2008-06-24 21:59:03 +0000468 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000469
470
471def acl_group_remove_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000472 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000473 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000474 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000475 group.hosts.remove(*hosts)
showard08f981b2008-06-24 21:59:03 +0000476 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000477
478
479def delete_acl_group(id):
jadmanski0afbb632008-06-06 21:10:57 +0000480 models.AclGroup.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000481
482
483def get_acl_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000484 acl_groups = models.AclGroup.list_objects(filter_data)
485 for acl_group in acl_groups:
486 acl_group_obj = models.AclGroup.objects.get(id=acl_group['id'])
487 acl_group['users'] = [user.login
488 for user in acl_group_obj.users.all()]
489 acl_group['hosts'] = [host.hostname
490 for host in acl_group_obj.hosts.all()]
491 return rpc_utils.prepare_for_serialization(acl_groups)
mblighe8819cd2008-02-15 16:48:40 +0000492
493
494# jobs
495
mbligh120351e2009-01-24 01:40:45 +0000496def generate_control_file(tests=(), kernel=None, label=None, profilers=(),
showard91f85102009-10-12 20:34:52 +0000497 client_control_file='', use_container=False,
showard232b7ae2009-11-10 00:46:48 +0000498 profile_only=None, upload_kernel_config=False):
jadmanski0afbb632008-06-06 21:10:57 +0000499 """
mbligh120351e2009-01-24 01:40:45 +0000500 Generates a client-side control file to load a kernel and run tests.
501
502 @param tests List of tests to run.
mbligha3c58d22009-08-24 22:01:51 +0000503 @param kernel A list of kernel info dictionaries configuring which kernels
504 to boot for this job and other options for them
mbligh120351e2009-01-24 01:40:45 +0000505 @param label Name of label to grab kernel config from.
506 @param profilers List of profilers to activate during the job.
507 @param client_control_file The contents of a client-side control file to
508 run at the end of all tests. If this is supplied, all tests must be
509 client side.
510 TODO: in the future we should support server control files directly
511 to wrap with a kernel. That'll require changing the parameter
512 name and adding a boolean to indicate if it is a client or server
513 control file.
514 @param use_container unused argument today. TODO: Enable containers
515 on the host during a client side test.
showard91f85102009-10-12 20:34:52 +0000516 @param profile_only A boolean that indicates what default profile_only
517 mode to use in the control file. Passing None will generate a
518 control file that does not explcitly set the default mode at all.
showard232b7ae2009-11-10 00:46:48 +0000519 @param upload_kernel_config: if enabled it will generate server control
520 file code that uploads the kernel config file to the client and
521 tells the client of the new (local) path when compiling the kernel;
522 the tests must be server side tests
mbligh120351e2009-01-24 01:40:45 +0000523
524 @returns a dict with the following keys:
525 control_file: str, The control file text.
526 is_server: bool, is the control file a server-side control file?
527 synch_count: How many machines the job uses per autoserv execution.
528 synch_count == 1 means the job is asynchronous.
529 dependencies: A list of the names of labels on which the job depends.
530 """
showardd86debe2009-06-10 17:37:56 +0000531 if not tests and not client_control_file:
showard2bab8f42008-11-12 18:15:22 +0000532 return dict(control_file='', is_server=False, synch_count=1,
showard989f25d2008-10-01 11:38:11 +0000533 dependencies=[])
mblighe8819cd2008-02-15 16:48:40 +0000534
showard989f25d2008-10-01 11:38:11 +0000535 cf_info, test_objects, profiler_objects, label = (
showard2b9a88b2008-06-13 20:55:03 +0000536 rpc_utils.prepare_generate_control_file(tests, kernel, label,
537 profilers))
showard989f25d2008-10-01 11:38:11 +0000538 cf_info['control_file'] = control_file.generate_control(
mbligha3c58d22009-08-24 22:01:51 +0000539 tests=test_objects, kernels=kernel, platform=label,
mbligh120351e2009-01-24 01:40:45 +0000540 profilers=profiler_objects, is_server=cf_info['is_server'],
showard232b7ae2009-11-10 00:46:48 +0000541 client_control_file=client_control_file, profile_only=profile_only,
542 upload_kernel_config=upload_kernel_config)
showard989f25d2008-10-01 11:38:11 +0000543 return cf_info
mblighe8819cd2008-02-15 16:48:40 +0000544
545
jamesren4a41e012010-07-16 22:33:48 +0000546def create_parameterized_job(name, priority, test, parameters, kernel=None,
547 label=None, profilers=(), profiler_parameters=None,
548 use_container=False, profile_only=None,
549 upload_kernel_config=False, hosts=(),
550 meta_hosts=(), one_time_hosts=(),
551 atomic_group_name=None, synch_count=None,
552 is_template=False, timeout=None,
Simran Basi7e605742013-11-12 13:43:36 -0800553 timeout_mins=None, max_runtime_mins=None,
554 run_verify=False, email_list='', dependencies=(),
555 reboot_before=None, reboot_after=None,
556 parse_failed_repair=None, hostless=False,
557 keyvals=None, drone_set=None, run_reset=True):
jamesren4a41e012010-07-16 22:33:48 +0000558 """
559 Creates and enqueues a parameterized job.
560
561 Most parameters a combination of the parameters for generate_control_file()
562 and create_job(), with the exception of:
563
564 @param test name or ID of the test to run
565 @param parameters a map of parameter name ->
566 tuple of (param value, param type)
567 @param profiler_parameters a dictionary of parameters for the profilers:
568 key: profiler name
569 value: dict of param name -> tuple of
570 (param value,
571 param type)
572 """
573 # Save the values of the passed arguments here. What we're going to do with
574 # them is pass them all to rpc_utils.get_create_job_common_args(), which
575 # will extract the subset of these arguments that apply for
576 # rpc_utils.create_job_common(), which we then pass in to that function.
577 args = locals()
578
579 # Set up the parameterized job configs
580 test_obj = models.Test.smart_get(test)
Aviv Keshet3dd8beb2013-05-13 17:36:04 -0700581 control_type = test_obj.test_type
jamesren4a41e012010-07-16 22:33:48 +0000582
583 try:
584 label = models.Label.smart_get(label)
585 except models.Label.DoesNotExist:
586 label = None
587
588 kernel_objs = models.Kernel.create_kernels(kernel)
589 profiler_objs = [models.Profiler.smart_get(profiler)
590 for profiler in profilers]
591
592 parameterized_job = models.ParameterizedJob.objects.create(
593 test=test_obj, label=label, use_container=use_container,
594 profile_only=profile_only,
595 upload_kernel_config=upload_kernel_config)
596 parameterized_job.kernels.add(*kernel_objs)
597
598 for profiler in profiler_objs:
599 parameterized_profiler = models.ParameterizedJobProfiler.objects.create(
600 parameterized_job=parameterized_job,
601 profiler=profiler)
602 profiler_params = profiler_parameters.get(profiler.name, {})
603 for name, (value, param_type) in profiler_params.iteritems():
604 models.ParameterizedJobProfilerParameter.objects.create(
605 parameterized_job_profiler=parameterized_profiler,
606 parameter_name=name,
607 parameter_value=value,
608 parameter_type=param_type)
609
610 try:
611 for parameter in test_obj.testparameter_set.all():
612 if parameter.name in parameters:
613 param_value, param_type = parameters.pop(parameter.name)
614 parameterized_job.parameterizedjobparameter_set.create(
615 test_parameter=parameter, parameter_value=param_value,
616 parameter_type=param_type)
617
618 if parameters:
619 raise Exception('Extra parameters remain: %r' % parameters)
620
621 return rpc_utils.create_job_common(
622 parameterized_job=parameterized_job.id,
623 control_type=control_type,
624 **rpc_utils.get_create_job_common_args(args))
625 except:
626 parameterized_job.delete()
627 raise
628
629
Simran Basib6ec8ae2014-04-23 12:05:08 -0700630def create_job_page_handler(name, priority, control_file, control_type,
631 image=None, hostless=False, **kwargs):
632 """\
633 Create and enqueue a job.
634
635 @param name name of this job
636 @param priority Integer priority of this job. Higher is more important.
637 @param control_file String contents of the control file.
638 @param control_type Type of control file, Client or Server.
639 @param kwargs extra args that will be required by create_suite_job or
640 create_job.
641
642 @returns The created Job id number.
643 """
644 control_file = rpc_utils.encode_ascii(control_file)
Jiaxi Luodd67beb2014-07-18 16:28:31 -0700645 if not control_file:
646 raise model_logic.ValidationError({
647 'control_file' : "Control file cannot be empty"})
Simran Basib6ec8ae2014-04-23 12:05:08 -0700648
649 if image and hostless:
650 return site_rpc_interface.create_suite_job(
651 name=name, control_file=control_file, priority=priority,
652 build=image, **kwargs)
653 return create_job(name, priority, control_file, control_type, image=image,
654 hostless=hostless, **kwargs)
655
656
showard12f3e322009-05-13 21:27:42 +0000657def create_job(name, priority, control_file, control_type,
658 hosts=(), meta_hosts=(), one_time_hosts=(),
659 atomic_group_name=None, synch_count=None, is_template=False,
Simran Basi7e605742013-11-12 13:43:36 -0800660 timeout=None, timeout_mins=None, max_runtime_mins=None,
661 run_verify=False, email_list='', dependencies=(),
662 reboot_before=None, reboot_after=None, parse_failed_repair=None,
663 hostless=False, keyvals=None, drone_set=None, image=None,
Jiaxi Luo90190c92014-06-18 12:35:57 -0700664 parent_job_id=None, test_retry=0, run_reset=True, args=(),
665 **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +0000666 """\
667 Create and enqueue a job.
mblighe8819cd2008-02-15 16:48:40 +0000668
showarda1e74b32009-05-12 17:32:04 +0000669 @param name name of this job
Alex Miller7d658cf2013-09-04 16:00:35 -0700670 @param priority Integer priority of this job. Higher is more important.
showarda1e74b32009-05-12 17:32:04 +0000671 @param control_file String contents of the control file.
672 @param control_type Type of control file, Client or Server.
673 @param synch_count How many machines the job uses per autoserv execution.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700674 synch_count == 1 means the job is asynchronous. If an atomic group is
675 given this value is treated as a minimum.
showarda1e74b32009-05-12 17:32:04 +0000676 @param is_template If true then create a template job.
677 @param timeout Hours after this call returns until the job times out.
Simran Basi7e605742013-11-12 13:43:36 -0800678 @param timeout_mins Minutes after this call returns until the job times
Jiaxi Luo90190c92014-06-18 12:35:57 -0700679 out.
Simran Basi34217022012-11-06 13:43:15 -0800680 @param max_runtime_mins Minutes from job starting time until job times out
showarda1e74b32009-05-12 17:32:04 +0000681 @param run_verify Should the host be verified before running the test?
682 @param email_list String containing emails to mail when the job is done
683 @param dependencies List of label names on which this job depends
684 @param reboot_before Never, If dirty, or Always
685 @param reboot_after Never, If all tests passed, or Always
686 @param parse_failed_repair if true, results of failed repairs launched by
Jiaxi Luo90190c92014-06-18 12:35:57 -0700687 this job will be parsed as part of the job.
showarda9545c02009-12-18 22:44:26 +0000688 @param hostless if true, create a hostless job
showardc1a98d12010-01-15 00:22:22 +0000689 @param keyvals dict of keyvals to associate with the job
showarda1e74b32009-05-12 17:32:04 +0000690 @param hosts List of hosts to run job on.
691 @param meta_hosts List where each entry is a label name, and for each entry
Jiaxi Luo90190c92014-06-18 12:35:57 -0700692 one host will be chosen from that label to run the job on.
showarda1e74b32009-05-12 17:32:04 +0000693 @param one_time_hosts List of hosts not in the database to run the job on.
694 @param atomic_group_name The name of an atomic group to schedule the job on.
jamesren76fcf192010-04-21 20:39:50 +0000695 @param drone_set The name of the drone set to run this test on.
Paul Pendlebury5a8c6ad2011-02-01 07:20:17 -0800696 @param image OS image to install before running job.
Aviv Keshet0b9cfc92013-02-05 11:36:02 -0800697 @param parent_job_id id of a job considered to be parent of created job.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700698 @param test_retry Number of times to retry test if the test did not
Jiaxi Luo90190c92014-06-18 12:35:57 -0700699 complete successfully. (optional, default: 0)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700700 @param run_reset Should the host be reset before running the test?
Jiaxi Luo90190c92014-06-18 12:35:57 -0700701 @param args A list of args to be injected into control file.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700702 @param kwargs extra keyword args. NOT USED.
showardc92da832009-04-07 18:14:34 +0000703
704 @returns The created Job id number.
jadmanski0afbb632008-06-06 21:10:57 +0000705 """
Jiaxi Luo90190c92014-06-18 12:35:57 -0700706 if args:
707 control_file = tools.inject_vars({'args': args}, control_file)
708
Simran Basiab5a1bf2014-05-28 15:39:44 -0700709 if image is None:
710 return rpc_utils.create_job_common(
711 **rpc_utils.get_create_job_common_args(locals()))
712
713 # When image is supplied use a known parameterized test already in the
714 # database to pass the OS image path from the front end, through the
715 # scheduler, and finally to autoserv as the --image parameter.
716
717 # The test autoupdate_ParameterizedJob is in afe_autotests and used to
718 # instantiate a Test object and from there a ParameterizedJob.
719 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
720 known_parameterized_job = models.ParameterizedJob.objects.create(
721 test=known_test_obj)
722
723 # autoupdate_ParameterizedJob has a single parameter, the image parameter,
724 # stored in the table afe_test_parameters. We retrieve and set this
725 # instance of the parameter to the OS image path.
726 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
727 name='image')
728 known_parameterized_job.parameterizedjobparameter_set.create(
729 test_parameter=image_parameter, parameter_value=image,
730 parameter_type='string')
731
732 # By passing a parameterized_job to create_job_common the job entry in
733 # the afe_jobs table will have the field parameterized_job_id set.
734 # The scheduler uses this id in the afe_parameterized_jobs table to
735 # match this job to our known test, and then with the
736 # afe_parameterized_job_parameters table to get the actual image path.
jamesren4a41e012010-07-16 22:33:48 +0000737 return rpc_utils.create_job_common(
Simran Basiab5a1bf2014-05-28 15:39:44 -0700738 parameterized_job=known_parameterized_job.id,
jamesren4a41e012010-07-16 22:33:48 +0000739 **rpc_utils.get_create_job_common_args(locals()))
mblighe8819cd2008-02-15 16:48:40 +0000740
741
showard9dbdcda2008-10-14 17:34:36 +0000742def abort_host_queue_entries(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000743 """\
showard9dbdcda2008-10-14 17:34:36 +0000744 Abort a set of host queue entries.
Fang Deng63b0e452014-12-19 14:38:15 -0800745
746 @return: A list of dictionaries, each contains information
747 about an aborted HQE.
jadmanski0afbb632008-06-06 21:10:57 +0000748 """
showard9dbdcda2008-10-14 17:34:36 +0000749 query = models.HostQueueEntry.query_objects(filter_data)
beepsfaecbce2013-10-29 11:35:10 -0700750
751 # Dont allow aborts on:
752 # 1. Jobs that have already completed (whether or not they were aborted)
753 # 2. Jobs that we have already been aborted (but may not have completed)
754 query = query.filter(complete=False).filter(aborted=False)
showarddc817512008-11-12 18:16:41 +0000755 models.AclGroup.check_abort_permissions(query)
showard9dbdcda2008-10-14 17:34:36 +0000756 host_queue_entries = list(query.select_related())
showard2bab8f42008-11-12 18:15:22 +0000757 rpc_utils.check_abort_synchronous_jobs(host_queue_entries)
mblighe8819cd2008-02-15 16:48:40 +0000758
Simran Basic1b26762013-06-26 14:23:21 -0700759 models.HostQueueEntry.abort_host_queue_entries(host_queue_entries)
Fang Deng63b0e452014-12-19 14:38:15 -0800760 hqe_info = [{'HostQueueEntry': hqe.id, 'Job': hqe.job_id,
761 'Job name': hqe.job.name} for hqe in host_queue_entries]
762 return hqe_info
showard9d821ab2008-07-11 16:54:29 +0000763
764
beeps8bb1f7d2013-08-05 01:30:09 -0700765def abort_special_tasks(**filter_data):
766 """\
767 Abort the special task, or tasks, specified in the filter.
768 """
769 query = models.SpecialTask.query_objects(filter_data)
770 special_tasks = query.filter(is_active=True)
771 for task in special_tasks:
772 task.abort()
773
774
Simran Basi73dae552013-02-25 14:57:46 -0800775def _call_special_tasks_on_hosts(task, hosts):
776 """\
777 Schedules a set of hosts for a special task.
778
779 @returns A list of hostnames that a special task was created for.
780 """
781 models.AclGroup.check_for_acl_violation_hosts(hosts)
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -0800782 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800783 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -0800784 raise ValueError('The following hosts are on shards, please '
785 'follow the link to the shards and create jobs '
786 'there instead. %s.' % shard_host_map)
Simran Basi73dae552013-02-25 14:57:46 -0800787 for host in hosts:
788 models.SpecialTask.schedule_special_task(host, task)
789 return list(sorted(host.hostname for host in hosts))
790
791
showard1ff7b2e2009-05-15 23:17:18 +0000792def reverify_hosts(**filter_data):
793 """\
794 Schedules a set of hosts for verify.
mbligh4e545a52009-12-19 05:30:39 +0000795
796 @returns A list of hostnames that a verify task was created for.
showard1ff7b2e2009-05-15 23:17:18 +0000797 """
Prashanth Balasubramanian40981232014-12-16 19:01:58 -0800798 hosts = models.Host.query_objects(filter_data)
799 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts, rpc_hostnames=True)
800
801 # Filter out hosts on a shard from those on the master, forward
802 # rpcs to the shard with an additional hostname__in filter, and
803 # create a local SpecialTask for each remaining host.
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800804 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian40981232014-12-16 19:01:58 -0800805 hosts = [h for h in hosts if h.shard is None]
806 for shard, hostnames in shard_host_map.iteritems():
807
808 # The main client of this module is the frontend website, and
809 # it invokes it with an 'id' or an 'id__in' filter. Regardless,
810 # the 'hostname' filter should narrow down the list of hosts on
811 # each shard even though we supply all the ids in filter_data.
812 # This method uses hostname instead of id because it fits better
813 # with the overall architecture of redirection functions in rpc_utils.
814 shard_filter = filter_data.copy()
815 shard_filter['hostname__in'] = hostnames
816 rpc_utils.run_rpc_on_multiple_hostnames(
817 'reverify_hosts', [shard], **shard_filter)
818
819 # There is a race condition here if someone assigns a shard to one of these
820 # hosts before we create the task. The host will stay on the master if:
821 # 1. The host is not Ready
822 # 2. The host is Ready but has a task
823 # But if the host is Ready and doesn't have a task yet, it will get sent
824 # to the shard as we're creating a task here.
825
826 # Given that we only rarely verify Ready hosts it isn't worth putting this
827 # entire method in a transaction. The worst case scenario is that we have
828 # a verify running on a Ready host while the shard is using it, if the verify
829 # fails no subsequent tasks will be created against the host on the master,
830 # and verifies are safe enough that this is OK.
831 return _call_special_tasks_on_hosts(models.SpecialTask.Task.VERIFY, hosts)
Simran Basi73dae552013-02-25 14:57:46 -0800832
833
834def repair_hosts(**filter_data):
835 """\
836 Schedules a set of hosts for repair.
837
838 @returns A list of hostnames that a repair task was created for.
839 """
840 return _call_special_tasks_on_hosts(models.SpecialTask.Task.REPAIR,
841 models.Host.query_objects(filter_data))
showard1ff7b2e2009-05-15 23:17:18 +0000842
843
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700844def get_jobs(not_yet_run=False, running=False, finished=False,
845 suite=False, sub=False, standalone=False, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000846 """\
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700847 Extra status filter args for get_jobs:
jadmanski0afbb632008-06-06 21:10:57 +0000848 -not_yet_run: Include only jobs that have not yet started running.
849 -running: Include only jobs that have start running but for which not
850 all hosts have completed.
851 -finished: Include only jobs for which all hosts have completed (or
852 aborted).
853 At most one of these three fields should be specified.
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700854
855 Extra type filter args for get_jobs:
856 -suite: Include only jobs with child jobs.
857 -sub: Include only jobs with a parent job.
858 -standalone: Inlcude only jobs with no child or parent jobs.
859 At most one of these three fields should be specified.
jadmanski0afbb632008-06-06 21:10:57 +0000860 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700861 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
862 running,
863 finished)
864 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
865 suite,
866 sub,
867 standalone)
showard0957a842009-05-11 19:25:08 +0000868 job_dicts = []
869 jobs = list(models.Job.query_objects(filter_data))
870 models.Job.objects.populate_relationships(jobs, models.Label,
871 'dependencies')
showardc1a98d12010-01-15 00:22:22 +0000872 models.Job.objects.populate_relationships(jobs, models.JobKeyval, 'keyvals')
showard0957a842009-05-11 19:25:08 +0000873 for job in jobs:
874 job_dict = job.get_object_dict()
875 job_dict['dependencies'] = ','.join(label.name
876 for label in job.dependencies)
showardc1a98d12010-01-15 00:22:22 +0000877 job_dict['keyvals'] = dict((keyval.key, keyval.value)
878 for keyval in job.keyvals)
Eric Lid23bc192011-02-09 14:38:57 -0800879 if job.parameterized_job:
880 job_dict['image'] = get_parameterized_autoupdate_image_url(job)
showard0957a842009-05-11 19:25:08 +0000881 job_dicts.append(job_dict)
882 return rpc_utils.prepare_for_serialization(job_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000883
884
885def get_num_jobs(not_yet_run=False, running=False, finished=False,
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700886 suite=False, sub=False, standalone=False,
jadmanski0afbb632008-06-06 21:10:57 +0000887 **filter_data):
888 """\
889 See get_jobs() for documentation of extra filter parameters.
890 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700891 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
892 running,
893 finished)
894 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
895 suite,
896 sub,
897 standalone)
jadmanski0afbb632008-06-06 21:10:57 +0000898 return models.Job.query_count(filter_data)
mblighe8819cd2008-02-15 16:48:40 +0000899
900
mblighe8819cd2008-02-15 16:48:40 +0000901def get_jobs_summary(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000902 """\
Jiaxi Luoaac54572014-06-04 13:57:02 -0700903 Like get_jobs(), but adds 'status_counts' and 'result_counts' field.
904
905 'status_counts' filed is a dictionary mapping status strings to the number
906 of hosts currently with that status, i.e. {'Queued' : 4, 'Running' : 2}.
907
908 'result_counts' field is piped to tko's rpc_interface and has the return
909 format specified under get_group_counts.
jadmanski0afbb632008-06-06 21:10:57 +0000910 """
911 jobs = get_jobs(**filter_data)
912 ids = [job['id'] for job in jobs]
913 all_status_counts = models.Job.objects.get_status_counts(ids)
914 for job in jobs:
915 job['status_counts'] = all_status_counts[job['id']]
Jiaxi Luoaac54572014-06-04 13:57:02 -0700916 job['result_counts'] = tko_rpc_interface.get_status_counts(
917 ['afe_job_id', 'afe_job_id'],
918 header_groups=[['afe_job_id'], ['afe_job_id']],
919 **{'afe_job_id': job['id']})
jadmanski0afbb632008-06-06 21:10:57 +0000920 return rpc_utils.prepare_for_serialization(jobs)
mblighe8819cd2008-02-15 16:48:40 +0000921
922
showarda965cef2009-05-15 23:17:41 +0000923def get_info_for_clone(id, preserve_metahosts, queue_entry_filter_data=None):
showarda8709c52008-07-03 19:44:54 +0000924 """\
925 Retrieves all the information needed to clone a job.
926 """
showarda8709c52008-07-03 19:44:54 +0000927 job = models.Job.objects.get(id=id)
showard29f7cd22009-04-29 21:16:24 +0000928 job_info = rpc_utils.get_job_info(job,
showarda965cef2009-05-15 23:17:41 +0000929 preserve_metahosts,
930 queue_entry_filter_data)
showard945072f2008-09-03 20:34:59 +0000931
showardd9992fe2008-07-31 02:15:03 +0000932 host_dicts = []
showard29f7cd22009-04-29 21:16:24 +0000933 for host in job_info['hosts']:
934 host_dict = get_hosts(id=host.id)[0]
935 other_labels = host_dict['labels']
936 if host_dict['platform']:
937 other_labels.remove(host_dict['platform'])
938 host_dict['other_labels'] = ', '.join(other_labels)
showardd9992fe2008-07-31 02:15:03 +0000939 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +0000940
showard29f7cd22009-04-29 21:16:24 +0000941 for host in job_info['one_time_hosts']:
942 host_dict = dict(hostname=host.hostname,
943 id=host.id,
944 platform='(one-time host)',
945 locked_text='')
946 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +0000947
showard4d077562009-05-08 18:24:36 +0000948 # convert keys from Label objects to strings (names of labels)
showard29f7cd22009-04-29 21:16:24 +0000949 meta_host_counts = dict((meta_host.name, count) for meta_host, count
showard4d077562009-05-08 18:24:36 +0000950 in job_info['meta_host_counts'].iteritems())
showard29f7cd22009-04-29 21:16:24 +0000951
952 info = dict(job=job.get_object_dict(),
953 meta_host_counts=meta_host_counts,
954 hosts=host_dicts)
955 info['job']['dependencies'] = job_info['dependencies']
956 if job_info['atomic_group']:
957 info['atomic_group_name'] = (job_info['atomic_group']).name
958 else:
959 info['atomic_group_name'] = None
jamesren2275ef12010-04-12 18:25:06 +0000960 info['hostless'] = job_info['hostless']
jamesren76fcf192010-04-21 20:39:50 +0000961 info['drone_set'] = job.drone_set and job.drone_set.name
showarda8709c52008-07-03 19:44:54 +0000962
Eric Lid23bc192011-02-09 14:38:57 -0800963 if job.parameterized_job:
964 info['job']['image'] = get_parameterized_autoupdate_image_url(job)
965
showarda8709c52008-07-03 19:44:54 +0000966 return rpc_utils.prepare_for_serialization(info)
967
968
showard34dc5fa2008-04-24 20:58:40 +0000969# host queue entries
970
Jiaxi Luo57bc1952014-07-22 15:27:30 -0700971def get_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000972 """\
showardc92da832009-04-07 18:14:34 +0000973 @returns A sequence of nested dictionaries of host and job information.
jadmanski0afbb632008-06-06 21:10:57 +0000974 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -0700975 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
976 'started_on__lte',
977 start_time,
978 end_time,
979 **filter_data)
showardc92da832009-04-07 18:14:34 +0000980 return rpc_utils.prepare_rows_as_nested_dicts(
981 models.HostQueueEntry.query_objects(filter_data),
982 ('host', 'atomic_group', 'job'))
showard34dc5fa2008-04-24 20:58:40 +0000983
984
Jiaxi Luo57bc1952014-07-22 15:27:30 -0700985def get_num_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000986 """\
987 Get the number of host queue entries associated with this job.
988 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -0700989 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
990 'started_on__lte',
991 start_time,
992 end_time,
993 **filter_data)
jadmanski0afbb632008-06-06 21:10:57 +0000994 return models.HostQueueEntry.query_count(filter_data)
showard34dc5fa2008-04-24 20:58:40 +0000995
996
showard1e935f12008-07-11 00:11:36 +0000997def get_hqe_percentage_complete(**filter_data):
998 """
showardc92da832009-04-07 18:14:34 +0000999 Computes the fraction of host queue entries matching the given filter data
showard1e935f12008-07-11 00:11:36 +00001000 that are complete.
1001 """
1002 query = models.HostQueueEntry.query_objects(filter_data)
1003 complete_count = query.filter(complete=True).count()
1004 total_count = query.count()
1005 if total_count == 0:
1006 return 1
1007 return float(complete_count) / total_count
1008
1009
showard1a5a4082009-07-28 20:01:37 +00001010# special tasks
1011
1012def get_special_tasks(**filter_data):
1013 return rpc_utils.prepare_rows_as_nested_dicts(
1014 models.SpecialTask.query_objects(filter_data),
1015 ('host', 'queue_entry'))
1016
1017
showardc0ac3a72009-07-08 21:14:45 +00001018# support for host detail view
1019
Jiaxi Luo79ce6422014-06-13 17:08:09 -07001020def get_host_queue_entries_and_special_tasks(host_id, query_start=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001021 query_limit=None, start_time=None,
1022 end_time=None):
showardc0ac3a72009-07-08 21:14:45 +00001023 """
1024 @returns an interleaved list of HostQueueEntries and SpecialTasks,
1025 in approximate run order. each dict contains keys for type, host,
1026 job, status, started_on, execution_path, and ID.
1027 """
1028 total_limit = None
1029 if query_limit is not None:
1030 total_limit = query_start + query_limit
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001031 filter_data_common = {'host': host_id,
1032 'query_limit': total_limit,
1033 'sort_by': ['-id']}
showardc0ac3a72009-07-08 21:14:45 +00001034
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001035 filter_data_queue_entries, filter_data_special_tasks = (
1036 rpc_utils.inject_times_to_hqe_special_tasks_filters(
1037 filter_data_common, start_time, end_time))
1038
1039 queue_entries = list(models.HostQueueEntry.query_objects(
1040 filter_data_queue_entries))
1041 special_tasks = list(models.SpecialTask.query_objects(
1042 filter_data_special_tasks))
showardc0ac3a72009-07-08 21:14:45 +00001043
1044 interleaved_entries = rpc_utils.interleave_entries(queue_entries,
1045 special_tasks)
1046 if query_start is not None:
1047 interleaved_entries = interleaved_entries[query_start:]
1048 if query_limit is not None:
1049 interleaved_entries = interleaved_entries[:query_limit]
1050 return rpc_utils.prepare_for_serialization(interleaved_entries)
1051
1052
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001053def get_num_host_queue_entries_and_special_tasks(host_id, start_time=None,
1054 end_time=None):
1055 filter_data_common = {'host': host_id}
1056
1057 filter_data_queue_entries, filter_data_special_tasks = (
1058 rpc_utils.inject_times_to_hqe_special_tasks_filters(
1059 filter_data_common, start_time, end_time))
1060
1061 return (models.HostQueueEntry.query_count(filter_data_queue_entries)
1062 + models.SpecialTask.query_count(filter_data_special_tasks))
showardc0ac3a72009-07-08 21:14:45 +00001063
1064
showard29f7cd22009-04-29 21:16:24 +00001065# recurring run
1066
1067def get_recurring(**filter_data):
1068 return rpc_utils.prepare_rows_as_nested_dicts(
1069 models.RecurringRun.query_objects(filter_data),
1070 ('job', 'owner'))
1071
1072
1073def get_num_recurring(**filter_data):
1074 return models.RecurringRun.query_count(filter_data)
1075
1076
1077def delete_recurring_runs(**filter_data):
1078 to_delete = models.RecurringRun.query_objects(filter_data)
1079 to_delete.delete()
1080
1081
1082def create_recurring_run(job_id, start_date, loop_period, loop_count):
showard64a95952010-01-13 21:27:16 +00001083 owner = models.User.current_user().login
showard29f7cd22009-04-29 21:16:24 +00001084 job = models.Job.objects.get(id=job_id)
1085 return job.create_recurring_job(start_date=start_date,
1086 loop_period=loop_period,
1087 loop_count=loop_count,
1088 owner=owner)
1089
1090
mblighe8819cd2008-02-15 16:48:40 +00001091# other
1092
showarde0b63622008-08-04 20:58:47 +00001093def echo(data=""):
1094 """\
1095 Returns a passed in string. For doing a basic test to see if RPC calls
1096 can successfully be made.
1097 """
1098 return data
1099
1100
showardb7a52fd2009-04-27 20:10:56 +00001101def get_motd():
1102 """\
1103 Returns the message of the day as a string.
1104 """
1105 return rpc_utils.get_motd()
1106
1107
mblighe8819cd2008-02-15 16:48:40 +00001108def get_static_data():
jadmanski0afbb632008-06-06 21:10:57 +00001109 """\
1110 Returns a dictionary containing a bunch of data that shouldn't change
1111 often and is otherwise inaccessible. This includes:
showardc92da832009-04-07 18:14:34 +00001112
1113 priorities: List of job priority choices.
1114 default_priority: Default priority value for new jobs.
1115 users: Sorted list of all users.
Jiaxi Luo31874592014-06-11 10:36:35 -07001116 labels: Sorted list of labels not start with 'cros-version' and
1117 'fw-version'.
showardc92da832009-04-07 18:14:34 +00001118 atomic_groups: Sorted list of all atomic groups.
1119 tests: Sorted list of all tests.
1120 profilers: Sorted list of all profilers.
1121 current_user: Logged-in username.
1122 host_statuses: Sorted list of possible Host statuses.
1123 job_statuses: Sorted list of possible HostQueueEntry statuses.
Simran Basi7e605742013-11-12 13:43:36 -08001124 job_timeout_default: The default job timeout length in minutes.
showarda1e74b32009-05-12 17:32:04 +00001125 parse_failed_repair_default: Default value for the parse_failed_repair job
Jiaxi Luo31874592014-06-11 10:36:35 -07001126 option.
showardc92da832009-04-07 18:14:34 +00001127 reboot_before_options: A list of valid RebootBefore string enums.
1128 reboot_after_options: A list of valid RebootAfter string enums.
1129 motd: Server's message of the day.
1130 status_dictionary: A mapping from one word job status names to a more
1131 informative description.
jadmanski0afbb632008-06-06 21:10:57 +00001132 """
showard21baa452008-10-21 00:08:39 +00001133
1134 job_fields = models.Job.get_field_dict()
jamesren76fcf192010-04-21 20:39:50 +00001135 default_drone_set_name = models.DroneSet.default_drone_set_name()
1136 drone_sets = ([default_drone_set_name] +
1137 sorted(drone_set.name for drone_set in
1138 models.DroneSet.objects.exclude(
1139 name=default_drone_set_name)))
showard21baa452008-10-21 00:08:39 +00001140
jadmanski0afbb632008-06-06 21:10:57 +00001141 result = {}
Alex Miller7d658cf2013-09-04 16:00:35 -07001142 result['priorities'] = priorities.Priority.choices()
1143 default_priority = priorities.Priority.DEFAULT
1144 result['default_priority'] = 'Default'
1145 result['max_schedulable_priority'] = priorities.Priority.DEFAULT
jadmanski0afbb632008-06-06 21:10:57 +00001146 result['users'] = get_users(sort_by=['login'])
Jiaxi Luo31874592014-06-11 10:36:35 -07001147
1148 label_exclude_filters = [{'name__startswith': 'cros-version'},
1149 {'name__startswith': 'fw-version'}]
1150 result['labels'] = get_labels(
1151 label_exclude_filters,
1152 sort_by=['-platform', 'name'])
1153
showardc92da832009-04-07 18:14:34 +00001154 result['atomic_groups'] = get_atomic_groups(sort_by=['name'])
jadmanski0afbb632008-06-06 21:10:57 +00001155 result['tests'] = get_tests(sort_by=['name'])
showard2b9a88b2008-06-13 20:55:03 +00001156 result['profilers'] = get_profilers(sort_by=['name'])
showard0fc38302008-10-23 00:44:07 +00001157 result['current_user'] = rpc_utils.prepare_for_serialization(
showard64a95952010-01-13 21:27:16 +00001158 models.User.current_user().get_object_dict())
showard2b9a88b2008-06-13 20:55:03 +00001159 result['host_statuses'] = sorted(models.Host.Status.names)
mbligh5a198b92008-12-11 19:33:29 +00001160 result['job_statuses'] = sorted(models.HostQueueEntry.Status.names)
Simran Basi7e605742013-11-12 13:43:36 -08001161 result['job_timeout_mins_default'] = models.Job.DEFAULT_TIMEOUT_MINS
Simran Basi34217022012-11-06 13:43:15 -08001162 result['job_max_runtime_mins_default'] = (
1163 models.Job.DEFAULT_MAX_RUNTIME_MINS)
showarda1e74b32009-05-12 17:32:04 +00001164 result['parse_failed_repair_default'] = bool(
1165 models.Job.DEFAULT_PARSE_FAILED_REPAIR)
jamesrendd855242010-03-02 22:23:44 +00001166 result['reboot_before_options'] = model_attributes.RebootBefore.names
1167 result['reboot_after_options'] = model_attributes.RebootAfter.names
showard8fbae652009-01-20 23:23:10 +00001168 result['motd'] = rpc_utils.get_motd()
jamesren76fcf192010-04-21 20:39:50 +00001169 result['drone_sets_enabled'] = models.DroneSet.drone_sets_enabled()
1170 result['drone_sets'] = drone_sets
jamesren4a41e012010-07-16 22:33:48 +00001171 result['parameterized_jobs'] = models.Job.parameterized_jobs_enabled()
showard8ac29b42008-07-17 17:01:55 +00001172
showardd3dc1992009-04-22 21:01:40 +00001173 result['status_dictionary'] = {"Aborted": "Aborted",
showard8ac29b42008-07-17 17:01:55 +00001174 "Verifying": "Verifying Host",
Alex Millerdfff2fd2013-05-28 13:05:06 -07001175 "Provisioning": "Provisioning Host",
showard8ac29b42008-07-17 17:01:55 +00001176 "Pending": "Waiting on other hosts",
1177 "Running": "Running autoserv",
1178 "Completed": "Autoserv completed",
1179 "Failed": "Failed to complete",
showardd823b362008-07-24 16:35:46 +00001180 "Queued": "Queued",
showard5deb6772008-11-04 21:54:33 +00001181 "Starting": "Next in host's queue",
1182 "Stopped": "Other host(s) failed verify",
showardd3dc1992009-04-22 21:01:40 +00001183 "Parsing": "Awaiting parse of final results",
showard29f7cd22009-04-29 21:16:24 +00001184 "Gathering": "Gathering log files",
showard8cc058f2009-09-08 16:26:33 +00001185 "Template": "Template job for recurring run",
mbligh4608b002010-01-05 18:22:35 +00001186 "Waiting": "Waiting for scheduler action",
Dan Shi07e09af2013-04-12 09:31:29 -07001187 "Archiving": "Archiving results",
1188 "Resetting": "Resetting hosts"}
Jiaxi Luo421608e2014-07-07 14:38:00 -07001189
1190 result['wmatrix_url'] = rpc_utils.get_wmatrix_url()
Simran Basi71206ef2014-08-13 13:51:18 -07001191 result['is_moblab'] = bool(utils.is_moblab())
Jiaxi Luo421608e2014-07-07 14:38:00 -07001192
jadmanski0afbb632008-06-06 21:10:57 +00001193 return result
showard29f7cd22009-04-29 21:16:24 +00001194
1195
1196def get_server_time():
1197 return datetime.datetime.now().strftime("%Y-%m-%d %H:%M")