blob: fc750207db10f6cf40745823567e32852df071fa [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
showard989f25d2008-10-01 11:38:11 +000062def add_label(name, kernel_config=None, platform=None, only_if_needed=None):
showardc92da832009-04-07 18:14:34 +000063 return models.Label.add_object(
64 name=name, kernel_config=kernel_config, platform=platform,
65 only_if_needed=only_if_needed).id
mblighe8819cd2008-02-15 16:48:40 +000066
67
68def 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
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -080075@rpc_utils.forward_multi_host_rpc_to_shards
showardbbabf502008-06-06 00:02:02 +000076def label_add_hosts(id, hosts):
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -080077 """Add the label with the given id to the list of hosts.
78
79 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
82 @param id: An id or label name. More often a label name.
83 @param hosts: A list of hostnames or ids. More often hostnames.
84
85 @raises models.Label.DoesNotExist: If the id specified is an int/long
86 and a label with that id doesn't exist.
87 """
showardbe3ec042008-11-12 18:16:07 +000088 host_objs = models.Host.smart_get_bulk(hosts)
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -080089 try:
90 # In the rare event that we're given an id and not a label name,
91 # it should already exist.
92 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:
100 raise
101
showardcafd16e2009-05-29 18:37:49 +0000102 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:
213 affected_shard_hostnames.add(host.shard.hostname)
214 affected_host_ids.append(host.id)
215
216 if not rpc_utils.is_shard():
217 # 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.
jadmanski0afbb632008-06-06 21:10:57 +0000745 """
showard9dbdcda2008-10-14 17:34:36 +0000746 query = models.HostQueueEntry.query_objects(filter_data)
beepsfaecbce2013-10-29 11:35:10 -0700747
748 # Dont allow aborts on:
749 # 1. Jobs that have already completed (whether or not they were aborted)
750 # 2. Jobs that we have already been aborted (but may not have completed)
751 query = query.filter(complete=False).filter(aborted=False)
showarddc817512008-11-12 18:16:41 +0000752 models.AclGroup.check_abort_permissions(query)
showard9dbdcda2008-10-14 17:34:36 +0000753 host_queue_entries = list(query.select_related())
showard2bab8f42008-11-12 18:15:22 +0000754 rpc_utils.check_abort_synchronous_jobs(host_queue_entries)
mblighe8819cd2008-02-15 16:48:40 +0000755
Simran Basic1b26762013-06-26 14:23:21 -0700756 models.HostQueueEntry.abort_host_queue_entries(host_queue_entries)
showard9d821ab2008-07-11 16:54:29 +0000757
758
beeps8bb1f7d2013-08-05 01:30:09 -0700759def abort_special_tasks(**filter_data):
760 """\
761 Abort the special task, or tasks, specified in the filter.
762 """
763 query = models.SpecialTask.query_objects(filter_data)
764 special_tasks = query.filter(is_active=True)
765 for task in special_tasks:
766 task.abort()
767
768
Simran Basi73dae552013-02-25 14:57:46 -0800769def _call_special_tasks_on_hosts(task, hosts):
770 """\
771 Schedules a set of hosts for a special task.
772
773 @returns A list of hostnames that a special task was created for.
774 """
775 models.AclGroup.check_for_acl_violation_hosts(hosts)
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -0800776 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts)
777 if shard_host_map:
778 raise ValueError('The following hosts are on shards, please '
779 'follow the link to the shards and create jobs '
780 'there instead. %s.' % shard_host_map)
Simran Basi73dae552013-02-25 14:57:46 -0800781 for host in hosts:
782 models.SpecialTask.schedule_special_task(host, task)
783 return list(sorted(host.hostname for host in hosts))
784
785
showard1ff7b2e2009-05-15 23:17:18 +0000786def reverify_hosts(**filter_data):
787 """\
788 Schedules a set of hosts for verify.
mbligh4e545a52009-12-19 05:30:39 +0000789
790 @returns A list of hostnames that a verify task was created for.
showard1ff7b2e2009-05-15 23:17:18 +0000791 """
Simran Basi73dae552013-02-25 14:57:46 -0800792 return _call_special_tasks_on_hosts(models.SpecialTask.Task.VERIFY,
793 models.Host.query_objects(filter_data))
794
795
796def repair_hosts(**filter_data):
797 """\
798 Schedules a set of hosts for repair.
799
800 @returns A list of hostnames that a repair task was created for.
801 """
802 return _call_special_tasks_on_hosts(models.SpecialTask.Task.REPAIR,
803 models.Host.query_objects(filter_data))
showard1ff7b2e2009-05-15 23:17:18 +0000804
805
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700806def get_jobs(not_yet_run=False, running=False, finished=False,
807 suite=False, sub=False, standalone=False, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000808 """\
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700809 Extra status filter args for get_jobs:
jadmanski0afbb632008-06-06 21:10:57 +0000810 -not_yet_run: Include only jobs that have not yet started running.
811 -running: Include only jobs that have start running but for which not
812 all hosts have completed.
813 -finished: Include only jobs for which all hosts have completed (or
814 aborted).
815 At most one of these three fields should be specified.
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700816
817 Extra type filter args for get_jobs:
818 -suite: Include only jobs with child jobs.
819 -sub: Include only jobs with a parent job.
820 -standalone: Inlcude only jobs with no child or parent jobs.
821 At most one of these three fields should be specified.
jadmanski0afbb632008-06-06 21:10:57 +0000822 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700823 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
824 running,
825 finished)
826 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
827 suite,
828 sub,
829 standalone)
showard0957a842009-05-11 19:25:08 +0000830 job_dicts = []
831 jobs = list(models.Job.query_objects(filter_data))
832 models.Job.objects.populate_relationships(jobs, models.Label,
833 'dependencies')
showardc1a98d12010-01-15 00:22:22 +0000834 models.Job.objects.populate_relationships(jobs, models.JobKeyval, 'keyvals')
showard0957a842009-05-11 19:25:08 +0000835 for job in jobs:
836 job_dict = job.get_object_dict()
837 job_dict['dependencies'] = ','.join(label.name
838 for label in job.dependencies)
showardc1a98d12010-01-15 00:22:22 +0000839 job_dict['keyvals'] = dict((keyval.key, keyval.value)
840 for keyval in job.keyvals)
Eric Lid23bc192011-02-09 14:38:57 -0800841 if job.parameterized_job:
842 job_dict['image'] = get_parameterized_autoupdate_image_url(job)
showard0957a842009-05-11 19:25:08 +0000843 job_dicts.append(job_dict)
844 return rpc_utils.prepare_for_serialization(job_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000845
846
847def get_num_jobs(not_yet_run=False, running=False, finished=False,
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700848 suite=False, sub=False, standalone=False,
jadmanski0afbb632008-06-06 21:10:57 +0000849 **filter_data):
850 """\
851 See get_jobs() for documentation of extra filter parameters.
852 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700853 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
854 running,
855 finished)
856 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
857 suite,
858 sub,
859 standalone)
jadmanski0afbb632008-06-06 21:10:57 +0000860 return models.Job.query_count(filter_data)
mblighe8819cd2008-02-15 16:48:40 +0000861
862
mblighe8819cd2008-02-15 16:48:40 +0000863def get_jobs_summary(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000864 """\
Jiaxi Luoaac54572014-06-04 13:57:02 -0700865 Like get_jobs(), but adds 'status_counts' and 'result_counts' field.
866
867 'status_counts' filed is a dictionary mapping status strings to the number
868 of hosts currently with that status, i.e. {'Queued' : 4, 'Running' : 2}.
869
870 'result_counts' field is piped to tko's rpc_interface and has the return
871 format specified under get_group_counts.
jadmanski0afbb632008-06-06 21:10:57 +0000872 """
873 jobs = get_jobs(**filter_data)
874 ids = [job['id'] for job in jobs]
875 all_status_counts = models.Job.objects.get_status_counts(ids)
876 for job in jobs:
877 job['status_counts'] = all_status_counts[job['id']]
Jiaxi Luoaac54572014-06-04 13:57:02 -0700878 job['result_counts'] = tko_rpc_interface.get_status_counts(
879 ['afe_job_id', 'afe_job_id'],
880 header_groups=[['afe_job_id'], ['afe_job_id']],
881 **{'afe_job_id': job['id']})
jadmanski0afbb632008-06-06 21:10:57 +0000882 return rpc_utils.prepare_for_serialization(jobs)
mblighe8819cd2008-02-15 16:48:40 +0000883
884
showarda965cef2009-05-15 23:17:41 +0000885def get_info_for_clone(id, preserve_metahosts, queue_entry_filter_data=None):
showarda8709c52008-07-03 19:44:54 +0000886 """\
887 Retrieves all the information needed to clone a job.
888 """
showarda8709c52008-07-03 19:44:54 +0000889 job = models.Job.objects.get(id=id)
showard29f7cd22009-04-29 21:16:24 +0000890 job_info = rpc_utils.get_job_info(job,
showarda965cef2009-05-15 23:17:41 +0000891 preserve_metahosts,
892 queue_entry_filter_data)
showard945072f2008-09-03 20:34:59 +0000893
showardd9992fe2008-07-31 02:15:03 +0000894 host_dicts = []
showard29f7cd22009-04-29 21:16:24 +0000895 for host in job_info['hosts']:
896 host_dict = get_hosts(id=host.id)[0]
897 other_labels = host_dict['labels']
898 if host_dict['platform']:
899 other_labels.remove(host_dict['platform'])
900 host_dict['other_labels'] = ', '.join(other_labels)
showardd9992fe2008-07-31 02:15:03 +0000901 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +0000902
showard29f7cd22009-04-29 21:16:24 +0000903 for host in job_info['one_time_hosts']:
904 host_dict = dict(hostname=host.hostname,
905 id=host.id,
906 platform='(one-time host)',
907 locked_text='')
908 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +0000909
showard4d077562009-05-08 18:24:36 +0000910 # convert keys from Label objects to strings (names of labels)
showard29f7cd22009-04-29 21:16:24 +0000911 meta_host_counts = dict((meta_host.name, count) for meta_host, count
showard4d077562009-05-08 18:24:36 +0000912 in job_info['meta_host_counts'].iteritems())
showard29f7cd22009-04-29 21:16:24 +0000913
914 info = dict(job=job.get_object_dict(),
915 meta_host_counts=meta_host_counts,
916 hosts=host_dicts)
917 info['job']['dependencies'] = job_info['dependencies']
918 if job_info['atomic_group']:
919 info['atomic_group_name'] = (job_info['atomic_group']).name
920 else:
921 info['atomic_group_name'] = None
jamesren2275ef12010-04-12 18:25:06 +0000922 info['hostless'] = job_info['hostless']
jamesren76fcf192010-04-21 20:39:50 +0000923 info['drone_set'] = job.drone_set and job.drone_set.name
showarda8709c52008-07-03 19:44:54 +0000924
Eric Lid23bc192011-02-09 14:38:57 -0800925 if job.parameterized_job:
926 info['job']['image'] = get_parameterized_autoupdate_image_url(job)
927
showarda8709c52008-07-03 19:44:54 +0000928 return rpc_utils.prepare_for_serialization(info)
929
930
showard34dc5fa2008-04-24 20:58:40 +0000931# host queue entries
932
Jiaxi Luo57bc1952014-07-22 15:27:30 -0700933def get_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000934 """\
showardc92da832009-04-07 18:14:34 +0000935 @returns A sequence of nested dictionaries of host and job information.
jadmanski0afbb632008-06-06 21:10:57 +0000936 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -0700937 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
938 'started_on__lte',
939 start_time,
940 end_time,
941 **filter_data)
showardc92da832009-04-07 18:14:34 +0000942 return rpc_utils.prepare_rows_as_nested_dicts(
943 models.HostQueueEntry.query_objects(filter_data),
944 ('host', 'atomic_group', 'job'))
showard34dc5fa2008-04-24 20:58:40 +0000945
946
Jiaxi Luo57bc1952014-07-22 15:27:30 -0700947def get_num_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000948 """\
949 Get the number of host queue entries associated with this job.
950 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -0700951 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
952 'started_on__lte',
953 start_time,
954 end_time,
955 **filter_data)
jadmanski0afbb632008-06-06 21:10:57 +0000956 return models.HostQueueEntry.query_count(filter_data)
showard34dc5fa2008-04-24 20:58:40 +0000957
958
showard1e935f12008-07-11 00:11:36 +0000959def get_hqe_percentage_complete(**filter_data):
960 """
showardc92da832009-04-07 18:14:34 +0000961 Computes the fraction of host queue entries matching the given filter data
showard1e935f12008-07-11 00:11:36 +0000962 that are complete.
963 """
964 query = models.HostQueueEntry.query_objects(filter_data)
965 complete_count = query.filter(complete=True).count()
966 total_count = query.count()
967 if total_count == 0:
968 return 1
969 return float(complete_count) / total_count
970
971
showard1a5a4082009-07-28 20:01:37 +0000972# special tasks
973
974def get_special_tasks(**filter_data):
975 return rpc_utils.prepare_rows_as_nested_dicts(
976 models.SpecialTask.query_objects(filter_data),
977 ('host', 'queue_entry'))
978
979
showardc0ac3a72009-07-08 21:14:45 +0000980# support for host detail view
981
Jiaxi Luo79ce6422014-06-13 17:08:09 -0700982def get_host_queue_entries_and_special_tasks(host_id, query_start=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -0700983 query_limit=None, start_time=None,
984 end_time=None):
showardc0ac3a72009-07-08 21:14:45 +0000985 """
986 @returns an interleaved list of HostQueueEntries and SpecialTasks,
987 in approximate run order. each dict contains keys for type, host,
988 job, status, started_on, execution_path, and ID.
989 """
990 total_limit = None
991 if query_limit is not None:
992 total_limit = query_start + query_limit
Jiaxi Luo57bc1952014-07-22 15:27:30 -0700993 filter_data_common = {'host': host_id,
994 'query_limit': total_limit,
995 'sort_by': ['-id']}
showardc0ac3a72009-07-08 21:14:45 +0000996
Jiaxi Luo57bc1952014-07-22 15:27:30 -0700997 filter_data_queue_entries, filter_data_special_tasks = (
998 rpc_utils.inject_times_to_hqe_special_tasks_filters(
999 filter_data_common, start_time, end_time))
1000
1001 queue_entries = list(models.HostQueueEntry.query_objects(
1002 filter_data_queue_entries))
1003 special_tasks = list(models.SpecialTask.query_objects(
1004 filter_data_special_tasks))
showardc0ac3a72009-07-08 21:14:45 +00001005
1006 interleaved_entries = rpc_utils.interleave_entries(queue_entries,
1007 special_tasks)
1008 if query_start is not None:
1009 interleaved_entries = interleaved_entries[query_start:]
1010 if query_limit is not None:
1011 interleaved_entries = interleaved_entries[:query_limit]
1012 return rpc_utils.prepare_for_serialization(interleaved_entries)
1013
1014
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001015def get_num_host_queue_entries_and_special_tasks(host_id, start_time=None,
1016 end_time=None):
1017 filter_data_common = {'host': host_id}
1018
1019 filter_data_queue_entries, filter_data_special_tasks = (
1020 rpc_utils.inject_times_to_hqe_special_tasks_filters(
1021 filter_data_common, start_time, end_time))
1022
1023 return (models.HostQueueEntry.query_count(filter_data_queue_entries)
1024 + models.SpecialTask.query_count(filter_data_special_tasks))
showardc0ac3a72009-07-08 21:14:45 +00001025
1026
showard29f7cd22009-04-29 21:16:24 +00001027# recurring run
1028
1029def get_recurring(**filter_data):
1030 return rpc_utils.prepare_rows_as_nested_dicts(
1031 models.RecurringRun.query_objects(filter_data),
1032 ('job', 'owner'))
1033
1034
1035def get_num_recurring(**filter_data):
1036 return models.RecurringRun.query_count(filter_data)
1037
1038
1039def delete_recurring_runs(**filter_data):
1040 to_delete = models.RecurringRun.query_objects(filter_data)
1041 to_delete.delete()
1042
1043
1044def create_recurring_run(job_id, start_date, loop_period, loop_count):
showard64a95952010-01-13 21:27:16 +00001045 owner = models.User.current_user().login
showard29f7cd22009-04-29 21:16:24 +00001046 job = models.Job.objects.get(id=job_id)
1047 return job.create_recurring_job(start_date=start_date,
1048 loop_period=loop_period,
1049 loop_count=loop_count,
1050 owner=owner)
1051
1052
mblighe8819cd2008-02-15 16:48:40 +00001053# other
1054
showarde0b63622008-08-04 20:58:47 +00001055def echo(data=""):
1056 """\
1057 Returns a passed in string. For doing a basic test to see if RPC calls
1058 can successfully be made.
1059 """
1060 return data
1061
1062
showardb7a52fd2009-04-27 20:10:56 +00001063def get_motd():
1064 """\
1065 Returns the message of the day as a string.
1066 """
1067 return rpc_utils.get_motd()
1068
1069
mblighe8819cd2008-02-15 16:48:40 +00001070def get_static_data():
jadmanski0afbb632008-06-06 21:10:57 +00001071 """\
1072 Returns a dictionary containing a bunch of data that shouldn't change
1073 often and is otherwise inaccessible. This includes:
showardc92da832009-04-07 18:14:34 +00001074
1075 priorities: List of job priority choices.
1076 default_priority: Default priority value for new jobs.
1077 users: Sorted list of all users.
Jiaxi Luo31874592014-06-11 10:36:35 -07001078 labels: Sorted list of labels not start with 'cros-version' and
1079 'fw-version'.
showardc92da832009-04-07 18:14:34 +00001080 atomic_groups: Sorted list of all atomic groups.
1081 tests: Sorted list of all tests.
1082 profilers: Sorted list of all profilers.
1083 current_user: Logged-in username.
1084 host_statuses: Sorted list of possible Host statuses.
1085 job_statuses: Sorted list of possible HostQueueEntry statuses.
Simran Basi7e605742013-11-12 13:43:36 -08001086 job_timeout_default: The default job timeout length in minutes.
showarda1e74b32009-05-12 17:32:04 +00001087 parse_failed_repair_default: Default value for the parse_failed_repair job
Jiaxi Luo31874592014-06-11 10:36:35 -07001088 option.
showardc92da832009-04-07 18:14:34 +00001089 reboot_before_options: A list of valid RebootBefore string enums.
1090 reboot_after_options: A list of valid RebootAfter string enums.
1091 motd: Server's message of the day.
1092 status_dictionary: A mapping from one word job status names to a more
1093 informative description.
jadmanski0afbb632008-06-06 21:10:57 +00001094 """
showard21baa452008-10-21 00:08:39 +00001095
1096 job_fields = models.Job.get_field_dict()
jamesren76fcf192010-04-21 20:39:50 +00001097 default_drone_set_name = models.DroneSet.default_drone_set_name()
1098 drone_sets = ([default_drone_set_name] +
1099 sorted(drone_set.name for drone_set in
1100 models.DroneSet.objects.exclude(
1101 name=default_drone_set_name)))
showard21baa452008-10-21 00:08:39 +00001102
jadmanski0afbb632008-06-06 21:10:57 +00001103 result = {}
Alex Miller7d658cf2013-09-04 16:00:35 -07001104 result['priorities'] = priorities.Priority.choices()
1105 default_priority = priorities.Priority.DEFAULT
1106 result['default_priority'] = 'Default'
1107 result['max_schedulable_priority'] = priorities.Priority.DEFAULT
jadmanski0afbb632008-06-06 21:10:57 +00001108 result['users'] = get_users(sort_by=['login'])
Jiaxi Luo31874592014-06-11 10:36:35 -07001109
1110 label_exclude_filters = [{'name__startswith': 'cros-version'},
1111 {'name__startswith': 'fw-version'}]
1112 result['labels'] = get_labels(
1113 label_exclude_filters,
1114 sort_by=['-platform', 'name'])
1115
showardc92da832009-04-07 18:14:34 +00001116 result['atomic_groups'] = get_atomic_groups(sort_by=['name'])
jadmanski0afbb632008-06-06 21:10:57 +00001117 result['tests'] = get_tests(sort_by=['name'])
showard2b9a88b2008-06-13 20:55:03 +00001118 result['profilers'] = get_profilers(sort_by=['name'])
showard0fc38302008-10-23 00:44:07 +00001119 result['current_user'] = rpc_utils.prepare_for_serialization(
showard64a95952010-01-13 21:27:16 +00001120 models.User.current_user().get_object_dict())
showard2b9a88b2008-06-13 20:55:03 +00001121 result['host_statuses'] = sorted(models.Host.Status.names)
mbligh5a198b92008-12-11 19:33:29 +00001122 result['job_statuses'] = sorted(models.HostQueueEntry.Status.names)
Simran Basi7e605742013-11-12 13:43:36 -08001123 result['job_timeout_mins_default'] = models.Job.DEFAULT_TIMEOUT_MINS
Simran Basi34217022012-11-06 13:43:15 -08001124 result['job_max_runtime_mins_default'] = (
1125 models.Job.DEFAULT_MAX_RUNTIME_MINS)
showarda1e74b32009-05-12 17:32:04 +00001126 result['parse_failed_repair_default'] = bool(
1127 models.Job.DEFAULT_PARSE_FAILED_REPAIR)
jamesrendd855242010-03-02 22:23:44 +00001128 result['reboot_before_options'] = model_attributes.RebootBefore.names
1129 result['reboot_after_options'] = model_attributes.RebootAfter.names
showard8fbae652009-01-20 23:23:10 +00001130 result['motd'] = rpc_utils.get_motd()
jamesren76fcf192010-04-21 20:39:50 +00001131 result['drone_sets_enabled'] = models.DroneSet.drone_sets_enabled()
1132 result['drone_sets'] = drone_sets
jamesren4a41e012010-07-16 22:33:48 +00001133 result['parameterized_jobs'] = models.Job.parameterized_jobs_enabled()
showard8ac29b42008-07-17 17:01:55 +00001134
showardd3dc1992009-04-22 21:01:40 +00001135 result['status_dictionary'] = {"Aborted": "Aborted",
showard8ac29b42008-07-17 17:01:55 +00001136 "Verifying": "Verifying Host",
Alex Millerdfff2fd2013-05-28 13:05:06 -07001137 "Provisioning": "Provisioning Host",
showard8ac29b42008-07-17 17:01:55 +00001138 "Pending": "Waiting on other hosts",
1139 "Running": "Running autoserv",
1140 "Completed": "Autoserv completed",
1141 "Failed": "Failed to complete",
showardd823b362008-07-24 16:35:46 +00001142 "Queued": "Queued",
showard5deb6772008-11-04 21:54:33 +00001143 "Starting": "Next in host's queue",
1144 "Stopped": "Other host(s) failed verify",
showardd3dc1992009-04-22 21:01:40 +00001145 "Parsing": "Awaiting parse of final results",
showard29f7cd22009-04-29 21:16:24 +00001146 "Gathering": "Gathering log files",
showard8cc058f2009-09-08 16:26:33 +00001147 "Template": "Template job for recurring run",
mbligh4608b002010-01-05 18:22:35 +00001148 "Waiting": "Waiting for scheduler action",
Dan Shi07e09af2013-04-12 09:31:29 -07001149 "Archiving": "Archiving results",
1150 "Resetting": "Resetting hosts"}
Jiaxi Luo421608e2014-07-07 14:38:00 -07001151
1152 result['wmatrix_url'] = rpc_utils.get_wmatrix_url()
Simran Basi71206ef2014-08-13 13:51:18 -07001153 result['is_moblab'] = bool(utils.is_moblab())
Jiaxi Luo421608e2014-07-07 14:38:00 -07001154
jadmanski0afbb632008-06-06 21:10:57 +00001155 return result
showard29f7cd22009-04-29 21:16:24 +00001156
1157
1158def get_server_time():
1159 return datetime.datetime.now().strftime("%Y-%m-%d %H:%M")