blob: ffa3093f26d431f2df5b5375182216197d751170 [file] [log] [blame]
Aviv Keshet0b9cfc92013-02-05 11:36:02 -08001# pylint: disable-msg=C0111
2
mblighe8819cd2008-02-15 16:48:40 +00003"""\
4Functions to expose over the RPC interface.
5
6For all modify* and delete* functions that ask for an 'id' parameter to
7identify the object to operate on, the id may be either
8 * the database row ID
9 * the name of the object (label name, hostname, user login, etc.)
10 * a dictionary containing uniquely identifying field (this option should seldom
11 be used)
12
13When specifying foreign key fields (i.e. adding hosts to a label, or adding
14users to an ACL group), the given value may be either the database row ID or the
15name of the object.
16
17All get* functions return lists of dictionaries. Each dictionary represents one
18object and maps field names to values.
19
20Some examples:
21modify_host(2, hostname='myhost') # modify hostname of host with database ID 2
22modify_host('ipaj2', hostname='myhost') # modify hostname of host 'ipaj2'
23modify_test('sleeptest', test_type='Client', params=', seconds=60')
24delete_acl_group(1) # delete by ID
25delete_acl_group('Everyone') # delete by name
26acl_group_add_users('Everyone', ['mbligh', 'showard'])
27get_jobs(owner='showard', status='Queued')
28
mbligh93c80e62009-02-03 17:48:30 +000029See doctests/001_rpc_test.txt for (lots) more examples.
mblighe8819cd2008-02-15 16:48:40 +000030"""
31
32__author__ = 'showard@google.com (Steve Howard)'
33
MK Ryu9c5fbbe2015-02-11 15:46:22 -080034import sys
showard29f7cd22009-04-29 21:16:24 +000035import datetime
MK Ryu9c5fbbe2015-02-11 15:46:22 -080036
Moises Osorio2dc7a102014-12-02 18:24:02 -080037from django.db.models import Count
showardcafd16e2009-05-29 18:37:49 +000038import common
Simran Basib6ec8ae2014-04-23 12:05:08 -070039from autotest_lib.client.common_lib import priorities
Gabe Black1e1c41b2015-02-04 23:55:15 -080040from autotest_lib.client.common_lib.cros.graphite import autotest_stats
jamesrendd855242010-03-02 22:23:44 +000041from autotest_lib.frontend.afe import models, model_logic, model_attributes
showard6d7b2ff2009-06-10 00:16:47 +000042from autotest_lib.frontend.afe import control_file, rpc_utils
Simran Basib6ec8ae2014-04-23 12:05:08 -070043from autotest_lib.frontend.afe import site_rpc_interface
Moises Osorio2dc7a102014-12-02 18:24:02 -080044from autotest_lib.frontend.tko import models as tko_models
Jiaxi Luoaac54572014-06-04 13:57:02 -070045from autotest_lib.frontend.tko import rpc_interface as tko_rpc_interface
Simran Basi71206ef2014-08-13 13:51:18 -070046from autotest_lib.server import utils
Jiaxi Luo90190c92014-06-18 12:35:57 -070047from autotest_lib.server.cros.dynamic_suite import tools
mblighe8819cd2008-02-15 16:48:40 +000048
Moises Osorio2dc7a102014-12-02 18:24:02 -080049
Gabe Black1e1c41b2015-02-04 23:55:15 -080050_timer = autotest_stats.Timer('rpc_interface')
Moises Osorio2dc7a102014-12-02 18:24:02 -080051
Eric Lid23bc192011-02-09 14:38:57 -080052def get_parameterized_autoupdate_image_url(job):
53 """Get the parameterized autoupdate image url from a parameterized job."""
54 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
55 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
beeps8bb1f7d2013-08-05 01:30:09 -070056 name='image')
Eric Lid23bc192011-02-09 14:38:57 -080057 para_set = job.parameterized_job.parameterizedjobparameter_set
58 job_test_para = para_set.get(test_parameter=image_parameter)
59 return job_test_para.parameter_value
60
61
mblighe8819cd2008-02-15 16:48:40 +000062# labels
63
mblighe8819cd2008-02-15 16:48:40 +000064def modify_label(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +000065 models.Label.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +000066
67
68def delete_label(id):
jadmanski0afbb632008-06-06 21:10:57 +000069 models.Label.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +000070
Prashanth Balasubramanian744898f2015-01-13 05:04:16 -080071
MK Ryu9c5fbbe2015-02-11 15:46:22 -080072def add_label(name, ignore_exception_if_exists=False, **kwargs):
73 """Add a new label with a given name.
74
75 @param name: label name.
76 @param ignore_exception_if_exists: If True and the exception was
77 thrown due to the duplicated label name when adding a label,
78 then suppress the exception. Default is False.
79 @param kwargs: keyword args that store more info about a label
80 other than the name.
81 @return: int/long id of a new label.
82 """
83 # models.Label.add_object() throws model_logic.ValidationError
84 # when it is given a label name that already exists.
85 # However, ValidationError can be thrown with different errors,
86 # and those errors should be thrown up to the call chain.
87 try:
88 label = models.Label.add_object(name=name, **kwargs)
89 except:
90 exc_info = sys.exc_info()
91 if ignore_exception_if_exists:
92 label = rpc_utils.get_label(name)
93 # If the exception is raised not because of duplicated
94 # "name", then raise the original exception.
95 if label is None:
96 raise exc_info[0], exc_info[1], exc_info[2]
97 else:
98 raise exc_info[0], exc_info[1], exc_info[2]
99 return label.id
100
101
102def add_label_to_hosts(id, hosts):
103 """Add a label with the given id to the given hosts only in local DB.
104
105 @param id: id or name of a label. More often a label name.
106 @param hosts: The hostnames of hosts that need the label.
107
108 @raises models.Label.DoesNotExist: If the label with id doesn't exist.
109 """
110 label = models.Label.smart_get(id)
111 host_objs = models.Host.smart_get_bulk(hosts)
112 if label.platform:
113 models.Host.check_no_platform(host_objs)
114 label.host_set.add(*host_objs)
115
116
117def label_add_hosts(id, hosts):
118 """Add a label with the given id to the given hosts.
119
120 This method should be run only on master not shards.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800121 The given label will be created if it doesn't exist, provided the `id`
122 supplied is a label name not an int/long id.
123
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800124 @param id: id or name of a label. More often a label name.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800125 @param hosts: A list of hostnames or ids. More often hostnames.
126
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800127 @raises ValueError: If the id specified is an int/long (label id)
128 while the label does not exist.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800129 """
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800130 # This RPC call should be accepted only by master.
131 if utils.is_shard():
132 rpc_utils.route_rpc_to_master('label_add_hosts', id=id, hosts=hosts)
133 return
134
showardbe3ec042008-11-12 18:16:07 +0000135 host_objs = models.Host.smart_get_bulk(hosts)
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800136 try:
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800137 label = models.Label.smart_get(id)
138 except models.Label.DoesNotExist:
139 # This matches the type checks in smart_get, which is a hack
140 # in and off itself. The aim here is to create any non-existent
141 # label, which we cannot do if the 'id' specified isn't a label name.
142 if isinstance(id, basestring):
143 label = models.Label.smart_get(add_label(id))
144 else:
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800145 raise ValueError('Label id (%s) does not exist. Please specify '
146 'the argument, id, as a string (label name).'
147 % id)
148 add_label_to_hosts(id, hosts)
149 # Make sure the label exists on the shard with the same id
150 # as it is on the master.
151 # It is possible that the label is already in a shard.
152 # We ignore exception in such a case.
153 rpc_utils.fanout_rpc(
154 host_objs, 'add_label', name=label.name, id=label.id,
155 include_hostnames=False, ignore_exception_if_exists=True)
156 rpc_utils.fanout_rpc(host_objs, 'add_label_to_hosts', id=id)
showardbbabf502008-06-06 00:02:02 +0000157
158
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800159@rpc_utils.forward_multi_host_rpc_to_shards
showardbbabf502008-06-06 00:02:02 +0000160def label_remove_hosts(id, hosts):
showardbe3ec042008-11-12 18:16:07 +0000161 host_objs = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000162 models.Label.smart_get(id).host_set.remove(*host_objs)
showardbbabf502008-06-06 00:02:02 +0000163
164
Jiaxi Luo31874592014-06-11 10:36:35 -0700165def get_labels(exclude_filters=(), **filter_data):
showardc92da832009-04-07 18:14:34 +0000166 """\
Jiaxi Luo31874592014-06-11 10:36:35 -0700167 @param exclude_filters: A sequence of dictionaries of filters.
168
showardc92da832009-04-07 18:14:34 +0000169 @returns A sequence of nested dictionaries of label information.
170 """
Jiaxi Luo31874592014-06-11 10:36:35 -0700171 labels = models.Label.query_objects(filter_data)
172 for exclude_filter in exclude_filters:
173 labels = labels.exclude(**exclude_filter)
174 return rpc_utils.prepare_rows_as_nested_dicts(labels, ('atomic_group',))
showardc92da832009-04-07 18:14:34 +0000175
176
177# atomic groups
178
showarde9450c92009-06-30 01:58:52 +0000179def add_atomic_group(name, max_number_of_machines=None, description=None):
showardc92da832009-04-07 18:14:34 +0000180 return models.AtomicGroup.add_object(
181 name=name, max_number_of_machines=max_number_of_machines,
182 description=description).id
183
184
185def modify_atomic_group(id, **data):
186 models.AtomicGroup.smart_get(id).update_object(data)
187
188
189def delete_atomic_group(id):
190 models.AtomicGroup.smart_get(id).delete()
191
192
193def atomic_group_add_labels(id, labels):
194 label_objs = models.Label.smart_get_bulk(labels)
195 models.AtomicGroup.smart_get(id).label_set.add(*label_objs)
196
197
198def atomic_group_remove_labels(id, labels):
199 label_objs = models.Label.smart_get_bulk(labels)
200 models.AtomicGroup.smart_get(id).label_set.remove(*label_objs)
201
202
203def get_atomic_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000204 return rpc_utils.prepare_for_serialization(
showardc92da832009-04-07 18:14:34 +0000205 models.AtomicGroup.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000206
207
208# hosts
209
showarddf062562008-07-03 19:56:37 +0000210def add_host(hostname, status=None, locked=None, protection=None):
jadmanski0afbb632008-06-06 21:10:57 +0000211 return models.Host.add_object(hostname=hostname, status=status,
showarddf062562008-07-03 19:56:37 +0000212 locked=locked, protection=protection).id
mblighe8819cd2008-02-15 16:48:40 +0000213
214
Jakob Juelich50e91f72014-10-01 12:43:23 -0700215@rpc_utils.forward_single_host_rpc_to_shard
mblighe8819cd2008-02-15 16:48:40 +0000216def modify_host(id, **data):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700217 """Modify local attributes of a host.
218
219 If this is called on the master, but the host is assigned to a shard, this
220 will also forward the call to the responsible shard. This means i.e. if a
221 host is being locked using this function, this change will also propagate to
222 shards.
223
224 @param id: id of the host to modify.
225 @param **data: key=value pairs of values to set on the host.
226 """
showardbe0d8692009-08-20 23:42:44 +0000227 rpc_utils.check_modify_host(data)
showardce7c0922009-09-11 18:39:24 +0000228 host = models.Host.smart_get(id)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700229
showardce7c0922009-09-11 18:39:24 +0000230 rpc_utils.check_modify_host_locking(host, data)
231 host.update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000232
233
showard276f9442009-05-20 00:33:16 +0000234def modify_hosts(host_filter_data, update_data):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700235 """Modify local attributes of multiple hosts.
236
237 If this is called on the master, but one of the hosts in that match the
238 filters is assigned to a shard, this will also forward the call to the
239 responsible shard.
240
241 The filters are always applied on the master, not on the shards. This means
242 if the states of a host differ on the master and a shard, the state on the
243 master will be used. I.e. this means:
244 A host was synced to Shard 1. On Shard 1 the status of the host was set to
245 'Repair Failed'.
246 - A call to modify_hosts with host_filter_data={'status': 'Ready'} will
247 update the host (both on the shard and on the master), because the state
248 of the host as the master knows it is still 'Ready'.
249 - A call to modify_hosts with host_filter_data={'status': 'Repair failed'
250 will not update the host, because the filter doesn't apply on the master.
251
showardbe0d8692009-08-20 23:42:44 +0000252 @param host_filter_data: Filters out which hosts to modify.
253 @param update_data: A dictionary with the changes to make to the hosts.
showard276f9442009-05-20 00:33:16 +0000254 """
showardbe0d8692009-08-20 23:42:44 +0000255 rpc_utils.check_modify_host(update_data)
showard276f9442009-05-20 00:33:16 +0000256 hosts = models.Host.query_objects(host_filter_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700257
258 affected_shard_hostnames = set()
259 affected_host_ids = []
260
Alex Miller9658a952013-05-14 16:40:02 -0700261 # Check all hosts before changing data for exception safety.
262 for host in hosts:
263 rpc_utils.check_modify_host_locking(host, update_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700264 if host.shard:
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800265 affected_shard_hostnames.add(host.shard.rpc_hostname())
Jakob Juelich50e91f72014-10-01 12:43:23 -0700266 affected_host_ids.append(host.id)
267
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800268 if not utils.is_shard():
Jakob Juelich50e91f72014-10-01 12:43:23 -0700269 # Caution: Changing the filter from the original here. See docstring.
270 rpc_utils.run_rpc_on_multiple_hostnames(
271 'modify_hosts', affected_shard_hostnames,
272 host_filter_data={'id__in': affected_host_ids},
273 update_data=update_data)
274
showard276f9442009-05-20 00:33:16 +0000275 for host in hosts:
276 host.update_object(update_data)
277
278
mblighe8819cd2008-02-15 16:48:40 +0000279def host_add_labels(id, labels):
showardbe3ec042008-11-12 18:16:07 +0000280 labels = models.Label.smart_get_bulk(labels)
showardcafd16e2009-05-29 18:37:49 +0000281 host = models.Host.smart_get(id)
282
283 platforms = [label.name for label in labels if label.platform]
284 if len(platforms) > 1:
285 raise model_logic.ValidationError(
286 {'labels': 'Adding more than one platform label: %s' %
287 ', '.join(platforms)})
288 if len(platforms) == 1:
289 models.Host.check_no_platform([host])
290 host.labels.add(*labels)
mblighe8819cd2008-02-15 16:48:40 +0000291
292
293def host_remove_labels(id, labels):
showardbe3ec042008-11-12 18:16:07 +0000294 labels = models.Label.smart_get_bulk(labels)
jadmanski0afbb632008-06-06 21:10:57 +0000295 models.Host.smart_get(id).labels.remove(*labels)
mblighe8819cd2008-02-15 16:48:40 +0000296
297
MK Ryuacf35922014-10-03 14:56:49 -0700298def get_host_attribute(attribute, **host_filter_data):
299 """
300 @param attribute: string name of attribute
301 @param host_filter_data: filter data to apply to Hosts to choose hosts to
302 act upon
303 """
304 hosts = rpc_utils.get_host_query((), False, False, True, host_filter_data)
305 hosts = list(hosts)
306 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
307 'attribute_list')
308 host_attr_dicts = []
309 for host_obj in hosts:
310 for attr_obj in host_obj.attribute_list:
311 if attr_obj.attribute == attribute:
312 host_attr_dicts.append(attr_obj.get_object_dict())
313 return rpc_utils.prepare_for_serialization(host_attr_dicts)
314
315
showard0957a842009-05-11 19:25:08 +0000316def set_host_attribute(attribute, value, **host_filter_data):
317 """
318 @param attribute string name of attribute
319 @param value string, or None to delete an attribute
320 @param host_filter_data filter data to apply to Hosts to choose hosts to act
321 upon
322 """
323 assert host_filter_data # disallow accidental actions on all hosts
324 hosts = models.Host.query_objects(host_filter_data)
325 models.AclGroup.check_for_acl_violation_hosts(hosts)
326
327 for host in hosts:
showardf8b19042009-05-12 17:22:49 +0000328 host.set_or_delete_attribute(attribute, value)
showard0957a842009-05-11 19:25:08 +0000329
330
Jakob Juelich50e91f72014-10-01 12:43:23 -0700331@rpc_utils.forward_single_host_rpc_to_shard
mblighe8819cd2008-02-15 16:48:40 +0000332def delete_host(id):
jadmanski0afbb632008-06-06 21:10:57 +0000333 models.Host.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000334
335
showard87cc38f2009-08-20 23:37:04 +0000336def get_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000337 exclude_atomic_group_hosts=False, valid_only=True, **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000338 """
339 @param multiple_labels: match hosts in all of the labels given. Should
340 be a list of label names.
341 @param exclude_only_if_needed_labels: Exclude hosts with at least one
342 "only_if_needed" label applied.
343 @param exclude_atomic_group_hosts: Exclude hosts that have one or more
344 atomic group labels associated with them.
jadmanski0afbb632008-06-06 21:10:57 +0000345 """
showard43a3d262008-11-12 18:17:05 +0000346 hosts = rpc_utils.get_host_query(multiple_labels,
347 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000348 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000349 valid_only, filter_data)
showard0957a842009-05-11 19:25:08 +0000350 hosts = list(hosts)
351 models.Host.objects.populate_relationships(hosts, models.Label,
352 'label_list')
353 models.Host.objects.populate_relationships(hosts, models.AclGroup,
354 'acl_list')
355 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
356 'attribute_list')
showard43a3d262008-11-12 18:17:05 +0000357 host_dicts = []
358 for host_obj in hosts:
359 host_dict = host_obj.get_object_dict()
showard0957a842009-05-11 19:25:08 +0000360 host_dict['labels'] = [label.name for label in host_obj.label_list]
showard909c9142009-07-07 20:54:42 +0000361 host_dict['platform'], host_dict['atomic_group'] = (rpc_utils.
362 find_platform_and_atomic_group(host_obj))
showard0957a842009-05-11 19:25:08 +0000363 host_dict['acls'] = [acl.name for acl in host_obj.acl_list]
364 host_dict['attributes'] = dict((attribute.attribute, attribute.value)
365 for attribute in host_obj.attribute_list)
showard43a3d262008-11-12 18:17:05 +0000366 host_dicts.append(host_dict)
367 return rpc_utils.prepare_for_serialization(host_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000368
369
showard87cc38f2009-08-20 23:37:04 +0000370def get_num_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000371 exclude_atomic_group_hosts=False, valid_only=True,
372 **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000373 """
374 Same parameters as get_hosts().
375
376 @returns The number of matching hosts.
377 """
showard43a3d262008-11-12 18:17:05 +0000378 hosts = rpc_utils.get_host_query(multiple_labels,
379 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000380 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000381 valid_only, filter_data)
showard43a3d262008-11-12 18:17:05 +0000382 return hosts.count()
showard1385b162008-03-13 15:59:40 +0000383
mblighe8819cd2008-02-15 16:48:40 +0000384
385# tests
386
showard909c7a62008-07-15 21:52:38 +0000387def add_test(name, test_type, path, author=None, dependencies=None,
showard3d9899a2008-07-31 02:11:58 +0000388 experimental=True, run_verify=None, test_class=None,
showard909c7a62008-07-15 21:52:38 +0000389 test_time=None, test_category=None, description=None,
390 sync_count=1):
jadmanski0afbb632008-06-06 21:10:57 +0000391 return models.Test.add_object(name=name, test_type=test_type, path=path,
showard909c7a62008-07-15 21:52:38 +0000392 author=author, dependencies=dependencies,
393 experimental=experimental,
394 run_verify=run_verify, test_time=test_time,
395 test_category=test_category,
396 sync_count=sync_count,
jadmanski0afbb632008-06-06 21:10:57 +0000397 test_class=test_class,
398 description=description).id
mblighe8819cd2008-02-15 16:48:40 +0000399
400
401def modify_test(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000402 models.Test.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000403
404
405def delete_test(id):
jadmanski0afbb632008-06-06 21:10:57 +0000406 models.Test.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000407
408
409def get_tests(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000410 return rpc_utils.prepare_for_serialization(
411 models.Test.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000412
413
Moises Osorio2dc7a102014-12-02 18:24:02 -0800414@_timer.decorate
415def get_tests_status_counts_by_job_name_label(job_name_prefix, label_name):
416 """Gets the counts of all passed and failed tests from the matching jobs.
417
418 @param job_name_prefix: Name prefix of the jobs to get the summary from, e.g.,
419 'butterfly-release/R40-6457.21.0/bvt-cq/'.
420 @param label_name: Label that must be set in the jobs, e.g.,
421 'cros-version:butterfly-release/R40-6457.21.0'.
422
423 @returns A summary of the counts of all the passed and failed tests.
424 """
425 job_ids = list(models.Job.objects.filter(
426 name__startswith=job_name_prefix,
427 dependency_labels__name=label_name).values_list(
428 'pk', flat=True))
429 summary = {'passed': 0, 'failed': 0}
430 if not job_ids:
431 return summary
432
433 counts = (tko_models.TestView.objects.filter(
434 afe_job_id__in=job_ids).exclude(
435 test_name='SERVER_JOB').exclude(
436 test_name__startswith='CLIENT_JOB').values(
437 'status').annotate(
438 count=Count('status')))
439 for status in counts:
440 if status['status'] == 'GOOD':
441 summary['passed'] += status['count']
442 else:
443 summary['failed'] += status['count']
444 return summary
445
446
showard2b9a88b2008-06-13 20:55:03 +0000447# profilers
448
449def add_profiler(name, description=None):
450 return models.Profiler.add_object(name=name, description=description).id
451
452
453def modify_profiler(id, **data):
454 models.Profiler.smart_get(id).update_object(data)
455
456
457def delete_profiler(id):
458 models.Profiler.smart_get(id).delete()
459
460
461def get_profilers(**filter_data):
462 return rpc_utils.prepare_for_serialization(
463 models.Profiler.list_objects(filter_data))
464
465
mblighe8819cd2008-02-15 16:48:40 +0000466# users
467
468def add_user(login, access_level=None):
jadmanski0afbb632008-06-06 21:10:57 +0000469 return models.User.add_object(login=login, access_level=access_level).id
mblighe8819cd2008-02-15 16:48:40 +0000470
471
472def modify_user(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000473 models.User.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000474
475
476def delete_user(id):
jadmanski0afbb632008-06-06 21:10:57 +0000477 models.User.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000478
479
480def get_users(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000481 return rpc_utils.prepare_for_serialization(
482 models.User.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000483
484
485# acl groups
486
487def add_acl_group(name, description=None):
showard04f2cd82008-07-25 20:53:31 +0000488 group = models.AclGroup.add_object(name=name, description=description)
showard64a95952010-01-13 21:27:16 +0000489 group.users.add(models.User.current_user())
showard04f2cd82008-07-25 20:53:31 +0000490 return group.id
mblighe8819cd2008-02-15 16:48:40 +0000491
492
493def modify_acl_group(id, **data):
showard04f2cd82008-07-25 20:53:31 +0000494 group = models.AclGroup.smart_get(id)
495 group.check_for_acl_violation_acl_group()
496 group.update_object(data)
497 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000498
499
500def acl_group_add_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000501 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000502 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000503 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000504 group.users.add(*users)
mblighe8819cd2008-02-15 16:48:40 +0000505
506
507def acl_group_remove_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000508 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000509 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000510 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000511 group.users.remove(*users)
showard04f2cd82008-07-25 20:53:31 +0000512 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000513
514
515def acl_group_add_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000516 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000517 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000518 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000519 group.hosts.add(*hosts)
showard08f981b2008-06-24 21:59:03 +0000520 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000521
522
523def acl_group_remove_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000524 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000525 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000526 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000527 group.hosts.remove(*hosts)
showard08f981b2008-06-24 21:59:03 +0000528 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000529
530
531def delete_acl_group(id):
jadmanski0afbb632008-06-06 21:10:57 +0000532 models.AclGroup.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000533
534
535def get_acl_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000536 acl_groups = models.AclGroup.list_objects(filter_data)
537 for acl_group in acl_groups:
538 acl_group_obj = models.AclGroup.objects.get(id=acl_group['id'])
539 acl_group['users'] = [user.login
540 for user in acl_group_obj.users.all()]
541 acl_group['hosts'] = [host.hostname
542 for host in acl_group_obj.hosts.all()]
543 return rpc_utils.prepare_for_serialization(acl_groups)
mblighe8819cd2008-02-15 16:48:40 +0000544
545
546# jobs
547
mbligh120351e2009-01-24 01:40:45 +0000548def generate_control_file(tests=(), kernel=None, label=None, profilers=(),
showard91f85102009-10-12 20:34:52 +0000549 client_control_file='', use_container=False,
showard232b7ae2009-11-10 00:46:48 +0000550 profile_only=None, upload_kernel_config=False):
jadmanski0afbb632008-06-06 21:10:57 +0000551 """
mbligh120351e2009-01-24 01:40:45 +0000552 Generates a client-side control file to load a kernel and run tests.
553
554 @param tests List of tests to run.
mbligha3c58d22009-08-24 22:01:51 +0000555 @param kernel A list of kernel info dictionaries configuring which kernels
556 to boot for this job and other options for them
mbligh120351e2009-01-24 01:40:45 +0000557 @param label Name of label to grab kernel config from.
558 @param profilers List of profilers to activate during the job.
559 @param client_control_file The contents of a client-side control file to
560 run at the end of all tests. If this is supplied, all tests must be
561 client side.
562 TODO: in the future we should support server control files directly
563 to wrap with a kernel. That'll require changing the parameter
564 name and adding a boolean to indicate if it is a client or server
565 control file.
566 @param use_container unused argument today. TODO: Enable containers
567 on the host during a client side test.
showard91f85102009-10-12 20:34:52 +0000568 @param profile_only A boolean that indicates what default profile_only
569 mode to use in the control file. Passing None will generate a
570 control file that does not explcitly set the default mode at all.
showard232b7ae2009-11-10 00:46:48 +0000571 @param upload_kernel_config: if enabled it will generate server control
572 file code that uploads the kernel config file to the client and
573 tells the client of the new (local) path when compiling the kernel;
574 the tests must be server side tests
mbligh120351e2009-01-24 01:40:45 +0000575
576 @returns a dict with the following keys:
577 control_file: str, The control file text.
578 is_server: bool, is the control file a server-side control file?
579 synch_count: How many machines the job uses per autoserv execution.
580 synch_count == 1 means the job is asynchronous.
581 dependencies: A list of the names of labels on which the job depends.
582 """
showardd86debe2009-06-10 17:37:56 +0000583 if not tests and not client_control_file:
showard2bab8f42008-11-12 18:15:22 +0000584 return dict(control_file='', is_server=False, synch_count=1,
showard989f25d2008-10-01 11:38:11 +0000585 dependencies=[])
mblighe8819cd2008-02-15 16:48:40 +0000586
showard989f25d2008-10-01 11:38:11 +0000587 cf_info, test_objects, profiler_objects, label = (
showard2b9a88b2008-06-13 20:55:03 +0000588 rpc_utils.prepare_generate_control_file(tests, kernel, label,
589 profilers))
showard989f25d2008-10-01 11:38:11 +0000590 cf_info['control_file'] = control_file.generate_control(
mbligha3c58d22009-08-24 22:01:51 +0000591 tests=test_objects, kernels=kernel, platform=label,
mbligh120351e2009-01-24 01:40:45 +0000592 profilers=profiler_objects, is_server=cf_info['is_server'],
showard232b7ae2009-11-10 00:46:48 +0000593 client_control_file=client_control_file, profile_only=profile_only,
594 upload_kernel_config=upload_kernel_config)
showard989f25d2008-10-01 11:38:11 +0000595 return cf_info
mblighe8819cd2008-02-15 16:48:40 +0000596
597
jamesren4a41e012010-07-16 22:33:48 +0000598def create_parameterized_job(name, priority, test, parameters, kernel=None,
599 label=None, profilers=(), profiler_parameters=None,
600 use_container=False, profile_only=None,
601 upload_kernel_config=False, hosts=(),
602 meta_hosts=(), one_time_hosts=(),
603 atomic_group_name=None, synch_count=None,
604 is_template=False, timeout=None,
Simran Basi7e605742013-11-12 13:43:36 -0800605 timeout_mins=None, max_runtime_mins=None,
606 run_verify=False, email_list='', dependencies=(),
607 reboot_before=None, reboot_after=None,
608 parse_failed_repair=None, hostless=False,
609 keyvals=None, drone_set=None, run_reset=True):
jamesren4a41e012010-07-16 22:33:48 +0000610 """
611 Creates and enqueues a parameterized job.
612
613 Most parameters a combination of the parameters for generate_control_file()
614 and create_job(), with the exception of:
615
616 @param test name or ID of the test to run
617 @param parameters a map of parameter name ->
618 tuple of (param value, param type)
619 @param profiler_parameters a dictionary of parameters for the profilers:
620 key: profiler name
621 value: dict of param name -> tuple of
622 (param value,
623 param type)
624 """
625 # Save the values of the passed arguments here. What we're going to do with
626 # them is pass them all to rpc_utils.get_create_job_common_args(), which
627 # will extract the subset of these arguments that apply for
628 # rpc_utils.create_job_common(), which we then pass in to that function.
629 args = locals()
630
631 # Set up the parameterized job configs
632 test_obj = models.Test.smart_get(test)
Aviv Keshet3dd8beb2013-05-13 17:36:04 -0700633 control_type = test_obj.test_type
jamesren4a41e012010-07-16 22:33:48 +0000634
635 try:
636 label = models.Label.smart_get(label)
637 except models.Label.DoesNotExist:
638 label = None
639
640 kernel_objs = models.Kernel.create_kernels(kernel)
641 profiler_objs = [models.Profiler.smart_get(profiler)
642 for profiler in profilers]
643
644 parameterized_job = models.ParameterizedJob.objects.create(
645 test=test_obj, label=label, use_container=use_container,
646 profile_only=profile_only,
647 upload_kernel_config=upload_kernel_config)
648 parameterized_job.kernels.add(*kernel_objs)
649
650 for profiler in profiler_objs:
651 parameterized_profiler = models.ParameterizedJobProfiler.objects.create(
652 parameterized_job=parameterized_job,
653 profiler=profiler)
654 profiler_params = profiler_parameters.get(profiler.name, {})
655 for name, (value, param_type) in profiler_params.iteritems():
656 models.ParameterizedJobProfilerParameter.objects.create(
657 parameterized_job_profiler=parameterized_profiler,
658 parameter_name=name,
659 parameter_value=value,
660 parameter_type=param_type)
661
662 try:
663 for parameter in test_obj.testparameter_set.all():
664 if parameter.name in parameters:
665 param_value, param_type = parameters.pop(parameter.name)
666 parameterized_job.parameterizedjobparameter_set.create(
667 test_parameter=parameter, parameter_value=param_value,
668 parameter_type=param_type)
669
670 if parameters:
671 raise Exception('Extra parameters remain: %r' % parameters)
672
673 return rpc_utils.create_job_common(
674 parameterized_job=parameterized_job.id,
675 control_type=control_type,
676 **rpc_utils.get_create_job_common_args(args))
677 except:
678 parameterized_job.delete()
679 raise
680
681
Simran Basib6ec8ae2014-04-23 12:05:08 -0700682def create_job_page_handler(name, priority, control_file, control_type,
683 image=None, hostless=False, **kwargs):
684 """\
685 Create and enqueue a job.
686
687 @param name name of this job
688 @param priority Integer priority of this job. Higher is more important.
689 @param control_file String contents of the control file.
690 @param control_type Type of control file, Client or Server.
691 @param kwargs extra args that will be required by create_suite_job or
692 create_job.
693
694 @returns The created Job id number.
695 """
696 control_file = rpc_utils.encode_ascii(control_file)
Jiaxi Luodd67beb2014-07-18 16:28:31 -0700697 if not control_file:
698 raise model_logic.ValidationError({
699 'control_file' : "Control file cannot be empty"})
Simran Basib6ec8ae2014-04-23 12:05:08 -0700700
701 if image and hostless:
702 return site_rpc_interface.create_suite_job(
703 name=name, control_file=control_file, priority=priority,
704 build=image, **kwargs)
705 return create_job(name, priority, control_file, control_type, image=image,
706 hostless=hostless, **kwargs)
707
708
showard12f3e322009-05-13 21:27:42 +0000709def create_job(name, priority, control_file, control_type,
710 hosts=(), meta_hosts=(), one_time_hosts=(),
711 atomic_group_name=None, synch_count=None, is_template=False,
Simran Basi7e605742013-11-12 13:43:36 -0800712 timeout=None, timeout_mins=None, max_runtime_mins=None,
713 run_verify=False, email_list='', dependencies=(),
714 reboot_before=None, reboot_after=None, parse_failed_repair=None,
715 hostless=False, keyvals=None, drone_set=None, image=None,
Jiaxi Luo90190c92014-06-18 12:35:57 -0700716 parent_job_id=None, test_retry=0, run_reset=True, args=(),
717 **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +0000718 """\
719 Create and enqueue a job.
mblighe8819cd2008-02-15 16:48:40 +0000720
showarda1e74b32009-05-12 17:32:04 +0000721 @param name name of this job
Alex Miller7d658cf2013-09-04 16:00:35 -0700722 @param priority Integer priority of this job. Higher is more important.
showarda1e74b32009-05-12 17:32:04 +0000723 @param control_file String contents of the control file.
724 @param control_type Type of control file, Client or Server.
725 @param synch_count How many machines the job uses per autoserv execution.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700726 synch_count == 1 means the job is asynchronous. If an atomic group is
727 given this value is treated as a minimum.
showarda1e74b32009-05-12 17:32:04 +0000728 @param is_template If true then create a template job.
729 @param timeout Hours after this call returns until the job times out.
Simran Basi7e605742013-11-12 13:43:36 -0800730 @param timeout_mins Minutes after this call returns until the job times
Jiaxi Luo90190c92014-06-18 12:35:57 -0700731 out.
Simran Basi34217022012-11-06 13:43:15 -0800732 @param max_runtime_mins Minutes from job starting time until job times out
showarda1e74b32009-05-12 17:32:04 +0000733 @param run_verify Should the host be verified before running the test?
734 @param email_list String containing emails to mail when the job is done
735 @param dependencies List of label names on which this job depends
736 @param reboot_before Never, If dirty, or Always
737 @param reboot_after Never, If all tests passed, or Always
738 @param parse_failed_repair if true, results of failed repairs launched by
Jiaxi Luo90190c92014-06-18 12:35:57 -0700739 this job will be parsed as part of the job.
showarda9545c02009-12-18 22:44:26 +0000740 @param hostless if true, create a hostless job
showardc1a98d12010-01-15 00:22:22 +0000741 @param keyvals dict of keyvals to associate with the job
showarda1e74b32009-05-12 17:32:04 +0000742 @param hosts List of hosts to run job on.
743 @param meta_hosts List where each entry is a label name, and for each entry
Jiaxi Luo90190c92014-06-18 12:35:57 -0700744 one host will be chosen from that label to run the job on.
showarda1e74b32009-05-12 17:32:04 +0000745 @param one_time_hosts List of hosts not in the database to run the job on.
746 @param atomic_group_name The name of an atomic group to schedule the job on.
jamesren76fcf192010-04-21 20:39:50 +0000747 @param drone_set The name of the drone set to run this test on.
Paul Pendlebury5a8c6ad2011-02-01 07:20:17 -0800748 @param image OS image to install before running job.
Aviv Keshet0b9cfc92013-02-05 11:36:02 -0800749 @param parent_job_id id of a job considered to be parent of created job.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700750 @param test_retry Number of times to retry test if the test did not
Jiaxi Luo90190c92014-06-18 12:35:57 -0700751 complete successfully. (optional, default: 0)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700752 @param run_reset Should the host be reset before running the test?
Jiaxi Luo90190c92014-06-18 12:35:57 -0700753 @param args A list of args to be injected into control file.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700754 @param kwargs extra keyword args. NOT USED.
showardc92da832009-04-07 18:14:34 +0000755
756 @returns The created Job id number.
jadmanski0afbb632008-06-06 21:10:57 +0000757 """
Jiaxi Luo90190c92014-06-18 12:35:57 -0700758 if args:
759 control_file = tools.inject_vars({'args': args}, control_file)
760
Simran Basiab5a1bf2014-05-28 15:39:44 -0700761 if image is None:
762 return rpc_utils.create_job_common(
763 **rpc_utils.get_create_job_common_args(locals()))
764
765 # When image is supplied use a known parameterized test already in the
766 # database to pass the OS image path from the front end, through the
767 # scheduler, and finally to autoserv as the --image parameter.
768
769 # The test autoupdate_ParameterizedJob is in afe_autotests and used to
770 # instantiate a Test object and from there a ParameterizedJob.
771 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
772 known_parameterized_job = models.ParameterizedJob.objects.create(
773 test=known_test_obj)
774
775 # autoupdate_ParameterizedJob has a single parameter, the image parameter,
776 # stored in the table afe_test_parameters. We retrieve and set this
777 # instance of the parameter to the OS image path.
778 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
779 name='image')
780 known_parameterized_job.parameterizedjobparameter_set.create(
781 test_parameter=image_parameter, parameter_value=image,
782 parameter_type='string')
783
784 # By passing a parameterized_job to create_job_common the job entry in
785 # the afe_jobs table will have the field parameterized_job_id set.
786 # The scheduler uses this id in the afe_parameterized_jobs table to
787 # match this job to our known test, and then with the
788 # afe_parameterized_job_parameters table to get the actual image path.
jamesren4a41e012010-07-16 22:33:48 +0000789 return rpc_utils.create_job_common(
Simran Basiab5a1bf2014-05-28 15:39:44 -0700790 parameterized_job=known_parameterized_job.id,
jamesren4a41e012010-07-16 22:33:48 +0000791 **rpc_utils.get_create_job_common_args(locals()))
mblighe8819cd2008-02-15 16:48:40 +0000792
793
showard9dbdcda2008-10-14 17:34:36 +0000794def abort_host_queue_entries(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000795 """\
showard9dbdcda2008-10-14 17:34:36 +0000796 Abort a set of host queue entries.
Fang Deng63b0e452014-12-19 14:38:15 -0800797
798 @return: A list of dictionaries, each contains information
799 about an aborted HQE.
jadmanski0afbb632008-06-06 21:10:57 +0000800 """
showard9dbdcda2008-10-14 17:34:36 +0000801 query = models.HostQueueEntry.query_objects(filter_data)
beepsfaecbce2013-10-29 11:35:10 -0700802
803 # Dont allow aborts on:
804 # 1. Jobs that have already completed (whether or not they were aborted)
805 # 2. Jobs that we have already been aborted (but may not have completed)
806 query = query.filter(complete=False).filter(aborted=False)
showarddc817512008-11-12 18:16:41 +0000807 models.AclGroup.check_abort_permissions(query)
showard9dbdcda2008-10-14 17:34:36 +0000808 host_queue_entries = list(query.select_related())
showard2bab8f42008-11-12 18:15:22 +0000809 rpc_utils.check_abort_synchronous_jobs(host_queue_entries)
mblighe8819cd2008-02-15 16:48:40 +0000810
Simran Basic1b26762013-06-26 14:23:21 -0700811 models.HostQueueEntry.abort_host_queue_entries(host_queue_entries)
Fang Deng63b0e452014-12-19 14:38:15 -0800812 hqe_info = [{'HostQueueEntry': hqe.id, 'Job': hqe.job_id,
813 'Job name': hqe.job.name} for hqe in host_queue_entries]
814 return hqe_info
showard9d821ab2008-07-11 16:54:29 +0000815
816
beeps8bb1f7d2013-08-05 01:30:09 -0700817def abort_special_tasks(**filter_data):
818 """\
819 Abort the special task, or tasks, specified in the filter.
820 """
821 query = models.SpecialTask.query_objects(filter_data)
822 special_tasks = query.filter(is_active=True)
823 for task in special_tasks:
824 task.abort()
825
826
Simran Basi73dae552013-02-25 14:57:46 -0800827def _call_special_tasks_on_hosts(task, hosts):
828 """\
829 Schedules a set of hosts for a special task.
830
831 @returns A list of hostnames that a special task was created for.
832 """
833 models.AclGroup.check_for_acl_violation_hosts(hosts)
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -0800834 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800835 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -0800836 raise ValueError('The following hosts are on shards, please '
837 'follow the link to the shards and create jobs '
838 'there instead. %s.' % shard_host_map)
Simran Basi73dae552013-02-25 14:57:46 -0800839 for host in hosts:
840 models.SpecialTask.schedule_special_task(host, task)
841 return list(sorted(host.hostname for host in hosts))
842
843
showard1ff7b2e2009-05-15 23:17:18 +0000844def reverify_hosts(**filter_data):
845 """\
846 Schedules a set of hosts for verify.
mbligh4e545a52009-12-19 05:30:39 +0000847
848 @returns A list of hostnames that a verify task was created for.
showard1ff7b2e2009-05-15 23:17:18 +0000849 """
Prashanth Balasubramanian40981232014-12-16 19:01:58 -0800850 hosts = models.Host.query_objects(filter_data)
851 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts, rpc_hostnames=True)
852
853 # Filter out hosts on a shard from those on the master, forward
854 # rpcs to the shard with an additional hostname__in filter, and
855 # create a local SpecialTask for each remaining host.
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800856 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian40981232014-12-16 19:01:58 -0800857 hosts = [h for h in hosts if h.shard is None]
858 for shard, hostnames in shard_host_map.iteritems():
859
860 # The main client of this module is the frontend website, and
861 # it invokes it with an 'id' or an 'id__in' filter. Regardless,
862 # the 'hostname' filter should narrow down the list of hosts on
863 # each shard even though we supply all the ids in filter_data.
864 # This method uses hostname instead of id because it fits better
865 # with the overall architecture of redirection functions in rpc_utils.
866 shard_filter = filter_data.copy()
867 shard_filter['hostname__in'] = hostnames
868 rpc_utils.run_rpc_on_multiple_hostnames(
869 'reverify_hosts', [shard], **shard_filter)
870
871 # There is a race condition here if someone assigns a shard to one of these
872 # hosts before we create the task. The host will stay on the master if:
873 # 1. The host is not Ready
874 # 2. The host is Ready but has a task
875 # But if the host is Ready and doesn't have a task yet, it will get sent
876 # to the shard as we're creating a task here.
877
878 # Given that we only rarely verify Ready hosts it isn't worth putting this
879 # entire method in a transaction. The worst case scenario is that we have
880 # a verify running on a Ready host while the shard is using it, if the verify
881 # fails no subsequent tasks will be created against the host on the master,
882 # and verifies are safe enough that this is OK.
883 return _call_special_tasks_on_hosts(models.SpecialTask.Task.VERIFY, hosts)
Simran Basi73dae552013-02-25 14:57:46 -0800884
885
886def repair_hosts(**filter_data):
887 """\
888 Schedules a set of hosts for repair.
889
890 @returns A list of hostnames that a repair task was created for.
891 """
892 return _call_special_tasks_on_hosts(models.SpecialTask.Task.REPAIR,
893 models.Host.query_objects(filter_data))
showard1ff7b2e2009-05-15 23:17:18 +0000894
895
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700896def get_jobs(not_yet_run=False, running=False, finished=False,
897 suite=False, sub=False, standalone=False, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000898 """\
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700899 Extra status filter args for get_jobs:
jadmanski0afbb632008-06-06 21:10:57 +0000900 -not_yet_run: Include only jobs that have not yet started running.
901 -running: Include only jobs that have start running but for which not
902 all hosts have completed.
903 -finished: Include only jobs for which all hosts have completed (or
904 aborted).
905 At most one of these three fields should be specified.
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700906
907 Extra type filter args for get_jobs:
908 -suite: Include only jobs with child jobs.
909 -sub: Include only jobs with a parent job.
910 -standalone: Inlcude only jobs with no child or parent jobs.
911 At most one of these three fields should be specified.
jadmanski0afbb632008-06-06 21:10:57 +0000912 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700913 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
914 running,
915 finished)
916 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
917 suite,
918 sub,
919 standalone)
showard0957a842009-05-11 19:25:08 +0000920 job_dicts = []
921 jobs = list(models.Job.query_objects(filter_data))
922 models.Job.objects.populate_relationships(jobs, models.Label,
923 'dependencies')
showardc1a98d12010-01-15 00:22:22 +0000924 models.Job.objects.populate_relationships(jobs, models.JobKeyval, 'keyvals')
showard0957a842009-05-11 19:25:08 +0000925 for job in jobs:
926 job_dict = job.get_object_dict()
927 job_dict['dependencies'] = ','.join(label.name
928 for label in job.dependencies)
showardc1a98d12010-01-15 00:22:22 +0000929 job_dict['keyvals'] = dict((keyval.key, keyval.value)
930 for keyval in job.keyvals)
Eric Lid23bc192011-02-09 14:38:57 -0800931 if job.parameterized_job:
932 job_dict['image'] = get_parameterized_autoupdate_image_url(job)
showard0957a842009-05-11 19:25:08 +0000933 job_dicts.append(job_dict)
934 return rpc_utils.prepare_for_serialization(job_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000935
936
937def get_num_jobs(not_yet_run=False, running=False, finished=False,
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700938 suite=False, sub=False, standalone=False,
jadmanski0afbb632008-06-06 21:10:57 +0000939 **filter_data):
940 """\
941 See get_jobs() for documentation of extra filter parameters.
942 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700943 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
944 running,
945 finished)
946 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
947 suite,
948 sub,
949 standalone)
jadmanski0afbb632008-06-06 21:10:57 +0000950 return models.Job.query_count(filter_data)
mblighe8819cd2008-02-15 16:48:40 +0000951
952
mblighe8819cd2008-02-15 16:48:40 +0000953def get_jobs_summary(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000954 """\
Jiaxi Luoaac54572014-06-04 13:57:02 -0700955 Like get_jobs(), but adds 'status_counts' and 'result_counts' field.
956
957 'status_counts' filed is a dictionary mapping status strings to the number
958 of hosts currently with that status, i.e. {'Queued' : 4, 'Running' : 2}.
959
960 'result_counts' field is piped to tko's rpc_interface and has the return
961 format specified under get_group_counts.
jadmanski0afbb632008-06-06 21:10:57 +0000962 """
963 jobs = get_jobs(**filter_data)
964 ids = [job['id'] for job in jobs]
965 all_status_counts = models.Job.objects.get_status_counts(ids)
966 for job in jobs:
967 job['status_counts'] = all_status_counts[job['id']]
Jiaxi Luoaac54572014-06-04 13:57:02 -0700968 job['result_counts'] = tko_rpc_interface.get_status_counts(
969 ['afe_job_id', 'afe_job_id'],
970 header_groups=[['afe_job_id'], ['afe_job_id']],
971 **{'afe_job_id': job['id']})
jadmanski0afbb632008-06-06 21:10:57 +0000972 return rpc_utils.prepare_for_serialization(jobs)
mblighe8819cd2008-02-15 16:48:40 +0000973
974
showarda965cef2009-05-15 23:17:41 +0000975def get_info_for_clone(id, preserve_metahosts, queue_entry_filter_data=None):
showarda8709c52008-07-03 19:44:54 +0000976 """\
977 Retrieves all the information needed to clone a job.
978 """
showarda8709c52008-07-03 19:44:54 +0000979 job = models.Job.objects.get(id=id)
showard29f7cd22009-04-29 21:16:24 +0000980 job_info = rpc_utils.get_job_info(job,
showarda965cef2009-05-15 23:17:41 +0000981 preserve_metahosts,
982 queue_entry_filter_data)
showard945072f2008-09-03 20:34:59 +0000983
showardd9992fe2008-07-31 02:15:03 +0000984 host_dicts = []
showard29f7cd22009-04-29 21:16:24 +0000985 for host in job_info['hosts']:
986 host_dict = get_hosts(id=host.id)[0]
987 other_labels = host_dict['labels']
988 if host_dict['platform']:
989 other_labels.remove(host_dict['platform'])
990 host_dict['other_labels'] = ', '.join(other_labels)
showardd9992fe2008-07-31 02:15:03 +0000991 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +0000992
showard29f7cd22009-04-29 21:16:24 +0000993 for host in job_info['one_time_hosts']:
994 host_dict = dict(hostname=host.hostname,
995 id=host.id,
996 platform='(one-time host)',
997 locked_text='')
998 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +0000999
showard4d077562009-05-08 18:24:36 +00001000 # convert keys from Label objects to strings (names of labels)
showard29f7cd22009-04-29 21:16:24 +00001001 meta_host_counts = dict((meta_host.name, count) for meta_host, count
showard4d077562009-05-08 18:24:36 +00001002 in job_info['meta_host_counts'].iteritems())
showard29f7cd22009-04-29 21:16:24 +00001003
1004 info = dict(job=job.get_object_dict(),
1005 meta_host_counts=meta_host_counts,
1006 hosts=host_dicts)
1007 info['job']['dependencies'] = job_info['dependencies']
1008 if job_info['atomic_group']:
1009 info['atomic_group_name'] = (job_info['atomic_group']).name
1010 else:
1011 info['atomic_group_name'] = None
jamesren2275ef12010-04-12 18:25:06 +00001012 info['hostless'] = job_info['hostless']
jamesren76fcf192010-04-21 20:39:50 +00001013 info['drone_set'] = job.drone_set and job.drone_set.name
showarda8709c52008-07-03 19:44:54 +00001014
Eric Lid23bc192011-02-09 14:38:57 -08001015 if job.parameterized_job:
1016 info['job']['image'] = get_parameterized_autoupdate_image_url(job)
1017
showarda8709c52008-07-03 19:44:54 +00001018 return rpc_utils.prepare_for_serialization(info)
1019
1020
showard34dc5fa2008-04-24 20:58:40 +00001021# host queue entries
1022
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001023def get_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001024 """\
showardc92da832009-04-07 18:14:34 +00001025 @returns A sequence of nested dictionaries of host and job information.
jadmanski0afbb632008-06-06 21:10:57 +00001026 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001027 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1028 'started_on__lte',
1029 start_time,
1030 end_time,
1031 **filter_data)
showardc92da832009-04-07 18:14:34 +00001032 return rpc_utils.prepare_rows_as_nested_dicts(
1033 models.HostQueueEntry.query_objects(filter_data),
1034 ('host', 'atomic_group', 'job'))
showard34dc5fa2008-04-24 20:58:40 +00001035
1036
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001037def get_num_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001038 """\
1039 Get the number of host queue entries associated with this job.
1040 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001041 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1042 'started_on__lte',
1043 start_time,
1044 end_time,
1045 **filter_data)
jadmanski0afbb632008-06-06 21:10:57 +00001046 return models.HostQueueEntry.query_count(filter_data)
showard34dc5fa2008-04-24 20:58:40 +00001047
1048
showard1e935f12008-07-11 00:11:36 +00001049def get_hqe_percentage_complete(**filter_data):
1050 """
showardc92da832009-04-07 18:14:34 +00001051 Computes the fraction of host queue entries matching the given filter data
showard1e935f12008-07-11 00:11:36 +00001052 that are complete.
1053 """
1054 query = models.HostQueueEntry.query_objects(filter_data)
1055 complete_count = query.filter(complete=True).count()
1056 total_count = query.count()
1057 if total_count == 0:
1058 return 1
1059 return float(complete_count) / total_count
1060
1061
showard1a5a4082009-07-28 20:01:37 +00001062# special tasks
1063
1064def get_special_tasks(**filter_data):
1065 return rpc_utils.prepare_rows_as_nested_dicts(
1066 models.SpecialTask.query_objects(filter_data),
1067 ('host', 'queue_entry'))
1068
1069
showardc0ac3a72009-07-08 21:14:45 +00001070# support for host detail view
1071
Jiaxi Luo79ce6422014-06-13 17:08:09 -07001072def get_host_queue_entries_and_special_tasks(host_id, query_start=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001073 query_limit=None, start_time=None,
1074 end_time=None):
showardc0ac3a72009-07-08 21:14:45 +00001075 """
1076 @returns an interleaved list of HostQueueEntries and SpecialTasks,
1077 in approximate run order. each dict contains keys for type, host,
1078 job, status, started_on, execution_path, and ID.
1079 """
1080 total_limit = None
1081 if query_limit is not None:
1082 total_limit = query_start + query_limit
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001083 filter_data_common = {'host': host_id,
1084 'query_limit': total_limit,
1085 'sort_by': ['-id']}
showardc0ac3a72009-07-08 21:14:45 +00001086
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001087 filter_data_queue_entries, filter_data_special_tasks = (
1088 rpc_utils.inject_times_to_hqe_special_tasks_filters(
1089 filter_data_common, start_time, end_time))
1090
1091 queue_entries = list(models.HostQueueEntry.query_objects(
1092 filter_data_queue_entries))
1093 special_tasks = list(models.SpecialTask.query_objects(
1094 filter_data_special_tasks))
showardc0ac3a72009-07-08 21:14:45 +00001095
1096 interleaved_entries = rpc_utils.interleave_entries(queue_entries,
1097 special_tasks)
1098 if query_start is not None:
1099 interleaved_entries = interleaved_entries[query_start:]
1100 if query_limit is not None:
1101 interleaved_entries = interleaved_entries[:query_limit]
1102 return rpc_utils.prepare_for_serialization(interleaved_entries)
1103
1104
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001105def get_num_host_queue_entries_and_special_tasks(host_id, start_time=None,
1106 end_time=None):
1107 filter_data_common = {'host': host_id}
1108
1109 filter_data_queue_entries, filter_data_special_tasks = (
1110 rpc_utils.inject_times_to_hqe_special_tasks_filters(
1111 filter_data_common, start_time, end_time))
1112
1113 return (models.HostQueueEntry.query_count(filter_data_queue_entries)
1114 + models.SpecialTask.query_count(filter_data_special_tasks))
showardc0ac3a72009-07-08 21:14:45 +00001115
1116
showard29f7cd22009-04-29 21:16:24 +00001117# recurring run
1118
1119def get_recurring(**filter_data):
1120 return rpc_utils.prepare_rows_as_nested_dicts(
1121 models.RecurringRun.query_objects(filter_data),
1122 ('job', 'owner'))
1123
1124
1125def get_num_recurring(**filter_data):
1126 return models.RecurringRun.query_count(filter_data)
1127
1128
1129def delete_recurring_runs(**filter_data):
1130 to_delete = models.RecurringRun.query_objects(filter_data)
1131 to_delete.delete()
1132
1133
1134def create_recurring_run(job_id, start_date, loop_period, loop_count):
showard64a95952010-01-13 21:27:16 +00001135 owner = models.User.current_user().login
showard29f7cd22009-04-29 21:16:24 +00001136 job = models.Job.objects.get(id=job_id)
1137 return job.create_recurring_job(start_date=start_date,
1138 loop_period=loop_period,
1139 loop_count=loop_count,
1140 owner=owner)
1141
1142
mblighe8819cd2008-02-15 16:48:40 +00001143# other
1144
showarde0b63622008-08-04 20:58:47 +00001145def echo(data=""):
1146 """\
1147 Returns a passed in string. For doing a basic test to see if RPC calls
1148 can successfully be made.
1149 """
1150 return data
1151
1152
showardb7a52fd2009-04-27 20:10:56 +00001153def get_motd():
1154 """\
1155 Returns the message of the day as a string.
1156 """
1157 return rpc_utils.get_motd()
1158
1159
mblighe8819cd2008-02-15 16:48:40 +00001160def get_static_data():
jadmanski0afbb632008-06-06 21:10:57 +00001161 """\
1162 Returns a dictionary containing a bunch of data that shouldn't change
1163 often and is otherwise inaccessible. This includes:
showardc92da832009-04-07 18:14:34 +00001164
1165 priorities: List of job priority choices.
1166 default_priority: Default priority value for new jobs.
1167 users: Sorted list of all users.
Jiaxi Luo31874592014-06-11 10:36:35 -07001168 labels: Sorted list of labels not start with 'cros-version' and
1169 'fw-version'.
showardc92da832009-04-07 18:14:34 +00001170 atomic_groups: Sorted list of all atomic groups.
1171 tests: Sorted list of all tests.
1172 profilers: Sorted list of all profilers.
1173 current_user: Logged-in username.
1174 host_statuses: Sorted list of possible Host statuses.
1175 job_statuses: Sorted list of possible HostQueueEntry statuses.
Simran Basi7e605742013-11-12 13:43:36 -08001176 job_timeout_default: The default job timeout length in minutes.
showarda1e74b32009-05-12 17:32:04 +00001177 parse_failed_repair_default: Default value for the parse_failed_repair job
Jiaxi Luo31874592014-06-11 10:36:35 -07001178 option.
showardc92da832009-04-07 18:14:34 +00001179 reboot_before_options: A list of valid RebootBefore string enums.
1180 reboot_after_options: A list of valid RebootAfter string enums.
1181 motd: Server's message of the day.
1182 status_dictionary: A mapping from one word job status names to a more
1183 informative description.
jadmanski0afbb632008-06-06 21:10:57 +00001184 """
showard21baa452008-10-21 00:08:39 +00001185
1186 job_fields = models.Job.get_field_dict()
jamesren76fcf192010-04-21 20:39:50 +00001187 default_drone_set_name = models.DroneSet.default_drone_set_name()
1188 drone_sets = ([default_drone_set_name] +
1189 sorted(drone_set.name for drone_set in
1190 models.DroneSet.objects.exclude(
1191 name=default_drone_set_name)))
showard21baa452008-10-21 00:08:39 +00001192
jadmanski0afbb632008-06-06 21:10:57 +00001193 result = {}
Alex Miller7d658cf2013-09-04 16:00:35 -07001194 result['priorities'] = priorities.Priority.choices()
1195 default_priority = priorities.Priority.DEFAULT
1196 result['default_priority'] = 'Default'
1197 result['max_schedulable_priority'] = priorities.Priority.DEFAULT
jadmanski0afbb632008-06-06 21:10:57 +00001198 result['users'] = get_users(sort_by=['login'])
Jiaxi Luo31874592014-06-11 10:36:35 -07001199
1200 label_exclude_filters = [{'name__startswith': 'cros-version'},
1201 {'name__startswith': 'fw-version'}]
1202 result['labels'] = get_labels(
1203 label_exclude_filters,
1204 sort_by=['-platform', 'name'])
1205
showardc92da832009-04-07 18:14:34 +00001206 result['atomic_groups'] = get_atomic_groups(sort_by=['name'])
jadmanski0afbb632008-06-06 21:10:57 +00001207 result['tests'] = get_tests(sort_by=['name'])
showard2b9a88b2008-06-13 20:55:03 +00001208 result['profilers'] = get_profilers(sort_by=['name'])
showard0fc38302008-10-23 00:44:07 +00001209 result['current_user'] = rpc_utils.prepare_for_serialization(
showard64a95952010-01-13 21:27:16 +00001210 models.User.current_user().get_object_dict())
showard2b9a88b2008-06-13 20:55:03 +00001211 result['host_statuses'] = sorted(models.Host.Status.names)
mbligh5a198b92008-12-11 19:33:29 +00001212 result['job_statuses'] = sorted(models.HostQueueEntry.Status.names)
Simran Basi7e605742013-11-12 13:43:36 -08001213 result['job_timeout_mins_default'] = models.Job.DEFAULT_TIMEOUT_MINS
Simran Basi34217022012-11-06 13:43:15 -08001214 result['job_max_runtime_mins_default'] = (
1215 models.Job.DEFAULT_MAX_RUNTIME_MINS)
showarda1e74b32009-05-12 17:32:04 +00001216 result['parse_failed_repair_default'] = bool(
1217 models.Job.DEFAULT_PARSE_FAILED_REPAIR)
jamesrendd855242010-03-02 22:23:44 +00001218 result['reboot_before_options'] = model_attributes.RebootBefore.names
1219 result['reboot_after_options'] = model_attributes.RebootAfter.names
showard8fbae652009-01-20 23:23:10 +00001220 result['motd'] = rpc_utils.get_motd()
jamesren76fcf192010-04-21 20:39:50 +00001221 result['drone_sets_enabled'] = models.DroneSet.drone_sets_enabled()
1222 result['drone_sets'] = drone_sets
jamesren4a41e012010-07-16 22:33:48 +00001223 result['parameterized_jobs'] = models.Job.parameterized_jobs_enabled()
showard8ac29b42008-07-17 17:01:55 +00001224
showardd3dc1992009-04-22 21:01:40 +00001225 result['status_dictionary'] = {"Aborted": "Aborted",
showard8ac29b42008-07-17 17:01:55 +00001226 "Verifying": "Verifying Host",
Alex Millerdfff2fd2013-05-28 13:05:06 -07001227 "Provisioning": "Provisioning Host",
showard8ac29b42008-07-17 17:01:55 +00001228 "Pending": "Waiting on other hosts",
1229 "Running": "Running autoserv",
1230 "Completed": "Autoserv completed",
1231 "Failed": "Failed to complete",
showardd823b362008-07-24 16:35:46 +00001232 "Queued": "Queued",
showard5deb6772008-11-04 21:54:33 +00001233 "Starting": "Next in host's queue",
1234 "Stopped": "Other host(s) failed verify",
showardd3dc1992009-04-22 21:01:40 +00001235 "Parsing": "Awaiting parse of final results",
showard29f7cd22009-04-29 21:16:24 +00001236 "Gathering": "Gathering log files",
showard8cc058f2009-09-08 16:26:33 +00001237 "Template": "Template job for recurring run",
mbligh4608b002010-01-05 18:22:35 +00001238 "Waiting": "Waiting for scheduler action",
Dan Shi07e09af2013-04-12 09:31:29 -07001239 "Archiving": "Archiving results",
1240 "Resetting": "Resetting hosts"}
Jiaxi Luo421608e2014-07-07 14:38:00 -07001241
1242 result['wmatrix_url'] = rpc_utils.get_wmatrix_url()
Simran Basi71206ef2014-08-13 13:51:18 -07001243 result['is_moblab'] = bool(utils.is_moblab())
Jiaxi Luo421608e2014-07-07 14:38:00 -07001244
jadmanski0afbb632008-06-06 21:10:57 +00001245 return result
showard29f7cd22009-04-29 21:16:24 +00001246
1247
1248def get_server_time():
1249 return datetime.datetime.now().strftime("%Y-%m-%d %H:%M")