blob: 2f21ecf84ac6b5cb596ff0197f18ebe6b858e20b [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
Shuqian Zhao4c0d2902016-01-12 17:03:15 -080036import logging
MK Ryu9c5fbbe2015-02-11 15:46:22 -080037
Moises Osorio2dc7a102014-12-02 18:24:02 -080038from django.db.models import Count
showardcafd16e2009-05-29 18:37:49 +000039import common
Simran Basib6ec8ae2014-04-23 12:05:08 -070040from autotest_lib.client.common_lib import priorities
Simran Basi6157e8e2015-12-07 18:22:34 -080041from autotest_lib.client.common_lib.cros import dev_server
Gabe Black1e1c41b2015-02-04 23:55:15 -080042from autotest_lib.client.common_lib.cros.graphite import autotest_stats
showard6d7b2ff2009-06-10 00:16:47 +000043from autotest_lib.frontend.afe import control_file, rpc_utils
J. Richard Barnetteb5164d62015-04-13 12:59:31 -070044from autotest_lib.frontend.afe import models, model_logic, model_attributes
Simran Basib6ec8ae2014-04-23 12:05:08 -070045from autotest_lib.frontend.afe import site_rpc_interface
Moises Osorio2dc7a102014-12-02 18:24:02 -080046from autotest_lib.frontend.tko import models as tko_models
Jiaxi Luoaac54572014-06-04 13:57:02 -070047from autotest_lib.frontend.tko import rpc_interface as tko_rpc_interface
J. Richard Barnetteb5164d62015-04-13 12:59:31 -070048from autotest_lib.server import frontend
Simran Basi71206ef2014-08-13 13:51:18 -070049from autotest_lib.server import utils
Dan Shid215dbe2015-06-18 16:14:59 -070050from autotest_lib.server.cros import provision
Jiaxi Luo90190c92014-06-18 12:35:57 -070051from autotest_lib.server.cros.dynamic_suite import tools
J. Richard Barnette39255fa2015-04-14 17:23:41 -070052from autotest_lib.site_utils import status_history
mblighe8819cd2008-02-15 16:48:40 +000053
Moises Osorio2dc7a102014-12-02 18:24:02 -080054
Gabe Black1e1c41b2015-02-04 23:55:15 -080055_timer = autotest_stats.Timer('rpc_interface')
Moises Osorio2dc7a102014-12-02 18:24:02 -080056
Eric Lid23bc192011-02-09 14:38:57 -080057def get_parameterized_autoupdate_image_url(job):
58 """Get the parameterized autoupdate image url from a parameterized job."""
59 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
60 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
beeps8bb1f7d2013-08-05 01:30:09 -070061 name='image')
Eric Lid23bc192011-02-09 14:38:57 -080062 para_set = job.parameterized_job.parameterizedjobparameter_set
63 job_test_para = para_set.get(test_parameter=image_parameter)
64 return job_test_para.parameter_value
65
66
mblighe8819cd2008-02-15 16:48:40 +000067# labels
68
mblighe8819cd2008-02-15 16:48:40 +000069def modify_label(id, **data):
MK Ryu8c554cf2015-06-12 11:45:50 -070070 """Modify a label.
71
72 @param id: id or name of a label. More often a label name.
73 @param data: New data for a label.
74 """
75 label_model = models.Label.smart_get(id)
MK Ryu8e2c2d02016-01-06 15:24:38 -080076 label_model.update_object(data)
MK Ryu8c554cf2015-06-12 11:45:50 -070077
78 # Master forwards the RPC to shards
79 if not utils.is_shard():
80 rpc_utils.fanout_rpc(label_model.host_set.all(), 'modify_label', False,
81 id=id, **data)
82
mblighe8819cd2008-02-15 16:48:40 +000083
84def delete_label(id):
MK Ryu8c554cf2015-06-12 11:45:50 -070085 """Delete a label.
86
87 @param id: id or name of a label. More often a label name.
88 """
89 label_model = models.Label.smart_get(id)
MK Ryu8e2c2d02016-01-06 15:24:38 -080090 # Hosts that have the label to be deleted. Save this info before
91 # the label is deleted to use it later.
92 hosts = []
93 for h in label_model.host_set.all():
94 hosts.append(models.Host.smart_get(h.id))
95 label_model.delete()
MK Ryu8c554cf2015-06-12 11:45:50 -070096
97 # Master forwards the RPC to shards
98 if not utils.is_shard():
MK Ryu8e2c2d02016-01-06 15:24:38 -080099 rpc_utils.fanout_rpc(hosts, 'delete_label', False, id=id)
mblighe8819cd2008-02-15 16:48:40 +0000100
Prashanth Balasubramanian744898f2015-01-13 05:04:16 -0800101
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800102def add_label(name, ignore_exception_if_exists=False, **kwargs):
MK Ryucf027c62015-03-04 12:00:50 -0800103 """Adds a new label of a given name.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800104
105 @param name: label name.
106 @param ignore_exception_if_exists: If True and the exception was
107 thrown due to the duplicated label name when adding a label,
108 then suppress the exception. Default is False.
109 @param kwargs: keyword args that store more info about a label
110 other than the name.
111 @return: int/long id of a new label.
112 """
113 # models.Label.add_object() throws model_logic.ValidationError
114 # when it is given a label name that already exists.
115 # However, ValidationError can be thrown with different errors,
116 # and those errors should be thrown up to the call chain.
117 try:
118 label = models.Label.add_object(name=name, **kwargs)
119 except:
120 exc_info = sys.exc_info()
121 if ignore_exception_if_exists:
122 label = rpc_utils.get_label(name)
123 # If the exception is raised not because of duplicated
124 # "name", then raise the original exception.
125 if label is None:
126 raise exc_info[0], exc_info[1], exc_info[2]
127 else:
128 raise exc_info[0], exc_info[1], exc_info[2]
129 return label.id
130
131
132def add_label_to_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800133 """Adds a label of the given id to the given hosts only in local DB.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800134
135 @param id: id or name of a label. More often a label name.
136 @param hosts: The hostnames of hosts that need the label.
137
138 @raises models.Label.DoesNotExist: If the label with id doesn't exist.
139 """
140 label = models.Label.smart_get(id)
141 host_objs = models.Host.smart_get_bulk(hosts)
142 if label.platform:
143 models.Host.check_no_platform(host_objs)
144 label.host_set.add(*host_objs)
145
146
MK Ryufbb002c2015-06-08 14:13:16 -0700147@rpc_utils.route_rpc_to_master
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800148def label_add_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800149 """Adds a label with the given id to the given hosts.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800150
151 This method should be run only on master not shards.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800152 The given label will be created if it doesn't exist, provided the `id`
153 supplied is a label name not an int/long id.
154
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800155 @param id: id or name of a label. More often a label name.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800156 @param hosts: A list of hostnames or ids. More often hostnames.
157
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800158 @raises ValueError: If the id specified is an int/long (label id)
159 while the label does not exist.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800160 """
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800161 try:
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800162 label = models.Label.smart_get(id)
163 except models.Label.DoesNotExist:
164 # This matches the type checks in smart_get, which is a hack
165 # in and off itself. The aim here is to create any non-existent
166 # label, which we cannot do if the 'id' specified isn't a label name.
167 if isinstance(id, basestring):
168 label = models.Label.smart_get(add_label(id))
169 else:
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800170 raise ValueError('Label id (%s) does not exist. Please specify '
171 'the argument, id, as a string (label name).'
172 % id)
MK Ryu8e2c2d02016-01-06 15:24:38 -0800173 add_label_to_hosts(id, hosts)
MK Ryucf027c62015-03-04 12:00:50 -0800174
175 host_objs = models.Host.smart_get_bulk(hosts)
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800176 # Make sure the label exists on the shard with the same id
177 # as it is on the master.
MK Ryucf027c62015-03-04 12:00:50 -0800178 # It is possible that the label is already in a shard because
179 # we are adding a new label only to shards of hosts that the label
180 # is going to be attached.
181 # For example, we add a label L1 to a host in shard S1.
182 # Master and S1 will have L1 but other shards won't.
183 # Later, when we add the same label L1 to hosts in shards S1 and S2,
184 # S1 already has the label but S2 doesn't.
185 # S2 should have the new label without any problem.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800186 # We ignore exception in such a case.
187 rpc_utils.fanout_rpc(
MK Ryue019aae2015-07-07 12:46:07 -0700188 host_objs, 'add_label', include_hostnames=False,
189 name=label.name, ignore_exception_if_exists=True,
190 id=label.id, platform=label.platform)
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800191 rpc_utils.fanout_rpc(host_objs, 'add_label_to_hosts', id=id)
showardbbabf502008-06-06 00:02:02 +0000192
193
MK Ryucf027c62015-03-04 12:00:50 -0800194def remove_label_from_hosts(id, hosts):
195 """Removes a label of the given id from the given hosts only in local DB.
196
197 @param id: id or name of a label.
198 @param hosts: The hostnames of hosts that need to remove the label from.
199 """
showardbe3ec042008-11-12 18:16:07 +0000200 host_objs = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000201 models.Label.smart_get(id).host_set.remove(*host_objs)
showardbbabf502008-06-06 00:02:02 +0000202
203
MK Ryufbb002c2015-06-08 14:13:16 -0700204@rpc_utils.route_rpc_to_master
MK Ryucf027c62015-03-04 12:00:50 -0800205def label_remove_hosts(id, hosts):
206 """Removes a label of the given id from the given hosts.
207
208 This method should be run only on master not shards.
209
210 @param id: id or name of a label.
211 @param hosts: A list of hostnames or ids. More often hostnames.
212 """
MK Ryucf027c62015-03-04 12:00:50 -0800213 host_objs = models.Host.smart_get_bulk(hosts)
MK Ryu26f0c932015-05-28 18:14:33 -0700214 remove_label_from_hosts(id, hosts)
215
MK Ryu8e2c2d02016-01-06 15:24:38 -0800216 rpc_utils.fanout_rpc(host_objs, 'remove_label_from_hosts', id=id)
217
MK Ryucf027c62015-03-04 12:00:50 -0800218
Jiaxi Luo31874592014-06-11 10:36:35 -0700219def get_labels(exclude_filters=(), **filter_data):
showardc92da832009-04-07 18:14:34 +0000220 """\
Jiaxi Luo31874592014-06-11 10:36:35 -0700221 @param exclude_filters: A sequence of dictionaries of filters.
222
showardc92da832009-04-07 18:14:34 +0000223 @returns A sequence of nested dictionaries of label information.
224 """
Jiaxi Luo31874592014-06-11 10:36:35 -0700225 labels = models.Label.query_objects(filter_data)
226 for exclude_filter in exclude_filters:
227 labels = labels.exclude(**exclude_filter)
228 return rpc_utils.prepare_rows_as_nested_dicts(labels, ('atomic_group',))
showardc92da832009-04-07 18:14:34 +0000229
230
231# atomic groups
232
showarde9450c92009-06-30 01:58:52 +0000233def add_atomic_group(name, max_number_of_machines=None, description=None):
showardc92da832009-04-07 18:14:34 +0000234 return models.AtomicGroup.add_object(
235 name=name, max_number_of_machines=max_number_of_machines,
236 description=description).id
237
238
239def modify_atomic_group(id, **data):
240 models.AtomicGroup.smart_get(id).update_object(data)
241
242
243def delete_atomic_group(id):
244 models.AtomicGroup.smart_get(id).delete()
245
246
247def atomic_group_add_labels(id, labels):
248 label_objs = models.Label.smart_get_bulk(labels)
249 models.AtomicGroup.smart_get(id).label_set.add(*label_objs)
250
251
252def atomic_group_remove_labels(id, labels):
253 label_objs = models.Label.smart_get_bulk(labels)
254 models.AtomicGroup.smart_get(id).label_set.remove(*label_objs)
255
256
257def get_atomic_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000258 return rpc_utils.prepare_for_serialization(
showardc92da832009-04-07 18:14:34 +0000259 models.AtomicGroup.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000260
261
262# hosts
263
Matthew Sartori68186332015-04-27 17:19:53 -0700264def add_host(hostname, status=None, locked=None, lock_reason='', protection=None):
265 if locked and not lock_reason:
266 raise model_logic.ValidationError(
267 {'locked': 'Please provide a reason for locking when adding host.'})
268
jadmanski0afbb632008-06-06 21:10:57 +0000269 return models.Host.add_object(hostname=hostname, status=status,
Matthew Sartori68186332015-04-27 17:19:53 -0700270 locked=locked, lock_reason=lock_reason,
271 protection=protection).id
mblighe8819cd2008-02-15 16:48:40 +0000272
273
MK Ryu33889612015-09-04 14:32:35 -0700274@rpc_utils.route_rpc_to_master
275def modify_host(id, **kwargs):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700276 """Modify local attributes of a host.
277
278 If this is called on the master, but the host is assigned to a shard, this
MK Ryu33889612015-09-04 14:32:35 -0700279 will call `modify_host_local` RPC to the responsible shard. This means if
280 a host is being locked using this function, this change will also propagate
281 to shards.
282 When this is called on a shard, the shard just routes the RPC to the master
283 and does nothing.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700284
285 @param id: id of the host to modify.
MK Ryu33889612015-09-04 14:32:35 -0700286 @param kwargs: key=value pairs of values to set on the host.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700287 """
MK Ryu33889612015-09-04 14:32:35 -0700288 rpc_utils.check_modify_host(kwargs)
showardce7c0922009-09-11 18:39:24 +0000289 host = models.Host.smart_get(id)
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800290 try:
291 rpc_utils.check_modify_host_locking(host, kwargs)
292 except model_logic.ValidationError as e:
293 if not kwargs.get('force_modify_locking', False):
294 raise
295 logging.exception('The following exception will be ignored and lock '
296 'modification will be enforced. %s', e)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700297
MK Ryud53e1492015-12-15 12:09:03 -0800298 # This is required to make `lock_time` for a host be exactly same
299 # between the master and a shard.
300 if kwargs.get('locked', None) and 'lock_time' not in kwargs:
301 kwargs['lock_time'] = datetime.datetime.now()
MK Ryu8e2c2d02016-01-06 15:24:38 -0800302 host.update_object(kwargs)
MK Ryud53e1492015-12-15 12:09:03 -0800303
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800304 # force_modifying_locking is not an internal field in database, remove.
305 kwargs.pop('force_modify_locking', None)
MK Ryu33889612015-09-04 14:32:35 -0700306 rpc_utils.fanout_rpc([host], 'modify_host_local',
307 include_hostnames=False, id=id, **kwargs)
mblighe8819cd2008-02-15 16:48:40 +0000308
309
MK Ryu33889612015-09-04 14:32:35 -0700310def modify_host_local(id, **kwargs):
311 """Modify host attributes in local DB.
312
313 @param id: Host id.
314 @param kwargs: key=value pairs of values to set on the host.
315 """
316 models.Host.smart_get(id).update_object(kwargs)
317
318
319@rpc_utils.route_rpc_to_master
showard276f9442009-05-20 00:33:16 +0000320def modify_hosts(host_filter_data, update_data):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700321 """Modify local attributes of multiple hosts.
322
323 If this is called on the master, but one of the hosts in that match the
MK Ryu33889612015-09-04 14:32:35 -0700324 filters is assigned to a shard, this will call `modify_hosts_local` RPC
325 to the responsible shard.
326 When this is called on a shard, the shard just routes the RPC to the master
327 and does nothing.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700328
329 The filters are always applied on the master, not on the shards. This means
330 if the states of a host differ on the master and a shard, the state on the
331 master will be used. I.e. this means:
332 A host was synced to Shard 1. On Shard 1 the status of the host was set to
333 'Repair Failed'.
334 - A call to modify_hosts with host_filter_data={'status': 'Ready'} will
335 update the host (both on the shard and on the master), because the state
336 of the host as the master knows it is still 'Ready'.
337 - A call to modify_hosts with host_filter_data={'status': 'Repair failed'
338 will not update the host, because the filter doesn't apply on the master.
339
showardbe0d8692009-08-20 23:42:44 +0000340 @param host_filter_data: Filters out which hosts to modify.
341 @param update_data: A dictionary with the changes to make to the hosts.
showard276f9442009-05-20 00:33:16 +0000342 """
MK Ryu93161712015-12-21 10:41:32 -0800343 update_data = update_data.copy()
showardbe0d8692009-08-20 23:42:44 +0000344 rpc_utils.check_modify_host(update_data)
showard276f9442009-05-20 00:33:16 +0000345 hosts = models.Host.query_objects(host_filter_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700346
347 affected_shard_hostnames = set()
348 affected_host_ids = []
349
Alex Miller9658a952013-05-14 16:40:02 -0700350 # Check all hosts before changing data for exception safety.
351 for host in hosts:
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800352 try:
353 rpc_utils.check_modify_host_locking(host, update_data)
354 except model_logic.ValidationError as e:
355 if not update_data.get('force_modify_locking', False):
356 raise
357 logging.exception('The following exception will be ignored and '
358 'lock modification will be enforced. %s', e)
359
Jakob Juelich50e91f72014-10-01 12:43:23 -0700360 if host.shard:
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800361 affected_shard_hostnames.add(host.shard.rpc_hostname())
Jakob Juelich50e91f72014-10-01 12:43:23 -0700362 affected_host_ids.append(host.id)
363
MK Ryud53e1492015-12-15 12:09:03 -0800364 # This is required to make `lock_time` for a host be exactly same
365 # between the master and a shard.
366 if update_data.get('locked', None) and 'lock_time' not in update_data:
367 update_data['lock_time'] = datetime.datetime.now()
MK Ryu8e2c2d02016-01-06 15:24:38 -0800368 for host in hosts:
369 host.update_object(update_data)
MK Ryud53e1492015-12-15 12:09:03 -0800370
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800371 update_data.pop('force_modify_locking', None)
MK Ryu33889612015-09-04 14:32:35 -0700372 # Caution: Changing the filter from the original here. See docstring.
373 rpc_utils.run_rpc_on_multiple_hostnames(
374 'modify_hosts_local', affected_shard_hostnames,
Jakob Juelich50e91f72014-10-01 12:43:23 -0700375 host_filter_data={'id__in': affected_host_ids},
376 update_data=update_data)
377
showard276f9442009-05-20 00:33:16 +0000378
MK Ryu33889612015-09-04 14:32:35 -0700379def modify_hosts_local(host_filter_data, update_data):
380 """Modify attributes of hosts in local DB.
381
382 @param host_filter_data: Filters out which hosts to modify.
383 @param update_data: A dictionary with the changes to make to the hosts.
384 """
385 for host in models.Host.query_objects(host_filter_data):
386 host.update_object(update_data)
387
388
MK Ryufbb002c2015-06-08 14:13:16 -0700389def add_labels_to_host(id, labels):
390 """Adds labels to a given host only in local DB.
showardcafd16e2009-05-29 18:37:49 +0000391
MK Ryufbb002c2015-06-08 14:13:16 -0700392 @param id: id or hostname for a host.
393 @param labels: ids or names for labels.
394 """
395 label_objs = models.Label.smart_get_bulk(labels)
396 models.Host.smart_get(id).labels.add(*label_objs)
397
398
399@rpc_utils.route_rpc_to_master
400def host_add_labels(id, labels):
401 """Adds labels to a given host.
402
403 @param id: id or hostname for a host.
404 @param labels: ids or names for labels.
405
406 @raises ValidationError: If adding more than one platform label.
407 """
408 label_objs = models.Label.smart_get_bulk(labels)
409 platforms = [label.name for label in label_objs if label.platform]
showardcafd16e2009-05-29 18:37:49 +0000410 if len(platforms) > 1:
411 raise model_logic.ValidationError(
412 {'labels': 'Adding more than one platform label: %s' %
413 ', '.join(platforms)})
MK Ryufbb002c2015-06-08 14:13:16 -0700414
415 host_obj = models.Host.smart_get(id)
showardcafd16e2009-05-29 18:37:49 +0000416 if len(platforms) == 1:
MK Ryufbb002c2015-06-08 14:13:16 -0700417 models.Host.check_no_platform([host_obj])
MK Ryu8e2c2d02016-01-06 15:24:38 -0800418 add_labels_to_host(id, labels)
MK Ryufbb002c2015-06-08 14:13:16 -0700419
420 rpc_utils.fanout_rpc([host_obj], 'add_labels_to_host', False,
421 id=id, labels=labels)
mblighe8819cd2008-02-15 16:48:40 +0000422
423
MK Ryufbb002c2015-06-08 14:13:16 -0700424def remove_labels_from_host(id, labels):
425 """Removes labels from a given host only in local DB.
426
427 @param id: id or hostname for a host.
428 @param labels: ids or names for labels.
429 """
430 label_objs = models.Label.smart_get_bulk(labels)
431 models.Host.smart_get(id).labels.remove(*label_objs)
432
433
434@rpc_utils.route_rpc_to_master
mblighe8819cd2008-02-15 16:48:40 +0000435def host_remove_labels(id, labels):
MK Ryufbb002c2015-06-08 14:13:16 -0700436 """Removes labels from a given host.
437
438 @param id: id or hostname for a host.
439 @param labels: ids or names for labels.
440 """
MK Ryu8e2c2d02016-01-06 15:24:38 -0800441 remove_labels_from_host(id, labels)
442
MK Ryufbb002c2015-06-08 14:13:16 -0700443 host_obj = models.Host.smart_get(id)
444 rpc_utils.fanout_rpc([host_obj], 'remove_labels_from_host', False,
445 id=id, labels=labels)
mblighe8819cd2008-02-15 16:48:40 +0000446
447
MK Ryuacf35922014-10-03 14:56:49 -0700448def get_host_attribute(attribute, **host_filter_data):
449 """
450 @param attribute: string name of attribute
451 @param host_filter_data: filter data to apply to Hosts to choose hosts to
452 act upon
453 """
454 hosts = rpc_utils.get_host_query((), False, False, True, host_filter_data)
455 hosts = list(hosts)
456 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
457 'attribute_list')
458 host_attr_dicts = []
459 for host_obj in hosts:
460 for attr_obj in host_obj.attribute_list:
461 if attr_obj.attribute == attribute:
462 host_attr_dicts.append(attr_obj.get_object_dict())
463 return rpc_utils.prepare_for_serialization(host_attr_dicts)
464
465
showard0957a842009-05-11 19:25:08 +0000466def set_host_attribute(attribute, value, **host_filter_data):
467 """
MK Ryu26f0c932015-05-28 18:14:33 -0700468 @param attribute: string name of attribute
469 @param value: string, or None to delete an attribute
470 @param host_filter_data: filter data to apply to Hosts to choose hosts to
471 act upon
showard0957a842009-05-11 19:25:08 +0000472 """
473 assert host_filter_data # disallow accidental actions on all hosts
474 hosts = models.Host.query_objects(host_filter_data)
475 models.AclGroup.check_for_acl_violation_hosts(hosts)
MK Ryu8e2c2d02016-01-06 15:24:38 -0800476 for host in hosts:
477 host.set_or_delete_attribute(attribute, value)
showard0957a842009-05-11 19:25:08 +0000478
MK Ryu26f0c932015-05-28 18:14:33 -0700479 # Master forwards this RPC to shards.
480 if not utils.is_shard():
481 rpc_utils.fanout_rpc(hosts, 'set_host_attribute', False,
482 attribute=attribute, value=value, **host_filter_data)
483
showard0957a842009-05-11 19:25:08 +0000484
Jakob Juelich50e91f72014-10-01 12:43:23 -0700485@rpc_utils.forward_single_host_rpc_to_shard
mblighe8819cd2008-02-15 16:48:40 +0000486def delete_host(id):
jadmanski0afbb632008-06-06 21:10:57 +0000487 models.Host.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000488
489
showard87cc38f2009-08-20 23:37:04 +0000490def get_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
Dan Shi37df54d2015-12-14 11:16:28 -0800491 exclude_atomic_group_hosts=False, valid_only=True,
492 include_current_job=False, **filter_data):
493 """Get a list of dictionaries which contains the information of hosts.
494
showard87cc38f2009-08-20 23:37:04 +0000495 @param multiple_labels: match hosts in all of the labels given. Should
496 be a list of label names.
497 @param exclude_only_if_needed_labels: Exclude hosts with at least one
498 "only_if_needed" label applied.
499 @param exclude_atomic_group_hosts: Exclude hosts that have one or more
500 atomic group labels associated with them.
Dan Shi37df54d2015-12-14 11:16:28 -0800501 @param include_current_job: Set to True to include ids of currently running
502 job and special task.
jadmanski0afbb632008-06-06 21:10:57 +0000503 """
showard43a3d262008-11-12 18:17:05 +0000504 hosts = rpc_utils.get_host_query(multiple_labels,
505 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000506 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000507 valid_only, filter_data)
showard0957a842009-05-11 19:25:08 +0000508 hosts = list(hosts)
509 models.Host.objects.populate_relationships(hosts, models.Label,
510 'label_list')
511 models.Host.objects.populate_relationships(hosts, models.AclGroup,
512 'acl_list')
513 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
514 'attribute_list')
showard43a3d262008-11-12 18:17:05 +0000515 host_dicts = []
516 for host_obj in hosts:
517 host_dict = host_obj.get_object_dict()
showard0957a842009-05-11 19:25:08 +0000518 host_dict['labels'] = [label.name for label in host_obj.label_list]
showard909c9142009-07-07 20:54:42 +0000519 host_dict['platform'], host_dict['atomic_group'] = (rpc_utils.
520 find_platform_and_atomic_group(host_obj))
showard0957a842009-05-11 19:25:08 +0000521 host_dict['acls'] = [acl.name for acl in host_obj.acl_list]
522 host_dict['attributes'] = dict((attribute.attribute, attribute.value)
523 for attribute in host_obj.attribute_list)
Dan Shi37df54d2015-12-14 11:16:28 -0800524 if include_current_job:
525 host_dict['current_job'] = None
526 host_dict['current_special_task'] = None
527 entries = models.HostQueueEntry.objects.filter(
528 host_id=host_dict['id'], active=True, complete=False)
529 if entries:
530 host_dict['current_job'] = (
531 entries[0].get_object_dict()['job'])
532 tasks = models.SpecialTask.objects.filter(
533 host_id=host_dict['id'], is_active=True, is_complete=False)
534 if tasks:
535 host_dict['current_special_task'] = (
536 '%d-%s' % (tasks[0].get_object_dict()['id'],
537 tasks[0].get_object_dict()['task'].lower()))
showard43a3d262008-11-12 18:17:05 +0000538 host_dicts.append(host_dict)
539 return rpc_utils.prepare_for_serialization(host_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000540
541
showard87cc38f2009-08-20 23:37:04 +0000542def get_num_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000543 exclude_atomic_group_hosts=False, valid_only=True,
544 **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000545 """
546 Same parameters as get_hosts().
547
548 @returns The number of matching hosts.
549 """
showard43a3d262008-11-12 18:17:05 +0000550 hosts = rpc_utils.get_host_query(multiple_labels,
551 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000552 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000553 valid_only, filter_data)
showard43a3d262008-11-12 18:17:05 +0000554 return hosts.count()
showard1385b162008-03-13 15:59:40 +0000555
mblighe8819cd2008-02-15 16:48:40 +0000556
557# tests
558
showard909c7a62008-07-15 21:52:38 +0000559def add_test(name, test_type, path, author=None, dependencies=None,
showard3d9899a2008-07-31 02:11:58 +0000560 experimental=True, run_verify=None, test_class=None,
showard909c7a62008-07-15 21:52:38 +0000561 test_time=None, test_category=None, description=None,
562 sync_count=1):
jadmanski0afbb632008-06-06 21:10:57 +0000563 return models.Test.add_object(name=name, test_type=test_type, path=path,
showard909c7a62008-07-15 21:52:38 +0000564 author=author, dependencies=dependencies,
565 experimental=experimental,
566 run_verify=run_verify, test_time=test_time,
567 test_category=test_category,
568 sync_count=sync_count,
jadmanski0afbb632008-06-06 21:10:57 +0000569 test_class=test_class,
570 description=description).id
mblighe8819cd2008-02-15 16:48:40 +0000571
572
573def modify_test(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000574 models.Test.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000575
576
577def delete_test(id):
jadmanski0afbb632008-06-06 21:10:57 +0000578 models.Test.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000579
580
581def get_tests(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000582 return rpc_utils.prepare_for_serialization(
583 models.Test.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000584
585
Moises Osorio2dc7a102014-12-02 18:24:02 -0800586@_timer.decorate
587def get_tests_status_counts_by_job_name_label(job_name_prefix, label_name):
588 """Gets the counts of all passed and failed tests from the matching jobs.
589
590 @param job_name_prefix: Name prefix of the jobs to get the summary from, e.g.,
591 'butterfly-release/R40-6457.21.0/bvt-cq/'.
592 @param label_name: Label that must be set in the jobs, e.g.,
593 'cros-version:butterfly-release/R40-6457.21.0'.
594
595 @returns A summary of the counts of all the passed and failed tests.
596 """
597 job_ids = list(models.Job.objects.filter(
598 name__startswith=job_name_prefix,
599 dependency_labels__name=label_name).values_list(
600 'pk', flat=True))
601 summary = {'passed': 0, 'failed': 0}
602 if not job_ids:
603 return summary
604
605 counts = (tko_models.TestView.objects.filter(
606 afe_job_id__in=job_ids).exclude(
607 test_name='SERVER_JOB').exclude(
608 test_name__startswith='CLIENT_JOB').values(
609 'status').annotate(
610 count=Count('status')))
611 for status in counts:
612 if status['status'] == 'GOOD':
613 summary['passed'] += status['count']
614 else:
615 summary['failed'] += status['count']
616 return summary
617
618
showard2b9a88b2008-06-13 20:55:03 +0000619# profilers
620
621def add_profiler(name, description=None):
622 return models.Profiler.add_object(name=name, description=description).id
623
624
625def modify_profiler(id, **data):
626 models.Profiler.smart_get(id).update_object(data)
627
628
629def delete_profiler(id):
630 models.Profiler.smart_get(id).delete()
631
632
633def get_profilers(**filter_data):
634 return rpc_utils.prepare_for_serialization(
635 models.Profiler.list_objects(filter_data))
636
637
mblighe8819cd2008-02-15 16:48:40 +0000638# users
639
640def add_user(login, access_level=None):
jadmanski0afbb632008-06-06 21:10:57 +0000641 return models.User.add_object(login=login, access_level=access_level).id
mblighe8819cd2008-02-15 16:48:40 +0000642
643
644def modify_user(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000645 models.User.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000646
647
648def delete_user(id):
jadmanski0afbb632008-06-06 21:10:57 +0000649 models.User.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000650
651
652def get_users(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000653 return rpc_utils.prepare_for_serialization(
654 models.User.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000655
656
657# acl groups
658
659def add_acl_group(name, description=None):
showard04f2cd82008-07-25 20:53:31 +0000660 group = models.AclGroup.add_object(name=name, description=description)
showard64a95952010-01-13 21:27:16 +0000661 group.users.add(models.User.current_user())
showard04f2cd82008-07-25 20:53:31 +0000662 return group.id
mblighe8819cd2008-02-15 16:48:40 +0000663
664
665def modify_acl_group(id, **data):
showard04f2cd82008-07-25 20:53:31 +0000666 group = models.AclGroup.smart_get(id)
667 group.check_for_acl_violation_acl_group()
668 group.update_object(data)
669 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000670
671
672def acl_group_add_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000673 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000674 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000675 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000676 group.users.add(*users)
mblighe8819cd2008-02-15 16:48:40 +0000677
678
679def acl_group_remove_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000680 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000681 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000682 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000683 group.users.remove(*users)
showard04f2cd82008-07-25 20:53:31 +0000684 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000685
686
687def acl_group_add_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000688 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000689 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000690 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000691 group.hosts.add(*hosts)
showard08f981b2008-06-24 21:59:03 +0000692 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000693
694
695def acl_group_remove_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000696 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000697 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000698 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000699 group.hosts.remove(*hosts)
showard08f981b2008-06-24 21:59:03 +0000700 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000701
702
703def delete_acl_group(id):
jadmanski0afbb632008-06-06 21:10:57 +0000704 models.AclGroup.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000705
706
707def get_acl_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000708 acl_groups = models.AclGroup.list_objects(filter_data)
709 for acl_group in acl_groups:
710 acl_group_obj = models.AclGroup.objects.get(id=acl_group['id'])
711 acl_group['users'] = [user.login
712 for user in acl_group_obj.users.all()]
713 acl_group['hosts'] = [host.hostname
714 for host in acl_group_obj.hosts.all()]
715 return rpc_utils.prepare_for_serialization(acl_groups)
mblighe8819cd2008-02-15 16:48:40 +0000716
717
718# jobs
719
mbligh120351e2009-01-24 01:40:45 +0000720def generate_control_file(tests=(), kernel=None, label=None, profilers=(),
showard91f85102009-10-12 20:34:52 +0000721 client_control_file='', use_container=False,
Matthew Sartori10438092015-06-24 14:30:18 -0700722 profile_only=None, upload_kernel_config=False,
723 db_tests=True):
jadmanski0afbb632008-06-06 21:10:57 +0000724 """
mbligh120351e2009-01-24 01:40:45 +0000725 Generates a client-side control file to load a kernel and run tests.
726
Matthew Sartori10438092015-06-24 14:30:18 -0700727 @param tests List of tests to run. See db_tests for more information.
mbligha3c58d22009-08-24 22:01:51 +0000728 @param kernel A list of kernel info dictionaries configuring which kernels
729 to boot for this job and other options for them
mbligh120351e2009-01-24 01:40:45 +0000730 @param label Name of label to grab kernel config from.
731 @param profilers List of profilers to activate during the job.
732 @param client_control_file The contents of a client-side control file to
733 run at the end of all tests. If this is supplied, all tests must be
734 client side.
735 TODO: in the future we should support server control files directly
736 to wrap with a kernel. That'll require changing the parameter
737 name and adding a boolean to indicate if it is a client or server
738 control file.
739 @param use_container unused argument today. TODO: Enable containers
740 on the host during a client side test.
showard91f85102009-10-12 20:34:52 +0000741 @param profile_only A boolean that indicates what default profile_only
742 mode to use in the control file. Passing None will generate a
743 control file that does not explcitly set the default mode at all.
showard232b7ae2009-11-10 00:46:48 +0000744 @param upload_kernel_config: if enabled it will generate server control
745 file code that uploads the kernel config file to the client and
746 tells the client of the new (local) path when compiling the kernel;
747 the tests must be server side tests
Matthew Sartori10438092015-06-24 14:30:18 -0700748 @param db_tests: if True, the test object can be found in the database
749 backing the test model. In this case, tests is a tuple
750 of test IDs which are used to retrieve the test objects
751 from the database. If False, tests is a tuple of test
752 dictionaries stored client-side in the AFE.
mbligh120351e2009-01-24 01:40:45 +0000753
754 @returns a dict with the following keys:
755 control_file: str, The control file text.
756 is_server: bool, is the control file a server-side control file?
757 synch_count: How many machines the job uses per autoserv execution.
758 synch_count == 1 means the job is asynchronous.
759 dependencies: A list of the names of labels on which the job depends.
760 """
showardd86debe2009-06-10 17:37:56 +0000761 if not tests and not client_control_file:
showard2bab8f42008-11-12 18:15:22 +0000762 return dict(control_file='', is_server=False, synch_count=1,
showard989f25d2008-10-01 11:38:11 +0000763 dependencies=[])
mblighe8819cd2008-02-15 16:48:40 +0000764
showard989f25d2008-10-01 11:38:11 +0000765 cf_info, test_objects, profiler_objects, label = (
showard2b9a88b2008-06-13 20:55:03 +0000766 rpc_utils.prepare_generate_control_file(tests, kernel, label,
Matthew Sartori10438092015-06-24 14:30:18 -0700767 profilers, db_tests))
showard989f25d2008-10-01 11:38:11 +0000768 cf_info['control_file'] = control_file.generate_control(
mbligha3c58d22009-08-24 22:01:51 +0000769 tests=test_objects, kernels=kernel, platform=label,
mbligh120351e2009-01-24 01:40:45 +0000770 profilers=profiler_objects, is_server=cf_info['is_server'],
showard232b7ae2009-11-10 00:46:48 +0000771 client_control_file=client_control_file, profile_only=profile_only,
772 upload_kernel_config=upload_kernel_config)
showard989f25d2008-10-01 11:38:11 +0000773 return cf_info
mblighe8819cd2008-02-15 16:48:40 +0000774
775
jamesren4a41e012010-07-16 22:33:48 +0000776def create_parameterized_job(name, priority, test, parameters, kernel=None,
777 label=None, profilers=(), profiler_parameters=None,
778 use_container=False, profile_only=None,
779 upload_kernel_config=False, hosts=(),
780 meta_hosts=(), one_time_hosts=(),
781 atomic_group_name=None, synch_count=None,
782 is_template=False, timeout=None,
Simran Basi7e605742013-11-12 13:43:36 -0800783 timeout_mins=None, max_runtime_mins=None,
784 run_verify=False, email_list='', dependencies=(),
785 reboot_before=None, reboot_after=None,
786 parse_failed_repair=None, hostless=False,
Dan Shiec1d47d2015-02-13 11:38:13 -0800787 keyvals=None, drone_set=None, run_reset=True,
Dan Shi2a5297b2015-07-23 17:03:29 -0700788 require_ssp=None):
jamesren4a41e012010-07-16 22:33:48 +0000789 """
790 Creates and enqueues a parameterized job.
791
792 Most parameters a combination of the parameters for generate_control_file()
793 and create_job(), with the exception of:
794
795 @param test name or ID of the test to run
796 @param parameters a map of parameter name ->
797 tuple of (param value, param type)
798 @param profiler_parameters a dictionary of parameters for the profilers:
799 key: profiler name
800 value: dict of param name -> tuple of
801 (param value,
802 param type)
803 """
804 # Save the values of the passed arguments here. What we're going to do with
805 # them is pass them all to rpc_utils.get_create_job_common_args(), which
806 # will extract the subset of these arguments that apply for
807 # rpc_utils.create_job_common(), which we then pass in to that function.
808 args = locals()
809
810 # Set up the parameterized job configs
811 test_obj = models.Test.smart_get(test)
Aviv Keshet3dd8beb2013-05-13 17:36:04 -0700812 control_type = test_obj.test_type
jamesren4a41e012010-07-16 22:33:48 +0000813
814 try:
815 label = models.Label.smart_get(label)
816 except models.Label.DoesNotExist:
817 label = None
818
819 kernel_objs = models.Kernel.create_kernels(kernel)
820 profiler_objs = [models.Profiler.smart_get(profiler)
821 for profiler in profilers]
822
823 parameterized_job = models.ParameterizedJob.objects.create(
824 test=test_obj, label=label, use_container=use_container,
825 profile_only=profile_only,
826 upload_kernel_config=upload_kernel_config)
827 parameterized_job.kernels.add(*kernel_objs)
828
829 for profiler in profiler_objs:
830 parameterized_profiler = models.ParameterizedJobProfiler.objects.create(
831 parameterized_job=parameterized_job,
832 profiler=profiler)
833 profiler_params = profiler_parameters.get(profiler.name, {})
834 for name, (value, param_type) in profiler_params.iteritems():
835 models.ParameterizedJobProfilerParameter.objects.create(
836 parameterized_job_profiler=parameterized_profiler,
837 parameter_name=name,
838 parameter_value=value,
839 parameter_type=param_type)
840
841 try:
842 for parameter in test_obj.testparameter_set.all():
843 if parameter.name in parameters:
844 param_value, param_type = parameters.pop(parameter.name)
845 parameterized_job.parameterizedjobparameter_set.create(
846 test_parameter=parameter, parameter_value=param_value,
847 parameter_type=param_type)
848
849 if parameters:
850 raise Exception('Extra parameters remain: %r' % parameters)
851
852 return rpc_utils.create_job_common(
853 parameterized_job=parameterized_job.id,
854 control_type=control_type,
855 **rpc_utils.get_create_job_common_args(args))
856 except:
857 parameterized_job.delete()
858 raise
859
860
Simran Basib6ec8ae2014-04-23 12:05:08 -0700861def create_job_page_handler(name, priority, control_file, control_type,
Dan Shid215dbe2015-06-18 16:14:59 -0700862 image=None, hostless=False, firmware_rw_build=None,
863 firmware_ro_build=None, test_source_build=None,
864 **kwargs):
Simran Basib6ec8ae2014-04-23 12:05:08 -0700865 """\
866 Create and enqueue a job.
867
868 @param name name of this job
869 @param priority Integer priority of this job. Higher is more important.
870 @param control_file String contents of the control file.
871 @param control_type Type of control file, Client or Server.
Dan Shid215dbe2015-06-18 16:14:59 -0700872 @param image: ChromeOS build to be installed in the dut. Default to None.
873 @param firmware_rw_build: Firmware build to update RW firmware. Default to
874 None, i.e., RW firmware will not be updated.
875 @param firmware_ro_build: Firmware build to update RO firmware. Default to
876 None, i.e., RO firmware will not be updated.
877 @param test_source_build: Build to be used to retrieve test code. Default
878 to None.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700879 @param kwargs extra args that will be required by create_suite_job or
880 create_job.
881
882 @returns The created Job id number.
883 """
884 control_file = rpc_utils.encode_ascii(control_file)
Jiaxi Luodd67beb2014-07-18 16:28:31 -0700885 if not control_file:
886 raise model_logic.ValidationError({
887 'control_file' : "Control file cannot be empty"})
Simran Basib6ec8ae2014-04-23 12:05:08 -0700888
889 if image and hostless:
Dan Shid215dbe2015-06-18 16:14:59 -0700890 builds = {}
891 builds[provision.CROS_VERSION_PREFIX] = image
892 if firmware_rw_build:
Dan Shi0723bf52015-06-24 10:52:38 -0700893 builds[provision.FW_RW_VERSION_PREFIX] = firmware_rw_build
Dan Shid215dbe2015-06-18 16:14:59 -0700894 if firmware_ro_build:
895 builds[provision.FW_RO_VERSION_PREFIX] = firmware_ro_build
Simran Basib6ec8ae2014-04-23 12:05:08 -0700896 return site_rpc_interface.create_suite_job(
897 name=name, control_file=control_file, priority=priority,
Dan Shid215dbe2015-06-18 16:14:59 -0700898 builds=builds, test_source_build=test_source_build, **kwargs)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700899 return create_job(name, priority, control_file, control_type, image=image,
900 hostless=hostless, **kwargs)
901
902
MK Ryue301eb72015-06-25 12:51:02 -0700903@rpc_utils.route_rpc_to_master
showard12f3e322009-05-13 21:27:42 +0000904def create_job(name, priority, control_file, control_type,
905 hosts=(), meta_hosts=(), one_time_hosts=(),
906 atomic_group_name=None, synch_count=None, is_template=False,
Simran Basi7e605742013-11-12 13:43:36 -0800907 timeout=None, timeout_mins=None, max_runtime_mins=None,
908 run_verify=False, email_list='', dependencies=(),
909 reboot_before=None, reboot_after=None, parse_failed_repair=None,
910 hostless=False, keyvals=None, drone_set=None, image=None,
Dan Shiec1d47d2015-02-13 11:38:13 -0800911 parent_job_id=None, test_retry=0, run_reset=True,
912 require_ssp=None, args=(), **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +0000913 """\
914 Create and enqueue a job.
mblighe8819cd2008-02-15 16:48:40 +0000915
showarda1e74b32009-05-12 17:32:04 +0000916 @param name name of this job
Alex Miller7d658cf2013-09-04 16:00:35 -0700917 @param priority Integer priority of this job. Higher is more important.
showarda1e74b32009-05-12 17:32:04 +0000918 @param control_file String contents of the control file.
919 @param control_type Type of control file, Client or Server.
920 @param synch_count How many machines the job uses per autoserv execution.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700921 synch_count == 1 means the job is asynchronous. If an atomic group is
922 given this value is treated as a minimum.
showarda1e74b32009-05-12 17:32:04 +0000923 @param is_template If true then create a template job.
924 @param timeout Hours after this call returns until the job times out.
Simran Basi7e605742013-11-12 13:43:36 -0800925 @param timeout_mins Minutes after this call returns until the job times
Jiaxi Luo90190c92014-06-18 12:35:57 -0700926 out.
Simran Basi34217022012-11-06 13:43:15 -0800927 @param max_runtime_mins Minutes from job starting time until job times out
showarda1e74b32009-05-12 17:32:04 +0000928 @param run_verify Should the host be verified before running the test?
929 @param email_list String containing emails to mail when the job is done
930 @param dependencies List of label names on which this job depends
931 @param reboot_before Never, If dirty, or Always
932 @param reboot_after Never, If all tests passed, or Always
933 @param parse_failed_repair if true, results of failed repairs launched by
Jiaxi Luo90190c92014-06-18 12:35:57 -0700934 this job will be parsed as part of the job.
showarda9545c02009-12-18 22:44:26 +0000935 @param hostless if true, create a hostless job
showardc1a98d12010-01-15 00:22:22 +0000936 @param keyvals dict of keyvals to associate with the job
showarda1e74b32009-05-12 17:32:04 +0000937 @param hosts List of hosts to run job on.
938 @param meta_hosts List where each entry is a label name, and for each entry
Jiaxi Luo90190c92014-06-18 12:35:57 -0700939 one host will be chosen from that label to run the job on.
showarda1e74b32009-05-12 17:32:04 +0000940 @param one_time_hosts List of hosts not in the database to run the job on.
941 @param atomic_group_name The name of an atomic group to schedule the job on.
jamesren76fcf192010-04-21 20:39:50 +0000942 @param drone_set The name of the drone set to run this test on.
Paul Pendlebury5a8c6ad2011-02-01 07:20:17 -0800943 @param image OS image to install before running job.
Aviv Keshet0b9cfc92013-02-05 11:36:02 -0800944 @param parent_job_id id of a job considered to be parent of created job.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700945 @param test_retry Number of times to retry test if the test did not
Jiaxi Luo90190c92014-06-18 12:35:57 -0700946 complete successfully. (optional, default: 0)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700947 @param run_reset Should the host be reset before running the test?
Dan Shiec1d47d2015-02-13 11:38:13 -0800948 @param require_ssp Set to True to require server-side packaging to run the
949 test. If it's set to None, drone will still try to run
950 the server side with server-side packaging. If the
951 autotest-server package doesn't exist for the build or
952 image is not set, drone will run the test without server-
953 side packaging. Default is None.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700954 @param args A list of args to be injected into control file.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700955 @param kwargs extra keyword args. NOT USED.
showardc92da832009-04-07 18:14:34 +0000956
957 @returns The created Job id number.
jadmanski0afbb632008-06-06 21:10:57 +0000958 """
Jiaxi Luo90190c92014-06-18 12:35:57 -0700959 if args:
960 control_file = tools.inject_vars({'args': args}, control_file)
961
Simran Basiab5a1bf2014-05-28 15:39:44 -0700962 if image is None:
963 return rpc_utils.create_job_common(
964 **rpc_utils.get_create_job_common_args(locals()))
965
Simran Basi6157e8e2015-12-07 18:22:34 -0800966 # Translate the image name, in case its a relative build name.
967 ds = dev_server.ImageServer.resolve(image)
968 image = ds.translate(image)
969
Simran Basiab5a1bf2014-05-28 15:39:44 -0700970 # When image is supplied use a known parameterized test already in the
971 # database to pass the OS image path from the front end, through the
972 # scheduler, and finally to autoserv as the --image parameter.
973
974 # The test autoupdate_ParameterizedJob is in afe_autotests and used to
975 # instantiate a Test object and from there a ParameterizedJob.
976 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
977 known_parameterized_job = models.ParameterizedJob.objects.create(
978 test=known_test_obj)
979
980 # autoupdate_ParameterizedJob has a single parameter, the image parameter,
981 # stored in the table afe_test_parameters. We retrieve and set this
982 # instance of the parameter to the OS image path.
983 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
984 name='image')
985 known_parameterized_job.parameterizedjobparameter_set.create(
986 test_parameter=image_parameter, parameter_value=image,
987 parameter_type='string')
988
Dan Shid215dbe2015-06-18 16:14:59 -0700989 # TODO(crbug.com/502638): save firmware build etc to parameterized_job.
990
Simran Basiab5a1bf2014-05-28 15:39:44 -0700991 # By passing a parameterized_job to create_job_common the job entry in
992 # the afe_jobs table will have the field parameterized_job_id set.
993 # The scheduler uses this id in the afe_parameterized_jobs table to
994 # match this job to our known test, and then with the
995 # afe_parameterized_job_parameters table to get the actual image path.
jamesren4a41e012010-07-16 22:33:48 +0000996 return rpc_utils.create_job_common(
Simran Basiab5a1bf2014-05-28 15:39:44 -0700997 parameterized_job=known_parameterized_job.id,
jamesren4a41e012010-07-16 22:33:48 +0000998 **rpc_utils.get_create_job_common_args(locals()))
mblighe8819cd2008-02-15 16:48:40 +0000999
1000
showard9dbdcda2008-10-14 17:34:36 +00001001def abort_host_queue_entries(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001002 """\
showard9dbdcda2008-10-14 17:34:36 +00001003 Abort a set of host queue entries.
Fang Deng63b0e452014-12-19 14:38:15 -08001004
1005 @return: A list of dictionaries, each contains information
1006 about an aborted HQE.
jadmanski0afbb632008-06-06 21:10:57 +00001007 """
showard9dbdcda2008-10-14 17:34:36 +00001008 query = models.HostQueueEntry.query_objects(filter_data)
beepsfaecbce2013-10-29 11:35:10 -07001009
1010 # Dont allow aborts on:
1011 # 1. Jobs that have already completed (whether or not they were aborted)
1012 # 2. Jobs that we have already been aborted (but may not have completed)
1013 query = query.filter(complete=False).filter(aborted=False)
showarddc817512008-11-12 18:16:41 +00001014 models.AclGroup.check_abort_permissions(query)
showard9dbdcda2008-10-14 17:34:36 +00001015 host_queue_entries = list(query.select_related())
showard2bab8f42008-11-12 18:15:22 +00001016 rpc_utils.check_abort_synchronous_jobs(host_queue_entries)
mblighe8819cd2008-02-15 16:48:40 +00001017
Simran Basic1b26762013-06-26 14:23:21 -07001018 models.HostQueueEntry.abort_host_queue_entries(host_queue_entries)
Fang Deng63b0e452014-12-19 14:38:15 -08001019 hqe_info = [{'HostQueueEntry': hqe.id, 'Job': hqe.job_id,
1020 'Job name': hqe.job.name} for hqe in host_queue_entries]
1021 return hqe_info
showard9d821ab2008-07-11 16:54:29 +00001022
1023
beeps8bb1f7d2013-08-05 01:30:09 -07001024def abort_special_tasks(**filter_data):
1025 """\
1026 Abort the special task, or tasks, specified in the filter.
1027 """
1028 query = models.SpecialTask.query_objects(filter_data)
1029 special_tasks = query.filter(is_active=True)
1030 for task in special_tasks:
1031 task.abort()
1032
1033
Simran Basi73dae552013-02-25 14:57:46 -08001034def _call_special_tasks_on_hosts(task, hosts):
1035 """\
1036 Schedules a set of hosts for a special task.
1037
1038 @returns A list of hostnames that a special task was created for.
1039 """
1040 models.AclGroup.check_for_acl_violation_hosts(hosts)
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -08001041 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -08001042 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -08001043 raise ValueError('The following hosts are on shards, please '
1044 'follow the link to the shards and create jobs '
1045 'there instead. %s.' % shard_host_map)
Simran Basi73dae552013-02-25 14:57:46 -08001046 for host in hosts:
1047 models.SpecialTask.schedule_special_task(host, task)
1048 return list(sorted(host.hostname for host in hosts))
1049
1050
MK Ryu5aa25042015-07-28 16:08:04 -07001051def _forward_special_tasks_on_hosts(task, rpc, **filter_data):
1052 """Forward special tasks to corresponding shards.
mbligh4e545a52009-12-19 05:30:39 +00001053
MK Ryu5aa25042015-07-28 16:08:04 -07001054 For master, when special tasks are fired on hosts that are sharded,
1055 forward the RPC to corresponding shards.
1056
1057 For shard, create special task records in local DB.
1058
1059 @param task: Enum value of frontend.afe.models.SpecialTask.Task
1060 @param rpc: RPC name to forward.
1061 @param filter_data: Filter keywords to be used for DB query.
1062
1063 @return: A list of hostnames that a special task was created for.
showard1ff7b2e2009-05-15 23:17:18 +00001064 """
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001065 hosts = models.Host.query_objects(filter_data)
1066 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts, rpc_hostnames=True)
1067
1068 # Filter out hosts on a shard from those on the master, forward
1069 # rpcs to the shard with an additional hostname__in filter, and
1070 # create a local SpecialTask for each remaining host.
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -08001071 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001072 hosts = [h for h in hosts if h.shard is None]
1073 for shard, hostnames in shard_host_map.iteritems():
1074
1075 # The main client of this module is the frontend website, and
1076 # it invokes it with an 'id' or an 'id__in' filter. Regardless,
1077 # the 'hostname' filter should narrow down the list of hosts on
1078 # each shard even though we supply all the ids in filter_data.
1079 # This method uses hostname instead of id because it fits better
MK Ryu5aa25042015-07-28 16:08:04 -07001080 # with the overall architecture of redirection functions in
1081 # rpc_utils.
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001082 shard_filter = filter_data.copy()
1083 shard_filter['hostname__in'] = hostnames
1084 rpc_utils.run_rpc_on_multiple_hostnames(
MK Ryu5aa25042015-07-28 16:08:04 -07001085 rpc, [shard], **shard_filter)
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001086
1087 # There is a race condition here if someone assigns a shard to one of these
1088 # hosts before we create the task. The host will stay on the master if:
1089 # 1. The host is not Ready
1090 # 2. The host is Ready but has a task
1091 # But if the host is Ready and doesn't have a task yet, it will get sent
1092 # to the shard as we're creating a task here.
1093
1094 # Given that we only rarely verify Ready hosts it isn't worth putting this
1095 # entire method in a transaction. The worst case scenario is that we have
MK Ryu5aa25042015-07-28 16:08:04 -07001096 # a verify running on a Ready host while the shard is using it, if the
1097 # verify fails no subsequent tasks will be created against the host on the
1098 # master, and verifies are safe enough that this is OK.
1099 return _call_special_tasks_on_hosts(task, hosts)
1100
1101
1102def reverify_hosts(**filter_data):
1103 """\
1104 Schedules a set of hosts for verify.
1105
1106 @returns A list of hostnames that a verify task was created for.
1107 """
1108 return _forward_special_tasks_on_hosts(
1109 models.SpecialTask.Task.VERIFY, 'reverify_hosts', **filter_data)
Simran Basi73dae552013-02-25 14:57:46 -08001110
1111
1112def repair_hosts(**filter_data):
1113 """\
1114 Schedules a set of hosts for repair.
1115
1116 @returns A list of hostnames that a repair task was created for.
1117 """
MK Ryu5aa25042015-07-28 16:08:04 -07001118 return _forward_special_tasks_on_hosts(
1119 models.SpecialTask.Task.REPAIR, 'repair_hosts', **filter_data)
showard1ff7b2e2009-05-15 23:17:18 +00001120
1121
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001122def get_jobs(not_yet_run=False, running=False, finished=False,
1123 suite=False, sub=False, standalone=False, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001124 """\
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001125 Extra status filter args for get_jobs:
jadmanski0afbb632008-06-06 21:10:57 +00001126 -not_yet_run: Include only jobs that have not yet started running.
1127 -running: Include only jobs that have start running but for which not
1128 all hosts have completed.
1129 -finished: Include only jobs for which all hosts have completed (or
1130 aborted).
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001131
1132 Extra type filter args for get_jobs:
1133 -suite: Include only jobs with child jobs.
1134 -sub: Include only jobs with a parent job.
1135 -standalone: Inlcude only jobs with no child or parent jobs.
1136 At most one of these three fields should be specified.
jadmanski0afbb632008-06-06 21:10:57 +00001137 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001138 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
1139 running,
1140 finished)
1141 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
1142 suite,
1143 sub,
1144 standalone)
showard0957a842009-05-11 19:25:08 +00001145 job_dicts = []
1146 jobs = list(models.Job.query_objects(filter_data))
1147 models.Job.objects.populate_relationships(jobs, models.Label,
1148 'dependencies')
showardc1a98d12010-01-15 00:22:22 +00001149 models.Job.objects.populate_relationships(jobs, models.JobKeyval, 'keyvals')
showard0957a842009-05-11 19:25:08 +00001150 for job in jobs:
1151 job_dict = job.get_object_dict()
1152 job_dict['dependencies'] = ','.join(label.name
1153 for label in job.dependencies)
showardc1a98d12010-01-15 00:22:22 +00001154 job_dict['keyvals'] = dict((keyval.key, keyval.value)
1155 for keyval in job.keyvals)
Eric Lid23bc192011-02-09 14:38:57 -08001156 if job.parameterized_job:
1157 job_dict['image'] = get_parameterized_autoupdate_image_url(job)
showard0957a842009-05-11 19:25:08 +00001158 job_dicts.append(job_dict)
1159 return rpc_utils.prepare_for_serialization(job_dicts)
mblighe8819cd2008-02-15 16:48:40 +00001160
1161
1162def get_num_jobs(not_yet_run=False, running=False, finished=False,
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001163 suite=False, sub=False, standalone=False,
jadmanski0afbb632008-06-06 21:10:57 +00001164 **filter_data):
1165 """\
1166 See get_jobs() for documentation of extra filter parameters.
1167 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001168 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
1169 running,
1170 finished)
1171 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
1172 suite,
1173 sub,
1174 standalone)
jadmanski0afbb632008-06-06 21:10:57 +00001175 return models.Job.query_count(filter_data)
mblighe8819cd2008-02-15 16:48:40 +00001176
1177
mblighe8819cd2008-02-15 16:48:40 +00001178def get_jobs_summary(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001179 """\
Jiaxi Luoaac54572014-06-04 13:57:02 -07001180 Like get_jobs(), but adds 'status_counts' and 'result_counts' field.
1181
1182 'status_counts' filed is a dictionary mapping status strings to the number
1183 of hosts currently with that status, i.e. {'Queued' : 4, 'Running' : 2}.
1184
1185 'result_counts' field is piped to tko's rpc_interface and has the return
1186 format specified under get_group_counts.
jadmanski0afbb632008-06-06 21:10:57 +00001187 """
1188 jobs = get_jobs(**filter_data)
1189 ids = [job['id'] for job in jobs]
1190 all_status_counts = models.Job.objects.get_status_counts(ids)
1191 for job in jobs:
1192 job['status_counts'] = all_status_counts[job['id']]
Jiaxi Luoaac54572014-06-04 13:57:02 -07001193 job['result_counts'] = tko_rpc_interface.get_status_counts(
1194 ['afe_job_id', 'afe_job_id'],
1195 header_groups=[['afe_job_id'], ['afe_job_id']],
1196 **{'afe_job_id': job['id']})
jadmanski0afbb632008-06-06 21:10:57 +00001197 return rpc_utils.prepare_for_serialization(jobs)
mblighe8819cd2008-02-15 16:48:40 +00001198
1199
showarda965cef2009-05-15 23:17:41 +00001200def get_info_for_clone(id, preserve_metahosts, queue_entry_filter_data=None):
showarda8709c52008-07-03 19:44:54 +00001201 """\
1202 Retrieves all the information needed to clone a job.
1203 """
showarda8709c52008-07-03 19:44:54 +00001204 job = models.Job.objects.get(id=id)
showard29f7cd22009-04-29 21:16:24 +00001205 job_info = rpc_utils.get_job_info(job,
showarda965cef2009-05-15 23:17:41 +00001206 preserve_metahosts,
1207 queue_entry_filter_data)
showard945072f2008-09-03 20:34:59 +00001208
showardd9992fe2008-07-31 02:15:03 +00001209 host_dicts = []
showard29f7cd22009-04-29 21:16:24 +00001210 for host in job_info['hosts']:
1211 host_dict = get_hosts(id=host.id)[0]
1212 other_labels = host_dict['labels']
1213 if host_dict['platform']:
1214 other_labels.remove(host_dict['platform'])
1215 host_dict['other_labels'] = ', '.join(other_labels)
showardd9992fe2008-07-31 02:15:03 +00001216 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001217
showard29f7cd22009-04-29 21:16:24 +00001218 for host in job_info['one_time_hosts']:
1219 host_dict = dict(hostname=host.hostname,
1220 id=host.id,
1221 platform='(one-time host)',
1222 locked_text='')
1223 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001224
showard4d077562009-05-08 18:24:36 +00001225 # convert keys from Label objects to strings (names of labels)
showard29f7cd22009-04-29 21:16:24 +00001226 meta_host_counts = dict((meta_host.name, count) for meta_host, count
showard4d077562009-05-08 18:24:36 +00001227 in job_info['meta_host_counts'].iteritems())
showard29f7cd22009-04-29 21:16:24 +00001228
1229 info = dict(job=job.get_object_dict(),
1230 meta_host_counts=meta_host_counts,
1231 hosts=host_dicts)
1232 info['job']['dependencies'] = job_info['dependencies']
1233 if job_info['atomic_group']:
1234 info['atomic_group_name'] = (job_info['atomic_group']).name
1235 else:
1236 info['atomic_group_name'] = None
jamesren2275ef12010-04-12 18:25:06 +00001237 info['hostless'] = job_info['hostless']
jamesren76fcf192010-04-21 20:39:50 +00001238 info['drone_set'] = job.drone_set and job.drone_set.name
showarda8709c52008-07-03 19:44:54 +00001239
Eric Lid23bc192011-02-09 14:38:57 -08001240 if job.parameterized_job:
1241 info['job']['image'] = get_parameterized_autoupdate_image_url(job)
1242
showarda8709c52008-07-03 19:44:54 +00001243 return rpc_utils.prepare_for_serialization(info)
1244
1245
showard34dc5fa2008-04-24 20:58:40 +00001246# host queue entries
1247
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001248def get_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001249 """\
showardc92da832009-04-07 18:14:34 +00001250 @returns A sequence of nested dictionaries of host and job information.
jadmanski0afbb632008-06-06 21:10:57 +00001251 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001252 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1253 'started_on__lte',
1254 start_time,
1255 end_time,
1256 **filter_data)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001257 return rpc_utils.prepare_rows_as_nested_dicts(
1258 models.HostQueueEntry.query_objects(filter_data),
1259 ('host', 'atomic_group', 'job'))
showard34dc5fa2008-04-24 20:58:40 +00001260
1261
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001262def get_num_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001263 """\
1264 Get the number of host queue entries associated with this job.
1265 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001266 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1267 'started_on__lte',
1268 start_time,
1269 end_time,
1270 **filter_data)
jadmanski0afbb632008-06-06 21:10:57 +00001271 return models.HostQueueEntry.query_count(filter_data)
showard34dc5fa2008-04-24 20:58:40 +00001272
1273
showard1e935f12008-07-11 00:11:36 +00001274def get_hqe_percentage_complete(**filter_data):
1275 """
showardc92da832009-04-07 18:14:34 +00001276 Computes the fraction of host queue entries matching the given filter data
showard1e935f12008-07-11 00:11:36 +00001277 that are complete.
1278 """
1279 query = models.HostQueueEntry.query_objects(filter_data)
1280 complete_count = query.filter(complete=True).count()
1281 total_count = query.count()
1282 if total_count == 0:
1283 return 1
1284 return float(complete_count) / total_count
1285
1286
showard1a5a4082009-07-28 20:01:37 +00001287# special tasks
1288
1289def get_special_tasks(**filter_data):
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001290 """Get special task entries from the local database.
1291
1292 Query the special tasks table for tasks matching the given
1293 `filter_data`, and return a list of the results. No attempt is
1294 made to forward the call to shards; the buck will stop here.
1295 The caller is expected to know the target shard for such reasons
1296 as:
1297 * The caller is a service (such as gs_offloader) configured
1298 to operate on behalf of one specific shard, and no other.
1299 * The caller has a host as a parameter, and knows that this is
1300 the shard assigned to that host.
1301
1302 @param filter_data Filter keywords to pass to the underlying
1303 database query.
1304
1305 """
J. Richard Barnettefdfcd662015-04-13 17:20:29 -07001306 return rpc_utils.prepare_rows_as_nested_dicts(
1307 models.SpecialTask.query_objects(filter_data),
1308 ('host', 'queue_entry'))
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001309
1310
1311def get_host_special_tasks(host_id, **filter_data):
1312 """Get special task entries for a given host.
1313
1314 Query the special tasks table for tasks that ran on the host
1315 given by `host_id` and matching the given `filter_data`.
1316 Return a list of the results. If the host is assigned to a
1317 shard, forward this call to that shard.
1318
1319 @param host_id Id in the database of the target host.
1320 @param filter_data Filter keywords to pass to the underlying
1321 database query.
1322
1323 """
MK Ryu0c1a37d2015-04-30 12:00:55 -07001324 # Retrieve host data even if the host is in an invalid state.
1325 host = models.Host.smart_get(host_id, False)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001326 if not host.shard:
J. Richard Barnettefdfcd662015-04-13 17:20:29 -07001327 return get_special_tasks(host_id=host_id, **filter_data)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001328 else:
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001329 # The return values from AFE methods are post-processed
1330 # objects that aren't JSON-serializable. So, we have to
1331 # call AFE.run() to get the raw, serializable output from
1332 # the shard.
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001333 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1334 return shard_afe.run('get_special_tasks',
1335 host_id=host_id, **filter_data)
showard1a5a4082009-07-28 20:01:37 +00001336
1337
MK Ryu0c1a37d2015-04-30 12:00:55 -07001338def get_num_special_tasks(**kwargs):
1339 """Get the number of special task entries from the local database.
1340
1341 Query the special tasks table for tasks matching the given 'kwargs',
1342 and return the number of the results. No attempt is made to forward
1343 the call to shards; the buck will stop here.
1344
1345 @param kwargs Filter keywords to pass to the underlying database query.
1346
1347 """
1348 return models.SpecialTask.query_count(kwargs)
1349
1350
1351def get_host_num_special_tasks(host, **kwargs):
1352 """Get special task entries for a given host.
1353
1354 Query the special tasks table for tasks that ran on the host
1355 given by 'host' and matching the given 'kwargs'.
1356 Return a list of the results. If the host is assigned to a
1357 shard, forward this call to that shard.
1358
1359 @param host id or name of a host. More often a hostname.
1360 @param kwargs Filter keywords to pass to the underlying database query.
1361
1362 """
1363 # Retrieve host data even if the host is in an invalid state.
1364 host_model = models.Host.smart_get(host, False)
1365 if not host_model.shard:
1366 return get_num_special_tasks(host=host, **kwargs)
1367 else:
1368 shard_afe = frontend.AFE(server=host_model.shard.rpc_hostname())
1369 return shard_afe.run('get_num_special_tasks', host=host, **kwargs)
1370
1371
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001372def get_status_task(host_id, end_time):
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001373 """Get the "status task" for a host from the local shard.
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001374
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001375 Returns a single special task representing the given host's
1376 "status task". The status task is a completed special task that
1377 identifies whether the corresponding host was working or broken
1378 when it completed. A successful task indicates a working host;
1379 a failed task indicates broken.
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001380
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001381 This call will not be forward to a shard; the receiving server
1382 must be the shard that owns the host.
1383
1384 @param host_id Id in the database of the target host.
1385 @param end_time Time reference for the host's status.
1386
1387 @return A single task; its status (successful or not)
1388 corresponds to the status of the host (working or
1389 broken) at the given time. If no task is found, return
1390 `None`.
1391
1392 """
1393 tasklist = rpc_utils.prepare_rows_as_nested_dicts(
1394 status_history.get_status_task(host_id, end_time),
1395 ('host', 'queue_entry'))
1396 return tasklist[0] if tasklist else None
1397
1398
1399def get_host_status_task(host_id, end_time):
1400 """Get the "status task" for a host from its owning shard.
1401
1402 Finds the given host's owning shard, and forwards to it a call
1403 to `get_status_task()` (see above).
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001404
1405 @param host_id Id in the database of the target host.
1406 @param end_time Time reference for the host's status.
1407
1408 @return A single task; its status (successful or not)
1409 corresponds to the status of the host (working or
1410 broken) at the given time. If no task is found, return
1411 `None`.
1412
1413 """
1414 host = models.Host.smart_get(host_id)
1415 if not host.shard:
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001416 return get_status_task(host_id, end_time)
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001417 else:
1418 # The return values from AFE methods are post-processed
1419 # objects that aren't JSON-serializable. So, we have to
1420 # call AFE.run() to get the raw, serializable output from
1421 # the shard.
1422 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1423 return shard_afe.run('get_status_task',
1424 host_id=host_id, end_time=end_time)
1425
1426
J. Richard Barnette8abbfd62015-06-23 12:46:54 -07001427def get_host_diagnosis_interval(host_id, end_time, success):
1428 """Find a "diagnosis interval" for a given host.
1429
1430 A "diagnosis interval" identifies a start and end time where
1431 the host went from "working" to "broken", or vice versa. The
1432 interval's starting time is the starting time of the last status
1433 task with the old status; the end time is the finish time of the
1434 first status task with the new status.
1435
1436 This routine finds the most recent diagnosis interval for the
1437 given host prior to `end_time`, with a starting status matching
1438 `success`. If `success` is true, the interval will start with a
1439 successful status task; if false the interval will start with a
1440 failed status task.
1441
1442 @param host_id Id in the database of the target host.
1443 @param end_time Time reference for the diagnosis interval.
1444 @param success Whether the diagnosis interval should start
1445 with a successful or failed status task.
1446
1447 @return A list of two strings. The first is the timestamp for
1448 the beginning of the interval; the second is the
1449 timestamp for the end. If the host has never changed
1450 state, the list is empty.
1451
1452 """
1453 host = models.Host.smart_get(host_id)
J. Richard Barnette78f281a2015-06-29 13:24:51 -07001454 if not host.shard or utils.is_shard():
J. Richard Barnette8abbfd62015-06-23 12:46:54 -07001455 return status_history.get_diagnosis_interval(
1456 host_id, end_time, success)
1457 else:
1458 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1459 return shard_afe.get_host_diagnosis_interval(
1460 host_id, end_time, success)
1461
1462
showardc0ac3a72009-07-08 21:14:45 +00001463# support for host detail view
1464
MK Ryu0c1a37d2015-04-30 12:00:55 -07001465def get_host_queue_entries_and_special_tasks(host, query_start=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001466 query_limit=None, start_time=None,
1467 end_time=None):
showardc0ac3a72009-07-08 21:14:45 +00001468 """
1469 @returns an interleaved list of HostQueueEntries and SpecialTasks,
1470 in approximate run order. each dict contains keys for type, host,
1471 job, status, started_on, execution_path, and ID.
1472 """
1473 total_limit = None
1474 if query_limit is not None:
1475 total_limit = query_start + query_limit
MK Ryu0c1a37d2015-04-30 12:00:55 -07001476 filter_data_common = {'host': host,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001477 'query_limit': total_limit,
1478 'sort_by': ['-id']}
showardc0ac3a72009-07-08 21:14:45 +00001479
MK Ryu0c1a37d2015-04-30 12:00:55 -07001480 filter_data_special_tasks = rpc_utils.inject_times_to_filter(
1481 'time_started__gte', 'time_started__lte', start_time, end_time,
1482 **filter_data_common)
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001483
MK Ryu0c1a37d2015-04-30 12:00:55 -07001484 queue_entries = get_host_queue_entries(
1485 start_time, end_time, **filter_data_common)
1486 special_tasks = get_host_special_tasks(host, **filter_data_special_tasks)
showardc0ac3a72009-07-08 21:14:45 +00001487
1488 interleaved_entries = rpc_utils.interleave_entries(queue_entries,
1489 special_tasks)
1490 if query_start is not None:
1491 interleaved_entries = interleaved_entries[query_start:]
1492 if query_limit is not None:
1493 interleaved_entries = interleaved_entries[:query_limit]
MK Ryu0c1a37d2015-04-30 12:00:55 -07001494 return rpc_utils.prepare_host_queue_entries_and_special_tasks(
1495 interleaved_entries, queue_entries)
showardc0ac3a72009-07-08 21:14:45 +00001496
1497
MK Ryu0c1a37d2015-04-30 12:00:55 -07001498def get_num_host_queue_entries_and_special_tasks(host, start_time=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001499 end_time=None):
MK Ryu0c1a37d2015-04-30 12:00:55 -07001500 filter_data_common = {'host': host}
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001501
1502 filter_data_queue_entries, filter_data_special_tasks = (
1503 rpc_utils.inject_times_to_hqe_special_tasks_filters(
1504 filter_data_common, start_time, end_time))
1505
1506 return (models.HostQueueEntry.query_count(filter_data_queue_entries)
MK Ryu0c1a37d2015-04-30 12:00:55 -07001507 + get_host_num_special_tasks(**filter_data_special_tasks))
showardc0ac3a72009-07-08 21:14:45 +00001508
1509
showard29f7cd22009-04-29 21:16:24 +00001510# recurring run
1511
1512def get_recurring(**filter_data):
1513 return rpc_utils.prepare_rows_as_nested_dicts(
1514 models.RecurringRun.query_objects(filter_data),
1515 ('job', 'owner'))
1516
1517
1518def get_num_recurring(**filter_data):
1519 return models.RecurringRun.query_count(filter_data)
1520
1521
1522def delete_recurring_runs(**filter_data):
1523 to_delete = models.RecurringRun.query_objects(filter_data)
1524 to_delete.delete()
1525
1526
1527def create_recurring_run(job_id, start_date, loop_period, loop_count):
showard64a95952010-01-13 21:27:16 +00001528 owner = models.User.current_user().login
showard29f7cd22009-04-29 21:16:24 +00001529 job = models.Job.objects.get(id=job_id)
1530 return job.create_recurring_job(start_date=start_date,
1531 loop_period=loop_period,
1532 loop_count=loop_count,
1533 owner=owner)
1534
1535
mblighe8819cd2008-02-15 16:48:40 +00001536# other
1537
showarde0b63622008-08-04 20:58:47 +00001538def echo(data=""):
1539 """\
1540 Returns a passed in string. For doing a basic test to see if RPC calls
1541 can successfully be made.
1542 """
1543 return data
1544
1545
showardb7a52fd2009-04-27 20:10:56 +00001546def get_motd():
1547 """\
1548 Returns the message of the day as a string.
1549 """
1550 return rpc_utils.get_motd()
1551
1552
mblighe8819cd2008-02-15 16:48:40 +00001553def get_static_data():
jadmanski0afbb632008-06-06 21:10:57 +00001554 """\
1555 Returns a dictionary containing a bunch of data that shouldn't change
1556 often and is otherwise inaccessible. This includes:
showardc92da832009-04-07 18:14:34 +00001557
1558 priorities: List of job priority choices.
1559 default_priority: Default priority value for new jobs.
1560 users: Sorted list of all users.
Jiaxi Luo31874592014-06-11 10:36:35 -07001561 labels: Sorted list of labels not start with 'cros-version' and
1562 'fw-version'.
showardc92da832009-04-07 18:14:34 +00001563 atomic_groups: Sorted list of all atomic groups.
1564 tests: Sorted list of all tests.
1565 profilers: Sorted list of all profilers.
1566 current_user: Logged-in username.
1567 host_statuses: Sorted list of possible Host statuses.
1568 job_statuses: Sorted list of possible HostQueueEntry statuses.
Simran Basi7e605742013-11-12 13:43:36 -08001569 job_timeout_default: The default job timeout length in minutes.
showarda1e74b32009-05-12 17:32:04 +00001570 parse_failed_repair_default: Default value for the parse_failed_repair job
Jiaxi Luo31874592014-06-11 10:36:35 -07001571 option.
showardc92da832009-04-07 18:14:34 +00001572 reboot_before_options: A list of valid RebootBefore string enums.
1573 reboot_after_options: A list of valid RebootAfter string enums.
1574 motd: Server's message of the day.
1575 status_dictionary: A mapping from one word job status names to a more
1576 informative description.
jadmanski0afbb632008-06-06 21:10:57 +00001577 """
showard21baa452008-10-21 00:08:39 +00001578
1579 job_fields = models.Job.get_field_dict()
jamesren76fcf192010-04-21 20:39:50 +00001580 default_drone_set_name = models.DroneSet.default_drone_set_name()
1581 drone_sets = ([default_drone_set_name] +
1582 sorted(drone_set.name for drone_set in
1583 models.DroneSet.objects.exclude(
1584 name=default_drone_set_name)))
showard21baa452008-10-21 00:08:39 +00001585
jadmanski0afbb632008-06-06 21:10:57 +00001586 result = {}
Alex Miller7d658cf2013-09-04 16:00:35 -07001587 result['priorities'] = priorities.Priority.choices()
1588 default_priority = priorities.Priority.DEFAULT
1589 result['default_priority'] = 'Default'
1590 result['max_schedulable_priority'] = priorities.Priority.DEFAULT
jadmanski0afbb632008-06-06 21:10:57 +00001591 result['users'] = get_users(sort_by=['login'])
Jiaxi Luo31874592014-06-11 10:36:35 -07001592
1593 label_exclude_filters = [{'name__startswith': 'cros-version'},
Dan Shi65351d62015-08-03 12:03:23 -07001594 {'name__startswith': 'fw-version'},
1595 {'name__startswith': 'fwrw-version'},
1596 {'name__startswith': 'fwro-version'}]
Jiaxi Luo31874592014-06-11 10:36:35 -07001597 result['labels'] = get_labels(
1598 label_exclude_filters,
1599 sort_by=['-platform', 'name'])
1600
showardc92da832009-04-07 18:14:34 +00001601 result['atomic_groups'] = get_atomic_groups(sort_by=['name'])
jadmanski0afbb632008-06-06 21:10:57 +00001602 result['tests'] = get_tests(sort_by=['name'])
showard2b9a88b2008-06-13 20:55:03 +00001603 result['profilers'] = get_profilers(sort_by=['name'])
showard0fc38302008-10-23 00:44:07 +00001604 result['current_user'] = rpc_utils.prepare_for_serialization(
showard64a95952010-01-13 21:27:16 +00001605 models.User.current_user().get_object_dict())
showard2b9a88b2008-06-13 20:55:03 +00001606 result['host_statuses'] = sorted(models.Host.Status.names)
mbligh5a198b92008-12-11 19:33:29 +00001607 result['job_statuses'] = sorted(models.HostQueueEntry.Status.names)
Simran Basi7e605742013-11-12 13:43:36 -08001608 result['job_timeout_mins_default'] = models.Job.DEFAULT_TIMEOUT_MINS
Simran Basi34217022012-11-06 13:43:15 -08001609 result['job_max_runtime_mins_default'] = (
1610 models.Job.DEFAULT_MAX_RUNTIME_MINS)
showarda1e74b32009-05-12 17:32:04 +00001611 result['parse_failed_repair_default'] = bool(
1612 models.Job.DEFAULT_PARSE_FAILED_REPAIR)
jamesrendd855242010-03-02 22:23:44 +00001613 result['reboot_before_options'] = model_attributes.RebootBefore.names
1614 result['reboot_after_options'] = model_attributes.RebootAfter.names
showard8fbae652009-01-20 23:23:10 +00001615 result['motd'] = rpc_utils.get_motd()
jamesren76fcf192010-04-21 20:39:50 +00001616 result['drone_sets_enabled'] = models.DroneSet.drone_sets_enabled()
1617 result['drone_sets'] = drone_sets
jamesren4a41e012010-07-16 22:33:48 +00001618 result['parameterized_jobs'] = models.Job.parameterized_jobs_enabled()
showard8ac29b42008-07-17 17:01:55 +00001619
showardd3dc1992009-04-22 21:01:40 +00001620 result['status_dictionary'] = {"Aborted": "Aborted",
showard8ac29b42008-07-17 17:01:55 +00001621 "Verifying": "Verifying Host",
Alex Millerdfff2fd2013-05-28 13:05:06 -07001622 "Provisioning": "Provisioning Host",
showard8ac29b42008-07-17 17:01:55 +00001623 "Pending": "Waiting on other hosts",
1624 "Running": "Running autoserv",
1625 "Completed": "Autoserv completed",
1626 "Failed": "Failed to complete",
showardd823b362008-07-24 16:35:46 +00001627 "Queued": "Queued",
showard5deb6772008-11-04 21:54:33 +00001628 "Starting": "Next in host's queue",
1629 "Stopped": "Other host(s) failed verify",
showardd3dc1992009-04-22 21:01:40 +00001630 "Parsing": "Awaiting parse of final results",
showard29f7cd22009-04-29 21:16:24 +00001631 "Gathering": "Gathering log files",
showard8cc058f2009-09-08 16:26:33 +00001632 "Template": "Template job for recurring run",
mbligh4608b002010-01-05 18:22:35 +00001633 "Waiting": "Waiting for scheduler action",
Dan Shi07e09af2013-04-12 09:31:29 -07001634 "Archiving": "Archiving results",
1635 "Resetting": "Resetting hosts"}
Jiaxi Luo421608e2014-07-07 14:38:00 -07001636
1637 result['wmatrix_url'] = rpc_utils.get_wmatrix_url()
Simran Basi71206ef2014-08-13 13:51:18 -07001638 result['is_moblab'] = bool(utils.is_moblab())
Jiaxi Luo421608e2014-07-07 14:38:00 -07001639
jadmanski0afbb632008-06-06 21:10:57 +00001640 return result
showard29f7cd22009-04-29 21:16:24 +00001641
1642
1643def get_server_time():
1644 return datetime.datetime.now().strftime("%Y-%m-%d %H:%M")