blob: 387bff03912aa706f5aaeb2a2451f6ba1f9f95b8 [file] [log] [blame]
Richard Barnette7983b632016-05-09 09:02:57 -07001# pylint: disable=C0111
Aviv Keshet0b9cfc92013-02-05 11:36:02 -08002
mblighe8819cd2008-02-15 16:48:40 +00003"""\
4Functions to expose over the RPC interface.
5
6For all modify* and delete* functions that ask for an 'id' parameter to
7identify the object to operate on, the id may be either
8 * the database row ID
9 * the name of the object (label name, hostname, user login, etc.)
10 * a dictionary containing uniquely identifying field (this option should seldom
11 be used)
12
13When specifying foreign key fields (i.e. adding hosts to a label, or adding
14users to an ACL group), the given value may be either the database row ID or the
15name of the object.
16
17All get* functions return lists of dictionaries. Each dictionary represents one
18object and maps field names to values.
19
20Some examples:
21modify_host(2, hostname='myhost') # modify hostname of host with database ID 2
22modify_host('ipaj2', hostname='myhost') # modify hostname of host 'ipaj2'
23modify_test('sleeptest', test_type='Client', params=', seconds=60')
24delete_acl_group(1) # delete by ID
25delete_acl_group('Everyone') # delete by name
26acl_group_add_users('Everyone', ['mbligh', 'showard'])
27get_jobs(owner='showard', status='Queued')
28
mbligh93c80e62009-02-03 17:48:30 +000029See doctests/001_rpc_test.txt for (lots) more examples.
mblighe8819cd2008-02-15 16:48:40 +000030"""
31
32__author__ = 'showard@google.com (Steve Howard)'
33
MK Ryu9c5fbbe2015-02-11 15:46:22 -080034import sys
showard29f7cd22009-04-29 21:16:24 +000035import datetime
Shuqian Zhao4c0d2902016-01-12 17:03:15 -080036import logging
MK Ryu9c5fbbe2015-02-11 15:46:22 -080037
Moises Osorio2dc7a102014-12-02 18:24:02 -080038from django.db.models import Count
showardcafd16e2009-05-29 18:37:49 +000039import common
Simran Basib6ec8ae2014-04-23 12:05:08 -070040from autotest_lib.client.common_lib import priorities
Simran Basi6157e8e2015-12-07 18:22:34 -080041from autotest_lib.client.common_lib.cros import dev_server
Gabe Black1e1c41b2015-02-04 23:55:15 -080042from autotest_lib.client.common_lib.cros.graphite import autotest_stats
showard6d7b2ff2009-06-10 00:16:47 +000043from autotest_lib.frontend.afe import control_file, rpc_utils
J. Richard Barnetteb5164d62015-04-13 12:59:31 -070044from autotest_lib.frontend.afe import models, model_logic, model_attributes
Simran Basib6ec8ae2014-04-23 12:05:08 -070045from autotest_lib.frontend.afe import site_rpc_interface
Moises Osorio2dc7a102014-12-02 18:24:02 -080046from autotest_lib.frontend.tko import models as tko_models
Jiaxi Luoaac54572014-06-04 13:57:02 -070047from autotest_lib.frontend.tko import rpc_interface as tko_rpc_interface
J. Richard Barnetteb5164d62015-04-13 12:59:31 -070048from autotest_lib.server import frontend
Simran Basi71206ef2014-08-13 13:51:18 -070049from autotest_lib.server import utils
Dan Shid215dbe2015-06-18 16:14:59 -070050from autotest_lib.server.cros import provision
Jiaxi Luo90190c92014-06-18 12:35:57 -070051from autotest_lib.server.cros.dynamic_suite import tools
J. Richard Barnette39255fa2015-04-14 17:23:41 -070052from autotest_lib.site_utils import status_history
mblighe8819cd2008-02-15 16:48:40 +000053
Moises Osorio2dc7a102014-12-02 18:24:02 -080054
Gabe Black1e1c41b2015-02-04 23:55:15 -080055_timer = autotest_stats.Timer('rpc_interface')
Moises Osorio2dc7a102014-12-02 18:24:02 -080056
Eric Lid23bc192011-02-09 14:38:57 -080057def get_parameterized_autoupdate_image_url(job):
58 """Get the parameterized autoupdate image url from a parameterized job."""
59 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
60 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
beeps8bb1f7d2013-08-05 01:30:09 -070061 name='image')
Eric Lid23bc192011-02-09 14:38:57 -080062 para_set = job.parameterized_job.parameterizedjobparameter_set
63 job_test_para = para_set.get(test_parameter=image_parameter)
64 return job_test_para.parameter_value
65
66
mblighe8819cd2008-02-15 16:48:40 +000067# labels
68
mblighe8819cd2008-02-15 16:48:40 +000069def modify_label(id, **data):
MK Ryu8c554cf2015-06-12 11:45:50 -070070 """Modify a label.
71
72 @param id: id or name of a label. More often a label name.
73 @param data: New data for a label.
74 """
75 label_model = models.Label.smart_get(id)
MK Ryu8e2c2d02016-01-06 15:24:38 -080076 label_model.update_object(data)
MK Ryu8c554cf2015-06-12 11:45:50 -070077
78 # Master forwards the RPC to shards
79 if not utils.is_shard():
80 rpc_utils.fanout_rpc(label_model.host_set.all(), 'modify_label', False,
81 id=id, **data)
82
mblighe8819cd2008-02-15 16:48:40 +000083
84def delete_label(id):
MK Ryu8c554cf2015-06-12 11:45:50 -070085 """Delete a label.
86
87 @param id: id or name of a label. More often a label name.
88 """
89 label_model = models.Label.smart_get(id)
MK Ryu8e2c2d02016-01-06 15:24:38 -080090 # Hosts that have the label to be deleted. Save this info before
91 # the label is deleted to use it later.
92 hosts = []
93 for h in label_model.host_set.all():
94 hosts.append(models.Host.smart_get(h.id))
95 label_model.delete()
MK Ryu8c554cf2015-06-12 11:45:50 -070096
97 # Master forwards the RPC to shards
98 if not utils.is_shard():
MK Ryu8e2c2d02016-01-06 15:24:38 -080099 rpc_utils.fanout_rpc(hosts, 'delete_label', False, id=id)
mblighe8819cd2008-02-15 16:48:40 +0000100
Prashanth Balasubramanian744898f2015-01-13 05:04:16 -0800101
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800102def add_label(name, ignore_exception_if_exists=False, **kwargs):
MK Ryucf027c62015-03-04 12:00:50 -0800103 """Adds a new label of a given name.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800104
105 @param name: label name.
106 @param ignore_exception_if_exists: If True and the exception was
107 thrown due to the duplicated label name when adding a label,
108 then suppress the exception. Default is False.
109 @param kwargs: keyword args that store more info about a label
110 other than the name.
111 @return: int/long id of a new label.
112 """
113 # models.Label.add_object() throws model_logic.ValidationError
114 # when it is given a label name that already exists.
115 # However, ValidationError can be thrown with different errors,
116 # and those errors should be thrown up to the call chain.
117 try:
118 label = models.Label.add_object(name=name, **kwargs)
119 except:
120 exc_info = sys.exc_info()
121 if ignore_exception_if_exists:
122 label = rpc_utils.get_label(name)
123 # If the exception is raised not because of duplicated
124 # "name", then raise the original exception.
125 if label is None:
126 raise exc_info[0], exc_info[1], exc_info[2]
127 else:
128 raise exc_info[0], exc_info[1], exc_info[2]
129 return label.id
130
131
132def add_label_to_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800133 """Adds a label of the given id to the given hosts only in local DB.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800134
135 @param id: id or name of a label. More often a label name.
136 @param hosts: The hostnames of hosts that need the label.
137
138 @raises models.Label.DoesNotExist: If the label with id doesn't exist.
139 """
140 label = models.Label.smart_get(id)
141 host_objs = models.Host.smart_get_bulk(hosts)
142 if label.platform:
143 models.Host.check_no_platform(host_objs)
144 label.host_set.add(*host_objs)
145
146
Kevin Chengbdfc57d2016-04-14 13:46:58 -0700147def _create_label_everywhere(id, hosts):
148 """
149 Yet another method to create labels.
150
151 ALERT! This method should be run only on master not shards!
152 DO NOT RUN THIS ON A SHARD!!! Deputies will hate you if you do!!!
153
154 This method exists primarily to serve label_add_hosts() and
155 host_add_labels(). Basically it pulls out the label check/add logic
156 from label_add_hosts() into this nice method that not only creates
157 the label but also tells the shards that service the hosts to also
158 create the label.
159
160 @param id: id or name of a label. More often a label name.
161 @param hosts: A list of hostnames or ids. More often hostnames.
162 """
163 try:
164 label = models.Label.smart_get(id)
165 except models.Label.DoesNotExist:
166 # This matches the type checks in smart_get, which is a hack
167 # in and off itself. The aim here is to create any non-existent
168 # label, which we cannot do if the 'id' specified isn't a label name.
169 if isinstance(id, basestring):
170 label = models.Label.smart_get(add_label(id))
171 else:
172 raise ValueError('Label id (%s) does not exist. Please specify '
173 'the argument, id, as a string (label name).'
174 % id)
175
176 # Make sure the label exists on the shard with the same id
177 # as it is on the master.
178 # It is possible that the label is already in a shard because
179 # we are adding a new label only to shards of hosts that the label
180 # is going to be attached.
181 # For example, we add a label L1 to a host in shard S1.
182 # Master and S1 will have L1 but other shards won't.
183 # Later, when we add the same label L1 to hosts in shards S1 and S2,
184 # S1 already has the label but S2 doesn't.
185 # S2 should have the new label without any problem.
186 # We ignore exception in such a case.
187 host_objs = models.Host.smart_get_bulk(hosts)
188 rpc_utils.fanout_rpc(
189 host_objs, 'add_label', include_hostnames=False,
190 name=label.name, ignore_exception_if_exists=True,
191 id=label.id, platform=label.platform)
192
193
MK Ryufbb002c2015-06-08 14:13:16 -0700194@rpc_utils.route_rpc_to_master
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800195def label_add_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800196 """Adds a label with the given id to the given hosts.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800197
198 This method should be run only on master not shards.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800199 The given label will be created if it doesn't exist, provided the `id`
200 supplied is a label name not an int/long id.
201
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800202 @param id: id or name of a label. More often a label name.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800203 @param hosts: A list of hostnames or ids. More often hostnames.
204
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800205 @raises ValueError: If the id specified is an int/long (label id)
206 while the label does not exist.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800207 """
Kevin Chengbdfc57d2016-04-14 13:46:58 -0700208 # Create the label.
209 _create_label_everywhere(id, hosts)
210
211 # Add it to the master.
MK Ryu8e2c2d02016-01-06 15:24:38 -0800212 add_label_to_hosts(id, hosts)
MK Ryucf027c62015-03-04 12:00:50 -0800213
Kevin Chengbdfc57d2016-04-14 13:46:58 -0700214 # Add it to the shards.
MK Ryucf027c62015-03-04 12:00:50 -0800215 host_objs = models.Host.smart_get_bulk(hosts)
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800216 rpc_utils.fanout_rpc(host_objs, 'add_label_to_hosts', id=id)
showardbbabf502008-06-06 00:02:02 +0000217
218
MK Ryucf027c62015-03-04 12:00:50 -0800219def remove_label_from_hosts(id, hosts):
220 """Removes a label of the given id from the given hosts only in local DB.
221
222 @param id: id or name of a label.
223 @param hosts: The hostnames of hosts that need to remove the label from.
224 """
showardbe3ec042008-11-12 18:16:07 +0000225 host_objs = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000226 models.Label.smart_get(id).host_set.remove(*host_objs)
showardbbabf502008-06-06 00:02:02 +0000227
228
MK Ryufbb002c2015-06-08 14:13:16 -0700229@rpc_utils.route_rpc_to_master
MK Ryucf027c62015-03-04 12:00:50 -0800230def label_remove_hosts(id, hosts):
231 """Removes a label of the given id from the given hosts.
232
233 This method should be run only on master not shards.
234
235 @param id: id or name of a label.
236 @param hosts: A list of hostnames or ids. More often hostnames.
237 """
MK Ryucf027c62015-03-04 12:00:50 -0800238 host_objs = models.Host.smart_get_bulk(hosts)
MK Ryu26f0c932015-05-28 18:14:33 -0700239 remove_label_from_hosts(id, hosts)
240
MK Ryu8e2c2d02016-01-06 15:24:38 -0800241 rpc_utils.fanout_rpc(host_objs, 'remove_label_from_hosts', id=id)
242
MK Ryucf027c62015-03-04 12:00:50 -0800243
Jiaxi Luo31874592014-06-11 10:36:35 -0700244def get_labels(exclude_filters=(), **filter_data):
showardc92da832009-04-07 18:14:34 +0000245 """\
Jiaxi Luo31874592014-06-11 10:36:35 -0700246 @param exclude_filters: A sequence of dictionaries of filters.
247
showardc92da832009-04-07 18:14:34 +0000248 @returns A sequence of nested dictionaries of label information.
249 """
Jiaxi Luo31874592014-06-11 10:36:35 -0700250 labels = models.Label.query_objects(filter_data)
251 for exclude_filter in exclude_filters:
252 labels = labels.exclude(**exclude_filter)
253 return rpc_utils.prepare_rows_as_nested_dicts(labels, ('atomic_group',))
showardc92da832009-04-07 18:14:34 +0000254
255
256# atomic groups
257
showarde9450c92009-06-30 01:58:52 +0000258def add_atomic_group(name, max_number_of_machines=None, description=None):
showardc92da832009-04-07 18:14:34 +0000259 return models.AtomicGroup.add_object(
260 name=name, max_number_of_machines=max_number_of_machines,
261 description=description).id
262
263
264def modify_atomic_group(id, **data):
265 models.AtomicGroup.smart_get(id).update_object(data)
266
267
268def delete_atomic_group(id):
269 models.AtomicGroup.smart_get(id).delete()
270
271
272def atomic_group_add_labels(id, labels):
273 label_objs = models.Label.smart_get_bulk(labels)
274 models.AtomicGroup.smart_get(id).label_set.add(*label_objs)
275
276
277def atomic_group_remove_labels(id, labels):
278 label_objs = models.Label.smart_get_bulk(labels)
279 models.AtomicGroup.smart_get(id).label_set.remove(*label_objs)
280
281
282def get_atomic_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000283 return rpc_utils.prepare_for_serialization(
showardc92da832009-04-07 18:14:34 +0000284 models.AtomicGroup.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000285
286
287# hosts
288
Matthew Sartori68186332015-04-27 17:19:53 -0700289def add_host(hostname, status=None, locked=None, lock_reason='', protection=None):
290 if locked and not lock_reason:
291 raise model_logic.ValidationError(
292 {'locked': 'Please provide a reason for locking when adding host.'})
293
jadmanski0afbb632008-06-06 21:10:57 +0000294 return models.Host.add_object(hostname=hostname, status=status,
Matthew Sartori68186332015-04-27 17:19:53 -0700295 locked=locked, lock_reason=lock_reason,
296 protection=protection).id
mblighe8819cd2008-02-15 16:48:40 +0000297
298
MK Ryu33889612015-09-04 14:32:35 -0700299@rpc_utils.route_rpc_to_master
300def modify_host(id, **kwargs):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700301 """Modify local attributes of a host.
302
303 If this is called on the master, but the host is assigned to a shard, this
MK Ryu33889612015-09-04 14:32:35 -0700304 will call `modify_host_local` RPC to the responsible shard. This means if
305 a host is being locked using this function, this change will also propagate
306 to shards.
307 When this is called on a shard, the shard just routes the RPC to the master
308 and does nothing.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700309
310 @param id: id of the host to modify.
MK Ryu33889612015-09-04 14:32:35 -0700311 @param kwargs: key=value pairs of values to set on the host.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700312 """
MK Ryu33889612015-09-04 14:32:35 -0700313 rpc_utils.check_modify_host(kwargs)
showardce7c0922009-09-11 18:39:24 +0000314 host = models.Host.smart_get(id)
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800315 try:
316 rpc_utils.check_modify_host_locking(host, kwargs)
317 except model_logic.ValidationError as e:
318 if not kwargs.get('force_modify_locking', False):
319 raise
320 logging.exception('The following exception will be ignored and lock '
321 'modification will be enforced. %s', e)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700322
MK Ryud53e1492015-12-15 12:09:03 -0800323 # This is required to make `lock_time` for a host be exactly same
324 # between the master and a shard.
325 if kwargs.get('locked', None) and 'lock_time' not in kwargs:
326 kwargs['lock_time'] = datetime.datetime.now()
MK Ryu8e2c2d02016-01-06 15:24:38 -0800327 host.update_object(kwargs)
MK Ryud53e1492015-12-15 12:09:03 -0800328
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800329 # force_modifying_locking is not an internal field in database, remove.
330 kwargs.pop('force_modify_locking', None)
MK Ryu33889612015-09-04 14:32:35 -0700331 rpc_utils.fanout_rpc([host], 'modify_host_local',
332 include_hostnames=False, id=id, **kwargs)
mblighe8819cd2008-02-15 16:48:40 +0000333
334
MK Ryu33889612015-09-04 14:32:35 -0700335def modify_host_local(id, **kwargs):
336 """Modify host attributes in local DB.
337
338 @param id: Host id.
339 @param kwargs: key=value pairs of values to set on the host.
340 """
341 models.Host.smart_get(id).update_object(kwargs)
342
343
344@rpc_utils.route_rpc_to_master
showard276f9442009-05-20 00:33:16 +0000345def modify_hosts(host_filter_data, update_data):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700346 """Modify local attributes of multiple hosts.
347
348 If this is called on the master, but one of the hosts in that match the
MK Ryu33889612015-09-04 14:32:35 -0700349 filters is assigned to a shard, this will call `modify_hosts_local` RPC
350 to the responsible shard.
351 When this is called on a shard, the shard just routes the RPC to the master
352 and does nothing.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700353
354 The filters are always applied on the master, not on the shards. This means
355 if the states of a host differ on the master and a shard, the state on the
356 master will be used. I.e. this means:
357 A host was synced to Shard 1. On Shard 1 the status of the host was set to
358 'Repair Failed'.
359 - A call to modify_hosts with host_filter_data={'status': 'Ready'} will
360 update the host (both on the shard and on the master), because the state
361 of the host as the master knows it is still 'Ready'.
362 - A call to modify_hosts with host_filter_data={'status': 'Repair failed'
363 will not update the host, because the filter doesn't apply on the master.
364
showardbe0d8692009-08-20 23:42:44 +0000365 @param host_filter_data: Filters out which hosts to modify.
366 @param update_data: A dictionary with the changes to make to the hosts.
showard276f9442009-05-20 00:33:16 +0000367 """
MK Ryu93161712015-12-21 10:41:32 -0800368 update_data = update_data.copy()
showardbe0d8692009-08-20 23:42:44 +0000369 rpc_utils.check_modify_host(update_data)
showard276f9442009-05-20 00:33:16 +0000370 hosts = models.Host.query_objects(host_filter_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700371
372 affected_shard_hostnames = set()
373 affected_host_ids = []
374
Alex Miller9658a952013-05-14 16:40:02 -0700375 # Check all hosts before changing data for exception safety.
376 for host in hosts:
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800377 try:
378 rpc_utils.check_modify_host_locking(host, update_data)
379 except model_logic.ValidationError as e:
380 if not update_data.get('force_modify_locking', False):
381 raise
382 logging.exception('The following exception will be ignored and '
383 'lock modification will be enforced. %s', e)
384
Jakob Juelich50e91f72014-10-01 12:43:23 -0700385 if host.shard:
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800386 affected_shard_hostnames.add(host.shard.rpc_hostname())
Jakob Juelich50e91f72014-10-01 12:43:23 -0700387 affected_host_ids.append(host.id)
388
MK Ryud53e1492015-12-15 12:09:03 -0800389 # This is required to make `lock_time` for a host be exactly same
390 # between the master and a shard.
391 if update_data.get('locked', None) and 'lock_time' not in update_data:
392 update_data['lock_time'] = datetime.datetime.now()
MK Ryu8e2c2d02016-01-06 15:24:38 -0800393 for host in hosts:
394 host.update_object(update_data)
MK Ryud53e1492015-12-15 12:09:03 -0800395
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800396 update_data.pop('force_modify_locking', None)
MK Ryu33889612015-09-04 14:32:35 -0700397 # Caution: Changing the filter from the original here. See docstring.
398 rpc_utils.run_rpc_on_multiple_hostnames(
399 'modify_hosts_local', affected_shard_hostnames,
Jakob Juelich50e91f72014-10-01 12:43:23 -0700400 host_filter_data={'id__in': affected_host_ids},
401 update_data=update_data)
402
showard276f9442009-05-20 00:33:16 +0000403
MK Ryu33889612015-09-04 14:32:35 -0700404def modify_hosts_local(host_filter_data, update_data):
405 """Modify attributes of hosts in local DB.
406
407 @param host_filter_data: Filters out which hosts to modify.
408 @param update_data: A dictionary with the changes to make to the hosts.
409 """
410 for host in models.Host.query_objects(host_filter_data):
411 host.update_object(update_data)
412
413
MK Ryufbb002c2015-06-08 14:13:16 -0700414def add_labels_to_host(id, labels):
415 """Adds labels to a given host only in local DB.
showardcafd16e2009-05-29 18:37:49 +0000416
MK Ryufbb002c2015-06-08 14:13:16 -0700417 @param id: id or hostname for a host.
418 @param labels: ids or names for labels.
419 """
420 label_objs = models.Label.smart_get_bulk(labels)
421 models.Host.smart_get(id).labels.add(*label_objs)
422
423
424@rpc_utils.route_rpc_to_master
425def host_add_labels(id, labels):
426 """Adds labels to a given host.
427
428 @param id: id or hostname for a host.
429 @param labels: ids or names for labels.
430
431 @raises ValidationError: If adding more than one platform label.
432 """
Kevin Chengbdfc57d2016-04-14 13:46:58 -0700433 # Create the labels on the master/shards.
434 for label in labels:
435 _create_label_everywhere(label, [id])
436
MK Ryufbb002c2015-06-08 14:13:16 -0700437 label_objs = models.Label.smart_get_bulk(labels)
438 platforms = [label.name for label in label_objs if label.platform]
showardcafd16e2009-05-29 18:37:49 +0000439 if len(platforms) > 1:
440 raise model_logic.ValidationError(
441 {'labels': 'Adding more than one platform label: %s' %
442 ', '.join(platforms)})
MK Ryufbb002c2015-06-08 14:13:16 -0700443
444 host_obj = models.Host.smart_get(id)
showardcafd16e2009-05-29 18:37:49 +0000445 if len(platforms) == 1:
MK Ryufbb002c2015-06-08 14:13:16 -0700446 models.Host.check_no_platform([host_obj])
MK Ryu8e2c2d02016-01-06 15:24:38 -0800447 add_labels_to_host(id, labels)
MK Ryufbb002c2015-06-08 14:13:16 -0700448
449 rpc_utils.fanout_rpc([host_obj], 'add_labels_to_host', False,
450 id=id, labels=labels)
mblighe8819cd2008-02-15 16:48:40 +0000451
452
MK Ryufbb002c2015-06-08 14:13:16 -0700453def remove_labels_from_host(id, labels):
454 """Removes labels from a given host only in local DB.
455
456 @param id: id or hostname for a host.
457 @param labels: ids or names for labels.
458 """
459 label_objs = models.Label.smart_get_bulk(labels)
460 models.Host.smart_get(id).labels.remove(*label_objs)
461
462
463@rpc_utils.route_rpc_to_master
mblighe8819cd2008-02-15 16:48:40 +0000464def host_remove_labels(id, labels):
MK Ryufbb002c2015-06-08 14:13:16 -0700465 """Removes labels from a given host.
466
467 @param id: id or hostname for a host.
468 @param labels: ids or names for labels.
469 """
MK Ryu8e2c2d02016-01-06 15:24:38 -0800470 remove_labels_from_host(id, labels)
471
MK Ryufbb002c2015-06-08 14:13:16 -0700472 host_obj = models.Host.smart_get(id)
473 rpc_utils.fanout_rpc([host_obj], 'remove_labels_from_host', False,
474 id=id, labels=labels)
mblighe8819cd2008-02-15 16:48:40 +0000475
476
MK Ryuacf35922014-10-03 14:56:49 -0700477def get_host_attribute(attribute, **host_filter_data):
478 """
479 @param attribute: string name of attribute
480 @param host_filter_data: filter data to apply to Hosts to choose hosts to
481 act upon
482 """
483 hosts = rpc_utils.get_host_query((), False, False, True, host_filter_data)
484 hosts = list(hosts)
485 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
486 'attribute_list')
487 host_attr_dicts = []
488 for host_obj in hosts:
489 for attr_obj in host_obj.attribute_list:
490 if attr_obj.attribute == attribute:
491 host_attr_dicts.append(attr_obj.get_object_dict())
492 return rpc_utils.prepare_for_serialization(host_attr_dicts)
493
494
showard0957a842009-05-11 19:25:08 +0000495def set_host_attribute(attribute, value, **host_filter_data):
496 """
MK Ryu26f0c932015-05-28 18:14:33 -0700497 @param attribute: string name of attribute
498 @param value: string, or None to delete an attribute
499 @param host_filter_data: filter data to apply to Hosts to choose hosts to
500 act upon
showard0957a842009-05-11 19:25:08 +0000501 """
502 assert host_filter_data # disallow accidental actions on all hosts
503 hosts = models.Host.query_objects(host_filter_data)
504 models.AclGroup.check_for_acl_violation_hosts(hosts)
MK Ryu8e2c2d02016-01-06 15:24:38 -0800505 for host in hosts:
506 host.set_or_delete_attribute(attribute, value)
showard0957a842009-05-11 19:25:08 +0000507
MK Ryu26f0c932015-05-28 18:14:33 -0700508 # Master forwards this RPC to shards.
509 if not utils.is_shard():
510 rpc_utils.fanout_rpc(hosts, 'set_host_attribute', False,
511 attribute=attribute, value=value, **host_filter_data)
512
showard0957a842009-05-11 19:25:08 +0000513
Jakob Juelich50e91f72014-10-01 12:43:23 -0700514@rpc_utils.forward_single_host_rpc_to_shard
mblighe8819cd2008-02-15 16:48:40 +0000515def delete_host(id):
jadmanski0afbb632008-06-06 21:10:57 +0000516 models.Host.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000517
518
showard87cc38f2009-08-20 23:37:04 +0000519def get_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
Dan Shi37df54d2015-12-14 11:16:28 -0800520 exclude_atomic_group_hosts=False, valid_only=True,
521 include_current_job=False, **filter_data):
522 """Get a list of dictionaries which contains the information of hosts.
523
showard87cc38f2009-08-20 23:37:04 +0000524 @param multiple_labels: match hosts in all of the labels given. Should
525 be a list of label names.
526 @param exclude_only_if_needed_labels: Exclude hosts with at least one
527 "only_if_needed" label applied.
528 @param exclude_atomic_group_hosts: Exclude hosts that have one or more
529 atomic group labels associated with them.
Dan Shi37df54d2015-12-14 11:16:28 -0800530 @param include_current_job: Set to True to include ids of currently running
531 job and special task.
jadmanski0afbb632008-06-06 21:10:57 +0000532 """
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)
showard0957a842009-05-11 19:25:08 +0000537 hosts = list(hosts)
538 models.Host.objects.populate_relationships(hosts, models.Label,
539 'label_list')
540 models.Host.objects.populate_relationships(hosts, models.AclGroup,
541 'acl_list')
542 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
543 'attribute_list')
showard43a3d262008-11-12 18:17:05 +0000544 host_dicts = []
545 for host_obj in hosts:
546 host_dict = host_obj.get_object_dict()
showard0957a842009-05-11 19:25:08 +0000547 host_dict['labels'] = [label.name for label in host_obj.label_list]
showard909c9142009-07-07 20:54:42 +0000548 host_dict['platform'], host_dict['atomic_group'] = (rpc_utils.
549 find_platform_and_atomic_group(host_obj))
showard0957a842009-05-11 19:25:08 +0000550 host_dict['acls'] = [acl.name for acl in host_obj.acl_list]
551 host_dict['attributes'] = dict((attribute.attribute, attribute.value)
552 for attribute in host_obj.attribute_list)
Dan Shi37df54d2015-12-14 11:16:28 -0800553 if include_current_job:
554 host_dict['current_job'] = None
555 host_dict['current_special_task'] = None
556 entries = models.HostQueueEntry.objects.filter(
557 host_id=host_dict['id'], active=True, complete=False)
558 if entries:
559 host_dict['current_job'] = (
560 entries[0].get_object_dict()['job'])
561 tasks = models.SpecialTask.objects.filter(
562 host_id=host_dict['id'], is_active=True, is_complete=False)
563 if tasks:
564 host_dict['current_special_task'] = (
565 '%d-%s' % (tasks[0].get_object_dict()['id'],
566 tasks[0].get_object_dict()['task'].lower()))
showard43a3d262008-11-12 18:17:05 +0000567 host_dicts.append(host_dict)
568 return rpc_utils.prepare_for_serialization(host_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000569
570
showard87cc38f2009-08-20 23:37:04 +0000571def get_num_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000572 exclude_atomic_group_hosts=False, valid_only=True,
573 **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000574 """
575 Same parameters as get_hosts().
576
577 @returns The number of matching hosts.
578 """
showard43a3d262008-11-12 18:17:05 +0000579 hosts = rpc_utils.get_host_query(multiple_labels,
580 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000581 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000582 valid_only, filter_data)
showard43a3d262008-11-12 18:17:05 +0000583 return hosts.count()
showard1385b162008-03-13 15:59:40 +0000584
mblighe8819cd2008-02-15 16:48:40 +0000585
586# tests
587
showard909c7a62008-07-15 21:52:38 +0000588def add_test(name, test_type, path, author=None, dependencies=None,
showard3d9899a2008-07-31 02:11:58 +0000589 experimental=True, run_verify=None, test_class=None,
showard909c7a62008-07-15 21:52:38 +0000590 test_time=None, test_category=None, description=None,
591 sync_count=1):
jadmanski0afbb632008-06-06 21:10:57 +0000592 return models.Test.add_object(name=name, test_type=test_type, path=path,
showard909c7a62008-07-15 21:52:38 +0000593 author=author, dependencies=dependencies,
594 experimental=experimental,
595 run_verify=run_verify, test_time=test_time,
596 test_category=test_category,
597 sync_count=sync_count,
jadmanski0afbb632008-06-06 21:10:57 +0000598 test_class=test_class,
599 description=description).id
mblighe8819cd2008-02-15 16:48:40 +0000600
601
602def modify_test(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000603 models.Test.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000604
605
606def delete_test(id):
jadmanski0afbb632008-06-06 21:10:57 +0000607 models.Test.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000608
609
610def get_tests(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000611 return rpc_utils.prepare_for_serialization(
612 models.Test.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000613
614
Moises Osorio2dc7a102014-12-02 18:24:02 -0800615@_timer.decorate
616def get_tests_status_counts_by_job_name_label(job_name_prefix, label_name):
617 """Gets the counts of all passed and failed tests from the matching jobs.
618
619 @param job_name_prefix: Name prefix of the jobs to get the summary from, e.g.,
620 'butterfly-release/R40-6457.21.0/bvt-cq/'.
621 @param label_name: Label that must be set in the jobs, e.g.,
622 'cros-version:butterfly-release/R40-6457.21.0'.
623
624 @returns A summary of the counts of all the passed and failed tests.
625 """
626 job_ids = list(models.Job.objects.filter(
627 name__startswith=job_name_prefix,
628 dependency_labels__name=label_name).values_list(
629 'pk', flat=True))
630 summary = {'passed': 0, 'failed': 0}
631 if not job_ids:
632 return summary
633
634 counts = (tko_models.TestView.objects.filter(
635 afe_job_id__in=job_ids).exclude(
636 test_name='SERVER_JOB').exclude(
637 test_name__startswith='CLIENT_JOB').values(
638 'status').annotate(
639 count=Count('status')))
640 for status in counts:
641 if status['status'] == 'GOOD':
642 summary['passed'] += status['count']
643 else:
644 summary['failed'] += status['count']
645 return summary
646
647
showard2b9a88b2008-06-13 20:55:03 +0000648# profilers
649
650def add_profiler(name, description=None):
651 return models.Profiler.add_object(name=name, description=description).id
652
653
654def modify_profiler(id, **data):
655 models.Profiler.smart_get(id).update_object(data)
656
657
658def delete_profiler(id):
659 models.Profiler.smart_get(id).delete()
660
661
662def get_profilers(**filter_data):
663 return rpc_utils.prepare_for_serialization(
664 models.Profiler.list_objects(filter_data))
665
666
mblighe8819cd2008-02-15 16:48:40 +0000667# users
668
669def add_user(login, access_level=None):
jadmanski0afbb632008-06-06 21:10:57 +0000670 return models.User.add_object(login=login, access_level=access_level).id
mblighe8819cd2008-02-15 16:48:40 +0000671
672
673def modify_user(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000674 models.User.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000675
676
677def delete_user(id):
jadmanski0afbb632008-06-06 21:10:57 +0000678 models.User.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000679
680
681def get_users(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000682 return rpc_utils.prepare_for_serialization(
683 models.User.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000684
685
686# acl groups
687
688def add_acl_group(name, description=None):
showard04f2cd82008-07-25 20:53:31 +0000689 group = models.AclGroup.add_object(name=name, description=description)
showard64a95952010-01-13 21:27:16 +0000690 group.users.add(models.User.current_user())
showard04f2cd82008-07-25 20:53:31 +0000691 return group.id
mblighe8819cd2008-02-15 16:48:40 +0000692
693
694def modify_acl_group(id, **data):
showard04f2cd82008-07-25 20:53:31 +0000695 group = models.AclGroup.smart_get(id)
696 group.check_for_acl_violation_acl_group()
697 group.update_object(data)
698 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000699
700
701def acl_group_add_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000702 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000703 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000704 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000705 group.users.add(*users)
mblighe8819cd2008-02-15 16:48:40 +0000706
707
708def acl_group_remove_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000709 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000710 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000711 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000712 group.users.remove(*users)
showard04f2cd82008-07-25 20:53:31 +0000713 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000714
715
716def acl_group_add_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000717 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000718 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000719 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000720 group.hosts.add(*hosts)
showard08f981b2008-06-24 21:59:03 +0000721 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000722
723
724def acl_group_remove_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000725 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000726 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000727 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000728 group.hosts.remove(*hosts)
showard08f981b2008-06-24 21:59:03 +0000729 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000730
731
732def delete_acl_group(id):
jadmanski0afbb632008-06-06 21:10:57 +0000733 models.AclGroup.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000734
735
736def get_acl_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000737 acl_groups = models.AclGroup.list_objects(filter_data)
738 for acl_group in acl_groups:
739 acl_group_obj = models.AclGroup.objects.get(id=acl_group['id'])
740 acl_group['users'] = [user.login
741 for user in acl_group_obj.users.all()]
742 acl_group['hosts'] = [host.hostname
743 for host in acl_group_obj.hosts.all()]
744 return rpc_utils.prepare_for_serialization(acl_groups)
mblighe8819cd2008-02-15 16:48:40 +0000745
746
747# jobs
748
mbligh120351e2009-01-24 01:40:45 +0000749def generate_control_file(tests=(), kernel=None, label=None, profilers=(),
showard91f85102009-10-12 20:34:52 +0000750 client_control_file='', use_container=False,
Matthew Sartori10438092015-06-24 14:30:18 -0700751 profile_only=None, upload_kernel_config=False,
752 db_tests=True):
jadmanski0afbb632008-06-06 21:10:57 +0000753 """
mbligh120351e2009-01-24 01:40:45 +0000754 Generates a client-side control file to load a kernel and run tests.
755
Matthew Sartori10438092015-06-24 14:30:18 -0700756 @param tests List of tests to run. See db_tests for more information.
mbligha3c58d22009-08-24 22:01:51 +0000757 @param kernel A list of kernel info dictionaries configuring which kernels
758 to boot for this job and other options for them
mbligh120351e2009-01-24 01:40:45 +0000759 @param label Name of label to grab kernel config from.
760 @param profilers List of profilers to activate during the job.
761 @param client_control_file The contents of a client-side control file to
762 run at the end of all tests. If this is supplied, all tests must be
763 client side.
764 TODO: in the future we should support server control files directly
765 to wrap with a kernel. That'll require changing the parameter
766 name and adding a boolean to indicate if it is a client or server
767 control file.
768 @param use_container unused argument today. TODO: Enable containers
769 on the host during a client side test.
showard91f85102009-10-12 20:34:52 +0000770 @param profile_only A boolean that indicates what default profile_only
771 mode to use in the control file. Passing None will generate a
772 control file that does not explcitly set the default mode at all.
showard232b7ae2009-11-10 00:46:48 +0000773 @param upload_kernel_config: if enabled it will generate server control
774 file code that uploads the kernel config file to the client and
775 tells the client of the new (local) path when compiling the kernel;
776 the tests must be server side tests
Matthew Sartori10438092015-06-24 14:30:18 -0700777 @param db_tests: if True, the test object can be found in the database
778 backing the test model. In this case, tests is a tuple
779 of test IDs which are used to retrieve the test objects
780 from the database. If False, tests is a tuple of test
781 dictionaries stored client-side in the AFE.
mbligh120351e2009-01-24 01:40:45 +0000782
783 @returns a dict with the following keys:
784 control_file: str, The control file text.
785 is_server: bool, is the control file a server-side control file?
786 synch_count: How many machines the job uses per autoserv execution.
787 synch_count == 1 means the job is asynchronous.
788 dependencies: A list of the names of labels on which the job depends.
789 """
showardd86debe2009-06-10 17:37:56 +0000790 if not tests and not client_control_file:
showard2bab8f42008-11-12 18:15:22 +0000791 return dict(control_file='', is_server=False, synch_count=1,
showard989f25d2008-10-01 11:38:11 +0000792 dependencies=[])
mblighe8819cd2008-02-15 16:48:40 +0000793
showard989f25d2008-10-01 11:38:11 +0000794 cf_info, test_objects, profiler_objects, label = (
showard2b9a88b2008-06-13 20:55:03 +0000795 rpc_utils.prepare_generate_control_file(tests, kernel, label,
Matthew Sartori10438092015-06-24 14:30:18 -0700796 profilers, db_tests))
showard989f25d2008-10-01 11:38:11 +0000797 cf_info['control_file'] = control_file.generate_control(
mbligha3c58d22009-08-24 22:01:51 +0000798 tests=test_objects, kernels=kernel, platform=label,
mbligh120351e2009-01-24 01:40:45 +0000799 profilers=profiler_objects, is_server=cf_info['is_server'],
showard232b7ae2009-11-10 00:46:48 +0000800 client_control_file=client_control_file, profile_only=profile_only,
801 upload_kernel_config=upload_kernel_config)
showard989f25d2008-10-01 11:38:11 +0000802 return cf_info
mblighe8819cd2008-02-15 16:48:40 +0000803
804
Simran Basib6ec8ae2014-04-23 12:05:08 -0700805def create_job_page_handler(name, priority, control_file, control_type,
Dan Shid215dbe2015-06-18 16:14:59 -0700806 image=None, hostless=False, firmware_rw_build=None,
807 firmware_ro_build=None, test_source_build=None,
808 **kwargs):
Simran Basib6ec8ae2014-04-23 12:05:08 -0700809 """\
810 Create and enqueue a job.
811
812 @param name name of this job
813 @param priority Integer priority of this job. Higher is more important.
814 @param control_file String contents of the control file.
815 @param control_type Type of control file, Client or Server.
Dan Shid215dbe2015-06-18 16:14:59 -0700816 @param image: ChromeOS build to be installed in the dut. Default to None.
817 @param firmware_rw_build: Firmware build to update RW firmware. Default to
818 None, i.e., RW firmware will not be updated.
819 @param firmware_ro_build: Firmware build to update RO firmware. Default to
820 None, i.e., RO firmware will not be updated.
821 @param test_source_build: Build to be used to retrieve test code. Default
822 to None.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700823 @param kwargs extra args that will be required by create_suite_job or
824 create_job.
825
826 @returns The created Job id number.
827 """
828 control_file = rpc_utils.encode_ascii(control_file)
Jiaxi Luodd67beb2014-07-18 16:28:31 -0700829 if not control_file:
830 raise model_logic.ValidationError({
831 'control_file' : "Control file cannot be empty"})
Simran Basib6ec8ae2014-04-23 12:05:08 -0700832
833 if image and hostless:
Dan Shid215dbe2015-06-18 16:14:59 -0700834 builds = {}
835 builds[provision.CROS_VERSION_PREFIX] = image
836 if firmware_rw_build:
Dan Shi0723bf52015-06-24 10:52:38 -0700837 builds[provision.FW_RW_VERSION_PREFIX] = firmware_rw_build
Dan Shid215dbe2015-06-18 16:14:59 -0700838 if firmware_ro_build:
839 builds[provision.FW_RO_VERSION_PREFIX] = firmware_ro_build
Simran Basib6ec8ae2014-04-23 12:05:08 -0700840 return site_rpc_interface.create_suite_job(
841 name=name, control_file=control_file, priority=priority,
Dan Shid215dbe2015-06-18 16:14:59 -0700842 builds=builds, test_source_build=test_source_build, **kwargs)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700843 return create_job(name, priority, control_file, control_type, image=image,
844 hostless=hostless, **kwargs)
845
846
MK Ryue301eb72015-06-25 12:51:02 -0700847@rpc_utils.route_rpc_to_master
showard12f3e322009-05-13 21:27:42 +0000848def create_job(name, priority, control_file, control_type,
849 hosts=(), meta_hosts=(), one_time_hosts=(),
850 atomic_group_name=None, synch_count=None, is_template=False,
Simran Basi7e605742013-11-12 13:43:36 -0800851 timeout=None, timeout_mins=None, max_runtime_mins=None,
852 run_verify=False, email_list='', dependencies=(),
853 reboot_before=None, reboot_after=None, parse_failed_repair=None,
854 hostless=False, keyvals=None, drone_set=None, image=None,
Dan Shiec1d47d2015-02-13 11:38:13 -0800855 parent_job_id=None, test_retry=0, run_reset=True,
856 require_ssp=None, args=(), **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +0000857 """\
858 Create and enqueue a job.
mblighe8819cd2008-02-15 16:48:40 +0000859
showarda1e74b32009-05-12 17:32:04 +0000860 @param name name of this job
Alex Miller7d658cf2013-09-04 16:00:35 -0700861 @param priority Integer priority of this job. Higher is more important.
showarda1e74b32009-05-12 17:32:04 +0000862 @param control_file String contents of the control file.
863 @param control_type Type of control file, Client or Server.
864 @param synch_count How many machines the job uses per autoserv execution.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700865 synch_count == 1 means the job is asynchronous. If an atomic group is
866 given this value is treated as a minimum.
showarda1e74b32009-05-12 17:32:04 +0000867 @param is_template If true then create a template job.
868 @param timeout Hours after this call returns until the job times out.
Simran Basi7e605742013-11-12 13:43:36 -0800869 @param timeout_mins Minutes after this call returns until the job times
Jiaxi Luo90190c92014-06-18 12:35:57 -0700870 out.
Simran Basi34217022012-11-06 13:43:15 -0800871 @param max_runtime_mins Minutes from job starting time until job times out
showarda1e74b32009-05-12 17:32:04 +0000872 @param run_verify Should the host be verified before running the test?
873 @param email_list String containing emails to mail when the job is done
874 @param dependencies List of label names on which this job depends
875 @param reboot_before Never, If dirty, or Always
876 @param reboot_after Never, If all tests passed, or Always
877 @param parse_failed_repair if true, results of failed repairs launched by
Jiaxi Luo90190c92014-06-18 12:35:57 -0700878 this job will be parsed as part of the job.
showarda9545c02009-12-18 22:44:26 +0000879 @param hostless if true, create a hostless job
showardc1a98d12010-01-15 00:22:22 +0000880 @param keyvals dict of keyvals to associate with the job
showarda1e74b32009-05-12 17:32:04 +0000881 @param hosts List of hosts to run job on.
882 @param meta_hosts List where each entry is a label name, and for each entry
Jiaxi Luo90190c92014-06-18 12:35:57 -0700883 one host will be chosen from that label to run the job on.
showarda1e74b32009-05-12 17:32:04 +0000884 @param one_time_hosts List of hosts not in the database to run the job on.
885 @param atomic_group_name The name of an atomic group to schedule the job on.
jamesren76fcf192010-04-21 20:39:50 +0000886 @param drone_set The name of the drone set to run this test on.
Paul Pendlebury5a8c6ad2011-02-01 07:20:17 -0800887 @param image OS image to install before running job.
Aviv Keshet0b9cfc92013-02-05 11:36:02 -0800888 @param parent_job_id id of a job considered to be parent of created job.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700889 @param test_retry Number of times to retry test if the test did not
Jiaxi Luo90190c92014-06-18 12:35:57 -0700890 complete successfully. (optional, default: 0)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700891 @param run_reset Should the host be reset before running the test?
Dan Shiec1d47d2015-02-13 11:38:13 -0800892 @param require_ssp Set to True to require server-side packaging to run the
893 test. If it's set to None, drone will still try to run
894 the server side with server-side packaging. If the
895 autotest-server package doesn't exist for the build or
896 image is not set, drone will run the test without server-
897 side packaging. Default is None.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700898 @param args A list of args to be injected into control file.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700899 @param kwargs extra keyword args. NOT USED.
showardc92da832009-04-07 18:14:34 +0000900
901 @returns The created Job id number.
jadmanski0afbb632008-06-06 21:10:57 +0000902 """
Jiaxi Luo90190c92014-06-18 12:35:57 -0700903 if args:
904 control_file = tools.inject_vars({'args': args}, control_file)
905
Simran Basiab5a1bf2014-05-28 15:39:44 -0700906 if image is None:
907 return rpc_utils.create_job_common(
908 **rpc_utils.get_create_job_common_args(locals()))
909
Simran Basi6157e8e2015-12-07 18:22:34 -0800910 # Translate the image name, in case its a relative build name.
911 ds = dev_server.ImageServer.resolve(image)
912 image = ds.translate(image)
913
Simran Basiab5a1bf2014-05-28 15:39:44 -0700914 # When image is supplied use a known parameterized test already in the
915 # database to pass the OS image path from the front end, through the
916 # scheduler, and finally to autoserv as the --image parameter.
917
918 # The test autoupdate_ParameterizedJob is in afe_autotests and used to
919 # instantiate a Test object and from there a ParameterizedJob.
920 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
921 known_parameterized_job = models.ParameterizedJob.objects.create(
922 test=known_test_obj)
923
924 # autoupdate_ParameterizedJob has a single parameter, the image parameter,
925 # stored in the table afe_test_parameters. We retrieve and set this
926 # instance of the parameter to the OS image path.
927 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
928 name='image')
929 known_parameterized_job.parameterizedjobparameter_set.create(
930 test_parameter=image_parameter, parameter_value=image,
931 parameter_type='string')
932
Dan Shid215dbe2015-06-18 16:14:59 -0700933 # TODO(crbug.com/502638): save firmware build etc to parameterized_job.
934
Simran Basiab5a1bf2014-05-28 15:39:44 -0700935 # By passing a parameterized_job to create_job_common the job entry in
936 # the afe_jobs table will have the field parameterized_job_id set.
937 # The scheduler uses this id in the afe_parameterized_jobs table to
938 # match this job to our known test, and then with the
939 # afe_parameterized_job_parameters table to get the actual image path.
jamesren4a41e012010-07-16 22:33:48 +0000940 return rpc_utils.create_job_common(
Simran Basiab5a1bf2014-05-28 15:39:44 -0700941 parameterized_job=known_parameterized_job.id,
jamesren4a41e012010-07-16 22:33:48 +0000942 **rpc_utils.get_create_job_common_args(locals()))
mblighe8819cd2008-02-15 16:48:40 +0000943
944
showard9dbdcda2008-10-14 17:34:36 +0000945def abort_host_queue_entries(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000946 """\
showard9dbdcda2008-10-14 17:34:36 +0000947 Abort a set of host queue entries.
Fang Deng63b0e452014-12-19 14:38:15 -0800948
949 @return: A list of dictionaries, each contains information
950 about an aborted HQE.
jadmanski0afbb632008-06-06 21:10:57 +0000951 """
showard9dbdcda2008-10-14 17:34:36 +0000952 query = models.HostQueueEntry.query_objects(filter_data)
beepsfaecbce2013-10-29 11:35:10 -0700953
954 # Dont allow aborts on:
955 # 1. Jobs that have already completed (whether or not they were aborted)
956 # 2. Jobs that we have already been aborted (but may not have completed)
957 query = query.filter(complete=False).filter(aborted=False)
showarddc817512008-11-12 18:16:41 +0000958 models.AclGroup.check_abort_permissions(query)
showard9dbdcda2008-10-14 17:34:36 +0000959 host_queue_entries = list(query.select_related())
showard2bab8f42008-11-12 18:15:22 +0000960 rpc_utils.check_abort_synchronous_jobs(host_queue_entries)
mblighe8819cd2008-02-15 16:48:40 +0000961
Simran Basic1b26762013-06-26 14:23:21 -0700962 models.HostQueueEntry.abort_host_queue_entries(host_queue_entries)
Fang Deng63b0e452014-12-19 14:38:15 -0800963 hqe_info = [{'HostQueueEntry': hqe.id, 'Job': hqe.job_id,
964 'Job name': hqe.job.name} for hqe in host_queue_entries]
965 return hqe_info
showard9d821ab2008-07-11 16:54:29 +0000966
967
beeps8bb1f7d2013-08-05 01:30:09 -0700968def abort_special_tasks(**filter_data):
969 """\
970 Abort the special task, or tasks, specified in the filter.
971 """
972 query = models.SpecialTask.query_objects(filter_data)
973 special_tasks = query.filter(is_active=True)
974 for task in special_tasks:
975 task.abort()
976
977
Simran Basi73dae552013-02-25 14:57:46 -0800978def _call_special_tasks_on_hosts(task, hosts):
979 """\
980 Schedules a set of hosts for a special task.
981
982 @returns A list of hostnames that a special task was created for.
983 """
984 models.AclGroup.check_for_acl_violation_hosts(hosts)
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -0800985 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800986 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -0800987 raise ValueError('The following hosts are on shards, please '
988 'follow the link to the shards and create jobs '
989 'there instead. %s.' % shard_host_map)
Simran Basi73dae552013-02-25 14:57:46 -0800990 for host in hosts:
991 models.SpecialTask.schedule_special_task(host, task)
992 return list(sorted(host.hostname for host in hosts))
993
994
MK Ryu5aa25042015-07-28 16:08:04 -0700995def _forward_special_tasks_on_hosts(task, rpc, **filter_data):
996 """Forward special tasks to corresponding shards.
mbligh4e545a52009-12-19 05:30:39 +0000997
MK Ryu5aa25042015-07-28 16:08:04 -0700998 For master, when special tasks are fired on hosts that are sharded,
999 forward the RPC to corresponding shards.
1000
1001 For shard, create special task records in local DB.
1002
1003 @param task: Enum value of frontend.afe.models.SpecialTask.Task
1004 @param rpc: RPC name to forward.
1005 @param filter_data: Filter keywords to be used for DB query.
1006
1007 @return: A list of hostnames that a special task was created for.
showard1ff7b2e2009-05-15 23:17:18 +00001008 """
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001009 hosts = models.Host.query_objects(filter_data)
1010 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts, rpc_hostnames=True)
1011
1012 # Filter out hosts on a shard from those on the master, forward
1013 # rpcs to the shard with an additional hostname__in filter, and
1014 # create a local SpecialTask for each remaining host.
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -08001015 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001016 hosts = [h for h in hosts if h.shard is None]
1017 for shard, hostnames in shard_host_map.iteritems():
1018
1019 # The main client of this module is the frontend website, and
1020 # it invokes it with an 'id' or an 'id__in' filter. Regardless,
1021 # the 'hostname' filter should narrow down the list of hosts on
1022 # each shard even though we supply all the ids in filter_data.
1023 # This method uses hostname instead of id because it fits better
MK Ryu5aa25042015-07-28 16:08:04 -07001024 # with the overall architecture of redirection functions in
1025 # rpc_utils.
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001026 shard_filter = filter_data.copy()
1027 shard_filter['hostname__in'] = hostnames
1028 rpc_utils.run_rpc_on_multiple_hostnames(
MK Ryu5aa25042015-07-28 16:08:04 -07001029 rpc, [shard], **shard_filter)
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001030
1031 # There is a race condition here if someone assigns a shard to one of these
1032 # hosts before we create the task. The host will stay on the master if:
1033 # 1. The host is not Ready
1034 # 2. The host is Ready but has a task
1035 # But if the host is Ready and doesn't have a task yet, it will get sent
1036 # to the shard as we're creating a task here.
1037
1038 # Given that we only rarely verify Ready hosts it isn't worth putting this
1039 # entire method in a transaction. The worst case scenario is that we have
MK Ryu5aa25042015-07-28 16:08:04 -07001040 # a verify running on a Ready host while the shard is using it, if the
1041 # verify fails no subsequent tasks will be created against the host on the
1042 # master, and verifies are safe enough that this is OK.
1043 return _call_special_tasks_on_hosts(task, hosts)
1044
1045
1046def reverify_hosts(**filter_data):
1047 """\
1048 Schedules a set of hosts for verify.
1049
1050 @returns A list of hostnames that a verify task was created for.
1051 """
1052 return _forward_special_tasks_on_hosts(
1053 models.SpecialTask.Task.VERIFY, 'reverify_hosts', **filter_data)
Simran Basi73dae552013-02-25 14:57:46 -08001054
1055
1056def repair_hosts(**filter_data):
1057 """\
1058 Schedules a set of hosts for repair.
1059
1060 @returns A list of hostnames that a repair task was created for.
1061 """
MK Ryu5aa25042015-07-28 16:08:04 -07001062 return _forward_special_tasks_on_hosts(
1063 models.SpecialTask.Task.REPAIR, 'repair_hosts', **filter_data)
showard1ff7b2e2009-05-15 23:17:18 +00001064
1065
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001066def get_jobs(not_yet_run=False, running=False, finished=False,
1067 suite=False, sub=False, standalone=False, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001068 """\
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001069 Extra status filter args for get_jobs:
jadmanski0afbb632008-06-06 21:10:57 +00001070 -not_yet_run: Include only jobs that have not yet started running.
1071 -running: Include only jobs that have start running but for which not
1072 all hosts have completed.
1073 -finished: Include only jobs for which all hosts have completed (or
1074 aborted).
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001075
1076 Extra type filter args for get_jobs:
1077 -suite: Include only jobs with child jobs.
1078 -sub: Include only jobs with a parent job.
1079 -standalone: Inlcude only jobs with no child or parent jobs.
1080 At most one of these three fields should be specified.
jadmanski0afbb632008-06-06 21:10:57 +00001081 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001082 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
1083 running,
1084 finished)
1085 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
1086 suite,
1087 sub,
1088 standalone)
showard0957a842009-05-11 19:25:08 +00001089 job_dicts = []
1090 jobs = list(models.Job.query_objects(filter_data))
1091 models.Job.objects.populate_relationships(jobs, models.Label,
1092 'dependencies')
showardc1a98d12010-01-15 00:22:22 +00001093 models.Job.objects.populate_relationships(jobs, models.JobKeyval, 'keyvals')
showard0957a842009-05-11 19:25:08 +00001094 for job in jobs:
1095 job_dict = job.get_object_dict()
1096 job_dict['dependencies'] = ','.join(label.name
1097 for label in job.dependencies)
showardc1a98d12010-01-15 00:22:22 +00001098 job_dict['keyvals'] = dict((keyval.key, keyval.value)
1099 for keyval in job.keyvals)
Eric Lid23bc192011-02-09 14:38:57 -08001100 if job.parameterized_job:
1101 job_dict['image'] = get_parameterized_autoupdate_image_url(job)
showard0957a842009-05-11 19:25:08 +00001102 job_dicts.append(job_dict)
1103 return rpc_utils.prepare_for_serialization(job_dicts)
mblighe8819cd2008-02-15 16:48:40 +00001104
1105
1106def get_num_jobs(not_yet_run=False, running=False, finished=False,
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001107 suite=False, sub=False, standalone=False,
jadmanski0afbb632008-06-06 21:10:57 +00001108 **filter_data):
Aviv Keshet17660a52016-04-06 18:56:43 +00001109 """\
1110 See get_jobs() for documentation of extra filter parameters.
jadmanski0afbb632008-06-06 21:10:57 +00001111 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001112 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
1113 running,
1114 finished)
1115 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
1116 suite,
1117 sub,
1118 standalone)
Aviv Keshet17660a52016-04-06 18:56:43 +00001119 return models.Job.query_count(filter_data)
mblighe8819cd2008-02-15 16:48:40 +00001120
1121
mblighe8819cd2008-02-15 16:48:40 +00001122def get_jobs_summary(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001123 """\
Jiaxi Luoaac54572014-06-04 13:57:02 -07001124 Like get_jobs(), but adds 'status_counts' and 'result_counts' field.
1125
1126 'status_counts' filed is a dictionary mapping status strings to the number
1127 of hosts currently with that status, i.e. {'Queued' : 4, 'Running' : 2}.
1128
1129 'result_counts' field is piped to tko's rpc_interface and has the return
1130 format specified under get_group_counts.
jadmanski0afbb632008-06-06 21:10:57 +00001131 """
1132 jobs = get_jobs(**filter_data)
1133 ids = [job['id'] for job in jobs]
1134 all_status_counts = models.Job.objects.get_status_counts(ids)
1135 for job in jobs:
1136 job['status_counts'] = all_status_counts[job['id']]
Jiaxi Luoaac54572014-06-04 13:57:02 -07001137 job['result_counts'] = tko_rpc_interface.get_status_counts(
1138 ['afe_job_id', 'afe_job_id'],
1139 header_groups=[['afe_job_id'], ['afe_job_id']],
1140 **{'afe_job_id': job['id']})
jadmanski0afbb632008-06-06 21:10:57 +00001141 return rpc_utils.prepare_for_serialization(jobs)
mblighe8819cd2008-02-15 16:48:40 +00001142
1143
showarda965cef2009-05-15 23:17:41 +00001144def get_info_for_clone(id, preserve_metahosts, queue_entry_filter_data=None):
showarda8709c52008-07-03 19:44:54 +00001145 """\
1146 Retrieves all the information needed to clone a job.
1147 """
showarda8709c52008-07-03 19:44:54 +00001148 job = models.Job.objects.get(id=id)
showard29f7cd22009-04-29 21:16:24 +00001149 job_info = rpc_utils.get_job_info(job,
showarda965cef2009-05-15 23:17:41 +00001150 preserve_metahosts,
1151 queue_entry_filter_data)
showard945072f2008-09-03 20:34:59 +00001152
showardd9992fe2008-07-31 02:15:03 +00001153 host_dicts = []
showard29f7cd22009-04-29 21:16:24 +00001154 for host in job_info['hosts']:
1155 host_dict = get_hosts(id=host.id)[0]
1156 other_labels = host_dict['labels']
1157 if host_dict['platform']:
1158 other_labels.remove(host_dict['platform'])
1159 host_dict['other_labels'] = ', '.join(other_labels)
showardd9992fe2008-07-31 02:15:03 +00001160 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001161
showard29f7cd22009-04-29 21:16:24 +00001162 for host in job_info['one_time_hosts']:
1163 host_dict = dict(hostname=host.hostname,
1164 id=host.id,
1165 platform='(one-time host)',
1166 locked_text='')
1167 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001168
showard4d077562009-05-08 18:24:36 +00001169 # convert keys from Label objects to strings (names of labels)
showard29f7cd22009-04-29 21:16:24 +00001170 meta_host_counts = dict((meta_host.name, count) for meta_host, count
showard4d077562009-05-08 18:24:36 +00001171 in job_info['meta_host_counts'].iteritems())
showard29f7cd22009-04-29 21:16:24 +00001172
1173 info = dict(job=job.get_object_dict(),
1174 meta_host_counts=meta_host_counts,
1175 hosts=host_dicts)
1176 info['job']['dependencies'] = job_info['dependencies']
1177 if job_info['atomic_group']:
1178 info['atomic_group_name'] = (job_info['atomic_group']).name
1179 else:
1180 info['atomic_group_name'] = None
jamesren2275ef12010-04-12 18:25:06 +00001181 info['hostless'] = job_info['hostless']
jamesren76fcf192010-04-21 20:39:50 +00001182 info['drone_set'] = job.drone_set and job.drone_set.name
showarda8709c52008-07-03 19:44:54 +00001183
Eric Lid23bc192011-02-09 14:38:57 -08001184 if job.parameterized_job:
1185 info['job']['image'] = get_parameterized_autoupdate_image_url(job)
1186
showarda8709c52008-07-03 19:44:54 +00001187 return rpc_utils.prepare_for_serialization(info)
1188
1189
showard34dc5fa2008-04-24 20:58:40 +00001190# host queue entries
1191
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001192def get_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001193 """\
showardc92da832009-04-07 18:14:34 +00001194 @returns A sequence of nested dictionaries of host and job information.
jadmanski0afbb632008-06-06 21:10:57 +00001195 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001196 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1197 'started_on__lte',
1198 start_time,
1199 end_time,
1200 **filter_data)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001201 return rpc_utils.prepare_rows_as_nested_dicts(
1202 models.HostQueueEntry.query_objects(filter_data),
1203 ('host', 'atomic_group', 'job'))
showard34dc5fa2008-04-24 20:58:40 +00001204
1205
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001206def get_num_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001207 """\
1208 Get the number of host queue entries associated with this job.
1209 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001210 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1211 'started_on__lte',
1212 start_time,
1213 end_time,
1214 **filter_data)
jadmanski0afbb632008-06-06 21:10:57 +00001215 return models.HostQueueEntry.query_count(filter_data)
showard34dc5fa2008-04-24 20:58:40 +00001216
1217
showard1e935f12008-07-11 00:11:36 +00001218def get_hqe_percentage_complete(**filter_data):
1219 """
showardc92da832009-04-07 18:14:34 +00001220 Computes the fraction of host queue entries matching the given filter data
showard1e935f12008-07-11 00:11:36 +00001221 that are complete.
1222 """
1223 query = models.HostQueueEntry.query_objects(filter_data)
1224 complete_count = query.filter(complete=True).count()
1225 total_count = query.count()
1226 if total_count == 0:
1227 return 1
1228 return float(complete_count) / total_count
1229
1230
showard1a5a4082009-07-28 20:01:37 +00001231# special tasks
1232
1233def get_special_tasks(**filter_data):
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001234 """Get special task entries from the local database.
1235
1236 Query the special tasks table for tasks matching the given
1237 `filter_data`, and return a list of the results. No attempt is
1238 made to forward the call to shards; the buck will stop here.
1239 The caller is expected to know the target shard for such reasons
1240 as:
1241 * The caller is a service (such as gs_offloader) configured
1242 to operate on behalf of one specific shard, and no other.
1243 * The caller has a host as a parameter, and knows that this is
1244 the shard assigned to that host.
1245
1246 @param filter_data Filter keywords to pass to the underlying
1247 database query.
1248
1249 """
J. Richard Barnettefdfcd662015-04-13 17:20:29 -07001250 return rpc_utils.prepare_rows_as_nested_dicts(
1251 models.SpecialTask.query_objects(filter_data),
1252 ('host', 'queue_entry'))
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001253
1254
1255def get_host_special_tasks(host_id, **filter_data):
1256 """Get special task entries for a given host.
1257
1258 Query the special tasks table for tasks that ran on the host
1259 given by `host_id` and matching the given `filter_data`.
1260 Return a list of the results. If the host is assigned to a
1261 shard, forward this call to that shard.
1262
1263 @param host_id Id in the database of the target host.
1264 @param filter_data Filter keywords to pass to the underlying
1265 database query.
1266
1267 """
MK Ryu0c1a37d2015-04-30 12:00:55 -07001268 # Retrieve host data even if the host is in an invalid state.
1269 host = models.Host.smart_get(host_id, False)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001270 if not host.shard:
J. Richard Barnettefdfcd662015-04-13 17:20:29 -07001271 return get_special_tasks(host_id=host_id, **filter_data)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001272 else:
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001273 # The return values from AFE methods are post-processed
1274 # objects that aren't JSON-serializable. So, we have to
1275 # call AFE.run() to get the raw, serializable output from
1276 # the shard.
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001277 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1278 return shard_afe.run('get_special_tasks',
1279 host_id=host_id, **filter_data)
showard1a5a4082009-07-28 20:01:37 +00001280
1281
MK Ryu0c1a37d2015-04-30 12:00:55 -07001282def get_num_special_tasks(**kwargs):
1283 """Get the number of special task entries from the local database.
1284
1285 Query the special tasks table for tasks matching the given 'kwargs',
1286 and return the number of the results. No attempt is made to forward
1287 the call to shards; the buck will stop here.
1288
1289 @param kwargs Filter keywords to pass to the underlying database query.
1290
1291 """
1292 return models.SpecialTask.query_count(kwargs)
1293
1294
1295def get_host_num_special_tasks(host, **kwargs):
1296 """Get special task entries for a given host.
1297
1298 Query the special tasks table for tasks that ran on the host
1299 given by 'host' and matching the given 'kwargs'.
1300 Return a list of the results. If the host is assigned to a
1301 shard, forward this call to that shard.
1302
1303 @param host id or name of a host. More often a hostname.
1304 @param kwargs Filter keywords to pass to the underlying database query.
1305
1306 """
1307 # Retrieve host data even if the host is in an invalid state.
1308 host_model = models.Host.smart_get(host, False)
1309 if not host_model.shard:
1310 return get_num_special_tasks(host=host, **kwargs)
1311 else:
1312 shard_afe = frontend.AFE(server=host_model.shard.rpc_hostname())
1313 return shard_afe.run('get_num_special_tasks', host=host, **kwargs)
1314
1315
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001316def get_status_task(host_id, end_time):
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001317 """Get the "status task" for a host from the local shard.
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001318
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001319 Returns a single special task representing the given host's
1320 "status task". The status task is a completed special task that
1321 identifies whether the corresponding host was working or broken
1322 when it completed. A successful task indicates a working host;
1323 a failed task indicates broken.
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001324
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001325 This call will not be forward to a shard; the receiving server
1326 must be the shard that owns the host.
1327
1328 @param host_id Id in the database of the target host.
1329 @param end_time Time reference for the host's status.
1330
1331 @return A single task; its status (successful or not)
1332 corresponds to the status of the host (working or
1333 broken) at the given time. If no task is found, return
1334 `None`.
1335
1336 """
1337 tasklist = rpc_utils.prepare_rows_as_nested_dicts(
1338 status_history.get_status_task(host_id, end_time),
1339 ('host', 'queue_entry'))
1340 return tasklist[0] if tasklist else None
1341
1342
1343def get_host_status_task(host_id, end_time):
1344 """Get the "status task" for a host from its owning shard.
1345
1346 Finds the given host's owning shard, and forwards to it a call
1347 to `get_status_task()` (see above).
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001348
1349 @param host_id Id in the database of the target host.
1350 @param end_time Time reference for the host's status.
1351
1352 @return A single task; its status (successful or not)
1353 corresponds to the status of the host (working or
1354 broken) at the given time. If no task is found, return
1355 `None`.
1356
1357 """
1358 host = models.Host.smart_get(host_id)
1359 if not host.shard:
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001360 return get_status_task(host_id, end_time)
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001361 else:
1362 # The return values from AFE methods are post-processed
1363 # objects that aren't JSON-serializable. So, we have to
1364 # call AFE.run() to get the raw, serializable output from
1365 # the shard.
1366 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1367 return shard_afe.run('get_status_task',
1368 host_id=host_id, end_time=end_time)
1369
1370
J. Richard Barnette8abbfd62015-06-23 12:46:54 -07001371def get_host_diagnosis_interval(host_id, end_time, success):
1372 """Find a "diagnosis interval" for a given host.
1373
1374 A "diagnosis interval" identifies a start and end time where
1375 the host went from "working" to "broken", or vice versa. The
1376 interval's starting time is the starting time of the last status
1377 task with the old status; the end time is the finish time of the
1378 first status task with the new status.
1379
1380 This routine finds the most recent diagnosis interval for the
1381 given host prior to `end_time`, with a starting status matching
1382 `success`. If `success` is true, the interval will start with a
1383 successful status task; if false the interval will start with a
1384 failed status task.
1385
1386 @param host_id Id in the database of the target host.
1387 @param end_time Time reference for the diagnosis interval.
1388 @param success Whether the diagnosis interval should start
1389 with a successful or failed status task.
1390
1391 @return A list of two strings. The first is the timestamp for
1392 the beginning of the interval; the second is the
1393 timestamp for the end. If the host has never changed
1394 state, the list is empty.
1395
1396 """
1397 host = models.Host.smart_get(host_id)
J. Richard Barnette78f281a2015-06-29 13:24:51 -07001398 if not host.shard or utils.is_shard():
J. Richard Barnette8abbfd62015-06-23 12:46:54 -07001399 return status_history.get_diagnosis_interval(
1400 host_id, end_time, success)
1401 else:
1402 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1403 return shard_afe.get_host_diagnosis_interval(
1404 host_id, end_time, success)
1405
1406
showardc0ac3a72009-07-08 21:14:45 +00001407# support for host detail view
1408
MK Ryu0c1a37d2015-04-30 12:00:55 -07001409def get_host_queue_entries_and_special_tasks(host, query_start=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001410 query_limit=None, start_time=None,
1411 end_time=None):
showardc0ac3a72009-07-08 21:14:45 +00001412 """
1413 @returns an interleaved list of HostQueueEntries and SpecialTasks,
1414 in approximate run order. each dict contains keys for type, host,
1415 job, status, started_on, execution_path, and ID.
1416 """
1417 total_limit = None
1418 if query_limit is not None:
1419 total_limit = query_start + query_limit
MK Ryu0c1a37d2015-04-30 12:00:55 -07001420 filter_data_common = {'host': host,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001421 'query_limit': total_limit,
1422 'sort_by': ['-id']}
showardc0ac3a72009-07-08 21:14:45 +00001423
MK Ryu0c1a37d2015-04-30 12:00:55 -07001424 filter_data_special_tasks = rpc_utils.inject_times_to_filter(
1425 'time_started__gte', 'time_started__lte', start_time, end_time,
1426 **filter_data_common)
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001427
MK Ryu0c1a37d2015-04-30 12:00:55 -07001428 queue_entries = get_host_queue_entries(
1429 start_time, end_time, **filter_data_common)
1430 special_tasks = get_host_special_tasks(host, **filter_data_special_tasks)
showardc0ac3a72009-07-08 21:14:45 +00001431
1432 interleaved_entries = rpc_utils.interleave_entries(queue_entries,
1433 special_tasks)
1434 if query_start is not None:
1435 interleaved_entries = interleaved_entries[query_start:]
1436 if query_limit is not None:
1437 interleaved_entries = interleaved_entries[:query_limit]
MK Ryu0c1a37d2015-04-30 12:00:55 -07001438 return rpc_utils.prepare_host_queue_entries_and_special_tasks(
1439 interleaved_entries, queue_entries)
showardc0ac3a72009-07-08 21:14:45 +00001440
1441
MK Ryu0c1a37d2015-04-30 12:00:55 -07001442def get_num_host_queue_entries_and_special_tasks(host, start_time=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001443 end_time=None):
MK Ryu0c1a37d2015-04-30 12:00:55 -07001444 filter_data_common = {'host': host}
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001445
1446 filter_data_queue_entries, filter_data_special_tasks = (
1447 rpc_utils.inject_times_to_hqe_special_tasks_filters(
1448 filter_data_common, start_time, end_time))
1449
1450 return (models.HostQueueEntry.query_count(filter_data_queue_entries)
MK Ryu0c1a37d2015-04-30 12:00:55 -07001451 + get_host_num_special_tasks(**filter_data_special_tasks))
showardc0ac3a72009-07-08 21:14:45 +00001452
1453
showard29f7cd22009-04-29 21:16:24 +00001454# recurring run
1455
1456def get_recurring(**filter_data):
1457 return rpc_utils.prepare_rows_as_nested_dicts(
1458 models.RecurringRun.query_objects(filter_data),
1459 ('job', 'owner'))
1460
1461
1462def get_num_recurring(**filter_data):
1463 return models.RecurringRun.query_count(filter_data)
1464
1465
1466def delete_recurring_runs(**filter_data):
1467 to_delete = models.RecurringRun.query_objects(filter_data)
1468 to_delete.delete()
1469
1470
1471def create_recurring_run(job_id, start_date, loop_period, loop_count):
showard64a95952010-01-13 21:27:16 +00001472 owner = models.User.current_user().login
showard29f7cd22009-04-29 21:16:24 +00001473 job = models.Job.objects.get(id=job_id)
1474 return job.create_recurring_job(start_date=start_date,
1475 loop_period=loop_period,
1476 loop_count=loop_count,
1477 owner=owner)
1478
1479
mblighe8819cd2008-02-15 16:48:40 +00001480# other
1481
showarde0b63622008-08-04 20:58:47 +00001482def echo(data=""):
1483 """\
1484 Returns a passed in string. For doing a basic test to see if RPC calls
1485 can successfully be made.
1486 """
1487 return data
1488
1489
showardb7a52fd2009-04-27 20:10:56 +00001490def get_motd():
1491 """\
1492 Returns the message of the day as a string.
1493 """
1494 return rpc_utils.get_motd()
1495
1496
mblighe8819cd2008-02-15 16:48:40 +00001497def get_static_data():
jadmanski0afbb632008-06-06 21:10:57 +00001498 """\
1499 Returns a dictionary containing a bunch of data that shouldn't change
1500 often and is otherwise inaccessible. This includes:
showardc92da832009-04-07 18:14:34 +00001501
1502 priorities: List of job priority choices.
1503 default_priority: Default priority value for new jobs.
1504 users: Sorted list of all users.
Jiaxi Luo31874592014-06-11 10:36:35 -07001505 labels: Sorted list of labels not start with 'cros-version' and
1506 'fw-version'.
showardc92da832009-04-07 18:14:34 +00001507 atomic_groups: Sorted list of all atomic groups.
1508 tests: Sorted list of all tests.
1509 profilers: Sorted list of all profilers.
1510 current_user: Logged-in username.
1511 host_statuses: Sorted list of possible Host statuses.
1512 job_statuses: Sorted list of possible HostQueueEntry statuses.
Simran Basi7e605742013-11-12 13:43:36 -08001513 job_timeout_default: The default job timeout length in minutes.
showarda1e74b32009-05-12 17:32:04 +00001514 parse_failed_repair_default: Default value for the parse_failed_repair job
Jiaxi Luo31874592014-06-11 10:36:35 -07001515 option.
showardc92da832009-04-07 18:14:34 +00001516 reboot_before_options: A list of valid RebootBefore string enums.
1517 reboot_after_options: A list of valid RebootAfter string enums.
1518 motd: Server's message of the day.
1519 status_dictionary: A mapping from one word job status names to a more
1520 informative description.
jadmanski0afbb632008-06-06 21:10:57 +00001521 """
showard21baa452008-10-21 00:08:39 +00001522
1523 job_fields = models.Job.get_field_dict()
jamesren76fcf192010-04-21 20:39:50 +00001524 default_drone_set_name = models.DroneSet.default_drone_set_name()
1525 drone_sets = ([default_drone_set_name] +
1526 sorted(drone_set.name for drone_set in
1527 models.DroneSet.objects.exclude(
1528 name=default_drone_set_name)))
showard21baa452008-10-21 00:08:39 +00001529
jadmanski0afbb632008-06-06 21:10:57 +00001530 result = {}
Alex Miller7d658cf2013-09-04 16:00:35 -07001531 result['priorities'] = priorities.Priority.choices()
1532 default_priority = priorities.Priority.DEFAULT
1533 result['default_priority'] = 'Default'
1534 result['max_schedulable_priority'] = priorities.Priority.DEFAULT
jadmanski0afbb632008-06-06 21:10:57 +00001535 result['users'] = get_users(sort_by=['login'])
Jiaxi Luo31874592014-06-11 10:36:35 -07001536
1537 label_exclude_filters = [{'name__startswith': 'cros-version'},
Dan Shi65351d62015-08-03 12:03:23 -07001538 {'name__startswith': 'fw-version'},
1539 {'name__startswith': 'fwrw-version'},
Dan Shi27516972016-03-16 14:03:41 -07001540 {'name__startswith': 'fwro-version'},
1541 {'name__startswith': 'ab-version'},
1542 {'name__startswith': 'testbed-version'}]
Jiaxi Luo31874592014-06-11 10:36:35 -07001543 result['labels'] = get_labels(
1544 label_exclude_filters,
1545 sort_by=['-platform', 'name'])
1546
showardc92da832009-04-07 18:14:34 +00001547 result['atomic_groups'] = get_atomic_groups(sort_by=['name'])
jadmanski0afbb632008-06-06 21:10:57 +00001548 result['tests'] = get_tests(sort_by=['name'])
showard2b9a88b2008-06-13 20:55:03 +00001549 result['profilers'] = get_profilers(sort_by=['name'])
showard0fc38302008-10-23 00:44:07 +00001550 result['current_user'] = rpc_utils.prepare_for_serialization(
showard64a95952010-01-13 21:27:16 +00001551 models.User.current_user().get_object_dict())
showard2b9a88b2008-06-13 20:55:03 +00001552 result['host_statuses'] = sorted(models.Host.Status.names)
mbligh5a198b92008-12-11 19:33:29 +00001553 result['job_statuses'] = sorted(models.HostQueueEntry.Status.names)
Simran Basi7e605742013-11-12 13:43:36 -08001554 result['job_timeout_mins_default'] = models.Job.DEFAULT_TIMEOUT_MINS
Simran Basi34217022012-11-06 13:43:15 -08001555 result['job_max_runtime_mins_default'] = (
1556 models.Job.DEFAULT_MAX_RUNTIME_MINS)
showarda1e74b32009-05-12 17:32:04 +00001557 result['parse_failed_repair_default'] = bool(
1558 models.Job.DEFAULT_PARSE_FAILED_REPAIR)
jamesrendd855242010-03-02 22:23:44 +00001559 result['reboot_before_options'] = model_attributes.RebootBefore.names
1560 result['reboot_after_options'] = model_attributes.RebootAfter.names
showard8fbae652009-01-20 23:23:10 +00001561 result['motd'] = rpc_utils.get_motd()
jamesren76fcf192010-04-21 20:39:50 +00001562 result['drone_sets_enabled'] = models.DroneSet.drone_sets_enabled()
1563 result['drone_sets'] = drone_sets
jamesren4a41e012010-07-16 22:33:48 +00001564 result['parameterized_jobs'] = models.Job.parameterized_jobs_enabled()
showard8ac29b42008-07-17 17:01:55 +00001565
showardd3dc1992009-04-22 21:01:40 +00001566 result['status_dictionary'] = {"Aborted": "Aborted",
showard8ac29b42008-07-17 17:01:55 +00001567 "Verifying": "Verifying Host",
Alex Millerdfff2fd2013-05-28 13:05:06 -07001568 "Provisioning": "Provisioning Host",
showard8ac29b42008-07-17 17:01:55 +00001569 "Pending": "Waiting on other hosts",
1570 "Running": "Running autoserv",
1571 "Completed": "Autoserv completed",
1572 "Failed": "Failed to complete",
showardd823b362008-07-24 16:35:46 +00001573 "Queued": "Queued",
showard5deb6772008-11-04 21:54:33 +00001574 "Starting": "Next in host's queue",
1575 "Stopped": "Other host(s) failed verify",
showardd3dc1992009-04-22 21:01:40 +00001576 "Parsing": "Awaiting parse of final results",
showard29f7cd22009-04-29 21:16:24 +00001577 "Gathering": "Gathering log files",
showard8cc058f2009-09-08 16:26:33 +00001578 "Template": "Template job for recurring run",
mbligh4608b002010-01-05 18:22:35 +00001579 "Waiting": "Waiting for scheduler action",
Dan Shi07e09af2013-04-12 09:31:29 -07001580 "Archiving": "Archiving results",
1581 "Resetting": "Resetting hosts"}
Jiaxi Luo421608e2014-07-07 14:38:00 -07001582
1583 result['wmatrix_url'] = rpc_utils.get_wmatrix_url()
Simran Basi71206ef2014-08-13 13:51:18 -07001584 result['is_moblab'] = bool(utils.is_moblab())
Jiaxi Luo421608e2014-07-07 14:38:00 -07001585
jadmanski0afbb632008-06-06 21:10:57 +00001586 return result
showard29f7cd22009-04-29 21:16:24 +00001587
1588
1589def get_server_time():
1590 return datetime.datetime.now().strftime("%Y-%m-%d %H:%M")