blob: 50623dbef9c1d1b45c8bc10b74b19a5f32fd7c9c [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)
75
76 # Master forwards the RPC to shards
77 if not utils.is_shard():
78 rpc_utils.fanout_rpc(label_model.host_set.all(), 'modify_label', False,
79 id=id, **data)
80
81 label_model.update_object(data)
mblighe8819cd2008-02-15 16:48:40 +000082
83
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)
90
91 # Master forwards the RPC to shards
92 if not utils.is_shard():
93 rpc_utils.fanout_rpc(label_model.host_set.all(), 'delete_label', False,
94 id=id)
95
96 label_model.delete()
mblighe8819cd2008-02-15 16:48:40 +000097
Prashanth Balasubramanian744898f2015-01-13 05:04:16 -080098
MK Ryu9c5fbbe2015-02-11 15:46:22 -080099def add_label(name, ignore_exception_if_exists=False, **kwargs):
MK Ryucf027c62015-03-04 12:00:50 -0800100 """Adds a new label of a given name.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800101
102 @param name: label name.
103 @param ignore_exception_if_exists: If True and the exception was
104 thrown due to the duplicated label name when adding a label,
105 then suppress the exception. Default is False.
106 @param kwargs: keyword args that store more info about a label
107 other than the name.
108 @return: int/long id of a new label.
109 """
110 # models.Label.add_object() throws model_logic.ValidationError
111 # when it is given a label name that already exists.
112 # However, ValidationError can be thrown with different errors,
113 # and those errors should be thrown up to the call chain.
114 try:
115 label = models.Label.add_object(name=name, **kwargs)
116 except:
117 exc_info = sys.exc_info()
118 if ignore_exception_if_exists:
119 label = rpc_utils.get_label(name)
120 # If the exception is raised not because of duplicated
121 # "name", then raise the original exception.
122 if label is None:
123 raise exc_info[0], exc_info[1], exc_info[2]
124 else:
125 raise exc_info[0], exc_info[1], exc_info[2]
126 return label.id
127
128
129def add_label_to_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800130 """Adds a label of the given id to the given hosts only in local DB.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800131
132 @param id: id or name of a label. More often a label name.
133 @param hosts: The hostnames of hosts that need the label.
134
135 @raises models.Label.DoesNotExist: If the label with id doesn't exist.
136 """
137 label = models.Label.smart_get(id)
138 host_objs = models.Host.smart_get_bulk(hosts)
139 if label.platform:
140 models.Host.check_no_platform(host_objs)
141 label.host_set.add(*host_objs)
142
143
MK Ryufbb002c2015-06-08 14:13:16 -0700144@rpc_utils.route_rpc_to_master
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800145def label_add_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800146 """Adds a label with the given id to the given hosts.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800147
148 This method should be run only on master not shards.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800149 The given label will be created if it doesn't exist, provided the `id`
150 supplied is a label name not an int/long id.
151
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800152 @param id: id or name of a label. More often a label name.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800153 @param hosts: A list of hostnames or ids. More often hostnames.
154
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800155 @raises ValueError: If the id specified is an int/long (label id)
156 while the label does not exist.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800157 """
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800158 try:
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800159 label = models.Label.smart_get(id)
160 except models.Label.DoesNotExist:
161 # This matches the type checks in smart_get, which is a hack
162 # in and off itself. The aim here is to create any non-existent
163 # label, which we cannot do if the 'id' specified isn't a label name.
164 if isinstance(id, basestring):
165 label = models.Label.smart_get(add_label(id))
166 else:
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800167 raise ValueError('Label id (%s) does not exist. Please specify '
168 'the argument, id, as a string (label name).'
169 % id)
MK Ryucf027c62015-03-04 12:00:50 -0800170
171 host_objs = models.Host.smart_get_bulk(hosts)
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800172 # Make sure the label exists on the shard with the same id
173 # as it is on the master.
MK Ryucf027c62015-03-04 12:00:50 -0800174 # It is possible that the label is already in a shard because
175 # we are adding a new label only to shards of hosts that the label
176 # is going to be attached.
177 # For example, we add a label L1 to a host in shard S1.
178 # Master and S1 will have L1 but other shards won't.
179 # Later, when we add the same label L1 to hosts in shards S1 and S2,
180 # S1 already has the label but S2 doesn't.
181 # S2 should have the new label without any problem.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800182 # We ignore exception in such a case.
183 rpc_utils.fanout_rpc(
MK Ryue019aae2015-07-07 12:46:07 -0700184 host_objs, 'add_label', include_hostnames=False,
185 name=label.name, ignore_exception_if_exists=True,
186 id=label.id, platform=label.platform)
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800187 rpc_utils.fanout_rpc(host_objs, 'add_label_to_hosts', id=id)
showardbbabf502008-06-06 00:02:02 +0000188
MK Ryu26f0c932015-05-28 18:14:33 -0700189 add_label_to_hosts(id, hosts)
190
showardbbabf502008-06-06 00:02:02 +0000191
MK Ryucf027c62015-03-04 12:00:50 -0800192def remove_label_from_hosts(id, hosts):
193 """Removes a label of the given id from the given hosts only in local DB.
194
195 @param id: id or name of a label.
196 @param hosts: The hostnames of hosts that need to remove the label from.
197 """
showardbe3ec042008-11-12 18:16:07 +0000198 host_objs = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000199 models.Label.smart_get(id).host_set.remove(*host_objs)
showardbbabf502008-06-06 00:02:02 +0000200
201
MK Ryufbb002c2015-06-08 14:13:16 -0700202@rpc_utils.route_rpc_to_master
MK Ryucf027c62015-03-04 12:00:50 -0800203def label_remove_hosts(id, hosts):
204 """Removes a label of the given id from the given hosts.
205
206 This method should be run only on master not shards.
207
208 @param id: id or name of a label.
209 @param hosts: A list of hostnames or ids. More often hostnames.
210 """
MK Ryucf027c62015-03-04 12:00:50 -0800211 host_objs = models.Host.smart_get_bulk(hosts)
212 rpc_utils.fanout_rpc(host_objs, 'remove_label_from_hosts', id=id)
213
MK Ryu26f0c932015-05-28 18:14:33 -0700214 remove_label_from_hosts(id, hosts)
215
MK Ryucf027c62015-03-04 12:00:50 -0800216
Jiaxi Luo31874592014-06-11 10:36:35 -0700217def get_labels(exclude_filters=(), **filter_data):
showardc92da832009-04-07 18:14:34 +0000218 """\
Jiaxi Luo31874592014-06-11 10:36:35 -0700219 @param exclude_filters: A sequence of dictionaries of filters.
220
showardc92da832009-04-07 18:14:34 +0000221 @returns A sequence of nested dictionaries of label information.
222 """
Jiaxi Luo31874592014-06-11 10:36:35 -0700223 labels = models.Label.query_objects(filter_data)
224 for exclude_filter in exclude_filters:
225 labels = labels.exclude(**exclude_filter)
226 return rpc_utils.prepare_rows_as_nested_dicts(labels, ('atomic_group',))
showardc92da832009-04-07 18:14:34 +0000227
228
229# atomic groups
230
showarde9450c92009-06-30 01:58:52 +0000231def add_atomic_group(name, max_number_of_machines=None, description=None):
showardc92da832009-04-07 18:14:34 +0000232 return models.AtomicGroup.add_object(
233 name=name, max_number_of_machines=max_number_of_machines,
234 description=description).id
235
236
237def modify_atomic_group(id, **data):
238 models.AtomicGroup.smart_get(id).update_object(data)
239
240
241def delete_atomic_group(id):
242 models.AtomicGroup.smart_get(id).delete()
243
244
245def atomic_group_add_labels(id, labels):
246 label_objs = models.Label.smart_get_bulk(labels)
247 models.AtomicGroup.smart_get(id).label_set.add(*label_objs)
248
249
250def atomic_group_remove_labels(id, labels):
251 label_objs = models.Label.smart_get_bulk(labels)
252 models.AtomicGroup.smart_get(id).label_set.remove(*label_objs)
253
254
255def get_atomic_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000256 return rpc_utils.prepare_for_serialization(
showardc92da832009-04-07 18:14:34 +0000257 models.AtomicGroup.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000258
259
260# hosts
261
Matthew Sartori68186332015-04-27 17:19:53 -0700262def add_host(hostname, status=None, locked=None, lock_reason='', protection=None):
263 if locked and not lock_reason:
264 raise model_logic.ValidationError(
265 {'locked': 'Please provide a reason for locking when adding host.'})
266
jadmanski0afbb632008-06-06 21:10:57 +0000267 return models.Host.add_object(hostname=hostname, status=status,
Matthew Sartori68186332015-04-27 17:19:53 -0700268 locked=locked, lock_reason=lock_reason,
269 protection=protection).id
mblighe8819cd2008-02-15 16:48:40 +0000270
271
MK Ryu33889612015-09-04 14:32:35 -0700272@rpc_utils.route_rpc_to_master
273def modify_host(id, **kwargs):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700274 """Modify local attributes of a host.
275
276 If this is called on the master, but the host is assigned to a shard, this
MK Ryu33889612015-09-04 14:32:35 -0700277 will call `modify_host_local` RPC to the responsible shard. This means if
278 a host is being locked using this function, this change will also propagate
279 to shards.
280 When this is called on a shard, the shard just routes the RPC to the master
281 and does nothing.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700282
283 @param id: id of the host to modify.
MK Ryu33889612015-09-04 14:32:35 -0700284 @param kwargs: key=value pairs of values to set on the host.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700285 """
MK Ryu33889612015-09-04 14:32:35 -0700286 rpc_utils.check_modify_host(kwargs)
showardce7c0922009-09-11 18:39:24 +0000287 host = models.Host.smart_get(id)
MK Ryu33889612015-09-04 14:32:35 -0700288 rpc_utils.check_modify_host_locking(host, kwargs)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700289
MK Ryud53e1492015-12-15 12:09:03 -0800290 # This is required to make `lock_time` for a host be exactly same
291 # between the master and a shard.
292 if kwargs.get('locked', None) and 'lock_time' not in kwargs:
293 kwargs['lock_time'] = datetime.datetime.now()
294
MK Ryu33889612015-09-04 14:32:35 -0700295 rpc_utils.fanout_rpc([host], 'modify_host_local',
296 include_hostnames=False, id=id, **kwargs)
297 host.update_object(kwargs)
mblighe8819cd2008-02-15 16:48:40 +0000298
299
MK Ryu33889612015-09-04 14:32:35 -0700300def modify_host_local(id, **kwargs):
301 """Modify host attributes in local DB.
302
303 @param id: Host id.
304 @param kwargs: key=value pairs of values to set on the host.
305 """
306 models.Host.smart_get(id).update_object(kwargs)
307
308
309@rpc_utils.route_rpc_to_master
showard276f9442009-05-20 00:33:16 +0000310def modify_hosts(host_filter_data, update_data):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700311 """Modify local attributes of multiple hosts.
312
313 If this is called on the master, but one of the hosts in that match the
MK Ryu33889612015-09-04 14:32:35 -0700314 filters is assigned to a shard, this will call `modify_hosts_local` RPC
315 to the responsible shard.
316 When this is called on a shard, the shard just routes the RPC to the master
317 and does nothing.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700318
319 The filters are always applied on the master, not on the shards. This means
320 if the states of a host differ on the master and a shard, the state on the
321 master will be used. I.e. this means:
322 A host was synced to Shard 1. On Shard 1 the status of the host was set to
323 'Repair Failed'.
324 - A call to modify_hosts with host_filter_data={'status': 'Ready'} will
325 update the host (both on the shard and on the master), because the state
326 of the host as the master knows it is still 'Ready'.
327 - A call to modify_hosts with host_filter_data={'status': 'Repair failed'
328 will not update the host, because the filter doesn't apply on the master.
329
showardbe0d8692009-08-20 23:42:44 +0000330 @param host_filter_data: Filters out which hosts to modify.
331 @param update_data: A dictionary with the changes to make to the hosts.
showard276f9442009-05-20 00:33:16 +0000332 """
showardbe0d8692009-08-20 23:42:44 +0000333 rpc_utils.check_modify_host(update_data)
showard276f9442009-05-20 00:33:16 +0000334 hosts = models.Host.query_objects(host_filter_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700335
336 affected_shard_hostnames = set()
337 affected_host_ids = []
338
Alex Miller9658a952013-05-14 16:40:02 -0700339 # Check all hosts before changing data for exception safety.
340 for host in hosts:
341 rpc_utils.check_modify_host_locking(host, update_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700342 if host.shard:
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800343 affected_shard_hostnames.add(host.shard.rpc_hostname())
Jakob Juelich50e91f72014-10-01 12:43:23 -0700344 affected_host_ids.append(host.id)
345
MK Ryud53e1492015-12-15 12:09:03 -0800346 # This is required to make `lock_time` for a host be exactly same
347 # between the master and a shard.
348 if update_data.get('locked', None) and 'lock_time' not in update_data:
349 update_data['lock_time'] = datetime.datetime.now()
350
MK Ryu33889612015-09-04 14:32:35 -0700351 # Caution: Changing the filter from the original here. See docstring.
352 rpc_utils.run_rpc_on_multiple_hostnames(
353 'modify_hosts_local', affected_shard_hostnames,
Jakob Juelich50e91f72014-10-01 12:43:23 -0700354 host_filter_data={'id__in': affected_host_ids},
355 update_data=update_data)
356
showard276f9442009-05-20 00:33:16 +0000357 for host in hosts:
358 host.update_object(update_data)
359
360
MK Ryu33889612015-09-04 14:32:35 -0700361def modify_hosts_local(host_filter_data, update_data):
362 """Modify attributes of hosts in local DB.
363
364 @param host_filter_data: Filters out which hosts to modify.
365 @param update_data: A dictionary with the changes to make to the hosts.
366 """
367 for host in models.Host.query_objects(host_filter_data):
368 host.update_object(update_data)
369
370
MK Ryufbb002c2015-06-08 14:13:16 -0700371def add_labels_to_host(id, labels):
372 """Adds labels to a given host only in local DB.
showardcafd16e2009-05-29 18:37:49 +0000373
MK Ryufbb002c2015-06-08 14:13:16 -0700374 @param id: id or hostname for a host.
375 @param labels: ids or names for labels.
376 """
377 label_objs = models.Label.smart_get_bulk(labels)
378 models.Host.smart_get(id).labels.add(*label_objs)
379
380
381@rpc_utils.route_rpc_to_master
382def host_add_labels(id, labels):
383 """Adds labels to a given host.
384
385 @param id: id or hostname for a host.
386 @param labels: ids or names for labels.
387
388 @raises ValidationError: If adding more than one platform label.
389 """
390 label_objs = models.Label.smart_get_bulk(labels)
391 platforms = [label.name for label in label_objs if label.platform]
showardcafd16e2009-05-29 18:37:49 +0000392 if len(platforms) > 1:
393 raise model_logic.ValidationError(
394 {'labels': 'Adding more than one platform label: %s' %
395 ', '.join(platforms)})
MK Ryufbb002c2015-06-08 14:13:16 -0700396
397 host_obj = models.Host.smart_get(id)
showardcafd16e2009-05-29 18:37:49 +0000398 if len(platforms) == 1:
MK Ryufbb002c2015-06-08 14:13:16 -0700399 models.Host.check_no_platform([host_obj])
400
401 rpc_utils.fanout_rpc([host_obj], 'add_labels_to_host', False,
402 id=id, labels=labels)
403 add_labels_to_host(id, labels)
mblighe8819cd2008-02-15 16:48:40 +0000404
405
MK Ryufbb002c2015-06-08 14:13:16 -0700406def remove_labels_from_host(id, labels):
407 """Removes labels from a given host only in local DB.
408
409 @param id: id or hostname for a host.
410 @param labels: ids or names for labels.
411 """
412 label_objs = models.Label.smart_get_bulk(labels)
413 models.Host.smart_get(id).labels.remove(*label_objs)
414
415
416@rpc_utils.route_rpc_to_master
mblighe8819cd2008-02-15 16:48:40 +0000417def host_remove_labels(id, labels):
MK Ryufbb002c2015-06-08 14:13:16 -0700418 """Removes labels from a given host.
419
420 @param id: id or hostname for a host.
421 @param labels: ids or names for labels.
422 """
423 host_obj = models.Host.smart_get(id)
424 rpc_utils.fanout_rpc([host_obj], 'remove_labels_from_host', False,
425 id=id, labels=labels)
426 remove_labels_from_host(id, labels)
mblighe8819cd2008-02-15 16:48:40 +0000427
428
MK Ryuacf35922014-10-03 14:56:49 -0700429def get_host_attribute(attribute, **host_filter_data):
430 """
431 @param attribute: string name of attribute
432 @param host_filter_data: filter data to apply to Hosts to choose hosts to
433 act upon
434 """
435 hosts = rpc_utils.get_host_query((), False, False, True, host_filter_data)
436 hosts = list(hosts)
437 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
438 'attribute_list')
439 host_attr_dicts = []
440 for host_obj in hosts:
441 for attr_obj in host_obj.attribute_list:
442 if attr_obj.attribute == attribute:
443 host_attr_dicts.append(attr_obj.get_object_dict())
444 return rpc_utils.prepare_for_serialization(host_attr_dicts)
445
446
showard0957a842009-05-11 19:25:08 +0000447def set_host_attribute(attribute, value, **host_filter_data):
448 """
MK Ryu26f0c932015-05-28 18:14:33 -0700449 @param attribute: string name of attribute
450 @param value: string, or None to delete an attribute
451 @param host_filter_data: filter data to apply to Hosts to choose hosts to
452 act upon
showard0957a842009-05-11 19:25:08 +0000453 """
454 assert host_filter_data # disallow accidental actions on all hosts
455 hosts = models.Host.query_objects(host_filter_data)
456 models.AclGroup.check_for_acl_violation_hosts(hosts)
457
MK Ryu26f0c932015-05-28 18:14:33 -0700458 # Master forwards this RPC to shards.
459 if not utils.is_shard():
460 rpc_utils.fanout_rpc(hosts, 'set_host_attribute', False,
461 attribute=attribute, value=value, **host_filter_data)
462
showard0957a842009-05-11 19:25:08 +0000463 for host in hosts:
showardf8b19042009-05-12 17:22:49 +0000464 host.set_or_delete_attribute(attribute, value)
showard0957a842009-05-11 19:25:08 +0000465
466
Jakob Juelich50e91f72014-10-01 12:43:23 -0700467@rpc_utils.forward_single_host_rpc_to_shard
mblighe8819cd2008-02-15 16:48:40 +0000468def delete_host(id):
jadmanski0afbb632008-06-06 21:10:57 +0000469 models.Host.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000470
471
showard87cc38f2009-08-20 23:37:04 +0000472def get_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
Dan Shi37df54d2015-12-14 11:16:28 -0800473 exclude_atomic_group_hosts=False, valid_only=True,
474 include_current_job=False, **filter_data):
475 """Get a list of dictionaries which contains the information of hosts.
476
showard87cc38f2009-08-20 23:37:04 +0000477 @param multiple_labels: match hosts in all of the labels given. Should
478 be a list of label names.
479 @param exclude_only_if_needed_labels: Exclude hosts with at least one
480 "only_if_needed" label applied.
481 @param exclude_atomic_group_hosts: Exclude hosts that have one or more
482 atomic group labels associated with them.
Dan Shi37df54d2015-12-14 11:16:28 -0800483 @param include_current_job: Set to True to include ids of currently running
484 job and special task.
jadmanski0afbb632008-06-06 21:10:57 +0000485 """
showard43a3d262008-11-12 18:17:05 +0000486 hosts = rpc_utils.get_host_query(multiple_labels,
487 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000488 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000489 valid_only, filter_data)
showard0957a842009-05-11 19:25:08 +0000490 hosts = list(hosts)
491 models.Host.objects.populate_relationships(hosts, models.Label,
492 'label_list')
493 models.Host.objects.populate_relationships(hosts, models.AclGroup,
494 'acl_list')
495 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
496 'attribute_list')
showard43a3d262008-11-12 18:17:05 +0000497 host_dicts = []
498 for host_obj in hosts:
499 host_dict = host_obj.get_object_dict()
showard0957a842009-05-11 19:25:08 +0000500 host_dict['labels'] = [label.name for label in host_obj.label_list]
showard909c9142009-07-07 20:54:42 +0000501 host_dict['platform'], host_dict['atomic_group'] = (rpc_utils.
502 find_platform_and_atomic_group(host_obj))
showard0957a842009-05-11 19:25:08 +0000503 host_dict['acls'] = [acl.name for acl in host_obj.acl_list]
504 host_dict['attributes'] = dict((attribute.attribute, attribute.value)
505 for attribute in host_obj.attribute_list)
Dan Shi37df54d2015-12-14 11:16:28 -0800506 if include_current_job:
507 host_dict['current_job'] = None
508 host_dict['current_special_task'] = None
509 entries = models.HostQueueEntry.objects.filter(
510 host_id=host_dict['id'], active=True, complete=False)
511 if entries:
512 host_dict['current_job'] = (
513 entries[0].get_object_dict()['job'])
514 tasks = models.SpecialTask.objects.filter(
515 host_id=host_dict['id'], is_active=True, is_complete=False)
516 if tasks:
517 host_dict['current_special_task'] = (
518 '%d-%s' % (tasks[0].get_object_dict()['id'],
519 tasks[0].get_object_dict()['task'].lower()))
showard43a3d262008-11-12 18:17:05 +0000520 host_dicts.append(host_dict)
521 return rpc_utils.prepare_for_serialization(host_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000522
523
showard87cc38f2009-08-20 23:37:04 +0000524def get_num_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000525 exclude_atomic_group_hosts=False, valid_only=True,
526 **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000527 """
528 Same parameters as get_hosts().
529
530 @returns The number of matching hosts.
531 """
showard43a3d262008-11-12 18:17:05 +0000532 hosts = rpc_utils.get_host_query(multiple_labels,
533 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000534 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000535 valid_only, filter_data)
showard43a3d262008-11-12 18:17:05 +0000536 return hosts.count()
showard1385b162008-03-13 15:59:40 +0000537
mblighe8819cd2008-02-15 16:48:40 +0000538
539# tests
540
showard909c7a62008-07-15 21:52:38 +0000541def add_test(name, test_type, path, author=None, dependencies=None,
showard3d9899a2008-07-31 02:11:58 +0000542 experimental=True, run_verify=None, test_class=None,
showard909c7a62008-07-15 21:52:38 +0000543 test_time=None, test_category=None, description=None,
544 sync_count=1):
jadmanski0afbb632008-06-06 21:10:57 +0000545 return models.Test.add_object(name=name, test_type=test_type, path=path,
showard909c7a62008-07-15 21:52:38 +0000546 author=author, dependencies=dependencies,
547 experimental=experimental,
548 run_verify=run_verify, test_time=test_time,
549 test_category=test_category,
550 sync_count=sync_count,
jadmanski0afbb632008-06-06 21:10:57 +0000551 test_class=test_class,
552 description=description).id
mblighe8819cd2008-02-15 16:48:40 +0000553
554
555def modify_test(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000556 models.Test.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000557
558
559def delete_test(id):
jadmanski0afbb632008-06-06 21:10:57 +0000560 models.Test.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000561
562
563def get_tests(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000564 return rpc_utils.prepare_for_serialization(
565 models.Test.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000566
567
Moises Osorio2dc7a102014-12-02 18:24:02 -0800568@_timer.decorate
569def get_tests_status_counts_by_job_name_label(job_name_prefix, label_name):
570 """Gets the counts of all passed and failed tests from the matching jobs.
571
572 @param job_name_prefix: Name prefix of the jobs to get the summary from, e.g.,
573 'butterfly-release/R40-6457.21.0/bvt-cq/'.
574 @param label_name: Label that must be set in the jobs, e.g.,
575 'cros-version:butterfly-release/R40-6457.21.0'.
576
577 @returns A summary of the counts of all the passed and failed tests.
578 """
579 job_ids = list(models.Job.objects.filter(
580 name__startswith=job_name_prefix,
581 dependency_labels__name=label_name).values_list(
582 'pk', flat=True))
583 summary = {'passed': 0, 'failed': 0}
584 if not job_ids:
585 return summary
586
587 counts = (tko_models.TestView.objects.filter(
588 afe_job_id__in=job_ids).exclude(
589 test_name='SERVER_JOB').exclude(
590 test_name__startswith='CLIENT_JOB').values(
591 'status').annotate(
592 count=Count('status')))
593 for status in counts:
594 if status['status'] == 'GOOD':
595 summary['passed'] += status['count']
596 else:
597 summary['failed'] += status['count']
598 return summary
599
600
showard2b9a88b2008-06-13 20:55:03 +0000601# profilers
602
603def add_profiler(name, description=None):
604 return models.Profiler.add_object(name=name, description=description).id
605
606
607def modify_profiler(id, **data):
608 models.Profiler.smart_get(id).update_object(data)
609
610
611def delete_profiler(id):
612 models.Profiler.smart_get(id).delete()
613
614
615def get_profilers(**filter_data):
616 return rpc_utils.prepare_for_serialization(
617 models.Profiler.list_objects(filter_data))
618
619
mblighe8819cd2008-02-15 16:48:40 +0000620# users
621
622def add_user(login, access_level=None):
jadmanski0afbb632008-06-06 21:10:57 +0000623 return models.User.add_object(login=login, access_level=access_level).id
mblighe8819cd2008-02-15 16:48:40 +0000624
625
626def modify_user(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000627 models.User.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000628
629
630def delete_user(id):
jadmanski0afbb632008-06-06 21:10:57 +0000631 models.User.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000632
633
634def get_users(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000635 return rpc_utils.prepare_for_serialization(
636 models.User.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000637
638
639# acl groups
640
641def add_acl_group(name, description=None):
showard04f2cd82008-07-25 20:53:31 +0000642 group = models.AclGroup.add_object(name=name, description=description)
showard64a95952010-01-13 21:27:16 +0000643 group.users.add(models.User.current_user())
showard04f2cd82008-07-25 20:53:31 +0000644 return group.id
mblighe8819cd2008-02-15 16:48:40 +0000645
646
647def modify_acl_group(id, **data):
showard04f2cd82008-07-25 20:53:31 +0000648 group = models.AclGroup.smart_get(id)
649 group.check_for_acl_violation_acl_group()
650 group.update_object(data)
651 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000652
653
654def acl_group_add_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000655 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000656 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000657 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000658 group.users.add(*users)
mblighe8819cd2008-02-15 16:48:40 +0000659
660
661def acl_group_remove_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000662 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000663 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000664 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000665 group.users.remove(*users)
showard04f2cd82008-07-25 20:53:31 +0000666 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000667
668
669def acl_group_add_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000670 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000671 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000672 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000673 group.hosts.add(*hosts)
showard08f981b2008-06-24 21:59:03 +0000674 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000675
676
677def acl_group_remove_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000678 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000679 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000680 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000681 group.hosts.remove(*hosts)
showard08f981b2008-06-24 21:59:03 +0000682 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000683
684
685def delete_acl_group(id):
jadmanski0afbb632008-06-06 21:10:57 +0000686 models.AclGroup.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000687
688
689def get_acl_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000690 acl_groups = models.AclGroup.list_objects(filter_data)
691 for acl_group in acl_groups:
692 acl_group_obj = models.AclGroup.objects.get(id=acl_group['id'])
693 acl_group['users'] = [user.login
694 for user in acl_group_obj.users.all()]
695 acl_group['hosts'] = [host.hostname
696 for host in acl_group_obj.hosts.all()]
697 return rpc_utils.prepare_for_serialization(acl_groups)
mblighe8819cd2008-02-15 16:48:40 +0000698
699
700# jobs
701
mbligh120351e2009-01-24 01:40:45 +0000702def generate_control_file(tests=(), kernel=None, label=None, profilers=(),
showard91f85102009-10-12 20:34:52 +0000703 client_control_file='', use_container=False,
Matthew Sartori10438092015-06-24 14:30:18 -0700704 profile_only=None, upload_kernel_config=False,
705 db_tests=True):
jadmanski0afbb632008-06-06 21:10:57 +0000706 """
mbligh120351e2009-01-24 01:40:45 +0000707 Generates a client-side control file to load a kernel and run tests.
708
Matthew Sartori10438092015-06-24 14:30:18 -0700709 @param tests List of tests to run. See db_tests for more information.
mbligha3c58d22009-08-24 22:01:51 +0000710 @param kernel A list of kernel info dictionaries configuring which kernels
711 to boot for this job and other options for them
mbligh120351e2009-01-24 01:40:45 +0000712 @param label Name of label to grab kernel config from.
713 @param profilers List of profilers to activate during the job.
714 @param client_control_file The contents of a client-side control file to
715 run at the end of all tests. If this is supplied, all tests must be
716 client side.
717 TODO: in the future we should support server control files directly
718 to wrap with a kernel. That'll require changing the parameter
719 name and adding a boolean to indicate if it is a client or server
720 control file.
721 @param use_container unused argument today. TODO: Enable containers
722 on the host during a client side test.
showard91f85102009-10-12 20:34:52 +0000723 @param profile_only A boolean that indicates what default profile_only
724 mode to use in the control file. Passing None will generate a
725 control file that does not explcitly set the default mode at all.
showard232b7ae2009-11-10 00:46:48 +0000726 @param upload_kernel_config: if enabled it will generate server control
727 file code that uploads the kernel config file to the client and
728 tells the client of the new (local) path when compiling the kernel;
729 the tests must be server side tests
Matthew Sartori10438092015-06-24 14:30:18 -0700730 @param db_tests: if True, the test object can be found in the database
731 backing the test model. In this case, tests is a tuple
732 of test IDs which are used to retrieve the test objects
733 from the database. If False, tests is a tuple of test
734 dictionaries stored client-side in the AFE.
mbligh120351e2009-01-24 01:40:45 +0000735
736 @returns a dict with the following keys:
737 control_file: str, The control file text.
738 is_server: bool, is the control file a server-side control file?
739 synch_count: How many machines the job uses per autoserv execution.
740 synch_count == 1 means the job is asynchronous.
741 dependencies: A list of the names of labels on which the job depends.
742 """
showardd86debe2009-06-10 17:37:56 +0000743 if not tests and not client_control_file:
showard2bab8f42008-11-12 18:15:22 +0000744 return dict(control_file='', is_server=False, synch_count=1,
showard989f25d2008-10-01 11:38:11 +0000745 dependencies=[])
mblighe8819cd2008-02-15 16:48:40 +0000746
showard989f25d2008-10-01 11:38:11 +0000747 cf_info, test_objects, profiler_objects, label = (
showard2b9a88b2008-06-13 20:55:03 +0000748 rpc_utils.prepare_generate_control_file(tests, kernel, label,
Matthew Sartori10438092015-06-24 14:30:18 -0700749 profilers, db_tests))
showard989f25d2008-10-01 11:38:11 +0000750 cf_info['control_file'] = control_file.generate_control(
mbligha3c58d22009-08-24 22:01:51 +0000751 tests=test_objects, kernels=kernel, platform=label,
mbligh120351e2009-01-24 01:40:45 +0000752 profilers=profiler_objects, is_server=cf_info['is_server'],
showard232b7ae2009-11-10 00:46:48 +0000753 client_control_file=client_control_file, profile_only=profile_only,
754 upload_kernel_config=upload_kernel_config)
showard989f25d2008-10-01 11:38:11 +0000755 return cf_info
mblighe8819cd2008-02-15 16:48:40 +0000756
757
jamesren4a41e012010-07-16 22:33:48 +0000758def create_parameterized_job(name, priority, test, parameters, kernel=None,
759 label=None, profilers=(), profiler_parameters=None,
760 use_container=False, profile_only=None,
761 upload_kernel_config=False, hosts=(),
762 meta_hosts=(), one_time_hosts=(),
763 atomic_group_name=None, synch_count=None,
764 is_template=False, timeout=None,
Simran Basi7e605742013-11-12 13:43:36 -0800765 timeout_mins=None, max_runtime_mins=None,
766 run_verify=False, email_list='', dependencies=(),
767 reboot_before=None, reboot_after=None,
768 parse_failed_repair=None, hostless=False,
Dan Shiec1d47d2015-02-13 11:38:13 -0800769 keyvals=None, drone_set=None, run_reset=True,
Dan Shi2a5297b2015-07-23 17:03:29 -0700770 require_ssp=None):
jamesren4a41e012010-07-16 22:33:48 +0000771 """
772 Creates and enqueues a parameterized job.
773
774 Most parameters a combination of the parameters for generate_control_file()
775 and create_job(), with the exception of:
776
777 @param test name or ID of the test to run
778 @param parameters a map of parameter name ->
779 tuple of (param value, param type)
780 @param profiler_parameters a dictionary of parameters for the profilers:
781 key: profiler name
782 value: dict of param name -> tuple of
783 (param value,
784 param type)
785 """
786 # Save the values of the passed arguments here. What we're going to do with
787 # them is pass them all to rpc_utils.get_create_job_common_args(), which
788 # will extract the subset of these arguments that apply for
789 # rpc_utils.create_job_common(), which we then pass in to that function.
790 args = locals()
791
792 # Set up the parameterized job configs
793 test_obj = models.Test.smart_get(test)
Aviv Keshet3dd8beb2013-05-13 17:36:04 -0700794 control_type = test_obj.test_type
jamesren4a41e012010-07-16 22:33:48 +0000795
796 try:
797 label = models.Label.smart_get(label)
798 except models.Label.DoesNotExist:
799 label = None
800
801 kernel_objs = models.Kernel.create_kernels(kernel)
802 profiler_objs = [models.Profiler.smart_get(profiler)
803 for profiler in profilers]
804
805 parameterized_job = models.ParameterizedJob.objects.create(
806 test=test_obj, label=label, use_container=use_container,
807 profile_only=profile_only,
808 upload_kernel_config=upload_kernel_config)
809 parameterized_job.kernels.add(*kernel_objs)
810
811 for profiler in profiler_objs:
812 parameterized_profiler = models.ParameterizedJobProfiler.objects.create(
813 parameterized_job=parameterized_job,
814 profiler=profiler)
815 profiler_params = profiler_parameters.get(profiler.name, {})
816 for name, (value, param_type) in profiler_params.iteritems():
817 models.ParameterizedJobProfilerParameter.objects.create(
818 parameterized_job_profiler=parameterized_profiler,
819 parameter_name=name,
820 parameter_value=value,
821 parameter_type=param_type)
822
823 try:
824 for parameter in test_obj.testparameter_set.all():
825 if parameter.name in parameters:
826 param_value, param_type = parameters.pop(parameter.name)
827 parameterized_job.parameterizedjobparameter_set.create(
828 test_parameter=parameter, parameter_value=param_value,
829 parameter_type=param_type)
830
831 if parameters:
832 raise Exception('Extra parameters remain: %r' % parameters)
833
834 return rpc_utils.create_job_common(
835 parameterized_job=parameterized_job.id,
836 control_type=control_type,
837 **rpc_utils.get_create_job_common_args(args))
838 except:
839 parameterized_job.delete()
840 raise
841
842
Simran Basib6ec8ae2014-04-23 12:05:08 -0700843def create_job_page_handler(name, priority, control_file, control_type,
Dan Shid215dbe2015-06-18 16:14:59 -0700844 image=None, hostless=False, firmware_rw_build=None,
845 firmware_ro_build=None, test_source_build=None,
846 **kwargs):
Simran Basib6ec8ae2014-04-23 12:05:08 -0700847 """\
848 Create and enqueue a job.
849
850 @param name name of this job
851 @param priority Integer priority of this job. Higher is more important.
852 @param control_file String contents of the control file.
853 @param control_type Type of control file, Client or Server.
Dan Shid215dbe2015-06-18 16:14:59 -0700854 @param image: ChromeOS build to be installed in the dut. Default to None.
855 @param firmware_rw_build: Firmware build to update RW firmware. Default to
856 None, i.e., RW firmware will not be updated.
857 @param firmware_ro_build: Firmware build to update RO firmware. Default to
858 None, i.e., RO firmware will not be updated.
859 @param test_source_build: Build to be used to retrieve test code. Default
860 to None.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700861 @param kwargs extra args that will be required by create_suite_job or
862 create_job.
863
864 @returns The created Job id number.
865 """
866 control_file = rpc_utils.encode_ascii(control_file)
Jiaxi Luodd67beb2014-07-18 16:28:31 -0700867 if not control_file:
868 raise model_logic.ValidationError({
869 'control_file' : "Control file cannot be empty"})
Simran Basib6ec8ae2014-04-23 12:05:08 -0700870
871 if image and hostless:
Dan Shid215dbe2015-06-18 16:14:59 -0700872 builds = {}
873 builds[provision.CROS_VERSION_PREFIX] = image
874 if firmware_rw_build:
Dan Shi0723bf52015-06-24 10:52:38 -0700875 builds[provision.FW_RW_VERSION_PREFIX] = firmware_rw_build
Dan Shid215dbe2015-06-18 16:14:59 -0700876 if firmware_ro_build:
877 builds[provision.FW_RO_VERSION_PREFIX] = firmware_ro_build
Simran Basib6ec8ae2014-04-23 12:05:08 -0700878 return site_rpc_interface.create_suite_job(
879 name=name, control_file=control_file, priority=priority,
Dan Shid215dbe2015-06-18 16:14:59 -0700880 builds=builds, test_source_build=test_source_build, **kwargs)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700881 return create_job(name, priority, control_file, control_type, image=image,
882 hostless=hostless, **kwargs)
883
884
MK Ryue301eb72015-06-25 12:51:02 -0700885@rpc_utils.route_rpc_to_master
showard12f3e322009-05-13 21:27:42 +0000886def create_job(name, priority, control_file, control_type,
887 hosts=(), meta_hosts=(), one_time_hosts=(),
888 atomic_group_name=None, synch_count=None, is_template=False,
Simran Basi7e605742013-11-12 13:43:36 -0800889 timeout=None, timeout_mins=None, max_runtime_mins=None,
890 run_verify=False, email_list='', dependencies=(),
891 reboot_before=None, reboot_after=None, parse_failed_repair=None,
892 hostless=False, keyvals=None, drone_set=None, image=None,
Dan Shiec1d47d2015-02-13 11:38:13 -0800893 parent_job_id=None, test_retry=0, run_reset=True,
894 require_ssp=None, args=(), **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +0000895 """\
896 Create and enqueue a job.
mblighe8819cd2008-02-15 16:48:40 +0000897
showarda1e74b32009-05-12 17:32:04 +0000898 @param name name of this job
Alex Miller7d658cf2013-09-04 16:00:35 -0700899 @param priority Integer priority of this job. Higher is more important.
showarda1e74b32009-05-12 17:32:04 +0000900 @param control_file String contents of the control file.
901 @param control_type Type of control file, Client or Server.
902 @param synch_count How many machines the job uses per autoserv execution.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700903 synch_count == 1 means the job is asynchronous. If an atomic group is
904 given this value is treated as a minimum.
showarda1e74b32009-05-12 17:32:04 +0000905 @param is_template If true then create a template job.
906 @param timeout Hours after this call returns until the job times out.
Simran Basi7e605742013-11-12 13:43:36 -0800907 @param timeout_mins Minutes after this call returns until the job times
Jiaxi Luo90190c92014-06-18 12:35:57 -0700908 out.
Simran Basi34217022012-11-06 13:43:15 -0800909 @param max_runtime_mins Minutes from job starting time until job times out
showarda1e74b32009-05-12 17:32:04 +0000910 @param run_verify Should the host be verified before running the test?
911 @param email_list String containing emails to mail when the job is done
912 @param dependencies List of label names on which this job depends
913 @param reboot_before Never, If dirty, or Always
914 @param reboot_after Never, If all tests passed, or Always
915 @param parse_failed_repair if true, results of failed repairs launched by
Jiaxi Luo90190c92014-06-18 12:35:57 -0700916 this job will be parsed as part of the job.
showarda9545c02009-12-18 22:44:26 +0000917 @param hostless if true, create a hostless job
showardc1a98d12010-01-15 00:22:22 +0000918 @param keyvals dict of keyvals to associate with the job
showarda1e74b32009-05-12 17:32:04 +0000919 @param hosts List of hosts to run job on.
920 @param meta_hosts List where each entry is a label name, and for each entry
Jiaxi Luo90190c92014-06-18 12:35:57 -0700921 one host will be chosen from that label to run the job on.
showarda1e74b32009-05-12 17:32:04 +0000922 @param one_time_hosts List of hosts not in the database to run the job on.
923 @param atomic_group_name The name of an atomic group to schedule the job on.
jamesren76fcf192010-04-21 20:39:50 +0000924 @param drone_set The name of the drone set to run this test on.
Paul Pendlebury5a8c6ad2011-02-01 07:20:17 -0800925 @param image OS image to install before running job.
Aviv Keshet0b9cfc92013-02-05 11:36:02 -0800926 @param parent_job_id id of a job considered to be parent of created job.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700927 @param test_retry Number of times to retry test if the test did not
Jiaxi Luo90190c92014-06-18 12:35:57 -0700928 complete successfully. (optional, default: 0)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700929 @param run_reset Should the host be reset before running the test?
Dan Shiec1d47d2015-02-13 11:38:13 -0800930 @param require_ssp Set to True to require server-side packaging to run the
931 test. If it's set to None, drone will still try to run
932 the server side with server-side packaging. If the
933 autotest-server package doesn't exist for the build or
934 image is not set, drone will run the test without server-
935 side packaging. Default is None.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700936 @param args A list of args to be injected into control file.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700937 @param kwargs extra keyword args. NOT USED.
showardc92da832009-04-07 18:14:34 +0000938
939 @returns The created Job id number.
jadmanski0afbb632008-06-06 21:10:57 +0000940 """
Jiaxi Luo90190c92014-06-18 12:35:57 -0700941 if args:
942 control_file = tools.inject_vars({'args': args}, control_file)
943
Simran Basiab5a1bf2014-05-28 15:39:44 -0700944 if image is None:
945 return rpc_utils.create_job_common(
946 **rpc_utils.get_create_job_common_args(locals()))
947
Simran Basi6157e8e2015-12-07 18:22:34 -0800948 # Translate the image name, in case its a relative build name.
949 ds = dev_server.ImageServer.resolve(image)
950 image = ds.translate(image)
951
Simran Basiab5a1bf2014-05-28 15:39:44 -0700952 # When image is supplied use a known parameterized test already in the
953 # database to pass the OS image path from the front end, through the
954 # scheduler, and finally to autoserv as the --image parameter.
955
956 # The test autoupdate_ParameterizedJob is in afe_autotests and used to
957 # instantiate a Test object and from there a ParameterizedJob.
958 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
959 known_parameterized_job = models.ParameterizedJob.objects.create(
960 test=known_test_obj)
961
962 # autoupdate_ParameterizedJob has a single parameter, the image parameter,
963 # stored in the table afe_test_parameters. We retrieve and set this
964 # instance of the parameter to the OS image path.
965 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
966 name='image')
967 known_parameterized_job.parameterizedjobparameter_set.create(
968 test_parameter=image_parameter, parameter_value=image,
969 parameter_type='string')
970
Dan Shid215dbe2015-06-18 16:14:59 -0700971 # TODO(crbug.com/502638): save firmware build etc to parameterized_job.
972
Simran Basiab5a1bf2014-05-28 15:39:44 -0700973 # By passing a parameterized_job to create_job_common the job entry in
974 # the afe_jobs table will have the field parameterized_job_id set.
975 # The scheduler uses this id in the afe_parameterized_jobs table to
976 # match this job to our known test, and then with the
977 # afe_parameterized_job_parameters table to get the actual image path.
jamesren4a41e012010-07-16 22:33:48 +0000978 return rpc_utils.create_job_common(
Simran Basiab5a1bf2014-05-28 15:39:44 -0700979 parameterized_job=known_parameterized_job.id,
jamesren4a41e012010-07-16 22:33:48 +0000980 **rpc_utils.get_create_job_common_args(locals()))
mblighe8819cd2008-02-15 16:48:40 +0000981
982
showard9dbdcda2008-10-14 17:34:36 +0000983def abort_host_queue_entries(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000984 """\
showard9dbdcda2008-10-14 17:34:36 +0000985 Abort a set of host queue entries.
Fang Deng63b0e452014-12-19 14:38:15 -0800986
987 @return: A list of dictionaries, each contains information
988 about an aborted HQE.
jadmanski0afbb632008-06-06 21:10:57 +0000989 """
showard9dbdcda2008-10-14 17:34:36 +0000990 query = models.HostQueueEntry.query_objects(filter_data)
beepsfaecbce2013-10-29 11:35:10 -0700991
992 # Dont allow aborts on:
993 # 1. Jobs that have already completed (whether or not they were aborted)
994 # 2. Jobs that we have already been aborted (but may not have completed)
995 query = query.filter(complete=False).filter(aborted=False)
showarddc817512008-11-12 18:16:41 +0000996 models.AclGroup.check_abort_permissions(query)
showard9dbdcda2008-10-14 17:34:36 +0000997 host_queue_entries = list(query.select_related())
showard2bab8f42008-11-12 18:15:22 +0000998 rpc_utils.check_abort_synchronous_jobs(host_queue_entries)
mblighe8819cd2008-02-15 16:48:40 +0000999
Simran Basic1b26762013-06-26 14:23:21 -07001000 models.HostQueueEntry.abort_host_queue_entries(host_queue_entries)
Fang Deng63b0e452014-12-19 14:38:15 -08001001 hqe_info = [{'HostQueueEntry': hqe.id, 'Job': hqe.job_id,
1002 'Job name': hqe.job.name} for hqe in host_queue_entries]
1003 return hqe_info
showard9d821ab2008-07-11 16:54:29 +00001004
1005
beeps8bb1f7d2013-08-05 01:30:09 -07001006def abort_special_tasks(**filter_data):
1007 """\
1008 Abort the special task, or tasks, specified in the filter.
1009 """
1010 query = models.SpecialTask.query_objects(filter_data)
1011 special_tasks = query.filter(is_active=True)
1012 for task in special_tasks:
1013 task.abort()
1014
1015
Simran Basi73dae552013-02-25 14:57:46 -08001016def _call_special_tasks_on_hosts(task, hosts):
1017 """\
1018 Schedules a set of hosts for a special task.
1019
1020 @returns A list of hostnames that a special task was created for.
1021 """
1022 models.AclGroup.check_for_acl_violation_hosts(hosts)
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -08001023 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -08001024 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -08001025 raise ValueError('The following hosts are on shards, please '
1026 'follow the link to the shards and create jobs '
1027 'there instead. %s.' % shard_host_map)
Simran Basi73dae552013-02-25 14:57:46 -08001028 for host in hosts:
1029 models.SpecialTask.schedule_special_task(host, task)
1030 return list(sorted(host.hostname for host in hosts))
1031
1032
MK Ryu5aa25042015-07-28 16:08:04 -07001033def _forward_special_tasks_on_hosts(task, rpc, **filter_data):
1034 """Forward special tasks to corresponding shards.
mbligh4e545a52009-12-19 05:30:39 +00001035
MK Ryu5aa25042015-07-28 16:08:04 -07001036 For master, when special tasks are fired on hosts that are sharded,
1037 forward the RPC to corresponding shards.
1038
1039 For shard, create special task records in local DB.
1040
1041 @param task: Enum value of frontend.afe.models.SpecialTask.Task
1042 @param rpc: RPC name to forward.
1043 @param filter_data: Filter keywords to be used for DB query.
1044
1045 @return: A list of hostnames that a special task was created for.
showard1ff7b2e2009-05-15 23:17:18 +00001046 """
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001047 hosts = models.Host.query_objects(filter_data)
1048 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts, rpc_hostnames=True)
1049
1050 # Filter out hosts on a shard from those on the master, forward
1051 # rpcs to the shard with an additional hostname__in filter, and
1052 # create a local SpecialTask for each remaining host.
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -08001053 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001054 hosts = [h for h in hosts if h.shard is None]
1055 for shard, hostnames in shard_host_map.iteritems():
1056
1057 # The main client of this module is the frontend website, and
1058 # it invokes it with an 'id' or an 'id__in' filter. Regardless,
1059 # the 'hostname' filter should narrow down the list of hosts on
1060 # each shard even though we supply all the ids in filter_data.
1061 # This method uses hostname instead of id because it fits better
MK Ryu5aa25042015-07-28 16:08:04 -07001062 # with the overall architecture of redirection functions in
1063 # rpc_utils.
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001064 shard_filter = filter_data.copy()
1065 shard_filter['hostname__in'] = hostnames
1066 rpc_utils.run_rpc_on_multiple_hostnames(
MK Ryu5aa25042015-07-28 16:08:04 -07001067 rpc, [shard], **shard_filter)
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001068
1069 # There is a race condition here if someone assigns a shard to one of these
1070 # hosts before we create the task. The host will stay on the master if:
1071 # 1. The host is not Ready
1072 # 2. The host is Ready but has a task
1073 # But if the host is Ready and doesn't have a task yet, it will get sent
1074 # to the shard as we're creating a task here.
1075
1076 # Given that we only rarely verify Ready hosts it isn't worth putting this
1077 # entire method in a transaction. The worst case scenario is that we have
MK Ryu5aa25042015-07-28 16:08:04 -07001078 # a verify running on a Ready host while the shard is using it, if the
1079 # verify fails no subsequent tasks will be created against the host on the
1080 # master, and verifies are safe enough that this is OK.
1081 return _call_special_tasks_on_hosts(task, hosts)
1082
1083
1084def reverify_hosts(**filter_data):
1085 """\
1086 Schedules a set of hosts for verify.
1087
1088 @returns A list of hostnames that a verify task was created for.
1089 """
1090 return _forward_special_tasks_on_hosts(
1091 models.SpecialTask.Task.VERIFY, 'reverify_hosts', **filter_data)
Simran Basi73dae552013-02-25 14:57:46 -08001092
1093
1094def repair_hosts(**filter_data):
1095 """\
1096 Schedules a set of hosts for repair.
1097
1098 @returns A list of hostnames that a repair task was created for.
1099 """
MK Ryu5aa25042015-07-28 16:08:04 -07001100 return _forward_special_tasks_on_hosts(
1101 models.SpecialTask.Task.REPAIR, 'repair_hosts', **filter_data)
showard1ff7b2e2009-05-15 23:17:18 +00001102
1103
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001104def get_jobs(not_yet_run=False, running=False, finished=False,
1105 suite=False, sub=False, standalone=False, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001106 """\
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001107 Extra status filter args for get_jobs:
jadmanski0afbb632008-06-06 21:10:57 +00001108 -not_yet_run: Include only jobs that have not yet started running.
1109 -running: Include only jobs that have start running but for which not
1110 all hosts have completed.
1111 -finished: Include only jobs for which all hosts have completed (or
1112 aborted).
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001113
1114 Extra type filter args for get_jobs:
1115 -suite: Include only jobs with child jobs.
1116 -sub: Include only jobs with a parent job.
1117 -standalone: Inlcude only jobs with no child or parent jobs.
1118 At most one of these three fields should be specified.
jadmanski0afbb632008-06-06 21:10:57 +00001119 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001120 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
1121 running,
1122 finished)
1123 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
1124 suite,
1125 sub,
1126 standalone)
showard0957a842009-05-11 19:25:08 +00001127 job_dicts = []
1128 jobs = list(models.Job.query_objects(filter_data))
1129 models.Job.objects.populate_relationships(jobs, models.Label,
1130 'dependencies')
showardc1a98d12010-01-15 00:22:22 +00001131 models.Job.objects.populate_relationships(jobs, models.JobKeyval, 'keyvals')
showard0957a842009-05-11 19:25:08 +00001132 for job in jobs:
1133 job_dict = job.get_object_dict()
1134 job_dict['dependencies'] = ','.join(label.name
1135 for label in job.dependencies)
showardc1a98d12010-01-15 00:22:22 +00001136 job_dict['keyvals'] = dict((keyval.key, keyval.value)
1137 for keyval in job.keyvals)
Eric Lid23bc192011-02-09 14:38:57 -08001138 if job.parameterized_job:
1139 job_dict['image'] = get_parameterized_autoupdate_image_url(job)
showard0957a842009-05-11 19:25:08 +00001140 job_dicts.append(job_dict)
1141 return rpc_utils.prepare_for_serialization(job_dicts)
mblighe8819cd2008-02-15 16:48:40 +00001142
1143
1144def get_num_jobs(not_yet_run=False, running=False, finished=False,
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001145 suite=False, sub=False, standalone=False,
jadmanski0afbb632008-06-06 21:10:57 +00001146 **filter_data):
1147 """\
1148 See get_jobs() for documentation of extra filter parameters.
1149 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001150 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
1151 running,
1152 finished)
1153 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
1154 suite,
1155 sub,
1156 standalone)
jadmanski0afbb632008-06-06 21:10:57 +00001157 return models.Job.query_count(filter_data)
mblighe8819cd2008-02-15 16:48:40 +00001158
1159
mblighe8819cd2008-02-15 16:48:40 +00001160def get_jobs_summary(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001161 """\
Jiaxi Luoaac54572014-06-04 13:57:02 -07001162 Like get_jobs(), but adds 'status_counts' and 'result_counts' field.
1163
1164 'status_counts' filed is a dictionary mapping status strings to the number
1165 of hosts currently with that status, i.e. {'Queued' : 4, 'Running' : 2}.
1166
1167 'result_counts' field is piped to tko's rpc_interface and has the return
1168 format specified under get_group_counts.
jadmanski0afbb632008-06-06 21:10:57 +00001169 """
1170 jobs = get_jobs(**filter_data)
1171 ids = [job['id'] for job in jobs]
1172 all_status_counts = models.Job.objects.get_status_counts(ids)
1173 for job in jobs:
1174 job['status_counts'] = all_status_counts[job['id']]
Jiaxi Luoaac54572014-06-04 13:57:02 -07001175 job['result_counts'] = tko_rpc_interface.get_status_counts(
1176 ['afe_job_id', 'afe_job_id'],
1177 header_groups=[['afe_job_id'], ['afe_job_id']],
1178 **{'afe_job_id': job['id']})
jadmanski0afbb632008-06-06 21:10:57 +00001179 return rpc_utils.prepare_for_serialization(jobs)
mblighe8819cd2008-02-15 16:48:40 +00001180
1181
showarda965cef2009-05-15 23:17:41 +00001182def get_info_for_clone(id, preserve_metahosts, queue_entry_filter_data=None):
showarda8709c52008-07-03 19:44:54 +00001183 """\
1184 Retrieves all the information needed to clone a job.
1185 """
showarda8709c52008-07-03 19:44:54 +00001186 job = models.Job.objects.get(id=id)
showard29f7cd22009-04-29 21:16:24 +00001187 job_info = rpc_utils.get_job_info(job,
showarda965cef2009-05-15 23:17:41 +00001188 preserve_metahosts,
1189 queue_entry_filter_data)
showard945072f2008-09-03 20:34:59 +00001190
showardd9992fe2008-07-31 02:15:03 +00001191 host_dicts = []
showard29f7cd22009-04-29 21:16:24 +00001192 for host in job_info['hosts']:
1193 host_dict = get_hosts(id=host.id)[0]
1194 other_labels = host_dict['labels']
1195 if host_dict['platform']:
1196 other_labels.remove(host_dict['platform'])
1197 host_dict['other_labels'] = ', '.join(other_labels)
showardd9992fe2008-07-31 02:15:03 +00001198 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001199
showard29f7cd22009-04-29 21:16:24 +00001200 for host in job_info['one_time_hosts']:
1201 host_dict = dict(hostname=host.hostname,
1202 id=host.id,
1203 platform='(one-time host)',
1204 locked_text='')
1205 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001206
showard4d077562009-05-08 18:24:36 +00001207 # convert keys from Label objects to strings (names of labels)
showard29f7cd22009-04-29 21:16:24 +00001208 meta_host_counts = dict((meta_host.name, count) for meta_host, count
showard4d077562009-05-08 18:24:36 +00001209 in job_info['meta_host_counts'].iteritems())
showard29f7cd22009-04-29 21:16:24 +00001210
1211 info = dict(job=job.get_object_dict(),
1212 meta_host_counts=meta_host_counts,
1213 hosts=host_dicts)
1214 info['job']['dependencies'] = job_info['dependencies']
1215 if job_info['atomic_group']:
1216 info['atomic_group_name'] = (job_info['atomic_group']).name
1217 else:
1218 info['atomic_group_name'] = None
jamesren2275ef12010-04-12 18:25:06 +00001219 info['hostless'] = job_info['hostless']
jamesren76fcf192010-04-21 20:39:50 +00001220 info['drone_set'] = job.drone_set and job.drone_set.name
showarda8709c52008-07-03 19:44:54 +00001221
Eric Lid23bc192011-02-09 14:38:57 -08001222 if job.parameterized_job:
1223 info['job']['image'] = get_parameterized_autoupdate_image_url(job)
1224
showarda8709c52008-07-03 19:44:54 +00001225 return rpc_utils.prepare_for_serialization(info)
1226
1227
showard34dc5fa2008-04-24 20:58:40 +00001228# host queue entries
1229
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001230def get_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001231 """\
showardc92da832009-04-07 18:14:34 +00001232 @returns A sequence of nested dictionaries of host and job information.
jadmanski0afbb632008-06-06 21:10:57 +00001233 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001234 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1235 'started_on__lte',
1236 start_time,
1237 end_time,
1238 **filter_data)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001239 return rpc_utils.prepare_rows_as_nested_dicts(
1240 models.HostQueueEntry.query_objects(filter_data),
1241 ('host', 'atomic_group', 'job'))
showard34dc5fa2008-04-24 20:58:40 +00001242
1243
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001244def get_num_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001245 """\
1246 Get the number of host queue entries associated with this job.
1247 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001248 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1249 'started_on__lte',
1250 start_time,
1251 end_time,
1252 **filter_data)
jadmanski0afbb632008-06-06 21:10:57 +00001253 return models.HostQueueEntry.query_count(filter_data)
showard34dc5fa2008-04-24 20:58:40 +00001254
1255
showard1e935f12008-07-11 00:11:36 +00001256def get_hqe_percentage_complete(**filter_data):
1257 """
showardc92da832009-04-07 18:14:34 +00001258 Computes the fraction of host queue entries matching the given filter data
showard1e935f12008-07-11 00:11:36 +00001259 that are complete.
1260 """
1261 query = models.HostQueueEntry.query_objects(filter_data)
1262 complete_count = query.filter(complete=True).count()
1263 total_count = query.count()
1264 if total_count == 0:
1265 return 1
1266 return float(complete_count) / total_count
1267
1268
showard1a5a4082009-07-28 20:01:37 +00001269# special tasks
1270
1271def get_special_tasks(**filter_data):
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001272 """Get special task entries from the local database.
1273
1274 Query the special tasks table for tasks matching the given
1275 `filter_data`, and return a list of the results. No attempt is
1276 made to forward the call to shards; the buck will stop here.
1277 The caller is expected to know the target shard for such reasons
1278 as:
1279 * The caller is a service (such as gs_offloader) configured
1280 to operate on behalf of one specific shard, and no other.
1281 * The caller has a host as a parameter, and knows that this is
1282 the shard assigned to that host.
1283
1284 @param filter_data Filter keywords to pass to the underlying
1285 database query.
1286
1287 """
J. Richard Barnettefdfcd662015-04-13 17:20:29 -07001288 return rpc_utils.prepare_rows_as_nested_dicts(
1289 models.SpecialTask.query_objects(filter_data),
1290 ('host', 'queue_entry'))
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001291
1292
1293def get_host_special_tasks(host_id, **filter_data):
1294 """Get special task entries for a given host.
1295
1296 Query the special tasks table for tasks that ran on the host
1297 given by `host_id` and matching the given `filter_data`.
1298 Return a list of the results. If the host is assigned to a
1299 shard, forward this call to that shard.
1300
1301 @param host_id Id in the database of the target host.
1302 @param filter_data Filter keywords to pass to the underlying
1303 database query.
1304
1305 """
MK Ryu0c1a37d2015-04-30 12:00:55 -07001306 # Retrieve host data even if the host is in an invalid state.
1307 host = models.Host.smart_get(host_id, False)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001308 if not host.shard:
J. Richard Barnettefdfcd662015-04-13 17:20:29 -07001309 return get_special_tasks(host_id=host_id, **filter_data)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001310 else:
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001311 # The return values from AFE methods are post-processed
1312 # objects that aren't JSON-serializable. So, we have to
1313 # call AFE.run() to get the raw, serializable output from
1314 # the shard.
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001315 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1316 return shard_afe.run('get_special_tasks',
1317 host_id=host_id, **filter_data)
showard1a5a4082009-07-28 20:01:37 +00001318
1319
MK Ryu0c1a37d2015-04-30 12:00:55 -07001320def get_num_special_tasks(**kwargs):
1321 """Get the number of special task entries from the local database.
1322
1323 Query the special tasks table for tasks matching the given 'kwargs',
1324 and return the number of the results. No attempt is made to forward
1325 the call to shards; the buck will stop here.
1326
1327 @param kwargs Filter keywords to pass to the underlying database query.
1328
1329 """
1330 return models.SpecialTask.query_count(kwargs)
1331
1332
1333def get_host_num_special_tasks(host, **kwargs):
1334 """Get special task entries for a given host.
1335
1336 Query the special tasks table for tasks that ran on the host
1337 given by 'host' and matching the given 'kwargs'.
1338 Return a list of the results. If the host is assigned to a
1339 shard, forward this call to that shard.
1340
1341 @param host id or name of a host. More often a hostname.
1342 @param kwargs Filter keywords to pass to the underlying database query.
1343
1344 """
1345 # Retrieve host data even if the host is in an invalid state.
1346 host_model = models.Host.smart_get(host, False)
1347 if not host_model.shard:
1348 return get_num_special_tasks(host=host, **kwargs)
1349 else:
1350 shard_afe = frontend.AFE(server=host_model.shard.rpc_hostname())
1351 return shard_afe.run('get_num_special_tasks', host=host, **kwargs)
1352
1353
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001354def get_status_task(host_id, end_time):
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001355 """Get the "status task" for a host from the local shard.
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001356
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001357 Returns a single special task representing the given host's
1358 "status task". The status task is a completed special task that
1359 identifies whether the corresponding host was working or broken
1360 when it completed. A successful task indicates a working host;
1361 a failed task indicates broken.
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001362
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001363 This call will not be forward to a shard; the receiving server
1364 must be the shard that owns the host.
1365
1366 @param host_id Id in the database of the target host.
1367 @param end_time Time reference for the host's status.
1368
1369 @return A single task; its status (successful or not)
1370 corresponds to the status of the host (working or
1371 broken) at the given time. If no task is found, return
1372 `None`.
1373
1374 """
1375 tasklist = rpc_utils.prepare_rows_as_nested_dicts(
1376 status_history.get_status_task(host_id, end_time),
1377 ('host', 'queue_entry'))
1378 return tasklist[0] if tasklist else None
1379
1380
1381def get_host_status_task(host_id, end_time):
1382 """Get the "status task" for a host from its owning shard.
1383
1384 Finds the given host's owning shard, and forwards to it a call
1385 to `get_status_task()` (see above).
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001386
1387 @param host_id Id in the database of the target host.
1388 @param end_time Time reference for the host's status.
1389
1390 @return A single task; its status (successful or not)
1391 corresponds to the status of the host (working or
1392 broken) at the given time. If no task is found, return
1393 `None`.
1394
1395 """
1396 host = models.Host.smart_get(host_id)
1397 if not host.shard:
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001398 return get_status_task(host_id, end_time)
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001399 else:
1400 # The return values from AFE methods are post-processed
1401 # objects that aren't JSON-serializable. So, we have to
1402 # call AFE.run() to get the raw, serializable output from
1403 # the shard.
1404 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1405 return shard_afe.run('get_status_task',
1406 host_id=host_id, end_time=end_time)
1407
1408
J. Richard Barnette8abbfd62015-06-23 12:46:54 -07001409def get_host_diagnosis_interval(host_id, end_time, success):
1410 """Find a "diagnosis interval" for a given host.
1411
1412 A "diagnosis interval" identifies a start and end time where
1413 the host went from "working" to "broken", or vice versa. The
1414 interval's starting time is the starting time of the last status
1415 task with the old status; the end time is the finish time of the
1416 first status task with the new status.
1417
1418 This routine finds the most recent diagnosis interval for the
1419 given host prior to `end_time`, with a starting status matching
1420 `success`. If `success` is true, the interval will start with a
1421 successful status task; if false the interval will start with a
1422 failed status task.
1423
1424 @param host_id Id in the database of the target host.
1425 @param end_time Time reference for the diagnosis interval.
1426 @param success Whether the diagnosis interval should start
1427 with a successful or failed status task.
1428
1429 @return A list of two strings. The first is the timestamp for
1430 the beginning of the interval; the second is the
1431 timestamp for the end. If the host has never changed
1432 state, the list is empty.
1433
1434 """
1435 host = models.Host.smart_get(host_id)
J. Richard Barnette78f281a2015-06-29 13:24:51 -07001436 if not host.shard or utils.is_shard():
J. Richard Barnette8abbfd62015-06-23 12:46:54 -07001437 return status_history.get_diagnosis_interval(
1438 host_id, end_time, success)
1439 else:
1440 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1441 return shard_afe.get_host_diagnosis_interval(
1442 host_id, end_time, success)
1443
1444
showardc0ac3a72009-07-08 21:14:45 +00001445# support for host detail view
1446
MK Ryu0c1a37d2015-04-30 12:00:55 -07001447def get_host_queue_entries_and_special_tasks(host, query_start=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001448 query_limit=None, start_time=None,
1449 end_time=None):
showardc0ac3a72009-07-08 21:14:45 +00001450 """
1451 @returns an interleaved list of HostQueueEntries and SpecialTasks,
1452 in approximate run order. each dict contains keys for type, host,
1453 job, status, started_on, execution_path, and ID.
1454 """
1455 total_limit = None
1456 if query_limit is not None:
1457 total_limit = query_start + query_limit
MK Ryu0c1a37d2015-04-30 12:00:55 -07001458 filter_data_common = {'host': host,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001459 'query_limit': total_limit,
1460 'sort_by': ['-id']}
showardc0ac3a72009-07-08 21:14:45 +00001461
MK Ryu0c1a37d2015-04-30 12:00:55 -07001462 filter_data_special_tasks = rpc_utils.inject_times_to_filter(
1463 'time_started__gte', 'time_started__lte', start_time, end_time,
1464 **filter_data_common)
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001465
MK Ryu0c1a37d2015-04-30 12:00:55 -07001466 queue_entries = get_host_queue_entries(
1467 start_time, end_time, **filter_data_common)
1468 special_tasks = get_host_special_tasks(host, **filter_data_special_tasks)
showardc0ac3a72009-07-08 21:14:45 +00001469
1470 interleaved_entries = rpc_utils.interleave_entries(queue_entries,
1471 special_tasks)
1472 if query_start is not None:
1473 interleaved_entries = interleaved_entries[query_start:]
1474 if query_limit is not None:
1475 interleaved_entries = interleaved_entries[:query_limit]
MK Ryu0c1a37d2015-04-30 12:00:55 -07001476 return rpc_utils.prepare_host_queue_entries_and_special_tasks(
1477 interleaved_entries, queue_entries)
showardc0ac3a72009-07-08 21:14:45 +00001478
1479
MK Ryu0c1a37d2015-04-30 12:00:55 -07001480def get_num_host_queue_entries_and_special_tasks(host, start_time=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001481 end_time=None):
MK Ryu0c1a37d2015-04-30 12:00:55 -07001482 filter_data_common = {'host': host}
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001483
1484 filter_data_queue_entries, filter_data_special_tasks = (
1485 rpc_utils.inject_times_to_hqe_special_tasks_filters(
1486 filter_data_common, start_time, end_time))
1487
1488 return (models.HostQueueEntry.query_count(filter_data_queue_entries)
MK Ryu0c1a37d2015-04-30 12:00:55 -07001489 + get_host_num_special_tasks(**filter_data_special_tasks))
showardc0ac3a72009-07-08 21:14:45 +00001490
1491
showard29f7cd22009-04-29 21:16:24 +00001492# recurring run
1493
1494def get_recurring(**filter_data):
1495 return rpc_utils.prepare_rows_as_nested_dicts(
1496 models.RecurringRun.query_objects(filter_data),
1497 ('job', 'owner'))
1498
1499
1500def get_num_recurring(**filter_data):
1501 return models.RecurringRun.query_count(filter_data)
1502
1503
1504def delete_recurring_runs(**filter_data):
1505 to_delete = models.RecurringRun.query_objects(filter_data)
1506 to_delete.delete()
1507
1508
1509def create_recurring_run(job_id, start_date, loop_period, loop_count):
showard64a95952010-01-13 21:27:16 +00001510 owner = models.User.current_user().login
showard29f7cd22009-04-29 21:16:24 +00001511 job = models.Job.objects.get(id=job_id)
1512 return job.create_recurring_job(start_date=start_date,
1513 loop_period=loop_period,
1514 loop_count=loop_count,
1515 owner=owner)
1516
1517
mblighe8819cd2008-02-15 16:48:40 +00001518# other
1519
showarde0b63622008-08-04 20:58:47 +00001520def echo(data=""):
1521 """\
1522 Returns a passed in string. For doing a basic test to see if RPC calls
1523 can successfully be made.
1524 """
1525 return data
1526
1527
showardb7a52fd2009-04-27 20:10:56 +00001528def get_motd():
1529 """\
1530 Returns the message of the day as a string.
1531 """
1532 return rpc_utils.get_motd()
1533
1534
mblighe8819cd2008-02-15 16:48:40 +00001535def get_static_data():
jadmanski0afbb632008-06-06 21:10:57 +00001536 """\
1537 Returns a dictionary containing a bunch of data that shouldn't change
1538 often and is otherwise inaccessible. This includes:
showardc92da832009-04-07 18:14:34 +00001539
1540 priorities: List of job priority choices.
1541 default_priority: Default priority value for new jobs.
1542 users: Sorted list of all users.
Jiaxi Luo31874592014-06-11 10:36:35 -07001543 labels: Sorted list of labels not start with 'cros-version' and
1544 'fw-version'.
showardc92da832009-04-07 18:14:34 +00001545 atomic_groups: Sorted list of all atomic groups.
1546 tests: Sorted list of all tests.
1547 profilers: Sorted list of all profilers.
1548 current_user: Logged-in username.
1549 host_statuses: Sorted list of possible Host statuses.
1550 job_statuses: Sorted list of possible HostQueueEntry statuses.
Simran Basi7e605742013-11-12 13:43:36 -08001551 job_timeout_default: The default job timeout length in minutes.
showarda1e74b32009-05-12 17:32:04 +00001552 parse_failed_repair_default: Default value for the parse_failed_repair job
Jiaxi Luo31874592014-06-11 10:36:35 -07001553 option.
showardc92da832009-04-07 18:14:34 +00001554 reboot_before_options: A list of valid RebootBefore string enums.
1555 reboot_after_options: A list of valid RebootAfter string enums.
1556 motd: Server's message of the day.
1557 status_dictionary: A mapping from one word job status names to a more
1558 informative description.
jadmanski0afbb632008-06-06 21:10:57 +00001559 """
showard21baa452008-10-21 00:08:39 +00001560
1561 job_fields = models.Job.get_field_dict()
jamesren76fcf192010-04-21 20:39:50 +00001562 default_drone_set_name = models.DroneSet.default_drone_set_name()
1563 drone_sets = ([default_drone_set_name] +
1564 sorted(drone_set.name for drone_set in
1565 models.DroneSet.objects.exclude(
1566 name=default_drone_set_name)))
showard21baa452008-10-21 00:08:39 +00001567
jadmanski0afbb632008-06-06 21:10:57 +00001568 result = {}
Alex Miller7d658cf2013-09-04 16:00:35 -07001569 result['priorities'] = priorities.Priority.choices()
1570 default_priority = priorities.Priority.DEFAULT
1571 result['default_priority'] = 'Default'
1572 result['max_schedulable_priority'] = priorities.Priority.DEFAULT
jadmanski0afbb632008-06-06 21:10:57 +00001573 result['users'] = get_users(sort_by=['login'])
Jiaxi Luo31874592014-06-11 10:36:35 -07001574
1575 label_exclude_filters = [{'name__startswith': 'cros-version'},
Dan Shi65351d62015-08-03 12:03:23 -07001576 {'name__startswith': 'fw-version'},
1577 {'name__startswith': 'fwrw-version'},
1578 {'name__startswith': 'fwro-version'}]
Jiaxi Luo31874592014-06-11 10:36:35 -07001579 result['labels'] = get_labels(
1580 label_exclude_filters,
1581 sort_by=['-platform', 'name'])
1582
showardc92da832009-04-07 18:14:34 +00001583 result['atomic_groups'] = get_atomic_groups(sort_by=['name'])
jadmanski0afbb632008-06-06 21:10:57 +00001584 result['tests'] = get_tests(sort_by=['name'])
showard2b9a88b2008-06-13 20:55:03 +00001585 result['profilers'] = get_profilers(sort_by=['name'])
showard0fc38302008-10-23 00:44:07 +00001586 result['current_user'] = rpc_utils.prepare_for_serialization(
showard64a95952010-01-13 21:27:16 +00001587 models.User.current_user().get_object_dict())
showard2b9a88b2008-06-13 20:55:03 +00001588 result['host_statuses'] = sorted(models.Host.Status.names)
mbligh5a198b92008-12-11 19:33:29 +00001589 result['job_statuses'] = sorted(models.HostQueueEntry.Status.names)
Simran Basi7e605742013-11-12 13:43:36 -08001590 result['job_timeout_mins_default'] = models.Job.DEFAULT_TIMEOUT_MINS
Simran Basi34217022012-11-06 13:43:15 -08001591 result['job_max_runtime_mins_default'] = (
1592 models.Job.DEFAULT_MAX_RUNTIME_MINS)
showarda1e74b32009-05-12 17:32:04 +00001593 result['parse_failed_repair_default'] = bool(
1594 models.Job.DEFAULT_PARSE_FAILED_REPAIR)
jamesrendd855242010-03-02 22:23:44 +00001595 result['reboot_before_options'] = model_attributes.RebootBefore.names
1596 result['reboot_after_options'] = model_attributes.RebootAfter.names
showard8fbae652009-01-20 23:23:10 +00001597 result['motd'] = rpc_utils.get_motd()
jamesren76fcf192010-04-21 20:39:50 +00001598 result['drone_sets_enabled'] = models.DroneSet.drone_sets_enabled()
1599 result['drone_sets'] = drone_sets
jamesren4a41e012010-07-16 22:33:48 +00001600 result['parameterized_jobs'] = models.Job.parameterized_jobs_enabled()
showard8ac29b42008-07-17 17:01:55 +00001601
showardd3dc1992009-04-22 21:01:40 +00001602 result['status_dictionary'] = {"Aborted": "Aborted",
showard8ac29b42008-07-17 17:01:55 +00001603 "Verifying": "Verifying Host",
Alex Millerdfff2fd2013-05-28 13:05:06 -07001604 "Provisioning": "Provisioning Host",
showard8ac29b42008-07-17 17:01:55 +00001605 "Pending": "Waiting on other hosts",
1606 "Running": "Running autoserv",
1607 "Completed": "Autoserv completed",
1608 "Failed": "Failed to complete",
showardd823b362008-07-24 16:35:46 +00001609 "Queued": "Queued",
showard5deb6772008-11-04 21:54:33 +00001610 "Starting": "Next in host's queue",
1611 "Stopped": "Other host(s) failed verify",
showardd3dc1992009-04-22 21:01:40 +00001612 "Parsing": "Awaiting parse of final results",
showard29f7cd22009-04-29 21:16:24 +00001613 "Gathering": "Gathering log files",
showard8cc058f2009-09-08 16:26:33 +00001614 "Template": "Template job for recurring run",
mbligh4608b002010-01-05 18:22:35 +00001615 "Waiting": "Waiting for scheduler action",
Dan Shi07e09af2013-04-12 09:31:29 -07001616 "Archiving": "Archiving results",
1617 "Resetting": "Resetting hosts"}
Jiaxi Luo421608e2014-07-07 14:38:00 -07001618
1619 result['wmatrix_url'] = rpc_utils.get_wmatrix_url()
Simran Basi71206ef2014-08-13 13:51:18 -07001620 result['is_moblab'] = bool(utils.is_moblab())
Jiaxi Luo421608e2014-07-07 14:38:00 -07001621
jadmanski0afbb632008-06-06 21:10:57 +00001622 return result
showard29f7cd22009-04-29 21:16:24 +00001623
1624
1625def get_server_time():
1626 return datetime.datetime.now().strftime("%Y-%m-%d %H:%M")