blob: afe8a82a7c0c471fe94eb93503d86617f4fd4572 [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
Simran Basi6157e8e2015-12-07 18:22:34 -080040from autotest_lib.client.common_lib.cros import dev_server
Gabe Black1e1c41b2015-02-04 23:55:15 -080041from autotest_lib.client.common_lib.cros.graphite import autotest_stats
showard6d7b2ff2009-06-10 00:16:47 +000042from autotest_lib.frontend.afe import control_file, rpc_utils
J. Richard Barnetteb5164d62015-04-13 12:59:31 -070043from autotest_lib.frontend.afe import models, model_logic, model_attributes
Simran Basib6ec8ae2014-04-23 12:05:08 -070044from autotest_lib.frontend.afe import site_rpc_interface
Moises Osorio2dc7a102014-12-02 18:24:02 -080045from autotest_lib.frontend.tko import models as tko_models
Jiaxi Luoaac54572014-06-04 13:57:02 -070046from autotest_lib.frontend.tko import rpc_interface as tko_rpc_interface
J. Richard Barnetteb5164d62015-04-13 12:59:31 -070047from autotest_lib.server import frontend
Simran Basi71206ef2014-08-13 13:51:18 -070048from autotest_lib.server import utils
Dan Shid215dbe2015-06-18 16:14:59 -070049from autotest_lib.server.cros import provision
Jiaxi Luo90190c92014-06-18 12:35:57 -070050from autotest_lib.server.cros.dynamic_suite import tools
J. Richard Barnette39255fa2015-04-14 17:23:41 -070051from autotest_lib.site_utils import status_history
mblighe8819cd2008-02-15 16:48:40 +000052
Moises Osorio2dc7a102014-12-02 18:24:02 -080053
Gabe Black1e1c41b2015-02-04 23:55:15 -080054_timer = autotest_stats.Timer('rpc_interface')
Moises Osorio2dc7a102014-12-02 18:24:02 -080055
Eric Lid23bc192011-02-09 14:38:57 -080056def get_parameterized_autoupdate_image_url(job):
57 """Get the parameterized autoupdate image url from a parameterized job."""
58 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
59 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
beeps8bb1f7d2013-08-05 01:30:09 -070060 name='image')
Eric Lid23bc192011-02-09 14:38:57 -080061 para_set = job.parameterized_job.parameterizedjobparameter_set
62 job_test_para = para_set.get(test_parameter=image_parameter)
63 return job_test_para.parameter_value
64
65
mblighe8819cd2008-02-15 16:48:40 +000066# labels
67
mblighe8819cd2008-02-15 16:48:40 +000068def modify_label(id, **data):
MK Ryu8c554cf2015-06-12 11:45:50 -070069 """Modify a label.
70
71 @param id: id or name of a label. More often a label name.
72 @param data: New data for a label.
73 """
74 label_model = models.Label.smart_get(id)
MK Ryu8e2c2d02016-01-06 15:24:38 -080075 label_model.update_object(data)
MK Ryu8c554cf2015-06-12 11:45:50 -070076
77 # Master forwards the RPC to shards
78 if not utils.is_shard():
79 rpc_utils.fanout_rpc(label_model.host_set.all(), 'modify_label', False,
80 id=id, **data)
81
mblighe8819cd2008-02-15 16:48:40 +000082
83def delete_label(id):
MK Ryu8c554cf2015-06-12 11:45:50 -070084 """Delete a label.
85
86 @param id: id or name of a label. More often a label name.
87 """
88 label_model = models.Label.smart_get(id)
MK Ryu8e2c2d02016-01-06 15:24:38 -080089 # Hosts that have the label to be deleted. Save this info before
90 # the label is deleted to use it later.
91 hosts = []
92 for h in label_model.host_set.all():
93 hosts.append(models.Host.smart_get(h.id))
94 label_model.delete()
MK Ryu8c554cf2015-06-12 11:45:50 -070095
96 # Master forwards the RPC to shards
97 if not utils.is_shard():
MK Ryu8e2c2d02016-01-06 15:24:38 -080098 rpc_utils.fanout_rpc(hosts, 'delete_label', False, id=id)
mblighe8819cd2008-02-15 16:48:40 +000099
Prashanth Balasubramanian744898f2015-01-13 05:04:16 -0800100
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800101def add_label(name, ignore_exception_if_exists=False, **kwargs):
MK Ryucf027c62015-03-04 12:00:50 -0800102 """Adds a new label of a given name.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800103
104 @param name: label name.
105 @param ignore_exception_if_exists: If True and the exception was
106 thrown due to the duplicated label name when adding a label,
107 then suppress the exception. Default is False.
108 @param kwargs: keyword args that store more info about a label
109 other than the name.
110 @return: int/long id of a new label.
111 """
112 # models.Label.add_object() throws model_logic.ValidationError
113 # when it is given a label name that already exists.
114 # However, ValidationError can be thrown with different errors,
115 # and those errors should be thrown up to the call chain.
116 try:
117 label = models.Label.add_object(name=name, **kwargs)
118 except:
119 exc_info = sys.exc_info()
120 if ignore_exception_if_exists:
121 label = rpc_utils.get_label(name)
122 # If the exception is raised not because of duplicated
123 # "name", then raise the original exception.
124 if label is None:
125 raise exc_info[0], exc_info[1], exc_info[2]
126 else:
127 raise exc_info[0], exc_info[1], exc_info[2]
128 return label.id
129
130
131def add_label_to_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800132 """Adds a label of the given id to the given hosts only in local DB.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800133
134 @param id: id or name of a label. More often a label name.
135 @param hosts: The hostnames of hosts that need the label.
136
137 @raises models.Label.DoesNotExist: If the label with id doesn't exist.
138 """
139 label = models.Label.smart_get(id)
140 host_objs = models.Host.smart_get_bulk(hosts)
141 if label.platform:
142 models.Host.check_no_platform(host_objs)
143 label.host_set.add(*host_objs)
144
145
MK Ryufbb002c2015-06-08 14:13:16 -0700146@rpc_utils.route_rpc_to_master
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800147def label_add_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800148 """Adds a label with the given id to the given hosts.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800149
150 This method should be run only on master not shards.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800151 The given label will be created if it doesn't exist, provided the `id`
152 supplied is a label name not an int/long id.
153
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800154 @param id: id or name of a label. More often a label name.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800155 @param hosts: A list of hostnames or ids. More often hostnames.
156
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800157 @raises ValueError: If the id specified is an int/long (label id)
158 while the label does not exist.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800159 """
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800160 try:
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800161 label = models.Label.smart_get(id)
162 except models.Label.DoesNotExist:
163 # This matches the type checks in smart_get, which is a hack
164 # in and off itself. The aim here is to create any non-existent
165 # label, which we cannot do if the 'id' specified isn't a label name.
166 if isinstance(id, basestring):
167 label = models.Label.smart_get(add_label(id))
168 else:
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800169 raise ValueError('Label id (%s) does not exist. Please specify '
170 'the argument, id, as a string (label name).'
171 % id)
MK Ryu8e2c2d02016-01-06 15:24:38 -0800172 add_label_to_hosts(id, hosts)
MK Ryucf027c62015-03-04 12:00:50 -0800173
174 host_objs = models.Host.smart_get_bulk(hosts)
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800175 # Make sure the label exists on the shard with the same id
176 # as it is on the master.
MK Ryucf027c62015-03-04 12:00:50 -0800177 # It is possible that the label is already in a shard because
178 # we are adding a new label only to shards of hosts that the label
179 # is going to be attached.
180 # For example, we add a label L1 to a host in shard S1.
181 # Master and S1 will have L1 but other shards won't.
182 # Later, when we add the same label L1 to hosts in shards S1 and S2,
183 # S1 already has the label but S2 doesn't.
184 # S2 should have the new label without any problem.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800185 # We ignore exception in such a case.
186 rpc_utils.fanout_rpc(
MK Ryue019aae2015-07-07 12:46:07 -0700187 host_objs, 'add_label', include_hostnames=False,
188 name=label.name, ignore_exception_if_exists=True,
189 id=label.id, platform=label.platform)
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800190 rpc_utils.fanout_rpc(host_objs, 'add_label_to_hosts', id=id)
showardbbabf502008-06-06 00:02:02 +0000191
192
MK Ryucf027c62015-03-04 12:00:50 -0800193def remove_label_from_hosts(id, hosts):
194 """Removes a label of the given id from the given hosts only in local DB.
195
196 @param id: id or name of a label.
197 @param hosts: The hostnames of hosts that need to remove the label from.
198 """
showardbe3ec042008-11-12 18:16:07 +0000199 host_objs = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000200 models.Label.smart_get(id).host_set.remove(*host_objs)
showardbbabf502008-06-06 00:02:02 +0000201
202
MK Ryufbb002c2015-06-08 14:13:16 -0700203@rpc_utils.route_rpc_to_master
MK Ryucf027c62015-03-04 12:00:50 -0800204def label_remove_hosts(id, hosts):
205 """Removes a label of the given id from the given hosts.
206
207 This method should be run only on master not shards.
208
209 @param id: id or name of a label.
210 @param hosts: A list of hostnames or ids. More often hostnames.
211 """
MK Ryucf027c62015-03-04 12:00:50 -0800212 host_objs = models.Host.smart_get_bulk(hosts)
MK Ryu26f0c932015-05-28 18:14:33 -0700213 remove_label_from_hosts(id, hosts)
214
MK Ryu8e2c2d02016-01-06 15:24:38 -0800215 rpc_utils.fanout_rpc(host_objs, 'remove_label_from_hosts', id=id)
216
MK Ryucf027c62015-03-04 12:00:50 -0800217
Jiaxi Luo31874592014-06-11 10:36:35 -0700218def get_labels(exclude_filters=(), **filter_data):
showardc92da832009-04-07 18:14:34 +0000219 """\
Jiaxi Luo31874592014-06-11 10:36:35 -0700220 @param exclude_filters: A sequence of dictionaries of filters.
221
showardc92da832009-04-07 18:14:34 +0000222 @returns A sequence of nested dictionaries of label information.
223 """
Jiaxi Luo31874592014-06-11 10:36:35 -0700224 labels = models.Label.query_objects(filter_data)
225 for exclude_filter in exclude_filters:
226 labels = labels.exclude(**exclude_filter)
227 return rpc_utils.prepare_rows_as_nested_dicts(labels, ('atomic_group',))
showardc92da832009-04-07 18:14:34 +0000228
229
230# atomic groups
231
showarde9450c92009-06-30 01:58:52 +0000232def add_atomic_group(name, max_number_of_machines=None, description=None):
showardc92da832009-04-07 18:14:34 +0000233 return models.AtomicGroup.add_object(
234 name=name, max_number_of_machines=max_number_of_machines,
235 description=description).id
236
237
238def modify_atomic_group(id, **data):
239 models.AtomicGroup.smart_get(id).update_object(data)
240
241
242def delete_atomic_group(id):
243 models.AtomicGroup.smart_get(id).delete()
244
245
246def atomic_group_add_labels(id, labels):
247 label_objs = models.Label.smart_get_bulk(labels)
248 models.AtomicGroup.smart_get(id).label_set.add(*label_objs)
249
250
251def atomic_group_remove_labels(id, labels):
252 label_objs = models.Label.smart_get_bulk(labels)
253 models.AtomicGroup.smart_get(id).label_set.remove(*label_objs)
254
255
256def get_atomic_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000257 return rpc_utils.prepare_for_serialization(
showardc92da832009-04-07 18:14:34 +0000258 models.AtomicGroup.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000259
260
261# hosts
262
Matthew Sartori68186332015-04-27 17:19:53 -0700263def add_host(hostname, status=None, locked=None, lock_reason='', protection=None):
264 if locked and not lock_reason:
265 raise model_logic.ValidationError(
266 {'locked': 'Please provide a reason for locking when adding host.'})
267
jadmanski0afbb632008-06-06 21:10:57 +0000268 return models.Host.add_object(hostname=hostname, status=status,
Matthew Sartori68186332015-04-27 17:19:53 -0700269 locked=locked, lock_reason=lock_reason,
270 protection=protection).id
mblighe8819cd2008-02-15 16:48:40 +0000271
272
MK Ryu33889612015-09-04 14:32:35 -0700273@rpc_utils.route_rpc_to_master
274def modify_host(id, **kwargs):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700275 """Modify local attributes of a host.
276
277 If this is called on the master, but the host is assigned to a shard, this
MK Ryu33889612015-09-04 14:32:35 -0700278 will call `modify_host_local` RPC to the responsible shard. This means if
279 a host is being locked using this function, this change will also propagate
280 to shards.
281 When this is called on a shard, the shard just routes the RPC to the master
282 and does nothing.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700283
284 @param id: id of the host to modify.
MK Ryu33889612015-09-04 14:32:35 -0700285 @param kwargs: key=value pairs of values to set on the host.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700286 """
MK Ryu33889612015-09-04 14:32:35 -0700287 rpc_utils.check_modify_host(kwargs)
showardce7c0922009-09-11 18:39:24 +0000288 host = models.Host.smart_get(id)
MK Ryu33889612015-09-04 14:32:35 -0700289 rpc_utils.check_modify_host_locking(host, kwargs)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700290
MK Ryud53e1492015-12-15 12:09:03 -0800291 # This is required to make `lock_time` for a host be exactly same
292 # between the master and a shard.
293 if kwargs.get('locked', None) and 'lock_time' not in kwargs:
294 kwargs['lock_time'] = datetime.datetime.now()
MK Ryu8e2c2d02016-01-06 15:24:38 -0800295 host.update_object(kwargs)
MK Ryud53e1492015-12-15 12:09:03 -0800296
MK Ryu33889612015-09-04 14:32:35 -0700297 rpc_utils.fanout_rpc([host], 'modify_host_local',
298 include_hostnames=False, id=id, **kwargs)
mblighe8819cd2008-02-15 16:48:40 +0000299
300
MK Ryu33889612015-09-04 14:32:35 -0700301def modify_host_local(id, **kwargs):
302 """Modify host attributes in local DB.
303
304 @param id: Host id.
305 @param kwargs: key=value pairs of values to set on the host.
306 """
307 models.Host.smart_get(id).update_object(kwargs)
308
309
310@rpc_utils.route_rpc_to_master
showard276f9442009-05-20 00:33:16 +0000311def modify_hosts(host_filter_data, update_data):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700312 """Modify local attributes of multiple hosts.
313
314 If this is called on the master, but one of the hosts in that match the
MK Ryu33889612015-09-04 14:32:35 -0700315 filters is assigned to a shard, this will call `modify_hosts_local` RPC
316 to the responsible shard.
317 When this is called on a shard, the shard just routes the RPC to the master
318 and does nothing.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700319
320 The filters are always applied on the master, not on the shards. This means
321 if the states of a host differ on the master and a shard, the state on the
322 master will be used. I.e. this means:
323 A host was synced to Shard 1. On Shard 1 the status of the host was set to
324 'Repair Failed'.
325 - A call to modify_hosts with host_filter_data={'status': 'Ready'} will
326 update the host (both on the shard and on the master), because the state
327 of the host as the master knows it is still 'Ready'.
328 - A call to modify_hosts with host_filter_data={'status': 'Repair failed'
329 will not update the host, because the filter doesn't apply on the master.
330
showardbe0d8692009-08-20 23:42:44 +0000331 @param host_filter_data: Filters out which hosts to modify.
332 @param update_data: A dictionary with the changes to make to the hosts.
showard276f9442009-05-20 00:33:16 +0000333 """
MK Ryu93161712015-12-21 10:41:32 -0800334 update_data = update_data.copy()
showardbe0d8692009-08-20 23:42:44 +0000335 rpc_utils.check_modify_host(update_data)
showard276f9442009-05-20 00:33:16 +0000336 hosts = models.Host.query_objects(host_filter_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700337
338 affected_shard_hostnames = set()
339 affected_host_ids = []
340
Alex Miller9658a952013-05-14 16:40:02 -0700341 # Check all hosts before changing data for exception safety.
342 for host in hosts:
343 rpc_utils.check_modify_host_locking(host, update_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700344 if host.shard:
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800345 affected_shard_hostnames.add(host.shard.rpc_hostname())
Jakob Juelich50e91f72014-10-01 12:43:23 -0700346 affected_host_ids.append(host.id)
347
MK Ryud53e1492015-12-15 12:09:03 -0800348 # This is required to make `lock_time` for a host be exactly same
349 # between the master and a shard.
350 if update_data.get('locked', None) and 'lock_time' not in update_data:
351 update_data['lock_time'] = datetime.datetime.now()
MK Ryu8e2c2d02016-01-06 15:24:38 -0800352 for host in hosts:
353 host.update_object(update_data)
MK Ryud53e1492015-12-15 12:09:03 -0800354
MK Ryu33889612015-09-04 14:32:35 -0700355 # Caution: Changing the filter from the original here. See docstring.
356 rpc_utils.run_rpc_on_multiple_hostnames(
357 'modify_hosts_local', affected_shard_hostnames,
Jakob Juelich50e91f72014-10-01 12:43:23 -0700358 host_filter_data={'id__in': affected_host_ids},
359 update_data=update_data)
360
showard276f9442009-05-20 00:33:16 +0000361
MK Ryu33889612015-09-04 14:32:35 -0700362def modify_hosts_local(host_filter_data, update_data):
363 """Modify attributes of hosts in local DB.
364
365 @param host_filter_data: Filters out which hosts to modify.
366 @param update_data: A dictionary with the changes to make to the hosts.
367 """
368 for host in models.Host.query_objects(host_filter_data):
369 host.update_object(update_data)
370
371
MK Ryufbb002c2015-06-08 14:13:16 -0700372def add_labels_to_host(id, labels):
373 """Adds labels to a given host only in local DB.
showardcafd16e2009-05-29 18:37:49 +0000374
MK Ryufbb002c2015-06-08 14:13:16 -0700375 @param id: id or hostname for a host.
376 @param labels: ids or names for labels.
377 """
378 label_objs = models.Label.smart_get_bulk(labels)
379 models.Host.smart_get(id).labels.add(*label_objs)
380
381
382@rpc_utils.route_rpc_to_master
383def host_add_labels(id, labels):
384 """Adds labels to a given host.
385
386 @param id: id or hostname for a host.
387 @param labels: ids or names for labels.
388
389 @raises ValidationError: If adding more than one platform label.
390 """
391 label_objs = models.Label.smart_get_bulk(labels)
392 platforms = [label.name for label in label_objs if label.platform]
showardcafd16e2009-05-29 18:37:49 +0000393 if len(platforms) > 1:
394 raise model_logic.ValidationError(
395 {'labels': 'Adding more than one platform label: %s' %
396 ', '.join(platforms)})
MK Ryufbb002c2015-06-08 14:13:16 -0700397
398 host_obj = models.Host.smart_get(id)
showardcafd16e2009-05-29 18:37:49 +0000399 if len(platforms) == 1:
MK Ryufbb002c2015-06-08 14:13:16 -0700400 models.Host.check_no_platform([host_obj])
MK Ryu8e2c2d02016-01-06 15:24:38 -0800401 add_labels_to_host(id, labels)
MK Ryufbb002c2015-06-08 14:13:16 -0700402
403 rpc_utils.fanout_rpc([host_obj], 'add_labels_to_host', False,
404 id=id, labels=labels)
mblighe8819cd2008-02-15 16:48:40 +0000405
406
MK Ryufbb002c2015-06-08 14:13:16 -0700407def remove_labels_from_host(id, labels):
408 """Removes labels from a given host only in local DB.
409
410 @param id: id or hostname for a host.
411 @param labels: ids or names for labels.
412 """
413 label_objs = models.Label.smart_get_bulk(labels)
414 models.Host.smart_get(id).labels.remove(*label_objs)
415
416
417@rpc_utils.route_rpc_to_master
mblighe8819cd2008-02-15 16:48:40 +0000418def host_remove_labels(id, labels):
MK Ryufbb002c2015-06-08 14:13:16 -0700419 """Removes labels from a given host.
420
421 @param id: id or hostname for a host.
422 @param labels: ids or names for labels.
423 """
MK Ryu8e2c2d02016-01-06 15:24:38 -0800424 remove_labels_from_host(id, labels)
425
MK Ryufbb002c2015-06-08 14:13:16 -0700426 host_obj = models.Host.smart_get(id)
427 rpc_utils.fanout_rpc([host_obj], 'remove_labels_from_host', False,
428 id=id, labels=labels)
mblighe8819cd2008-02-15 16:48:40 +0000429
430
MK Ryuacf35922014-10-03 14:56:49 -0700431def get_host_attribute(attribute, **host_filter_data):
432 """
433 @param attribute: string name of attribute
434 @param host_filter_data: filter data to apply to Hosts to choose hosts to
435 act upon
436 """
437 hosts = rpc_utils.get_host_query((), False, False, True, host_filter_data)
438 hosts = list(hosts)
439 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
440 'attribute_list')
441 host_attr_dicts = []
442 for host_obj in hosts:
443 for attr_obj in host_obj.attribute_list:
444 if attr_obj.attribute == attribute:
445 host_attr_dicts.append(attr_obj.get_object_dict())
446 return rpc_utils.prepare_for_serialization(host_attr_dicts)
447
448
showard0957a842009-05-11 19:25:08 +0000449def set_host_attribute(attribute, value, **host_filter_data):
450 """
MK Ryu26f0c932015-05-28 18:14:33 -0700451 @param attribute: string name of attribute
452 @param value: string, or None to delete an attribute
453 @param host_filter_data: filter data to apply to Hosts to choose hosts to
454 act upon
showard0957a842009-05-11 19:25:08 +0000455 """
456 assert host_filter_data # disallow accidental actions on all hosts
457 hosts = models.Host.query_objects(host_filter_data)
458 models.AclGroup.check_for_acl_violation_hosts(hosts)
MK Ryu8e2c2d02016-01-06 15:24:38 -0800459 for host in hosts:
460 host.set_or_delete_attribute(attribute, value)
showard0957a842009-05-11 19:25:08 +0000461
MK Ryu26f0c932015-05-28 18:14:33 -0700462 # Master forwards this RPC to shards.
463 if not utils.is_shard():
464 rpc_utils.fanout_rpc(hosts, 'set_host_attribute', False,
465 attribute=attribute, value=value, **host_filter_data)
466
showard0957a842009-05-11 19:25:08 +0000467
Jakob Juelich50e91f72014-10-01 12:43:23 -0700468@rpc_utils.forward_single_host_rpc_to_shard
mblighe8819cd2008-02-15 16:48:40 +0000469def delete_host(id):
jadmanski0afbb632008-06-06 21:10:57 +0000470 models.Host.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000471
472
showard87cc38f2009-08-20 23:37:04 +0000473def get_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
Dan Shi37df54d2015-12-14 11:16:28 -0800474 exclude_atomic_group_hosts=False, valid_only=True,
475 include_current_job=False, **filter_data):
476 """Get a list of dictionaries which contains the information of hosts.
477
showard87cc38f2009-08-20 23:37:04 +0000478 @param multiple_labels: match hosts in all of the labels given. Should
479 be a list of label names.
480 @param exclude_only_if_needed_labels: Exclude hosts with at least one
481 "only_if_needed" label applied.
482 @param exclude_atomic_group_hosts: Exclude hosts that have one or more
483 atomic group labels associated with them.
Dan Shi37df54d2015-12-14 11:16:28 -0800484 @param include_current_job: Set to True to include ids of currently running
485 job and special task.
jadmanski0afbb632008-06-06 21:10:57 +0000486 """
showard43a3d262008-11-12 18:17:05 +0000487 hosts = rpc_utils.get_host_query(multiple_labels,
488 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000489 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000490 valid_only, filter_data)
showard0957a842009-05-11 19:25:08 +0000491 hosts = list(hosts)
492 models.Host.objects.populate_relationships(hosts, models.Label,
493 'label_list')
494 models.Host.objects.populate_relationships(hosts, models.AclGroup,
495 'acl_list')
496 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
497 'attribute_list')
showard43a3d262008-11-12 18:17:05 +0000498 host_dicts = []
499 for host_obj in hosts:
500 host_dict = host_obj.get_object_dict()
showard0957a842009-05-11 19:25:08 +0000501 host_dict['labels'] = [label.name for label in host_obj.label_list]
showard909c9142009-07-07 20:54:42 +0000502 host_dict['platform'], host_dict['atomic_group'] = (rpc_utils.
503 find_platform_and_atomic_group(host_obj))
showard0957a842009-05-11 19:25:08 +0000504 host_dict['acls'] = [acl.name for acl in host_obj.acl_list]
505 host_dict['attributes'] = dict((attribute.attribute, attribute.value)
506 for attribute in host_obj.attribute_list)
Dan Shi37df54d2015-12-14 11:16:28 -0800507 if include_current_job:
508 host_dict['current_job'] = None
509 host_dict['current_special_task'] = None
510 entries = models.HostQueueEntry.objects.filter(
511 host_id=host_dict['id'], active=True, complete=False)
512 if entries:
513 host_dict['current_job'] = (
514 entries[0].get_object_dict()['job'])
515 tasks = models.SpecialTask.objects.filter(
516 host_id=host_dict['id'], is_active=True, is_complete=False)
517 if tasks:
518 host_dict['current_special_task'] = (
519 '%d-%s' % (tasks[0].get_object_dict()['id'],
520 tasks[0].get_object_dict()['task'].lower()))
showard43a3d262008-11-12 18:17:05 +0000521 host_dicts.append(host_dict)
522 return rpc_utils.prepare_for_serialization(host_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000523
524
showard87cc38f2009-08-20 23:37:04 +0000525def get_num_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000526 exclude_atomic_group_hosts=False, valid_only=True,
527 **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000528 """
529 Same parameters as get_hosts().
530
531 @returns The number of matching hosts.
532 """
showard43a3d262008-11-12 18:17:05 +0000533 hosts = rpc_utils.get_host_query(multiple_labels,
534 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000535 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000536 valid_only, filter_data)
showard43a3d262008-11-12 18:17:05 +0000537 return hosts.count()
showard1385b162008-03-13 15:59:40 +0000538
mblighe8819cd2008-02-15 16:48:40 +0000539
540# tests
541
showard909c7a62008-07-15 21:52:38 +0000542def add_test(name, test_type, path, author=None, dependencies=None,
showard3d9899a2008-07-31 02:11:58 +0000543 experimental=True, run_verify=None, test_class=None,
showard909c7a62008-07-15 21:52:38 +0000544 test_time=None, test_category=None, description=None,
545 sync_count=1):
jadmanski0afbb632008-06-06 21:10:57 +0000546 return models.Test.add_object(name=name, test_type=test_type, path=path,
showard909c7a62008-07-15 21:52:38 +0000547 author=author, dependencies=dependencies,
548 experimental=experimental,
549 run_verify=run_verify, test_time=test_time,
550 test_category=test_category,
551 sync_count=sync_count,
jadmanski0afbb632008-06-06 21:10:57 +0000552 test_class=test_class,
553 description=description).id
mblighe8819cd2008-02-15 16:48:40 +0000554
555
556def modify_test(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000557 models.Test.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000558
559
560def delete_test(id):
jadmanski0afbb632008-06-06 21:10:57 +0000561 models.Test.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000562
563
564def get_tests(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000565 return rpc_utils.prepare_for_serialization(
566 models.Test.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000567
568
Moises Osorio2dc7a102014-12-02 18:24:02 -0800569@_timer.decorate
570def get_tests_status_counts_by_job_name_label(job_name_prefix, label_name):
571 """Gets the counts of all passed and failed tests from the matching jobs.
572
573 @param job_name_prefix: Name prefix of the jobs to get the summary from, e.g.,
574 'butterfly-release/R40-6457.21.0/bvt-cq/'.
575 @param label_name: Label that must be set in the jobs, e.g.,
576 'cros-version:butterfly-release/R40-6457.21.0'.
577
578 @returns A summary of the counts of all the passed and failed tests.
579 """
580 job_ids = list(models.Job.objects.filter(
581 name__startswith=job_name_prefix,
582 dependency_labels__name=label_name).values_list(
583 'pk', flat=True))
584 summary = {'passed': 0, 'failed': 0}
585 if not job_ids:
586 return summary
587
588 counts = (tko_models.TestView.objects.filter(
589 afe_job_id__in=job_ids).exclude(
590 test_name='SERVER_JOB').exclude(
591 test_name__startswith='CLIENT_JOB').values(
592 'status').annotate(
593 count=Count('status')))
594 for status in counts:
595 if status['status'] == 'GOOD':
596 summary['passed'] += status['count']
597 else:
598 summary['failed'] += status['count']
599 return summary
600
601
showard2b9a88b2008-06-13 20:55:03 +0000602# profilers
603
604def add_profiler(name, description=None):
605 return models.Profiler.add_object(name=name, description=description).id
606
607
608def modify_profiler(id, **data):
609 models.Profiler.smart_get(id).update_object(data)
610
611
612def delete_profiler(id):
613 models.Profiler.smart_get(id).delete()
614
615
616def get_profilers(**filter_data):
617 return rpc_utils.prepare_for_serialization(
618 models.Profiler.list_objects(filter_data))
619
620
mblighe8819cd2008-02-15 16:48:40 +0000621# users
622
623def add_user(login, access_level=None):
jadmanski0afbb632008-06-06 21:10:57 +0000624 return models.User.add_object(login=login, access_level=access_level).id
mblighe8819cd2008-02-15 16:48:40 +0000625
626
627def modify_user(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000628 models.User.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000629
630
631def delete_user(id):
jadmanski0afbb632008-06-06 21:10:57 +0000632 models.User.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000633
634
635def get_users(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000636 return rpc_utils.prepare_for_serialization(
637 models.User.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000638
639
640# acl groups
641
642def add_acl_group(name, description=None):
showard04f2cd82008-07-25 20:53:31 +0000643 group = models.AclGroup.add_object(name=name, description=description)
showard64a95952010-01-13 21:27:16 +0000644 group.users.add(models.User.current_user())
showard04f2cd82008-07-25 20:53:31 +0000645 return group.id
mblighe8819cd2008-02-15 16:48:40 +0000646
647
648def modify_acl_group(id, **data):
showard04f2cd82008-07-25 20:53:31 +0000649 group = models.AclGroup.smart_get(id)
650 group.check_for_acl_violation_acl_group()
651 group.update_object(data)
652 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000653
654
655def acl_group_add_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000656 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000657 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000658 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000659 group.users.add(*users)
mblighe8819cd2008-02-15 16:48:40 +0000660
661
662def acl_group_remove_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000663 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000664 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000665 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000666 group.users.remove(*users)
showard04f2cd82008-07-25 20:53:31 +0000667 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000668
669
670def acl_group_add_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000671 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000672 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000673 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000674 group.hosts.add(*hosts)
showard08f981b2008-06-24 21:59:03 +0000675 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000676
677
678def acl_group_remove_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000679 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000680 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000681 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000682 group.hosts.remove(*hosts)
showard08f981b2008-06-24 21:59:03 +0000683 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000684
685
686def delete_acl_group(id):
jadmanski0afbb632008-06-06 21:10:57 +0000687 models.AclGroup.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000688
689
690def get_acl_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000691 acl_groups = models.AclGroup.list_objects(filter_data)
692 for acl_group in acl_groups:
693 acl_group_obj = models.AclGroup.objects.get(id=acl_group['id'])
694 acl_group['users'] = [user.login
695 for user in acl_group_obj.users.all()]
696 acl_group['hosts'] = [host.hostname
697 for host in acl_group_obj.hosts.all()]
698 return rpc_utils.prepare_for_serialization(acl_groups)
mblighe8819cd2008-02-15 16:48:40 +0000699
700
701# jobs
702
mbligh120351e2009-01-24 01:40:45 +0000703def generate_control_file(tests=(), kernel=None, label=None, profilers=(),
showard91f85102009-10-12 20:34:52 +0000704 client_control_file='', use_container=False,
Matthew Sartori10438092015-06-24 14:30:18 -0700705 profile_only=None, upload_kernel_config=False,
706 db_tests=True):
jadmanski0afbb632008-06-06 21:10:57 +0000707 """
mbligh120351e2009-01-24 01:40:45 +0000708 Generates a client-side control file to load a kernel and run tests.
709
Matthew Sartori10438092015-06-24 14:30:18 -0700710 @param tests List of tests to run. See db_tests for more information.
mbligha3c58d22009-08-24 22:01:51 +0000711 @param kernel A list of kernel info dictionaries configuring which kernels
712 to boot for this job and other options for them
mbligh120351e2009-01-24 01:40:45 +0000713 @param label Name of label to grab kernel config from.
714 @param profilers List of profilers to activate during the job.
715 @param client_control_file The contents of a client-side control file to
716 run at the end of all tests. If this is supplied, all tests must be
717 client side.
718 TODO: in the future we should support server control files directly
719 to wrap with a kernel. That'll require changing the parameter
720 name and adding a boolean to indicate if it is a client or server
721 control file.
722 @param use_container unused argument today. TODO: Enable containers
723 on the host during a client side test.
showard91f85102009-10-12 20:34:52 +0000724 @param profile_only A boolean that indicates what default profile_only
725 mode to use in the control file. Passing None will generate a
726 control file that does not explcitly set the default mode at all.
showard232b7ae2009-11-10 00:46:48 +0000727 @param upload_kernel_config: if enabled it will generate server control
728 file code that uploads the kernel config file to the client and
729 tells the client of the new (local) path when compiling the kernel;
730 the tests must be server side tests
Matthew Sartori10438092015-06-24 14:30:18 -0700731 @param db_tests: if True, the test object can be found in the database
732 backing the test model. In this case, tests is a tuple
733 of test IDs which are used to retrieve the test objects
734 from the database. If False, tests is a tuple of test
735 dictionaries stored client-side in the AFE.
mbligh120351e2009-01-24 01:40:45 +0000736
737 @returns a dict with the following keys:
738 control_file: str, The control file text.
739 is_server: bool, is the control file a server-side control file?
740 synch_count: How many machines the job uses per autoserv execution.
741 synch_count == 1 means the job is asynchronous.
742 dependencies: A list of the names of labels on which the job depends.
743 """
showardd86debe2009-06-10 17:37:56 +0000744 if not tests and not client_control_file:
showard2bab8f42008-11-12 18:15:22 +0000745 return dict(control_file='', is_server=False, synch_count=1,
showard989f25d2008-10-01 11:38:11 +0000746 dependencies=[])
mblighe8819cd2008-02-15 16:48:40 +0000747
showard989f25d2008-10-01 11:38:11 +0000748 cf_info, test_objects, profiler_objects, label = (
showard2b9a88b2008-06-13 20:55:03 +0000749 rpc_utils.prepare_generate_control_file(tests, kernel, label,
Matthew Sartori10438092015-06-24 14:30:18 -0700750 profilers, db_tests))
showard989f25d2008-10-01 11:38:11 +0000751 cf_info['control_file'] = control_file.generate_control(
mbligha3c58d22009-08-24 22:01:51 +0000752 tests=test_objects, kernels=kernel, platform=label,
mbligh120351e2009-01-24 01:40:45 +0000753 profilers=profiler_objects, is_server=cf_info['is_server'],
showard232b7ae2009-11-10 00:46:48 +0000754 client_control_file=client_control_file, profile_only=profile_only,
755 upload_kernel_config=upload_kernel_config)
showard989f25d2008-10-01 11:38:11 +0000756 return cf_info
mblighe8819cd2008-02-15 16:48:40 +0000757
758
jamesren4a41e012010-07-16 22:33:48 +0000759def create_parameterized_job(name, priority, test, parameters, kernel=None,
760 label=None, profilers=(), profiler_parameters=None,
761 use_container=False, profile_only=None,
762 upload_kernel_config=False, hosts=(),
763 meta_hosts=(), one_time_hosts=(),
764 atomic_group_name=None, synch_count=None,
765 is_template=False, timeout=None,
Simran Basi7e605742013-11-12 13:43:36 -0800766 timeout_mins=None, max_runtime_mins=None,
767 run_verify=False, email_list='', dependencies=(),
768 reboot_before=None, reboot_after=None,
769 parse_failed_repair=None, hostless=False,
Dan Shiec1d47d2015-02-13 11:38:13 -0800770 keyvals=None, drone_set=None, run_reset=True,
Dan Shi2a5297b2015-07-23 17:03:29 -0700771 require_ssp=None):
jamesren4a41e012010-07-16 22:33:48 +0000772 """
773 Creates and enqueues a parameterized job.
774
775 Most parameters a combination of the parameters for generate_control_file()
776 and create_job(), with the exception of:
777
778 @param test name or ID of the test to run
779 @param parameters a map of parameter name ->
780 tuple of (param value, param type)
781 @param profiler_parameters a dictionary of parameters for the profilers:
782 key: profiler name
783 value: dict of param name -> tuple of
784 (param value,
785 param type)
786 """
787 # Save the values of the passed arguments here. What we're going to do with
788 # them is pass them all to rpc_utils.get_create_job_common_args(), which
789 # will extract the subset of these arguments that apply for
790 # rpc_utils.create_job_common(), which we then pass in to that function.
791 args = locals()
792
793 # Set up the parameterized job configs
794 test_obj = models.Test.smart_get(test)
Aviv Keshet3dd8beb2013-05-13 17:36:04 -0700795 control_type = test_obj.test_type
jamesren4a41e012010-07-16 22:33:48 +0000796
797 try:
798 label = models.Label.smart_get(label)
799 except models.Label.DoesNotExist:
800 label = None
801
802 kernel_objs = models.Kernel.create_kernels(kernel)
803 profiler_objs = [models.Profiler.smart_get(profiler)
804 for profiler in profilers]
805
806 parameterized_job = models.ParameterizedJob.objects.create(
807 test=test_obj, label=label, use_container=use_container,
808 profile_only=profile_only,
809 upload_kernel_config=upload_kernel_config)
810 parameterized_job.kernels.add(*kernel_objs)
811
812 for profiler in profiler_objs:
813 parameterized_profiler = models.ParameterizedJobProfiler.objects.create(
814 parameterized_job=parameterized_job,
815 profiler=profiler)
816 profiler_params = profiler_parameters.get(profiler.name, {})
817 for name, (value, param_type) in profiler_params.iteritems():
818 models.ParameterizedJobProfilerParameter.objects.create(
819 parameterized_job_profiler=parameterized_profiler,
820 parameter_name=name,
821 parameter_value=value,
822 parameter_type=param_type)
823
824 try:
825 for parameter in test_obj.testparameter_set.all():
826 if parameter.name in parameters:
827 param_value, param_type = parameters.pop(parameter.name)
828 parameterized_job.parameterizedjobparameter_set.create(
829 test_parameter=parameter, parameter_value=param_value,
830 parameter_type=param_type)
831
832 if parameters:
833 raise Exception('Extra parameters remain: %r' % parameters)
834
835 return rpc_utils.create_job_common(
836 parameterized_job=parameterized_job.id,
837 control_type=control_type,
838 **rpc_utils.get_create_job_common_args(args))
839 except:
840 parameterized_job.delete()
841 raise
842
843
Simran Basib6ec8ae2014-04-23 12:05:08 -0700844def create_job_page_handler(name, priority, control_file, control_type,
Dan Shid215dbe2015-06-18 16:14:59 -0700845 image=None, hostless=False, firmware_rw_build=None,
846 firmware_ro_build=None, test_source_build=None,
847 **kwargs):
Simran Basib6ec8ae2014-04-23 12:05:08 -0700848 """\
849 Create and enqueue a job.
850
851 @param name name of this job
852 @param priority Integer priority of this job. Higher is more important.
853 @param control_file String contents of the control file.
854 @param control_type Type of control file, Client or Server.
Dan Shid215dbe2015-06-18 16:14:59 -0700855 @param image: ChromeOS build to be installed in the dut. Default to None.
856 @param firmware_rw_build: Firmware build to update RW firmware. Default to
857 None, i.e., RW firmware will not be updated.
858 @param firmware_ro_build: Firmware build to update RO firmware. Default to
859 None, i.e., RO firmware will not be updated.
860 @param test_source_build: Build to be used to retrieve test code. Default
861 to None.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700862 @param kwargs extra args that will be required by create_suite_job or
863 create_job.
864
865 @returns The created Job id number.
866 """
867 control_file = rpc_utils.encode_ascii(control_file)
Jiaxi Luodd67beb2014-07-18 16:28:31 -0700868 if not control_file:
869 raise model_logic.ValidationError({
870 'control_file' : "Control file cannot be empty"})
Simran Basib6ec8ae2014-04-23 12:05:08 -0700871
872 if image and hostless:
Dan Shid215dbe2015-06-18 16:14:59 -0700873 builds = {}
874 builds[provision.CROS_VERSION_PREFIX] = image
875 if firmware_rw_build:
Dan Shi0723bf52015-06-24 10:52:38 -0700876 builds[provision.FW_RW_VERSION_PREFIX] = firmware_rw_build
Dan Shid215dbe2015-06-18 16:14:59 -0700877 if firmware_ro_build:
878 builds[provision.FW_RO_VERSION_PREFIX] = firmware_ro_build
Simran Basib6ec8ae2014-04-23 12:05:08 -0700879 return site_rpc_interface.create_suite_job(
880 name=name, control_file=control_file, priority=priority,
Dan Shid215dbe2015-06-18 16:14:59 -0700881 builds=builds, test_source_build=test_source_build, **kwargs)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700882 return create_job(name, priority, control_file, control_type, image=image,
883 hostless=hostless, **kwargs)
884
885
MK Ryue301eb72015-06-25 12:51:02 -0700886@rpc_utils.route_rpc_to_master
showard12f3e322009-05-13 21:27:42 +0000887def create_job(name, priority, control_file, control_type,
888 hosts=(), meta_hosts=(), one_time_hosts=(),
889 atomic_group_name=None, synch_count=None, is_template=False,
Simran Basi7e605742013-11-12 13:43:36 -0800890 timeout=None, timeout_mins=None, max_runtime_mins=None,
891 run_verify=False, email_list='', dependencies=(),
892 reboot_before=None, reboot_after=None, parse_failed_repair=None,
893 hostless=False, keyvals=None, drone_set=None, image=None,
Dan Shiec1d47d2015-02-13 11:38:13 -0800894 parent_job_id=None, test_retry=0, run_reset=True,
895 require_ssp=None, args=(), **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +0000896 """\
897 Create and enqueue a job.
mblighe8819cd2008-02-15 16:48:40 +0000898
showarda1e74b32009-05-12 17:32:04 +0000899 @param name name of this job
Alex Miller7d658cf2013-09-04 16:00:35 -0700900 @param priority Integer priority of this job. Higher is more important.
showarda1e74b32009-05-12 17:32:04 +0000901 @param control_file String contents of the control file.
902 @param control_type Type of control file, Client or Server.
903 @param synch_count How many machines the job uses per autoserv execution.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700904 synch_count == 1 means the job is asynchronous. If an atomic group is
905 given this value is treated as a minimum.
showarda1e74b32009-05-12 17:32:04 +0000906 @param is_template If true then create a template job.
907 @param timeout Hours after this call returns until the job times out.
Simran Basi7e605742013-11-12 13:43:36 -0800908 @param timeout_mins Minutes after this call returns until the job times
Jiaxi Luo90190c92014-06-18 12:35:57 -0700909 out.
Simran Basi34217022012-11-06 13:43:15 -0800910 @param max_runtime_mins Minutes from job starting time until job times out
showarda1e74b32009-05-12 17:32:04 +0000911 @param run_verify Should the host be verified before running the test?
912 @param email_list String containing emails to mail when the job is done
913 @param dependencies List of label names on which this job depends
914 @param reboot_before Never, If dirty, or Always
915 @param reboot_after Never, If all tests passed, or Always
916 @param parse_failed_repair if true, results of failed repairs launched by
Jiaxi Luo90190c92014-06-18 12:35:57 -0700917 this job will be parsed as part of the job.
showarda9545c02009-12-18 22:44:26 +0000918 @param hostless if true, create a hostless job
showardc1a98d12010-01-15 00:22:22 +0000919 @param keyvals dict of keyvals to associate with the job
showarda1e74b32009-05-12 17:32:04 +0000920 @param hosts List of hosts to run job on.
921 @param meta_hosts List where each entry is a label name, and for each entry
Jiaxi Luo90190c92014-06-18 12:35:57 -0700922 one host will be chosen from that label to run the job on.
showarda1e74b32009-05-12 17:32:04 +0000923 @param one_time_hosts List of hosts not in the database to run the job on.
924 @param atomic_group_name The name of an atomic group to schedule the job on.
jamesren76fcf192010-04-21 20:39:50 +0000925 @param drone_set The name of the drone set to run this test on.
Paul Pendlebury5a8c6ad2011-02-01 07:20:17 -0800926 @param image OS image to install before running job.
Aviv Keshet0b9cfc92013-02-05 11:36:02 -0800927 @param parent_job_id id of a job considered to be parent of created job.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700928 @param test_retry Number of times to retry test if the test did not
Jiaxi Luo90190c92014-06-18 12:35:57 -0700929 complete successfully. (optional, default: 0)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700930 @param run_reset Should the host be reset before running the test?
Dan Shiec1d47d2015-02-13 11:38:13 -0800931 @param require_ssp Set to True to require server-side packaging to run the
932 test. If it's set to None, drone will still try to run
933 the server side with server-side packaging. If the
934 autotest-server package doesn't exist for the build or
935 image is not set, drone will run the test without server-
936 side packaging. Default is None.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700937 @param args A list of args to be injected into control file.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700938 @param kwargs extra keyword args. NOT USED.
showardc92da832009-04-07 18:14:34 +0000939
940 @returns The created Job id number.
jadmanski0afbb632008-06-06 21:10:57 +0000941 """
Jiaxi Luo90190c92014-06-18 12:35:57 -0700942 if args:
943 control_file = tools.inject_vars({'args': args}, control_file)
944
Simran Basiab5a1bf2014-05-28 15:39:44 -0700945 if image is None:
946 return rpc_utils.create_job_common(
947 **rpc_utils.get_create_job_common_args(locals()))
948
Simran Basi6157e8e2015-12-07 18:22:34 -0800949 # Translate the image name, in case its a relative build name.
950 ds = dev_server.ImageServer.resolve(image)
951 image = ds.translate(image)
952
Simran Basiab5a1bf2014-05-28 15:39:44 -0700953 # When image is supplied use a known parameterized test already in the
954 # database to pass the OS image path from the front end, through the
955 # scheduler, and finally to autoserv as the --image parameter.
956
957 # The test autoupdate_ParameterizedJob is in afe_autotests and used to
958 # instantiate a Test object and from there a ParameterizedJob.
959 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
960 known_parameterized_job = models.ParameterizedJob.objects.create(
961 test=known_test_obj)
962
963 # autoupdate_ParameterizedJob has a single parameter, the image parameter,
964 # stored in the table afe_test_parameters. We retrieve and set this
965 # instance of the parameter to the OS image path.
966 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
967 name='image')
968 known_parameterized_job.parameterizedjobparameter_set.create(
969 test_parameter=image_parameter, parameter_value=image,
970 parameter_type='string')
971
Dan Shid215dbe2015-06-18 16:14:59 -0700972 # TODO(crbug.com/502638): save firmware build etc to parameterized_job.
973
Simran Basiab5a1bf2014-05-28 15:39:44 -0700974 # By passing a parameterized_job to create_job_common the job entry in
975 # the afe_jobs table will have the field parameterized_job_id set.
976 # The scheduler uses this id in the afe_parameterized_jobs table to
977 # match this job to our known test, and then with the
978 # afe_parameterized_job_parameters table to get the actual image path.
jamesren4a41e012010-07-16 22:33:48 +0000979 return rpc_utils.create_job_common(
Simran Basiab5a1bf2014-05-28 15:39:44 -0700980 parameterized_job=known_parameterized_job.id,
jamesren4a41e012010-07-16 22:33:48 +0000981 **rpc_utils.get_create_job_common_args(locals()))
mblighe8819cd2008-02-15 16:48:40 +0000982
983
showard9dbdcda2008-10-14 17:34:36 +0000984def abort_host_queue_entries(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000985 """\
showard9dbdcda2008-10-14 17:34:36 +0000986 Abort a set of host queue entries.
Fang Deng63b0e452014-12-19 14:38:15 -0800987
988 @return: A list of dictionaries, each contains information
989 about an aborted HQE.
jadmanski0afbb632008-06-06 21:10:57 +0000990 """
showard9dbdcda2008-10-14 17:34:36 +0000991 query = models.HostQueueEntry.query_objects(filter_data)
beepsfaecbce2013-10-29 11:35:10 -0700992
993 # Dont allow aborts on:
994 # 1. Jobs that have already completed (whether or not they were aborted)
995 # 2. Jobs that we have already been aborted (but may not have completed)
996 query = query.filter(complete=False).filter(aborted=False)
showarddc817512008-11-12 18:16:41 +0000997 models.AclGroup.check_abort_permissions(query)
showard9dbdcda2008-10-14 17:34:36 +0000998 host_queue_entries = list(query.select_related())
showard2bab8f42008-11-12 18:15:22 +0000999 rpc_utils.check_abort_synchronous_jobs(host_queue_entries)
mblighe8819cd2008-02-15 16:48:40 +00001000
Simran Basic1b26762013-06-26 14:23:21 -07001001 models.HostQueueEntry.abort_host_queue_entries(host_queue_entries)
Fang Deng63b0e452014-12-19 14:38:15 -08001002 hqe_info = [{'HostQueueEntry': hqe.id, 'Job': hqe.job_id,
1003 'Job name': hqe.job.name} for hqe in host_queue_entries]
1004 return hqe_info
showard9d821ab2008-07-11 16:54:29 +00001005
1006
beeps8bb1f7d2013-08-05 01:30:09 -07001007def abort_special_tasks(**filter_data):
1008 """\
1009 Abort the special task, or tasks, specified in the filter.
1010 """
1011 query = models.SpecialTask.query_objects(filter_data)
1012 special_tasks = query.filter(is_active=True)
1013 for task in special_tasks:
1014 task.abort()
1015
1016
Simran Basi73dae552013-02-25 14:57:46 -08001017def _call_special_tasks_on_hosts(task, hosts):
1018 """\
1019 Schedules a set of hosts for a special task.
1020
1021 @returns A list of hostnames that a special task was created for.
1022 """
1023 models.AclGroup.check_for_acl_violation_hosts(hosts)
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -08001024 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -08001025 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -08001026 raise ValueError('The following hosts are on shards, please '
1027 'follow the link to the shards and create jobs '
1028 'there instead. %s.' % shard_host_map)
Simran Basi73dae552013-02-25 14:57:46 -08001029 for host in hosts:
1030 models.SpecialTask.schedule_special_task(host, task)
1031 return list(sorted(host.hostname for host in hosts))
1032
1033
MK Ryu5aa25042015-07-28 16:08:04 -07001034def _forward_special_tasks_on_hosts(task, rpc, **filter_data):
1035 """Forward special tasks to corresponding shards.
mbligh4e545a52009-12-19 05:30:39 +00001036
MK Ryu5aa25042015-07-28 16:08:04 -07001037 For master, when special tasks are fired on hosts that are sharded,
1038 forward the RPC to corresponding shards.
1039
1040 For shard, create special task records in local DB.
1041
1042 @param task: Enum value of frontend.afe.models.SpecialTask.Task
1043 @param rpc: RPC name to forward.
1044 @param filter_data: Filter keywords to be used for DB query.
1045
1046 @return: A list of hostnames that a special task was created for.
showard1ff7b2e2009-05-15 23:17:18 +00001047 """
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001048 hosts = models.Host.query_objects(filter_data)
1049 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts, rpc_hostnames=True)
1050
1051 # Filter out hosts on a shard from those on the master, forward
1052 # rpcs to the shard with an additional hostname__in filter, and
1053 # create a local SpecialTask for each remaining host.
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -08001054 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001055 hosts = [h for h in hosts if h.shard is None]
1056 for shard, hostnames in shard_host_map.iteritems():
1057
1058 # The main client of this module is the frontend website, and
1059 # it invokes it with an 'id' or an 'id__in' filter. Regardless,
1060 # the 'hostname' filter should narrow down the list of hosts on
1061 # each shard even though we supply all the ids in filter_data.
1062 # This method uses hostname instead of id because it fits better
MK Ryu5aa25042015-07-28 16:08:04 -07001063 # with the overall architecture of redirection functions in
1064 # rpc_utils.
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001065 shard_filter = filter_data.copy()
1066 shard_filter['hostname__in'] = hostnames
1067 rpc_utils.run_rpc_on_multiple_hostnames(
MK Ryu5aa25042015-07-28 16:08:04 -07001068 rpc, [shard], **shard_filter)
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001069
1070 # There is a race condition here if someone assigns a shard to one of these
1071 # hosts before we create the task. The host will stay on the master if:
1072 # 1. The host is not Ready
1073 # 2. The host is Ready but has a task
1074 # But if the host is Ready and doesn't have a task yet, it will get sent
1075 # to the shard as we're creating a task here.
1076
1077 # Given that we only rarely verify Ready hosts it isn't worth putting this
1078 # entire method in a transaction. The worst case scenario is that we have
MK Ryu5aa25042015-07-28 16:08:04 -07001079 # a verify running on a Ready host while the shard is using it, if the
1080 # verify fails no subsequent tasks will be created against the host on the
1081 # master, and verifies are safe enough that this is OK.
1082 return _call_special_tasks_on_hosts(task, hosts)
1083
1084
1085def reverify_hosts(**filter_data):
1086 """\
1087 Schedules a set of hosts for verify.
1088
1089 @returns A list of hostnames that a verify task was created for.
1090 """
1091 return _forward_special_tasks_on_hosts(
1092 models.SpecialTask.Task.VERIFY, 'reverify_hosts', **filter_data)
Simran Basi73dae552013-02-25 14:57:46 -08001093
1094
1095def repair_hosts(**filter_data):
1096 """\
1097 Schedules a set of hosts for repair.
1098
1099 @returns A list of hostnames that a repair task was created for.
1100 """
MK Ryu5aa25042015-07-28 16:08:04 -07001101 return _forward_special_tasks_on_hosts(
1102 models.SpecialTask.Task.REPAIR, 'repair_hosts', **filter_data)
showard1ff7b2e2009-05-15 23:17:18 +00001103
1104
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001105def get_jobs(not_yet_run=False, running=False, finished=False,
1106 suite=False, sub=False, standalone=False, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001107 """\
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001108 Extra status filter args for get_jobs:
jadmanski0afbb632008-06-06 21:10:57 +00001109 -not_yet_run: Include only jobs that have not yet started running.
1110 -running: Include only jobs that have start running but for which not
1111 all hosts have completed.
1112 -finished: Include only jobs for which all hosts have completed (or
1113 aborted).
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001114
1115 Extra type filter args for get_jobs:
1116 -suite: Include only jobs with child jobs.
1117 -sub: Include only jobs with a parent job.
1118 -standalone: Inlcude only jobs with no child or parent jobs.
1119 At most one of these three fields should be specified.
jadmanski0afbb632008-06-06 21:10:57 +00001120 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001121 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
1122 running,
1123 finished)
1124 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
1125 suite,
1126 sub,
1127 standalone)
showard0957a842009-05-11 19:25:08 +00001128 job_dicts = []
1129 jobs = list(models.Job.query_objects(filter_data))
1130 models.Job.objects.populate_relationships(jobs, models.Label,
1131 'dependencies')
showardc1a98d12010-01-15 00:22:22 +00001132 models.Job.objects.populate_relationships(jobs, models.JobKeyval, 'keyvals')
showard0957a842009-05-11 19:25:08 +00001133 for job in jobs:
1134 job_dict = job.get_object_dict()
1135 job_dict['dependencies'] = ','.join(label.name
1136 for label in job.dependencies)
showardc1a98d12010-01-15 00:22:22 +00001137 job_dict['keyvals'] = dict((keyval.key, keyval.value)
1138 for keyval in job.keyvals)
Eric Lid23bc192011-02-09 14:38:57 -08001139 if job.parameterized_job:
1140 job_dict['image'] = get_parameterized_autoupdate_image_url(job)
showard0957a842009-05-11 19:25:08 +00001141 job_dicts.append(job_dict)
1142 return rpc_utils.prepare_for_serialization(job_dicts)
mblighe8819cd2008-02-15 16:48:40 +00001143
1144
1145def get_num_jobs(not_yet_run=False, running=False, finished=False,
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001146 suite=False, sub=False, standalone=False,
jadmanski0afbb632008-06-06 21:10:57 +00001147 **filter_data):
1148 """\
1149 See get_jobs() for documentation of extra filter parameters.
1150 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001151 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
1152 running,
1153 finished)
1154 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
1155 suite,
1156 sub,
1157 standalone)
jadmanski0afbb632008-06-06 21:10:57 +00001158 return models.Job.query_count(filter_data)
mblighe8819cd2008-02-15 16:48:40 +00001159
1160
mblighe8819cd2008-02-15 16:48:40 +00001161def get_jobs_summary(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001162 """\
Jiaxi Luoaac54572014-06-04 13:57:02 -07001163 Like get_jobs(), but adds 'status_counts' and 'result_counts' field.
1164
1165 'status_counts' filed is a dictionary mapping status strings to the number
1166 of hosts currently with that status, i.e. {'Queued' : 4, 'Running' : 2}.
1167
1168 'result_counts' field is piped to tko's rpc_interface and has the return
1169 format specified under get_group_counts.
jadmanski0afbb632008-06-06 21:10:57 +00001170 """
1171 jobs = get_jobs(**filter_data)
1172 ids = [job['id'] for job in jobs]
1173 all_status_counts = models.Job.objects.get_status_counts(ids)
1174 for job in jobs:
1175 job['status_counts'] = all_status_counts[job['id']]
Jiaxi Luoaac54572014-06-04 13:57:02 -07001176 job['result_counts'] = tko_rpc_interface.get_status_counts(
1177 ['afe_job_id', 'afe_job_id'],
1178 header_groups=[['afe_job_id'], ['afe_job_id']],
1179 **{'afe_job_id': job['id']})
jadmanski0afbb632008-06-06 21:10:57 +00001180 return rpc_utils.prepare_for_serialization(jobs)
mblighe8819cd2008-02-15 16:48:40 +00001181
1182
showarda965cef2009-05-15 23:17:41 +00001183def get_info_for_clone(id, preserve_metahosts, queue_entry_filter_data=None):
showarda8709c52008-07-03 19:44:54 +00001184 """\
1185 Retrieves all the information needed to clone a job.
1186 """
showarda8709c52008-07-03 19:44:54 +00001187 job = models.Job.objects.get(id=id)
showard29f7cd22009-04-29 21:16:24 +00001188 job_info = rpc_utils.get_job_info(job,
showarda965cef2009-05-15 23:17:41 +00001189 preserve_metahosts,
1190 queue_entry_filter_data)
showard945072f2008-09-03 20:34:59 +00001191
showardd9992fe2008-07-31 02:15:03 +00001192 host_dicts = []
showard29f7cd22009-04-29 21:16:24 +00001193 for host in job_info['hosts']:
1194 host_dict = get_hosts(id=host.id)[0]
1195 other_labels = host_dict['labels']
1196 if host_dict['platform']:
1197 other_labels.remove(host_dict['platform'])
1198 host_dict['other_labels'] = ', '.join(other_labels)
showardd9992fe2008-07-31 02:15:03 +00001199 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001200
showard29f7cd22009-04-29 21:16:24 +00001201 for host in job_info['one_time_hosts']:
1202 host_dict = dict(hostname=host.hostname,
1203 id=host.id,
1204 platform='(one-time host)',
1205 locked_text='')
1206 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001207
showard4d077562009-05-08 18:24:36 +00001208 # convert keys from Label objects to strings (names of labels)
showard29f7cd22009-04-29 21:16:24 +00001209 meta_host_counts = dict((meta_host.name, count) for meta_host, count
showard4d077562009-05-08 18:24:36 +00001210 in job_info['meta_host_counts'].iteritems())
showard29f7cd22009-04-29 21:16:24 +00001211
1212 info = dict(job=job.get_object_dict(),
1213 meta_host_counts=meta_host_counts,
1214 hosts=host_dicts)
1215 info['job']['dependencies'] = job_info['dependencies']
1216 if job_info['atomic_group']:
1217 info['atomic_group_name'] = (job_info['atomic_group']).name
1218 else:
1219 info['atomic_group_name'] = None
jamesren2275ef12010-04-12 18:25:06 +00001220 info['hostless'] = job_info['hostless']
jamesren76fcf192010-04-21 20:39:50 +00001221 info['drone_set'] = job.drone_set and job.drone_set.name
showarda8709c52008-07-03 19:44:54 +00001222
Eric Lid23bc192011-02-09 14:38:57 -08001223 if job.parameterized_job:
1224 info['job']['image'] = get_parameterized_autoupdate_image_url(job)
1225
showarda8709c52008-07-03 19:44:54 +00001226 return rpc_utils.prepare_for_serialization(info)
1227
1228
showard34dc5fa2008-04-24 20:58:40 +00001229# host queue entries
1230
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001231def get_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001232 """\
showardc92da832009-04-07 18:14:34 +00001233 @returns A sequence of nested dictionaries of host and job information.
jadmanski0afbb632008-06-06 21:10:57 +00001234 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001235 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1236 'started_on__lte',
1237 start_time,
1238 end_time,
1239 **filter_data)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001240 return rpc_utils.prepare_rows_as_nested_dicts(
1241 models.HostQueueEntry.query_objects(filter_data),
1242 ('host', 'atomic_group', 'job'))
showard34dc5fa2008-04-24 20:58:40 +00001243
1244
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001245def get_num_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001246 """\
1247 Get the number of host queue entries associated with this job.
1248 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001249 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1250 'started_on__lte',
1251 start_time,
1252 end_time,
1253 **filter_data)
jadmanski0afbb632008-06-06 21:10:57 +00001254 return models.HostQueueEntry.query_count(filter_data)
showard34dc5fa2008-04-24 20:58:40 +00001255
1256
showard1e935f12008-07-11 00:11:36 +00001257def get_hqe_percentage_complete(**filter_data):
1258 """
showardc92da832009-04-07 18:14:34 +00001259 Computes the fraction of host queue entries matching the given filter data
showard1e935f12008-07-11 00:11:36 +00001260 that are complete.
1261 """
1262 query = models.HostQueueEntry.query_objects(filter_data)
1263 complete_count = query.filter(complete=True).count()
1264 total_count = query.count()
1265 if total_count == 0:
1266 return 1
1267 return float(complete_count) / total_count
1268
1269
showard1a5a4082009-07-28 20:01:37 +00001270# special tasks
1271
1272def get_special_tasks(**filter_data):
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001273 """Get special task entries from the local database.
1274
1275 Query the special tasks table for tasks matching the given
1276 `filter_data`, and return a list of the results. No attempt is
1277 made to forward the call to shards; the buck will stop here.
1278 The caller is expected to know the target shard for such reasons
1279 as:
1280 * The caller is a service (such as gs_offloader) configured
1281 to operate on behalf of one specific shard, and no other.
1282 * The caller has a host as a parameter, and knows that this is
1283 the shard assigned to that host.
1284
1285 @param filter_data Filter keywords to pass to the underlying
1286 database query.
1287
1288 """
J. Richard Barnettefdfcd662015-04-13 17:20:29 -07001289 return rpc_utils.prepare_rows_as_nested_dicts(
1290 models.SpecialTask.query_objects(filter_data),
1291 ('host', 'queue_entry'))
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001292
1293
1294def get_host_special_tasks(host_id, **filter_data):
1295 """Get special task entries for a given host.
1296
1297 Query the special tasks table for tasks that ran on the host
1298 given by `host_id` and matching the given `filter_data`.
1299 Return a list of the results. If the host is assigned to a
1300 shard, forward this call to that shard.
1301
1302 @param host_id Id in the database of the target host.
1303 @param filter_data Filter keywords to pass to the underlying
1304 database query.
1305
1306 """
MK Ryu0c1a37d2015-04-30 12:00:55 -07001307 # Retrieve host data even if the host is in an invalid state.
1308 host = models.Host.smart_get(host_id, False)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001309 if not host.shard:
J. Richard Barnettefdfcd662015-04-13 17:20:29 -07001310 return get_special_tasks(host_id=host_id, **filter_data)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001311 else:
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001312 # The return values from AFE methods are post-processed
1313 # objects that aren't JSON-serializable. So, we have to
1314 # call AFE.run() to get the raw, serializable output from
1315 # the shard.
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001316 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1317 return shard_afe.run('get_special_tasks',
1318 host_id=host_id, **filter_data)
showard1a5a4082009-07-28 20:01:37 +00001319
1320
MK Ryu0c1a37d2015-04-30 12:00:55 -07001321def get_num_special_tasks(**kwargs):
1322 """Get the number of special task entries from the local database.
1323
1324 Query the special tasks table for tasks matching the given 'kwargs',
1325 and return the number of the results. No attempt is made to forward
1326 the call to shards; the buck will stop here.
1327
1328 @param kwargs Filter keywords to pass to the underlying database query.
1329
1330 """
1331 return models.SpecialTask.query_count(kwargs)
1332
1333
1334def get_host_num_special_tasks(host, **kwargs):
1335 """Get special task entries for a given host.
1336
1337 Query the special tasks table for tasks that ran on the host
1338 given by 'host' and matching the given 'kwargs'.
1339 Return a list of the results. If the host is assigned to a
1340 shard, forward this call to that shard.
1341
1342 @param host id or name of a host. More often a hostname.
1343 @param kwargs Filter keywords to pass to the underlying database query.
1344
1345 """
1346 # Retrieve host data even if the host is in an invalid state.
1347 host_model = models.Host.smart_get(host, False)
1348 if not host_model.shard:
1349 return get_num_special_tasks(host=host, **kwargs)
1350 else:
1351 shard_afe = frontend.AFE(server=host_model.shard.rpc_hostname())
1352 return shard_afe.run('get_num_special_tasks', host=host, **kwargs)
1353
1354
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001355def get_status_task(host_id, end_time):
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001356 """Get the "status task" for a host from the local shard.
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001357
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001358 Returns a single special task representing the given host's
1359 "status task". The status task is a completed special task that
1360 identifies whether the corresponding host was working or broken
1361 when it completed. A successful task indicates a working host;
1362 a failed task indicates broken.
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001363
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001364 This call will not be forward to a shard; the receiving server
1365 must be the shard that owns the host.
1366
1367 @param host_id Id in the database of the target host.
1368 @param end_time Time reference for the host's status.
1369
1370 @return A single task; its status (successful or not)
1371 corresponds to the status of the host (working or
1372 broken) at the given time. If no task is found, return
1373 `None`.
1374
1375 """
1376 tasklist = rpc_utils.prepare_rows_as_nested_dicts(
1377 status_history.get_status_task(host_id, end_time),
1378 ('host', 'queue_entry'))
1379 return tasklist[0] if tasklist else None
1380
1381
1382def get_host_status_task(host_id, end_time):
1383 """Get the "status task" for a host from its owning shard.
1384
1385 Finds the given host's owning shard, and forwards to it a call
1386 to `get_status_task()` (see above).
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001387
1388 @param host_id Id in the database of the target host.
1389 @param end_time Time reference for the host's status.
1390
1391 @return A single task; its status (successful or not)
1392 corresponds to the status of the host (working or
1393 broken) at the given time. If no task is found, return
1394 `None`.
1395
1396 """
1397 host = models.Host.smart_get(host_id)
1398 if not host.shard:
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001399 return get_status_task(host_id, end_time)
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001400 else:
1401 # The return values from AFE methods are post-processed
1402 # objects that aren't JSON-serializable. So, we have to
1403 # call AFE.run() to get the raw, serializable output from
1404 # the shard.
1405 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1406 return shard_afe.run('get_status_task',
1407 host_id=host_id, end_time=end_time)
1408
1409
J. Richard Barnette8abbfd62015-06-23 12:46:54 -07001410def get_host_diagnosis_interval(host_id, end_time, success):
1411 """Find a "diagnosis interval" for a given host.
1412
1413 A "diagnosis interval" identifies a start and end time where
1414 the host went from "working" to "broken", or vice versa. The
1415 interval's starting time is the starting time of the last status
1416 task with the old status; the end time is the finish time of the
1417 first status task with the new status.
1418
1419 This routine finds the most recent diagnosis interval for the
1420 given host prior to `end_time`, with a starting status matching
1421 `success`. If `success` is true, the interval will start with a
1422 successful status task; if false the interval will start with a
1423 failed status task.
1424
1425 @param host_id Id in the database of the target host.
1426 @param end_time Time reference for the diagnosis interval.
1427 @param success Whether the diagnosis interval should start
1428 with a successful or failed status task.
1429
1430 @return A list of two strings. The first is the timestamp for
1431 the beginning of the interval; the second is the
1432 timestamp for the end. If the host has never changed
1433 state, the list is empty.
1434
1435 """
1436 host = models.Host.smart_get(host_id)
J. Richard Barnette78f281a2015-06-29 13:24:51 -07001437 if not host.shard or utils.is_shard():
J. Richard Barnette8abbfd62015-06-23 12:46:54 -07001438 return status_history.get_diagnosis_interval(
1439 host_id, end_time, success)
1440 else:
1441 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1442 return shard_afe.get_host_diagnosis_interval(
1443 host_id, end_time, success)
1444
1445
showardc0ac3a72009-07-08 21:14:45 +00001446# support for host detail view
1447
MK Ryu0c1a37d2015-04-30 12:00:55 -07001448def get_host_queue_entries_and_special_tasks(host, query_start=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001449 query_limit=None, start_time=None,
1450 end_time=None):
showardc0ac3a72009-07-08 21:14:45 +00001451 """
1452 @returns an interleaved list of HostQueueEntries and SpecialTasks,
1453 in approximate run order. each dict contains keys for type, host,
1454 job, status, started_on, execution_path, and ID.
1455 """
1456 total_limit = None
1457 if query_limit is not None:
1458 total_limit = query_start + query_limit
MK Ryu0c1a37d2015-04-30 12:00:55 -07001459 filter_data_common = {'host': host,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001460 'query_limit': total_limit,
1461 'sort_by': ['-id']}
showardc0ac3a72009-07-08 21:14:45 +00001462
MK Ryu0c1a37d2015-04-30 12:00:55 -07001463 filter_data_special_tasks = rpc_utils.inject_times_to_filter(
1464 'time_started__gte', 'time_started__lte', start_time, end_time,
1465 **filter_data_common)
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001466
MK Ryu0c1a37d2015-04-30 12:00:55 -07001467 queue_entries = get_host_queue_entries(
1468 start_time, end_time, **filter_data_common)
1469 special_tasks = get_host_special_tasks(host, **filter_data_special_tasks)
showardc0ac3a72009-07-08 21:14:45 +00001470
1471 interleaved_entries = rpc_utils.interleave_entries(queue_entries,
1472 special_tasks)
1473 if query_start is not None:
1474 interleaved_entries = interleaved_entries[query_start:]
1475 if query_limit is not None:
1476 interleaved_entries = interleaved_entries[:query_limit]
MK Ryu0c1a37d2015-04-30 12:00:55 -07001477 return rpc_utils.prepare_host_queue_entries_and_special_tasks(
1478 interleaved_entries, queue_entries)
showardc0ac3a72009-07-08 21:14:45 +00001479
1480
MK Ryu0c1a37d2015-04-30 12:00:55 -07001481def get_num_host_queue_entries_and_special_tasks(host, start_time=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001482 end_time=None):
MK Ryu0c1a37d2015-04-30 12:00:55 -07001483 filter_data_common = {'host': host}
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001484
1485 filter_data_queue_entries, filter_data_special_tasks = (
1486 rpc_utils.inject_times_to_hqe_special_tasks_filters(
1487 filter_data_common, start_time, end_time))
1488
1489 return (models.HostQueueEntry.query_count(filter_data_queue_entries)
MK Ryu0c1a37d2015-04-30 12:00:55 -07001490 + get_host_num_special_tasks(**filter_data_special_tasks))
showardc0ac3a72009-07-08 21:14:45 +00001491
1492
showard29f7cd22009-04-29 21:16:24 +00001493# recurring run
1494
1495def get_recurring(**filter_data):
1496 return rpc_utils.prepare_rows_as_nested_dicts(
1497 models.RecurringRun.query_objects(filter_data),
1498 ('job', 'owner'))
1499
1500
1501def get_num_recurring(**filter_data):
1502 return models.RecurringRun.query_count(filter_data)
1503
1504
1505def delete_recurring_runs(**filter_data):
1506 to_delete = models.RecurringRun.query_objects(filter_data)
1507 to_delete.delete()
1508
1509
1510def create_recurring_run(job_id, start_date, loop_period, loop_count):
showard64a95952010-01-13 21:27:16 +00001511 owner = models.User.current_user().login
showard29f7cd22009-04-29 21:16:24 +00001512 job = models.Job.objects.get(id=job_id)
1513 return job.create_recurring_job(start_date=start_date,
1514 loop_period=loop_period,
1515 loop_count=loop_count,
1516 owner=owner)
1517
1518
mblighe8819cd2008-02-15 16:48:40 +00001519# other
1520
showarde0b63622008-08-04 20:58:47 +00001521def echo(data=""):
1522 """\
1523 Returns a passed in string. For doing a basic test to see if RPC calls
1524 can successfully be made.
1525 """
1526 return data
1527
1528
showardb7a52fd2009-04-27 20:10:56 +00001529def get_motd():
1530 """\
1531 Returns the message of the day as a string.
1532 """
1533 return rpc_utils.get_motd()
1534
1535
mblighe8819cd2008-02-15 16:48:40 +00001536def get_static_data():
jadmanski0afbb632008-06-06 21:10:57 +00001537 """\
1538 Returns a dictionary containing a bunch of data that shouldn't change
1539 often and is otherwise inaccessible. This includes:
showardc92da832009-04-07 18:14:34 +00001540
1541 priorities: List of job priority choices.
1542 default_priority: Default priority value for new jobs.
1543 users: Sorted list of all users.
Jiaxi Luo31874592014-06-11 10:36:35 -07001544 labels: Sorted list of labels not start with 'cros-version' and
1545 'fw-version'.
showardc92da832009-04-07 18:14:34 +00001546 atomic_groups: Sorted list of all atomic groups.
1547 tests: Sorted list of all tests.
1548 profilers: Sorted list of all profilers.
1549 current_user: Logged-in username.
1550 host_statuses: Sorted list of possible Host statuses.
1551 job_statuses: Sorted list of possible HostQueueEntry statuses.
Simran Basi7e605742013-11-12 13:43:36 -08001552 job_timeout_default: The default job timeout length in minutes.
showarda1e74b32009-05-12 17:32:04 +00001553 parse_failed_repair_default: Default value for the parse_failed_repair job
Jiaxi Luo31874592014-06-11 10:36:35 -07001554 option.
showardc92da832009-04-07 18:14:34 +00001555 reboot_before_options: A list of valid RebootBefore string enums.
1556 reboot_after_options: A list of valid RebootAfter string enums.
1557 motd: Server's message of the day.
1558 status_dictionary: A mapping from one word job status names to a more
1559 informative description.
jadmanski0afbb632008-06-06 21:10:57 +00001560 """
showard21baa452008-10-21 00:08:39 +00001561
1562 job_fields = models.Job.get_field_dict()
jamesren76fcf192010-04-21 20:39:50 +00001563 default_drone_set_name = models.DroneSet.default_drone_set_name()
1564 drone_sets = ([default_drone_set_name] +
1565 sorted(drone_set.name for drone_set in
1566 models.DroneSet.objects.exclude(
1567 name=default_drone_set_name)))
showard21baa452008-10-21 00:08:39 +00001568
jadmanski0afbb632008-06-06 21:10:57 +00001569 result = {}
Alex Miller7d658cf2013-09-04 16:00:35 -07001570 result['priorities'] = priorities.Priority.choices()
1571 default_priority = priorities.Priority.DEFAULT
1572 result['default_priority'] = 'Default'
1573 result['max_schedulable_priority'] = priorities.Priority.DEFAULT
jadmanski0afbb632008-06-06 21:10:57 +00001574 result['users'] = get_users(sort_by=['login'])
Jiaxi Luo31874592014-06-11 10:36:35 -07001575
1576 label_exclude_filters = [{'name__startswith': 'cros-version'},
Dan Shi65351d62015-08-03 12:03:23 -07001577 {'name__startswith': 'fw-version'},
1578 {'name__startswith': 'fwrw-version'},
1579 {'name__startswith': 'fwro-version'}]
Jiaxi Luo31874592014-06-11 10:36:35 -07001580 result['labels'] = get_labels(
1581 label_exclude_filters,
1582 sort_by=['-platform', 'name'])
1583
showardc92da832009-04-07 18:14:34 +00001584 result['atomic_groups'] = get_atomic_groups(sort_by=['name'])
jadmanski0afbb632008-06-06 21:10:57 +00001585 result['tests'] = get_tests(sort_by=['name'])
showard2b9a88b2008-06-13 20:55:03 +00001586 result['profilers'] = get_profilers(sort_by=['name'])
showard0fc38302008-10-23 00:44:07 +00001587 result['current_user'] = rpc_utils.prepare_for_serialization(
showard64a95952010-01-13 21:27:16 +00001588 models.User.current_user().get_object_dict())
showard2b9a88b2008-06-13 20:55:03 +00001589 result['host_statuses'] = sorted(models.Host.Status.names)
mbligh5a198b92008-12-11 19:33:29 +00001590 result['job_statuses'] = sorted(models.HostQueueEntry.Status.names)
Simran Basi7e605742013-11-12 13:43:36 -08001591 result['job_timeout_mins_default'] = models.Job.DEFAULT_TIMEOUT_MINS
Simran Basi34217022012-11-06 13:43:15 -08001592 result['job_max_runtime_mins_default'] = (
1593 models.Job.DEFAULT_MAX_RUNTIME_MINS)
showarda1e74b32009-05-12 17:32:04 +00001594 result['parse_failed_repair_default'] = bool(
1595 models.Job.DEFAULT_PARSE_FAILED_REPAIR)
jamesrendd855242010-03-02 22:23:44 +00001596 result['reboot_before_options'] = model_attributes.RebootBefore.names
1597 result['reboot_after_options'] = model_attributes.RebootAfter.names
showard8fbae652009-01-20 23:23:10 +00001598 result['motd'] = rpc_utils.get_motd()
jamesren76fcf192010-04-21 20:39:50 +00001599 result['drone_sets_enabled'] = models.DroneSet.drone_sets_enabled()
1600 result['drone_sets'] = drone_sets
jamesren4a41e012010-07-16 22:33:48 +00001601 result['parameterized_jobs'] = models.Job.parameterized_jobs_enabled()
showard8ac29b42008-07-17 17:01:55 +00001602
showardd3dc1992009-04-22 21:01:40 +00001603 result['status_dictionary'] = {"Aborted": "Aborted",
showard8ac29b42008-07-17 17:01:55 +00001604 "Verifying": "Verifying Host",
Alex Millerdfff2fd2013-05-28 13:05:06 -07001605 "Provisioning": "Provisioning Host",
showard8ac29b42008-07-17 17:01:55 +00001606 "Pending": "Waiting on other hosts",
1607 "Running": "Running autoserv",
1608 "Completed": "Autoserv completed",
1609 "Failed": "Failed to complete",
showardd823b362008-07-24 16:35:46 +00001610 "Queued": "Queued",
showard5deb6772008-11-04 21:54:33 +00001611 "Starting": "Next in host's queue",
1612 "Stopped": "Other host(s) failed verify",
showardd3dc1992009-04-22 21:01:40 +00001613 "Parsing": "Awaiting parse of final results",
showard29f7cd22009-04-29 21:16:24 +00001614 "Gathering": "Gathering log files",
showard8cc058f2009-09-08 16:26:33 +00001615 "Template": "Template job for recurring run",
mbligh4608b002010-01-05 18:22:35 +00001616 "Waiting": "Waiting for scheduler action",
Dan Shi07e09af2013-04-12 09:31:29 -07001617 "Archiving": "Archiving results",
1618 "Resetting": "Resetting hosts"}
Jiaxi Luo421608e2014-07-07 14:38:00 -07001619
1620 result['wmatrix_url'] = rpc_utils.get_wmatrix_url()
Simran Basi71206ef2014-08-13 13:51:18 -07001621 result['is_moblab'] = bool(utils.is_moblab())
Jiaxi Luo421608e2014-07-07 14:38:00 -07001622
jadmanski0afbb632008-06-06 21:10:57 +00001623 return result
showard29f7cd22009-04-29 21:16:24 +00001624
1625
1626def get_server_time():
1627 return datetime.datetime.now().strftime("%Y-%m-%d %H:%M")