blob: 1dbaf386fe1506d7024b98c05eaf9ca5e7e9a0ab [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
mblighe8819cd2008-02-15 16:48:40 +000062def modify_label(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +000063 models.Label.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +000064
65
66def delete_label(id):
jadmanski0afbb632008-06-06 21:10:57 +000067 models.Label.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +000068
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -080069
Prashanth Balasubramanian744898f2015-01-13 05:04:16 -080070def add_label(name, ignore_exception_if_exists=False, **kwargs):
71 """Add a new label with a given name.
72
73 @param name: label name.
74 @param ignore_exception_if_exists: If True and the exception was
75 thrown due to the duplicated label name when adding a label,
76 then suppress the exception. Default is False.
77 @param kwargs: keyword args that store more info about a label
78 other than the name.
79 @return: int/long id of a new label.
80 """
81 # models.Label.add_object() throws model_logic.ValidationError
82 # when it is given a label name that already exists.
83 # However, ValidationError can be thrown with different errors,
84 # and those errors should be thrown up to the call chain.
85 try:
86 label = models.Label.add_object(name=name, **kwargs)
87 except:
88 if ignore_exception_if_exists:
89 label = rpc_utils.get_label(name)
90 # If the exception is raised not because of duplicated
91 # "name", then raise the original exception.
92 if label is None:
93 raise
94 else:
95 raise
96 return label.id
97
98
99def add_label_to_hosts(id, hosts):
100 """Add a label with the given id to the given hosts only in local DB.
101
102 @param id: id or name of a label. More often a label name.
103 @param hosts: The hostnames of hosts that need the label.
104
105 @raises models.Label.DoesNotExist: If the label with id doesn't exist.
106 """
107 label = models.Label.smart_get(id)
108 host_objs = models.Host.smart_get_bulk(hosts)
109 if label.platform:
110 models.Host.check_no_platform(host_objs)
111 label.host_set.add(*host_objs)
112
113
114def label_add_hosts(id, hosts):
115 """Add a label with the given id to the given hosts.
116
117 This method should be run only on master not shards.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800118 The given label will be created if it doesn't exist, provided the `id`
119 supplied is a label name not an int/long id.
120
Prashanth Balasubramanian744898f2015-01-13 05:04:16 -0800121 @param id: id or name of a label. More often a label name.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800122 @param hosts: A list of hostnames or ids. More often hostnames.
123
Prashanth Balasubramanian744898f2015-01-13 05:04:16 -0800124 @raises Exception: If this RPC is called other than master.
125 @raises ValueError: If the id specified is an int/long (label id)
126 while the label does not exist.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800127 """
Prashanth Balasubramanian744898f2015-01-13 05:04:16 -0800128 # This RPC call should be accepted only by master.
129 if utils.is_shard():
130 raise Exception('RPC label_add_hosts should be called only on matser')
131
showardbe3ec042008-11-12 18:16:07 +0000132 host_objs = models.Host.smart_get_bulk(hosts)
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800133 try:
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800134 label = models.Label.smart_get(id)
135 except models.Label.DoesNotExist:
136 # This matches the type checks in smart_get, which is a hack
137 # in and off itself. The aim here is to create any non-existent
138 # label, which we cannot do if the 'id' specified isn't a label name.
139 if isinstance(id, basestring):
140 label = models.Label.smart_get(add_label(id))
141 else:
Prashanth Balasubramanian744898f2015-01-13 05:04:16 -0800142 raise ValueError('Label id (%s) does not exist. Please specify '
143 'the argument, id, as a string (label name).'
144 % id)
145 add_label_to_hosts(id, hosts)
146 # Make sure the label exists on the shard with the same id
147 # as it is on the master.
148 # It is possible that the label is already in a shard.
149 # We ignore exception in such a case.
150 rpc_utils.fanout_rpc(
151 host_objs, 'add_label', name=label.name, id=label.id,
152 include_hostnames=False, ignore_exception_if_exists=True)
153 rpc_utils.fanout_rpc(host_objs, 'add_label_to_hosts', id=id)
showardbbabf502008-06-06 00:02:02 +0000154
155
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800156@rpc_utils.forward_multi_host_rpc_to_shards
showardbbabf502008-06-06 00:02:02 +0000157def label_remove_hosts(id, hosts):
showardbe3ec042008-11-12 18:16:07 +0000158 host_objs = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000159 models.Label.smart_get(id).host_set.remove(*host_objs)
showardbbabf502008-06-06 00:02:02 +0000160
161
Jiaxi Luo31874592014-06-11 10:36:35 -0700162def get_labels(exclude_filters=(), **filter_data):
showardc92da832009-04-07 18:14:34 +0000163 """\
Jiaxi Luo31874592014-06-11 10:36:35 -0700164 @param exclude_filters: A sequence of dictionaries of filters.
165
showardc92da832009-04-07 18:14:34 +0000166 @returns A sequence of nested dictionaries of label information.
167 """
Jiaxi Luo31874592014-06-11 10:36:35 -0700168 labels = models.Label.query_objects(filter_data)
169 for exclude_filter in exclude_filters:
170 labels = labels.exclude(**exclude_filter)
171 return rpc_utils.prepare_rows_as_nested_dicts(labels, ('atomic_group',))
showardc92da832009-04-07 18:14:34 +0000172
173
174# atomic groups
175
showarde9450c92009-06-30 01:58:52 +0000176def add_atomic_group(name, max_number_of_machines=None, description=None):
showardc92da832009-04-07 18:14:34 +0000177 return models.AtomicGroup.add_object(
178 name=name, max_number_of_machines=max_number_of_machines,
179 description=description).id
180
181
182def modify_atomic_group(id, **data):
183 models.AtomicGroup.smart_get(id).update_object(data)
184
185
186def delete_atomic_group(id):
187 models.AtomicGroup.smart_get(id).delete()
188
189
190def atomic_group_add_labels(id, labels):
191 label_objs = models.Label.smart_get_bulk(labels)
192 models.AtomicGroup.smart_get(id).label_set.add(*label_objs)
193
194
195def atomic_group_remove_labels(id, labels):
196 label_objs = models.Label.smart_get_bulk(labels)
197 models.AtomicGroup.smart_get(id).label_set.remove(*label_objs)
198
199
200def get_atomic_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000201 return rpc_utils.prepare_for_serialization(
showardc92da832009-04-07 18:14:34 +0000202 models.AtomicGroup.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000203
204
205# hosts
206
showarddf062562008-07-03 19:56:37 +0000207def add_host(hostname, status=None, locked=None, protection=None):
jadmanski0afbb632008-06-06 21:10:57 +0000208 return models.Host.add_object(hostname=hostname, status=status,
showarddf062562008-07-03 19:56:37 +0000209 locked=locked, protection=protection).id
mblighe8819cd2008-02-15 16:48:40 +0000210
211
Jakob Juelich50e91f72014-10-01 12:43:23 -0700212@rpc_utils.forward_single_host_rpc_to_shard
mblighe8819cd2008-02-15 16:48:40 +0000213def modify_host(id, **data):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700214 """Modify local attributes of a host.
215
216 If this is called on the master, but the host is assigned to a shard, this
217 will also forward the call to the responsible shard. This means i.e. if a
218 host is being locked using this function, this change will also propagate to
219 shards.
220
221 @param id: id of the host to modify.
222 @param **data: key=value pairs of values to set on the host.
223 """
showardbe0d8692009-08-20 23:42:44 +0000224 rpc_utils.check_modify_host(data)
showardce7c0922009-09-11 18:39:24 +0000225 host = models.Host.smart_get(id)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700226
showardce7c0922009-09-11 18:39:24 +0000227 rpc_utils.check_modify_host_locking(host, data)
228 host.update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000229
230
showard276f9442009-05-20 00:33:16 +0000231def modify_hosts(host_filter_data, update_data):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700232 """Modify local attributes of multiple hosts.
233
234 If this is called on the master, but one of the hosts in that match the
235 filters is assigned to a shard, this will also forward the call to the
236 responsible shard.
237
238 The filters are always applied on the master, not on the shards. This means
239 if the states of a host differ on the master and a shard, the state on the
240 master will be used. I.e. this means:
241 A host was synced to Shard 1. On Shard 1 the status of the host was set to
242 'Repair Failed'.
243 - A call to modify_hosts with host_filter_data={'status': 'Ready'} will
244 update the host (both on the shard and on the master), because the state
245 of the host as the master knows it is still 'Ready'.
246 - A call to modify_hosts with host_filter_data={'status': 'Repair failed'
247 will not update the host, because the filter doesn't apply on the master.
248
showardbe0d8692009-08-20 23:42:44 +0000249 @param host_filter_data: Filters out which hosts to modify.
250 @param update_data: A dictionary with the changes to make to the hosts.
showard276f9442009-05-20 00:33:16 +0000251 """
showardbe0d8692009-08-20 23:42:44 +0000252 rpc_utils.check_modify_host(update_data)
showard276f9442009-05-20 00:33:16 +0000253 hosts = models.Host.query_objects(host_filter_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700254
255 affected_shard_hostnames = set()
256 affected_host_ids = []
257
Alex Miller9658a952013-05-14 16:40:02 -0700258 # Check all hosts before changing data for exception safety.
259 for host in hosts:
260 rpc_utils.check_modify_host_locking(host, update_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700261 if host.shard:
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800262 affected_shard_hostnames.add(host.shard.rpc_hostname())
Jakob Juelich50e91f72014-10-01 12:43:23 -0700263 affected_host_ids.append(host.id)
264
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800265 if not utils.is_shard():
Jakob Juelich50e91f72014-10-01 12:43:23 -0700266 # Caution: Changing the filter from the original here. See docstring.
267 rpc_utils.run_rpc_on_multiple_hostnames(
268 'modify_hosts', affected_shard_hostnames,
269 host_filter_data={'id__in': affected_host_ids},
270 update_data=update_data)
271
showard276f9442009-05-20 00:33:16 +0000272 for host in hosts:
273 host.update_object(update_data)
274
275
mblighe8819cd2008-02-15 16:48:40 +0000276def host_add_labels(id, labels):
showardbe3ec042008-11-12 18:16:07 +0000277 labels = models.Label.smart_get_bulk(labels)
showardcafd16e2009-05-29 18:37:49 +0000278 host = models.Host.smart_get(id)
279
280 platforms = [label.name for label in labels if label.platform]
281 if len(platforms) > 1:
282 raise model_logic.ValidationError(
283 {'labels': 'Adding more than one platform label: %s' %
284 ', '.join(platforms)})
285 if len(platforms) == 1:
286 models.Host.check_no_platform([host])
287 host.labels.add(*labels)
mblighe8819cd2008-02-15 16:48:40 +0000288
289
290def host_remove_labels(id, labels):
showardbe3ec042008-11-12 18:16:07 +0000291 labels = models.Label.smart_get_bulk(labels)
jadmanski0afbb632008-06-06 21:10:57 +0000292 models.Host.smart_get(id).labels.remove(*labels)
mblighe8819cd2008-02-15 16:48:40 +0000293
294
MK Ryuacf35922014-10-03 14:56:49 -0700295def get_host_attribute(attribute, **host_filter_data):
296 """
297 @param attribute: string name of attribute
298 @param host_filter_data: filter data to apply to Hosts to choose hosts to
299 act upon
300 """
301 hosts = rpc_utils.get_host_query((), False, False, True, host_filter_data)
302 hosts = list(hosts)
303 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
304 'attribute_list')
305 host_attr_dicts = []
306 for host_obj in hosts:
307 for attr_obj in host_obj.attribute_list:
308 if attr_obj.attribute == attribute:
309 host_attr_dicts.append(attr_obj.get_object_dict())
310 return rpc_utils.prepare_for_serialization(host_attr_dicts)
311
312
showard0957a842009-05-11 19:25:08 +0000313def set_host_attribute(attribute, value, **host_filter_data):
314 """
315 @param attribute string name of attribute
316 @param value string, or None to delete an attribute
317 @param host_filter_data filter data to apply to Hosts to choose hosts to act
318 upon
319 """
320 assert host_filter_data # disallow accidental actions on all hosts
321 hosts = models.Host.query_objects(host_filter_data)
322 models.AclGroup.check_for_acl_violation_hosts(hosts)
323
324 for host in hosts:
showardf8b19042009-05-12 17:22:49 +0000325 host.set_or_delete_attribute(attribute, value)
showard0957a842009-05-11 19:25:08 +0000326
327
Jakob Juelich50e91f72014-10-01 12:43:23 -0700328@rpc_utils.forward_single_host_rpc_to_shard
mblighe8819cd2008-02-15 16:48:40 +0000329def delete_host(id):
jadmanski0afbb632008-06-06 21:10:57 +0000330 models.Host.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000331
332
showard87cc38f2009-08-20 23:37:04 +0000333def get_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000334 exclude_atomic_group_hosts=False, valid_only=True, **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000335 """
336 @param multiple_labels: match hosts in all of the labels given. Should
337 be a list of label names.
338 @param exclude_only_if_needed_labels: Exclude hosts with at least one
339 "only_if_needed" label applied.
340 @param exclude_atomic_group_hosts: Exclude hosts that have one or more
341 atomic group labels associated with them.
jadmanski0afbb632008-06-06 21:10:57 +0000342 """
showard43a3d262008-11-12 18:17:05 +0000343 hosts = rpc_utils.get_host_query(multiple_labels,
344 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000345 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000346 valid_only, filter_data)
showard0957a842009-05-11 19:25:08 +0000347 hosts = list(hosts)
348 models.Host.objects.populate_relationships(hosts, models.Label,
349 'label_list')
350 models.Host.objects.populate_relationships(hosts, models.AclGroup,
351 'acl_list')
352 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
353 'attribute_list')
showard43a3d262008-11-12 18:17:05 +0000354 host_dicts = []
355 for host_obj in hosts:
356 host_dict = host_obj.get_object_dict()
showard0957a842009-05-11 19:25:08 +0000357 host_dict['labels'] = [label.name for label in host_obj.label_list]
showard909c9142009-07-07 20:54:42 +0000358 host_dict['platform'], host_dict['atomic_group'] = (rpc_utils.
359 find_platform_and_atomic_group(host_obj))
showard0957a842009-05-11 19:25:08 +0000360 host_dict['acls'] = [acl.name for acl in host_obj.acl_list]
361 host_dict['attributes'] = dict((attribute.attribute, attribute.value)
362 for attribute in host_obj.attribute_list)
showard43a3d262008-11-12 18:17:05 +0000363 host_dicts.append(host_dict)
364 return rpc_utils.prepare_for_serialization(host_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000365
366
showard87cc38f2009-08-20 23:37:04 +0000367def get_num_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000368 exclude_atomic_group_hosts=False, valid_only=True,
369 **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000370 """
371 Same parameters as get_hosts().
372
373 @returns The number of matching hosts.
374 """
showard43a3d262008-11-12 18:17:05 +0000375 hosts = rpc_utils.get_host_query(multiple_labels,
376 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000377 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000378 valid_only, filter_data)
showard43a3d262008-11-12 18:17:05 +0000379 return hosts.count()
showard1385b162008-03-13 15:59:40 +0000380
mblighe8819cd2008-02-15 16:48:40 +0000381
382# tests
383
showard909c7a62008-07-15 21:52:38 +0000384def add_test(name, test_type, path, author=None, dependencies=None,
showard3d9899a2008-07-31 02:11:58 +0000385 experimental=True, run_verify=None, test_class=None,
showard909c7a62008-07-15 21:52:38 +0000386 test_time=None, test_category=None, description=None,
387 sync_count=1):
jadmanski0afbb632008-06-06 21:10:57 +0000388 return models.Test.add_object(name=name, test_type=test_type, path=path,
showard909c7a62008-07-15 21:52:38 +0000389 author=author, dependencies=dependencies,
390 experimental=experimental,
391 run_verify=run_verify, test_time=test_time,
392 test_category=test_category,
393 sync_count=sync_count,
jadmanski0afbb632008-06-06 21:10:57 +0000394 test_class=test_class,
395 description=description).id
mblighe8819cd2008-02-15 16:48:40 +0000396
397
398def modify_test(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000399 models.Test.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000400
401
402def delete_test(id):
jadmanski0afbb632008-06-06 21:10:57 +0000403 models.Test.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000404
405
406def get_tests(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000407 return rpc_utils.prepare_for_serialization(
408 models.Test.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000409
410
Moises Osorio2dc7a102014-12-02 18:24:02 -0800411@_timer.decorate
412def get_tests_status_counts_by_job_name_label(job_name_prefix, label_name):
413 """Gets the counts of all passed and failed tests from the matching jobs.
414
415 @param job_name_prefix: Name prefix of the jobs to get the summary from, e.g.,
416 'butterfly-release/R40-6457.21.0/bvt-cq/'.
417 @param label_name: Label that must be set in the jobs, e.g.,
418 'cros-version:butterfly-release/R40-6457.21.0'.
419
420 @returns A summary of the counts of all the passed and failed tests.
421 """
422 job_ids = list(models.Job.objects.filter(
423 name__startswith=job_name_prefix,
424 dependency_labels__name=label_name).values_list(
425 'pk', flat=True))
426 summary = {'passed': 0, 'failed': 0}
427 if not job_ids:
428 return summary
429
430 counts = (tko_models.TestView.objects.filter(
431 afe_job_id__in=job_ids).exclude(
432 test_name='SERVER_JOB').exclude(
433 test_name__startswith='CLIENT_JOB').values(
434 'status').annotate(
435 count=Count('status')))
436 for status in counts:
437 if status['status'] == 'GOOD':
438 summary['passed'] += status['count']
439 else:
440 summary['failed'] += status['count']
441 return summary
442
443
showard2b9a88b2008-06-13 20:55:03 +0000444# profilers
445
446def add_profiler(name, description=None):
447 return models.Profiler.add_object(name=name, description=description).id
448
449
450def modify_profiler(id, **data):
451 models.Profiler.smart_get(id).update_object(data)
452
453
454def delete_profiler(id):
455 models.Profiler.smart_get(id).delete()
456
457
458def get_profilers(**filter_data):
459 return rpc_utils.prepare_for_serialization(
460 models.Profiler.list_objects(filter_data))
461
462
mblighe8819cd2008-02-15 16:48:40 +0000463# users
464
465def add_user(login, access_level=None):
jadmanski0afbb632008-06-06 21:10:57 +0000466 return models.User.add_object(login=login, access_level=access_level).id
mblighe8819cd2008-02-15 16:48:40 +0000467
468
469def modify_user(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000470 models.User.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000471
472
473def delete_user(id):
jadmanski0afbb632008-06-06 21:10:57 +0000474 models.User.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000475
476
477def get_users(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000478 return rpc_utils.prepare_for_serialization(
479 models.User.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000480
481
482# acl groups
483
484def add_acl_group(name, description=None):
showard04f2cd82008-07-25 20:53:31 +0000485 group = models.AclGroup.add_object(name=name, description=description)
showard64a95952010-01-13 21:27:16 +0000486 group.users.add(models.User.current_user())
showard04f2cd82008-07-25 20:53:31 +0000487 return group.id
mblighe8819cd2008-02-15 16:48:40 +0000488
489
490def modify_acl_group(id, **data):
showard04f2cd82008-07-25 20:53:31 +0000491 group = models.AclGroup.smart_get(id)
492 group.check_for_acl_violation_acl_group()
493 group.update_object(data)
494 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000495
496
497def acl_group_add_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000498 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000499 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000500 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000501 group.users.add(*users)
mblighe8819cd2008-02-15 16:48:40 +0000502
503
504def acl_group_remove_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000505 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000506 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000507 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000508 group.users.remove(*users)
showard04f2cd82008-07-25 20:53:31 +0000509 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000510
511
512def acl_group_add_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000513 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000514 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000515 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000516 group.hosts.add(*hosts)
showard08f981b2008-06-24 21:59:03 +0000517 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000518
519
520def acl_group_remove_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000521 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000522 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000523 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000524 group.hosts.remove(*hosts)
showard08f981b2008-06-24 21:59:03 +0000525 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000526
527
528def delete_acl_group(id):
jadmanski0afbb632008-06-06 21:10:57 +0000529 models.AclGroup.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000530
531
532def get_acl_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000533 acl_groups = models.AclGroup.list_objects(filter_data)
534 for acl_group in acl_groups:
535 acl_group_obj = models.AclGroup.objects.get(id=acl_group['id'])
536 acl_group['users'] = [user.login
537 for user in acl_group_obj.users.all()]
538 acl_group['hosts'] = [host.hostname
539 for host in acl_group_obj.hosts.all()]
540 return rpc_utils.prepare_for_serialization(acl_groups)
mblighe8819cd2008-02-15 16:48:40 +0000541
542
543# jobs
544
mbligh120351e2009-01-24 01:40:45 +0000545def generate_control_file(tests=(), kernel=None, label=None, profilers=(),
showard91f85102009-10-12 20:34:52 +0000546 client_control_file='', use_container=False,
showard232b7ae2009-11-10 00:46:48 +0000547 profile_only=None, upload_kernel_config=False):
jadmanski0afbb632008-06-06 21:10:57 +0000548 """
mbligh120351e2009-01-24 01:40:45 +0000549 Generates a client-side control file to load a kernel and run tests.
550
551 @param tests List of tests to run.
mbligha3c58d22009-08-24 22:01:51 +0000552 @param kernel A list of kernel info dictionaries configuring which kernels
553 to boot for this job and other options for them
mbligh120351e2009-01-24 01:40:45 +0000554 @param label Name of label to grab kernel config from.
555 @param profilers List of profilers to activate during the job.
556 @param client_control_file The contents of a client-side control file to
557 run at the end of all tests. If this is supplied, all tests must be
558 client side.
559 TODO: in the future we should support server control files directly
560 to wrap with a kernel. That'll require changing the parameter
561 name and adding a boolean to indicate if it is a client or server
562 control file.
563 @param use_container unused argument today. TODO: Enable containers
564 on the host during a client side test.
showard91f85102009-10-12 20:34:52 +0000565 @param profile_only A boolean that indicates what default profile_only
566 mode to use in the control file. Passing None will generate a
567 control file that does not explcitly set the default mode at all.
showard232b7ae2009-11-10 00:46:48 +0000568 @param upload_kernel_config: if enabled it will generate server control
569 file code that uploads the kernel config file to the client and
570 tells the client of the new (local) path when compiling the kernel;
571 the tests must be server side tests
mbligh120351e2009-01-24 01:40:45 +0000572
573 @returns a dict with the following keys:
574 control_file: str, The control file text.
575 is_server: bool, is the control file a server-side control file?
576 synch_count: How many machines the job uses per autoserv execution.
577 synch_count == 1 means the job is asynchronous.
578 dependencies: A list of the names of labels on which the job depends.
579 """
showardd86debe2009-06-10 17:37:56 +0000580 if not tests and not client_control_file:
showard2bab8f42008-11-12 18:15:22 +0000581 return dict(control_file='', is_server=False, synch_count=1,
showard989f25d2008-10-01 11:38:11 +0000582 dependencies=[])
mblighe8819cd2008-02-15 16:48:40 +0000583
showard989f25d2008-10-01 11:38:11 +0000584 cf_info, test_objects, profiler_objects, label = (
showard2b9a88b2008-06-13 20:55:03 +0000585 rpc_utils.prepare_generate_control_file(tests, kernel, label,
586 profilers))
showard989f25d2008-10-01 11:38:11 +0000587 cf_info['control_file'] = control_file.generate_control(
mbligha3c58d22009-08-24 22:01:51 +0000588 tests=test_objects, kernels=kernel, platform=label,
mbligh120351e2009-01-24 01:40:45 +0000589 profilers=profiler_objects, is_server=cf_info['is_server'],
showard232b7ae2009-11-10 00:46:48 +0000590 client_control_file=client_control_file, profile_only=profile_only,
591 upload_kernel_config=upload_kernel_config)
showard989f25d2008-10-01 11:38:11 +0000592 return cf_info
mblighe8819cd2008-02-15 16:48:40 +0000593
594
jamesren4a41e012010-07-16 22:33:48 +0000595def create_parameterized_job(name, priority, test, parameters, kernel=None,
596 label=None, profilers=(), profiler_parameters=None,
597 use_container=False, profile_only=None,
598 upload_kernel_config=False, hosts=(),
599 meta_hosts=(), one_time_hosts=(),
600 atomic_group_name=None, synch_count=None,
601 is_template=False, timeout=None,
Simran Basi7e605742013-11-12 13:43:36 -0800602 timeout_mins=None, max_runtime_mins=None,
603 run_verify=False, email_list='', dependencies=(),
604 reboot_before=None, reboot_after=None,
605 parse_failed_repair=None, hostless=False,
606 keyvals=None, drone_set=None, run_reset=True):
jamesren4a41e012010-07-16 22:33:48 +0000607 """
608 Creates and enqueues a parameterized job.
609
610 Most parameters a combination of the parameters for generate_control_file()
611 and create_job(), with the exception of:
612
613 @param test name or ID of the test to run
614 @param parameters a map of parameter name ->
615 tuple of (param value, param type)
616 @param profiler_parameters a dictionary of parameters for the profilers:
617 key: profiler name
618 value: dict of param name -> tuple of
619 (param value,
620 param type)
621 """
622 # Save the values of the passed arguments here. What we're going to do with
623 # them is pass them all to rpc_utils.get_create_job_common_args(), which
624 # will extract the subset of these arguments that apply for
625 # rpc_utils.create_job_common(), which we then pass in to that function.
626 args = locals()
627
628 # Set up the parameterized job configs
629 test_obj = models.Test.smart_get(test)
Aviv Keshet3dd8beb2013-05-13 17:36:04 -0700630 control_type = test_obj.test_type
jamesren4a41e012010-07-16 22:33:48 +0000631
632 try:
633 label = models.Label.smart_get(label)
634 except models.Label.DoesNotExist:
635 label = None
636
637 kernel_objs = models.Kernel.create_kernels(kernel)
638 profiler_objs = [models.Profiler.smart_get(profiler)
639 for profiler in profilers]
640
641 parameterized_job = models.ParameterizedJob.objects.create(
642 test=test_obj, label=label, use_container=use_container,
643 profile_only=profile_only,
644 upload_kernel_config=upload_kernel_config)
645 parameterized_job.kernels.add(*kernel_objs)
646
647 for profiler in profiler_objs:
648 parameterized_profiler = models.ParameterizedJobProfiler.objects.create(
649 parameterized_job=parameterized_job,
650 profiler=profiler)
651 profiler_params = profiler_parameters.get(profiler.name, {})
652 for name, (value, param_type) in profiler_params.iteritems():
653 models.ParameterizedJobProfilerParameter.objects.create(
654 parameterized_job_profiler=parameterized_profiler,
655 parameter_name=name,
656 parameter_value=value,
657 parameter_type=param_type)
658
659 try:
660 for parameter in test_obj.testparameter_set.all():
661 if parameter.name in parameters:
662 param_value, param_type = parameters.pop(parameter.name)
663 parameterized_job.parameterizedjobparameter_set.create(
664 test_parameter=parameter, parameter_value=param_value,
665 parameter_type=param_type)
666
667 if parameters:
668 raise Exception('Extra parameters remain: %r' % parameters)
669
670 return rpc_utils.create_job_common(
671 parameterized_job=parameterized_job.id,
672 control_type=control_type,
673 **rpc_utils.get_create_job_common_args(args))
674 except:
675 parameterized_job.delete()
676 raise
677
678
Simran Basib6ec8ae2014-04-23 12:05:08 -0700679def create_job_page_handler(name, priority, control_file, control_type,
680 image=None, hostless=False, **kwargs):
681 """\
682 Create and enqueue a job.
683
684 @param name name of this job
685 @param priority Integer priority of this job. Higher is more important.
686 @param control_file String contents of the control file.
687 @param control_type Type of control file, Client or Server.
688 @param kwargs extra args that will be required by create_suite_job or
689 create_job.
690
691 @returns The created Job id number.
692 """
693 control_file = rpc_utils.encode_ascii(control_file)
Jiaxi Luodd67beb2014-07-18 16:28:31 -0700694 if not control_file:
695 raise model_logic.ValidationError({
696 'control_file' : "Control file cannot be empty"})
Simran Basib6ec8ae2014-04-23 12:05:08 -0700697
698 if image and hostless:
699 return site_rpc_interface.create_suite_job(
700 name=name, control_file=control_file, priority=priority,
701 build=image, **kwargs)
702 return create_job(name, priority, control_file, control_type, image=image,
703 hostless=hostless, **kwargs)
704
705
showard12f3e322009-05-13 21:27:42 +0000706def create_job(name, priority, control_file, control_type,
707 hosts=(), meta_hosts=(), one_time_hosts=(),
708 atomic_group_name=None, synch_count=None, is_template=False,
Simran Basi7e605742013-11-12 13:43:36 -0800709 timeout=None, timeout_mins=None, max_runtime_mins=None,
710 run_verify=False, email_list='', dependencies=(),
711 reboot_before=None, reboot_after=None, parse_failed_repair=None,
712 hostless=False, keyvals=None, drone_set=None, image=None,
Jiaxi Luo90190c92014-06-18 12:35:57 -0700713 parent_job_id=None, test_retry=0, run_reset=True, args=(),
714 **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +0000715 """\
716 Create and enqueue a job.
mblighe8819cd2008-02-15 16:48:40 +0000717
showarda1e74b32009-05-12 17:32:04 +0000718 @param name name of this job
Alex Miller7d658cf2013-09-04 16:00:35 -0700719 @param priority Integer priority of this job. Higher is more important.
showarda1e74b32009-05-12 17:32:04 +0000720 @param control_file String contents of the control file.
721 @param control_type Type of control file, Client or Server.
722 @param synch_count How many machines the job uses per autoserv execution.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700723 synch_count == 1 means the job is asynchronous. If an atomic group is
724 given this value is treated as a minimum.
showarda1e74b32009-05-12 17:32:04 +0000725 @param is_template If true then create a template job.
726 @param timeout Hours after this call returns until the job times out.
Simran Basi7e605742013-11-12 13:43:36 -0800727 @param timeout_mins Minutes after this call returns until the job times
Jiaxi Luo90190c92014-06-18 12:35:57 -0700728 out.
Simran Basi34217022012-11-06 13:43:15 -0800729 @param max_runtime_mins Minutes from job starting time until job times out
showarda1e74b32009-05-12 17:32:04 +0000730 @param run_verify Should the host be verified before running the test?
731 @param email_list String containing emails to mail when the job is done
732 @param dependencies List of label names on which this job depends
733 @param reboot_before Never, If dirty, or Always
734 @param reboot_after Never, If all tests passed, or Always
735 @param parse_failed_repair if true, results of failed repairs launched by
Jiaxi Luo90190c92014-06-18 12:35:57 -0700736 this job will be parsed as part of the job.
showarda9545c02009-12-18 22:44:26 +0000737 @param hostless if true, create a hostless job
showardc1a98d12010-01-15 00:22:22 +0000738 @param keyvals dict of keyvals to associate with the job
showarda1e74b32009-05-12 17:32:04 +0000739 @param hosts List of hosts to run job on.
740 @param meta_hosts List where each entry is a label name, and for each entry
Jiaxi Luo90190c92014-06-18 12:35:57 -0700741 one host will be chosen from that label to run the job on.
showarda1e74b32009-05-12 17:32:04 +0000742 @param one_time_hosts List of hosts not in the database to run the job on.
743 @param atomic_group_name The name of an atomic group to schedule the job on.
jamesren76fcf192010-04-21 20:39:50 +0000744 @param drone_set The name of the drone set to run this test on.
Paul Pendlebury5a8c6ad2011-02-01 07:20:17 -0800745 @param image OS image to install before running job.
Aviv Keshet0b9cfc92013-02-05 11:36:02 -0800746 @param parent_job_id id of a job considered to be parent of created job.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700747 @param test_retry Number of times to retry test if the test did not
Jiaxi Luo90190c92014-06-18 12:35:57 -0700748 complete successfully. (optional, default: 0)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700749 @param run_reset Should the host be reset before running the test?
Jiaxi Luo90190c92014-06-18 12:35:57 -0700750 @param args A list of args to be injected into control file.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700751 @param kwargs extra keyword args. NOT USED.
showardc92da832009-04-07 18:14:34 +0000752
753 @returns The created Job id number.
jadmanski0afbb632008-06-06 21:10:57 +0000754 """
Jiaxi Luo90190c92014-06-18 12:35:57 -0700755 if args:
756 control_file = tools.inject_vars({'args': args}, control_file)
757
Simran Basiab5a1bf2014-05-28 15:39:44 -0700758 if image is None:
759 return rpc_utils.create_job_common(
760 **rpc_utils.get_create_job_common_args(locals()))
761
762 # When image is supplied use a known parameterized test already in the
763 # database to pass the OS image path from the front end, through the
764 # scheduler, and finally to autoserv as the --image parameter.
765
766 # The test autoupdate_ParameterizedJob is in afe_autotests and used to
767 # instantiate a Test object and from there a ParameterizedJob.
768 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
769 known_parameterized_job = models.ParameterizedJob.objects.create(
770 test=known_test_obj)
771
772 # autoupdate_ParameterizedJob has a single parameter, the image parameter,
773 # stored in the table afe_test_parameters. We retrieve and set this
774 # instance of the parameter to the OS image path.
775 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
776 name='image')
777 known_parameterized_job.parameterizedjobparameter_set.create(
778 test_parameter=image_parameter, parameter_value=image,
779 parameter_type='string')
780
781 # By passing a parameterized_job to create_job_common the job entry in
782 # the afe_jobs table will have the field parameterized_job_id set.
783 # The scheduler uses this id in the afe_parameterized_jobs table to
784 # match this job to our known test, and then with the
785 # afe_parameterized_job_parameters table to get the actual image path.
jamesren4a41e012010-07-16 22:33:48 +0000786 return rpc_utils.create_job_common(
Simran Basiab5a1bf2014-05-28 15:39:44 -0700787 parameterized_job=known_parameterized_job.id,
jamesren4a41e012010-07-16 22:33:48 +0000788 **rpc_utils.get_create_job_common_args(locals()))
mblighe8819cd2008-02-15 16:48:40 +0000789
790
showard9dbdcda2008-10-14 17:34:36 +0000791def abort_host_queue_entries(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000792 """\
showard9dbdcda2008-10-14 17:34:36 +0000793 Abort a set of host queue entries.
Fang Deng63b0e452014-12-19 14:38:15 -0800794
795 @return: A list of dictionaries, each contains information
796 about an aborted HQE.
jadmanski0afbb632008-06-06 21:10:57 +0000797 """
showard9dbdcda2008-10-14 17:34:36 +0000798 query = models.HostQueueEntry.query_objects(filter_data)
beepsfaecbce2013-10-29 11:35:10 -0700799
800 # Dont allow aborts on:
801 # 1. Jobs that have already completed (whether or not they were aborted)
802 # 2. Jobs that we have already been aborted (but may not have completed)
803 query = query.filter(complete=False).filter(aborted=False)
showarddc817512008-11-12 18:16:41 +0000804 models.AclGroup.check_abort_permissions(query)
showard9dbdcda2008-10-14 17:34:36 +0000805 host_queue_entries = list(query.select_related())
showard2bab8f42008-11-12 18:15:22 +0000806 rpc_utils.check_abort_synchronous_jobs(host_queue_entries)
mblighe8819cd2008-02-15 16:48:40 +0000807
Simran Basic1b26762013-06-26 14:23:21 -0700808 models.HostQueueEntry.abort_host_queue_entries(host_queue_entries)
Fang Deng63b0e452014-12-19 14:38:15 -0800809 hqe_info = [{'HostQueueEntry': hqe.id, 'Job': hqe.job_id,
810 'Job name': hqe.job.name} for hqe in host_queue_entries]
811 return hqe_info
showard9d821ab2008-07-11 16:54:29 +0000812
813
beeps8bb1f7d2013-08-05 01:30:09 -0700814def abort_special_tasks(**filter_data):
815 """\
816 Abort the special task, or tasks, specified in the filter.
817 """
818 query = models.SpecialTask.query_objects(filter_data)
819 special_tasks = query.filter(is_active=True)
820 for task in special_tasks:
821 task.abort()
822
823
Simran Basi73dae552013-02-25 14:57:46 -0800824def _call_special_tasks_on_hosts(task, hosts):
825 """\
826 Schedules a set of hosts for a special task.
827
828 @returns A list of hostnames that a special task was created for.
829 """
830 models.AclGroup.check_for_acl_violation_hosts(hosts)
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -0800831 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800832 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -0800833 raise ValueError('The following hosts are on shards, please '
834 'follow the link to the shards and create jobs '
835 'there instead. %s.' % shard_host_map)
Simran Basi73dae552013-02-25 14:57:46 -0800836 for host in hosts:
837 models.SpecialTask.schedule_special_task(host, task)
838 return list(sorted(host.hostname for host in hosts))
839
840
showard1ff7b2e2009-05-15 23:17:18 +0000841def reverify_hosts(**filter_data):
842 """\
843 Schedules a set of hosts for verify.
mbligh4e545a52009-12-19 05:30:39 +0000844
845 @returns A list of hostnames that a verify task was created for.
showard1ff7b2e2009-05-15 23:17:18 +0000846 """
Prashanth Balasubramanian40981232014-12-16 19:01:58 -0800847 hosts = models.Host.query_objects(filter_data)
848 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts, rpc_hostnames=True)
849
850 # Filter out hosts on a shard from those on the master, forward
851 # rpcs to the shard with an additional hostname__in filter, and
852 # create a local SpecialTask for each remaining host.
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800853 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian40981232014-12-16 19:01:58 -0800854 hosts = [h for h in hosts if h.shard is None]
855 for shard, hostnames in shard_host_map.iteritems():
856
857 # The main client of this module is the frontend website, and
858 # it invokes it with an 'id' or an 'id__in' filter. Regardless,
859 # the 'hostname' filter should narrow down the list of hosts on
860 # each shard even though we supply all the ids in filter_data.
861 # This method uses hostname instead of id because it fits better
862 # with the overall architecture of redirection functions in rpc_utils.
863 shard_filter = filter_data.copy()
864 shard_filter['hostname__in'] = hostnames
865 rpc_utils.run_rpc_on_multiple_hostnames(
866 'reverify_hosts', [shard], **shard_filter)
867
868 # There is a race condition here if someone assigns a shard to one of these
869 # hosts before we create the task. The host will stay on the master if:
870 # 1. The host is not Ready
871 # 2. The host is Ready but has a task
872 # But if the host is Ready and doesn't have a task yet, it will get sent
873 # to the shard as we're creating a task here.
874
875 # Given that we only rarely verify Ready hosts it isn't worth putting this
876 # entire method in a transaction. The worst case scenario is that we have
877 # a verify running on a Ready host while the shard is using it, if the verify
878 # fails no subsequent tasks will be created against the host on the master,
879 # and verifies are safe enough that this is OK.
880 return _call_special_tasks_on_hosts(models.SpecialTask.Task.VERIFY, hosts)
Simran Basi73dae552013-02-25 14:57:46 -0800881
882
883def repair_hosts(**filter_data):
884 """\
885 Schedules a set of hosts for repair.
886
887 @returns A list of hostnames that a repair task was created for.
888 """
889 return _call_special_tasks_on_hosts(models.SpecialTask.Task.REPAIR,
890 models.Host.query_objects(filter_data))
showard1ff7b2e2009-05-15 23:17:18 +0000891
892
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700893def get_jobs(not_yet_run=False, running=False, finished=False,
894 suite=False, sub=False, standalone=False, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000895 """\
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700896 Extra status filter args for get_jobs:
jadmanski0afbb632008-06-06 21:10:57 +0000897 -not_yet_run: Include only jobs that have not yet started running.
898 -running: Include only jobs that have start running but for which not
899 all hosts have completed.
900 -finished: Include only jobs for which all hosts have completed (or
901 aborted).
902 At most one of these three fields should be specified.
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700903
904 Extra type filter args for get_jobs:
905 -suite: Include only jobs with child jobs.
906 -sub: Include only jobs with a parent job.
907 -standalone: Inlcude only jobs with no child or parent jobs.
908 At most one of these three fields should be specified.
jadmanski0afbb632008-06-06 21:10:57 +0000909 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700910 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
911 running,
912 finished)
913 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
914 suite,
915 sub,
916 standalone)
showard0957a842009-05-11 19:25:08 +0000917 job_dicts = []
918 jobs = list(models.Job.query_objects(filter_data))
919 models.Job.objects.populate_relationships(jobs, models.Label,
920 'dependencies')
showardc1a98d12010-01-15 00:22:22 +0000921 models.Job.objects.populate_relationships(jobs, models.JobKeyval, 'keyvals')
showard0957a842009-05-11 19:25:08 +0000922 for job in jobs:
923 job_dict = job.get_object_dict()
924 job_dict['dependencies'] = ','.join(label.name
925 for label in job.dependencies)
showardc1a98d12010-01-15 00:22:22 +0000926 job_dict['keyvals'] = dict((keyval.key, keyval.value)
927 for keyval in job.keyvals)
Eric Lid23bc192011-02-09 14:38:57 -0800928 if job.parameterized_job:
929 job_dict['image'] = get_parameterized_autoupdate_image_url(job)
showard0957a842009-05-11 19:25:08 +0000930 job_dicts.append(job_dict)
931 return rpc_utils.prepare_for_serialization(job_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000932
933
934def get_num_jobs(not_yet_run=False, running=False, finished=False,
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700935 suite=False, sub=False, standalone=False,
jadmanski0afbb632008-06-06 21:10:57 +0000936 **filter_data):
937 """\
938 See get_jobs() for documentation of extra filter parameters.
939 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700940 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
941 running,
942 finished)
943 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
944 suite,
945 sub,
946 standalone)
jadmanski0afbb632008-06-06 21:10:57 +0000947 return models.Job.query_count(filter_data)
mblighe8819cd2008-02-15 16:48:40 +0000948
949
mblighe8819cd2008-02-15 16:48:40 +0000950def get_jobs_summary(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000951 """\
Jiaxi Luoaac54572014-06-04 13:57:02 -0700952 Like get_jobs(), but adds 'status_counts' and 'result_counts' field.
953
954 'status_counts' filed is a dictionary mapping status strings to the number
955 of hosts currently with that status, i.e. {'Queued' : 4, 'Running' : 2}.
956
957 'result_counts' field is piped to tko's rpc_interface and has the return
958 format specified under get_group_counts.
jadmanski0afbb632008-06-06 21:10:57 +0000959 """
960 jobs = get_jobs(**filter_data)
961 ids = [job['id'] for job in jobs]
962 all_status_counts = models.Job.objects.get_status_counts(ids)
963 for job in jobs:
964 job['status_counts'] = all_status_counts[job['id']]
Jiaxi Luoaac54572014-06-04 13:57:02 -0700965 job['result_counts'] = tko_rpc_interface.get_status_counts(
966 ['afe_job_id', 'afe_job_id'],
967 header_groups=[['afe_job_id'], ['afe_job_id']],
968 **{'afe_job_id': job['id']})
jadmanski0afbb632008-06-06 21:10:57 +0000969 return rpc_utils.prepare_for_serialization(jobs)
mblighe8819cd2008-02-15 16:48:40 +0000970
971
showarda965cef2009-05-15 23:17:41 +0000972def get_info_for_clone(id, preserve_metahosts, queue_entry_filter_data=None):
showarda8709c52008-07-03 19:44:54 +0000973 """\
974 Retrieves all the information needed to clone a job.
975 """
showarda8709c52008-07-03 19:44:54 +0000976 job = models.Job.objects.get(id=id)
showard29f7cd22009-04-29 21:16:24 +0000977 job_info = rpc_utils.get_job_info(job,
showarda965cef2009-05-15 23:17:41 +0000978 preserve_metahosts,
979 queue_entry_filter_data)
showard945072f2008-09-03 20:34:59 +0000980
showardd9992fe2008-07-31 02:15:03 +0000981 host_dicts = []
showard29f7cd22009-04-29 21:16:24 +0000982 for host in job_info['hosts']:
983 host_dict = get_hosts(id=host.id)[0]
984 other_labels = host_dict['labels']
985 if host_dict['platform']:
986 other_labels.remove(host_dict['platform'])
987 host_dict['other_labels'] = ', '.join(other_labels)
showardd9992fe2008-07-31 02:15:03 +0000988 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +0000989
showard29f7cd22009-04-29 21:16:24 +0000990 for host in job_info['one_time_hosts']:
991 host_dict = dict(hostname=host.hostname,
992 id=host.id,
993 platform='(one-time host)',
994 locked_text='')
995 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +0000996
showard4d077562009-05-08 18:24:36 +0000997 # convert keys from Label objects to strings (names of labels)
showard29f7cd22009-04-29 21:16:24 +0000998 meta_host_counts = dict((meta_host.name, count) for meta_host, count
showard4d077562009-05-08 18:24:36 +0000999 in job_info['meta_host_counts'].iteritems())
showard29f7cd22009-04-29 21:16:24 +00001000
1001 info = dict(job=job.get_object_dict(),
1002 meta_host_counts=meta_host_counts,
1003 hosts=host_dicts)
1004 info['job']['dependencies'] = job_info['dependencies']
1005 if job_info['atomic_group']:
1006 info['atomic_group_name'] = (job_info['atomic_group']).name
1007 else:
1008 info['atomic_group_name'] = None
jamesren2275ef12010-04-12 18:25:06 +00001009 info['hostless'] = job_info['hostless']
jamesren76fcf192010-04-21 20:39:50 +00001010 info['drone_set'] = job.drone_set and job.drone_set.name
showarda8709c52008-07-03 19:44:54 +00001011
Eric Lid23bc192011-02-09 14:38:57 -08001012 if job.parameterized_job:
1013 info['job']['image'] = get_parameterized_autoupdate_image_url(job)
1014
showarda8709c52008-07-03 19:44:54 +00001015 return rpc_utils.prepare_for_serialization(info)
1016
1017
showard34dc5fa2008-04-24 20:58:40 +00001018# host queue entries
1019
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001020def get_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001021 """\
showardc92da832009-04-07 18:14:34 +00001022 @returns A sequence of nested dictionaries of host and job information.
jadmanski0afbb632008-06-06 21:10:57 +00001023 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001024 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1025 'started_on__lte',
1026 start_time,
1027 end_time,
1028 **filter_data)
showardc92da832009-04-07 18:14:34 +00001029 return rpc_utils.prepare_rows_as_nested_dicts(
1030 models.HostQueueEntry.query_objects(filter_data),
1031 ('host', 'atomic_group', 'job'))
showard34dc5fa2008-04-24 20:58:40 +00001032
1033
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001034def get_num_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001035 """\
1036 Get the number of host queue entries associated with this job.
1037 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001038 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1039 'started_on__lte',
1040 start_time,
1041 end_time,
1042 **filter_data)
jadmanski0afbb632008-06-06 21:10:57 +00001043 return models.HostQueueEntry.query_count(filter_data)
showard34dc5fa2008-04-24 20:58:40 +00001044
1045
showard1e935f12008-07-11 00:11:36 +00001046def get_hqe_percentage_complete(**filter_data):
1047 """
showardc92da832009-04-07 18:14:34 +00001048 Computes the fraction of host queue entries matching the given filter data
showard1e935f12008-07-11 00:11:36 +00001049 that are complete.
1050 """
1051 query = models.HostQueueEntry.query_objects(filter_data)
1052 complete_count = query.filter(complete=True).count()
1053 total_count = query.count()
1054 if total_count == 0:
1055 return 1
1056 return float(complete_count) / total_count
1057
1058
showard1a5a4082009-07-28 20:01:37 +00001059# special tasks
1060
1061def get_special_tasks(**filter_data):
1062 return rpc_utils.prepare_rows_as_nested_dicts(
1063 models.SpecialTask.query_objects(filter_data),
1064 ('host', 'queue_entry'))
1065
1066
showardc0ac3a72009-07-08 21:14:45 +00001067# support for host detail view
1068
Jiaxi Luo79ce6422014-06-13 17:08:09 -07001069def get_host_queue_entries_and_special_tasks(host_id, query_start=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001070 query_limit=None, start_time=None,
1071 end_time=None):
showardc0ac3a72009-07-08 21:14:45 +00001072 """
1073 @returns an interleaved list of HostQueueEntries and SpecialTasks,
1074 in approximate run order. each dict contains keys for type, host,
1075 job, status, started_on, execution_path, and ID.
1076 """
1077 total_limit = None
1078 if query_limit is not None:
1079 total_limit = query_start + query_limit
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001080 filter_data_common = {'host': host_id,
1081 'query_limit': total_limit,
1082 'sort_by': ['-id']}
showardc0ac3a72009-07-08 21:14:45 +00001083
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001084 filter_data_queue_entries, filter_data_special_tasks = (
1085 rpc_utils.inject_times_to_hqe_special_tasks_filters(
1086 filter_data_common, start_time, end_time))
1087
1088 queue_entries = list(models.HostQueueEntry.query_objects(
1089 filter_data_queue_entries))
1090 special_tasks = list(models.SpecialTask.query_objects(
1091 filter_data_special_tasks))
showardc0ac3a72009-07-08 21:14:45 +00001092
1093 interleaved_entries = rpc_utils.interleave_entries(queue_entries,
1094 special_tasks)
1095 if query_start is not None:
1096 interleaved_entries = interleaved_entries[query_start:]
1097 if query_limit is not None:
1098 interleaved_entries = interleaved_entries[:query_limit]
1099 return rpc_utils.prepare_for_serialization(interleaved_entries)
1100
1101
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001102def get_num_host_queue_entries_and_special_tasks(host_id, start_time=None,
1103 end_time=None):
1104 filter_data_common = {'host': host_id}
1105
1106 filter_data_queue_entries, filter_data_special_tasks = (
1107 rpc_utils.inject_times_to_hqe_special_tasks_filters(
1108 filter_data_common, start_time, end_time))
1109
1110 return (models.HostQueueEntry.query_count(filter_data_queue_entries)
1111 + models.SpecialTask.query_count(filter_data_special_tasks))
showardc0ac3a72009-07-08 21:14:45 +00001112
1113
showard29f7cd22009-04-29 21:16:24 +00001114# recurring run
1115
1116def get_recurring(**filter_data):
1117 return rpc_utils.prepare_rows_as_nested_dicts(
1118 models.RecurringRun.query_objects(filter_data),
1119 ('job', 'owner'))
1120
1121
1122def get_num_recurring(**filter_data):
1123 return models.RecurringRun.query_count(filter_data)
1124
1125
1126def delete_recurring_runs(**filter_data):
1127 to_delete = models.RecurringRun.query_objects(filter_data)
1128 to_delete.delete()
1129
1130
1131def create_recurring_run(job_id, start_date, loop_period, loop_count):
showard64a95952010-01-13 21:27:16 +00001132 owner = models.User.current_user().login
showard29f7cd22009-04-29 21:16:24 +00001133 job = models.Job.objects.get(id=job_id)
1134 return job.create_recurring_job(start_date=start_date,
1135 loop_period=loop_period,
1136 loop_count=loop_count,
1137 owner=owner)
1138
1139
mblighe8819cd2008-02-15 16:48:40 +00001140# other
1141
showarde0b63622008-08-04 20:58:47 +00001142def echo(data=""):
1143 """\
1144 Returns a passed in string. For doing a basic test to see if RPC calls
1145 can successfully be made.
1146 """
1147 return data
1148
1149
showardb7a52fd2009-04-27 20:10:56 +00001150def get_motd():
1151 """\
1152 Returns the message of the day as a string.
1153 """
1154 return rpc_utils.get_motd()
1155
1156
mblighe8819cd2008-02-15 16:48:40 +00001157def get_static_data():
jadmanski0afbb632008-06-06 21:10:57 +00001158 """\
1159 Returns a dictionary containing a bunch of data that shouldn't change
1160 often and is otherwise inaccessible. This includes:
showardc92da832009-04-07 18:14:34 +00001161
1162 priorities: List of job priority choices.
1163 default_priority: Default priority value for new jobs.
1164 users: Sorted list of all users.
Jiaxi Luo31874592014-06-11 10:36:35 -07001165 labels: Sorted list of labels not start with 'cros-version' and
1166 'fw-version'.
showardc92da832009-04-07 18:14:34 +00001167 atomic_groups: Sorted list of all atomic groups.
1168 tests: Sorted list of all tests.
1169 profilers: Sorted list of all profilers.
1170 current_user: Logged-in username.
1171 host_statuses: Sorted list of possible Host statuses.
1172 job_statuses: Sorted list of possible HostQueueEntry statuses.
Simran Basi7e605742013-11-12 13:43:36 -08001173 job_timeout_default: The default job timeout length in minutes.
showarda1e74b32009-05-12 17:32:04 +00001174 parse_failed_repair_default: Default value for the parse_failed_repair job
Jiaxi Luo31874592014-06-11 10:36:35 -07001175 option.
showardc92da832009-04-07 18:14:34 +00001176 reboot_before_options: A list of valid RebootBefore string enums.
1177 reboot_after_options: A list of valid RebootAfter string enums.
1178 motd: Server's message of the day.
1179 status_dictionary: A mapping from one word job status names to a more
1180 informative description.
jadmanski0afbb632008-06-06 21:10:57 +00001181 """
showard21baa452008-10-21 00:08:39 +00001182
1183 job_fields = models.Job.get_field_dict()
jamesren76fcf192010-04-21 20:39:50 +00001184 default_drone_set_name = models.DroneSet.default_drone_set_name()
1185 drone_sets = ([default_drone_set_name] +
1186 sorted(drone_set.name for drone_set in
1187 models.DroneSet.objects.exclude(
1188 name=default_drone_set_name)))
showard21baa452008-10-21 00:08:39 +00001189
jadmanski0afbb632008-06-06 21:10:57 +00001190 result = {}
Alex Miller7d658cf2013-09-04 16:00:35 -07001191 result['priorities'] = priorities.Priority.choices()
1192 default_priority = priorities.Priority.DEFAULT
1193 result['default_priority'] = 'Default'
1194 result['max_schedulable_priority'] = priorities.Priority.DEFAULT
jadmanski0afbb632008-06-06 21:10:57 +00001195 result['users'] = get_users(sort_by=['login'])
Jiaxi Luo31874592014-06-11 10:36:35 -07001196
1197 label_exclude_filters = [{'name__startswith': 'cros-version'},
1198 {'name__startswith': 'fw-version'}]
1199 result['labels'] = get_labels(
1200 label_exclude_filters,
1201 sort_by=['-platform', 'name'])
1202
showardc92da832009-04-07 18:14:34 +00001203 result['atomic_groups'] = get_atomic_groups(sort_by=['name'])
jadmanski0afbb632008-06-06 21:10:57 +00001204 result['tests'] = get_tests(sort_by=['name'])
showard2b9a88b2008-06-13 20:55:03 +00001205 result['profilers'] = get_profilers(sort_by=['name'])
showard0fc38302008-10-23 00:44:07 +00001206 result['current_user'] = rpc_utils.prepare_for_serialization(
showard64a95952010-01-13 21:27:16 +00001207 models.User.current_user().get_object_dict())
showard2b9a88b2008-06-13 20:55:03 +00001208 result['host_statuses'] = sorted(models.Host.Status.names)
mbligh5a198b92008-12-11 19:33:29 +00001209 result['job_statuses'] = sorted(models.HostQueueEntry.Status.names)
Simran Basi7e605742013-11-12 13:43:36 -08001210 result['job_timeout_mins_default'] = models.Job.DEFAULT_TIMEOUT_MINS
Simran Basi34217022012-11-06 13:43:15 -08001211 result['job_max_runtime_mins_default'] = (
1212 models.Job.DEFAULT_MAX_RUNTIME_MINS)
showarda1e74b32009-05-12 17:32:04 +00001213 result['parse_failed_repair_default'] = bool(
1214 models.Job.DEFAULT_PARSE_FAILED_REPAIR)
jamesrendd855242010-03-02 22:23:44 +00001215 result['reboot_before_options'] = model_attributes.RebootBefore.names
1216 result['reboot_after_options'] = model_attributes.RebootAfter.names
showard8fbae652009-01-20 23:23:10 +00001217 result['motd'] = rpc_utils.get_motd()
jamesren76fcf192010-04-21 20:39:50 +00001218 result['drone_sets_enabled'] = models.DroneSet.drone_sets_enabled()
1219 result['drone_sets'] = drone_sets
jamesren4a41e012010-07-16 22:33:48 +00001220 result['parameterized_jobs'] = models.Job.parameterized_jobs_enabled()
showard8ac29b42008-07-17 17:01:55 +00001221
showardd3dc1992009-04-22 21:01:40 +00001222 result['status_dictionary'] = {"Aborted": "Aborted",
showard8ac29b42008-07-17 17:01:55 +00001223 "Verifying": "Verifying Host",
Alex Millerdfff2fd2013-05-28 13:05:06 -07001224 "Provisioning": "Provisioning Host",
showard8ac29b42008-07-17 17:01:55 +00001225 "Pending": "Waiting on other hosts",
1226 "Running": "Running autoserv",
1227 "Completed": "Autoserv completed",
1228 "Failed": "Failed to complete",
showardd823b362008-07-24 16:35:46 +00001229 "Queued": "Queued",
showard5deb6772008-11-04 21:54:33 +00001230 "Starting": "Next in host's queue",
1231 "Stopped": "Other host(s) failed verify",
showardd3dc1992009-04-22 21:01:40 +00001232 "Parsing": "Awaiting parse of final results",
showard29f7cd22009-04-29 21:16:24 +00001233 "Gathering": "Gathering log files",
showard8cc058f2009-09-08 16:26:33 +00001234 "Template": "Template job for recurring run",
mbligh4608b002010-01-05 18:22:35 +00001235 "Waiting": "Waiting for scheduler action",
Dan Shi07e09af2013-04-12 09:31:29 -07001236 "Archiving": "Archiving results",
1237 "Resetting": "Resetting hosts"}
Jiaxi Luo421608e2014-07-07 14:38:00 -07001238
1239 result['wmatrix_url'] = rpc_utils.get_wmatrix_url()
Simran Basi71206ef2014-08-13 13:51:18 -07001240 result['is_moblab'] = bool(utils.is_moblab())
Jiaxi Luo421608e2014-07-07 14:38:00 -07001241
jadmanski0afbb632008-06-06 21:10:57 +00001242 return result
showard29f7cd22009-04-29 21:16:24 +00001243
1244
1245def get_server_time():
1246 return datetime.datetime.now().strftime("%Y-%m-%d %H:%M")