blob: 74e61c7d9781abdfe4c6e0d0160475fde49e6cdf [file] [log] [blame]
Don Garretta06ea082017-01-13 00:04:26 +00001# 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
Michael Tang6dc174e2016-05-31 23:13:42 -070034import ast
showard29f7cd22009-04-29 21:16:24 +000035import datetime
Shuqian Zhao4c0d2902016-01-12 17:03:15 -080036import logging
Dan Shi4a3deb82016-10-27 21:32:30 -070037import sys
MK Ryu9c5fbbe2015-02-11 15:46:22 -080038
Moises Osorio2dc7a102014-12-02 18:24:02 -080039from django.db.models import Count
showardcafd16e2009-05-29 18:37:49 +000040import common
Michael Tang6dc174e2016-05-31 23:13:42 -070041from autotest_lib.client.common_lib import control_data
Simran Basib6ec8ae2014-04-23 12:05:08 -070042from autotest_lib.client.common_lib import priorities
Don Garretta06ea082017-01-13 00:04:26 +000043from autotest_lib.client.common_lib.cros import dev_server
Aviv Keshet14cac442016-11-20 21:44:11 -080044# TODO(akeshet): Replace with monarch stats once we know how to instrument rpc
45# server with ts_mon.
Gabe Black1e1c41b2015-02-04 23:55:15 -080046from autotest_lib.client.common_lib.cros.graphite import autotest_stats
Allen Lia59b1262016-12-14 12:53:51 -080047from autotest_lib.frontend.afe import control_file as control_file_lib
48from autotest_lib.frontend.afe import rpc_utils
J. Richard Barnetteb5164d62015-04-13 12:59:31 -070049from autotest_lib.frontend.afe import models, model_logic, model_attributes
Simran Basib6ec8ae2014-04-23 12:05:08 -070050from autotest_lib.frontend.afe import site_rpc_interface
Moises Osorio2dc7a102014-12-02 18:24:02 -080051from autotest_lib.frontend.tko import models as tko_models
Jiaxi Luoaac54572014-06-04 13:57:02 -070052from autotest_lib.frontend.tko import rpc_interface as tko_rpc_interface
J. Richard Barnetteb5164d62015-04-13 12:59:31 -070053from autotest_lib.server import frontend
Simran Basi71206ef2014-08-13 13:51:18 -070054from autotest_lib.server import utils
Dan Shid215dbe2015-06-18 16:14:59 -070055from autotest_lib.server.cros import provision
Jiaxi Luo90190c92014-06-18 12:35:57 -070056from autotest_lib.server.cros.dynamic_suite import tools
Aviv Keshet7ee95862016-08-30 15:18:27 -070057from autotest_lib.server.lib import status_history
mblighe8819cd2008-02-15 16:48:40 +000058
Moises Osorio2dc7a102014-12-02 18:24:02 -080059
Gabe Black1e1c41b2015-02-04 23:55:15 -080060_timer = autotest_stats.Timer('rpc_interface')
Moises Osorio2dc7a102014-12-02 18:24:02 -080061
Eric Lid23bc192011-02-09 14:38:57 -080062def get_parameterized_autoupdate_image_url(job):
63 """Get the parameterized autoupdate image url from a parameterized job."""
64 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
65 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
beeps8bb1f7d2013-08-05 01:30:09 -070066 name='image')
Eric Lid23bc192011-02-09 14:38:57 -080067 para_set = job.parameterized_job.parameterizedjobparameter_set
68 job_test_para = para_set.get(test_parameter=image_parameter)
69 return job_test_para.parameter_value
70
71
mblighe8819cd2008-02-15 16:48:40 +000072# labels
73
mblighe8819cd2008-02-15 16:48:40 +000074def modify_label(id, **data):
MK Ryu8c554cf2015-06-12 11:45:50 -070075 """Modify a label.
76
77 @param id: id or name of a label. More often a label name.
78 @param data: New data for a label.
79 """
80 label_model = models.Label.smart_get(id)
MK Ryu8e2c2d02016-01-06 15:24:38 -080081 label_model.update_object(data)
MK Ryu8c554cf2015-06-12 11:45:50 -070082
83 # Master forwards the RPC to shards
84 if not utils.is_shard():
85 rpc_utils.fanout_rpc(label_model.host_set.all(), 'modify_label', False,
86 id=id, **data)
87
mblighe8819cd2008-02-15 16:48:40 +000088
89def delete_label(id):
MK Ryu8c554cf2015-06-12 11:45:50 -070090 """Delete a label.
91
92 @param id: id or name of a label. More often a label name.
93 """
94 label_model = models.Label.smart_get(id)
MK Ryu8e2c2d02016-01-06 15:24:38 -080095 # Hosts that have the label to be deleted. Save this info before
96 # the label is deleted to use it later.
97 hosts = []
98 for h in label_model.host_set.all():
99 hosts.append(models.Host.smart_get(h.id))
100 label_model.delete()
MK Ryu8c554cf2015-06-12 11:45:50 -0700101
102 # Master forwards the RPC to shards
103 if not utils.is_shard():
MK Ryu8e2c2d02016-01-06 15:24:38 -0800104 rpc_utils.fanout_rpc(hosts, 'delete_label', False, id=id)
mblighe8819cd2008-02-15 16:48:40 +0000105
Prashanth Balasubramanian744898f2015-01-13 05:04:16 -0800106
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800107def add_label(name, ignore_exception_if_exists=False, **kwargs):
MK Ryucf027c62015-03-04 12:00:50 -0800108 """Adds a new label of a given name.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800109
110 @param name: label name.
111 @param ignore_exception_if_exists: If True and the exception was
112 thrown due to the duplicated label name when adding a label,
113 then suppress the exception. Default is False.
114 @param kwargs: keyword args that store more info about a label
115 other than the name.
116 @return: int/long id of a new label.
117 """
118 # models.Label.add_object() throws model_logic.ValidationError
119 # when it is given a label name that already exists.
120 # However, ValidationError can be thrown with different errors,
121 # and those errors should be thrown up to the call chain.
122 try:
123 label = models.Label.add_object(name=name, **kwargs)
124 except:
125 exc_info = sys.exc_info()
126 if ignore_exception_if_exists:
127 label = rpc_utils.get_label(name)
128 # If the exception is raised not because of duplicated
129 # "name", then raise the original exception.
130 if label is None:
131 raise exc_info[0], exc_info[1], exc_info[2]
132 else:
133 raise exc_info[0], exc_info[1], exc_info[2]
134 return label.id
135
136
137def add_label_to_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800138 """Adds a label of the given id to the given hosts only in local DB.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800139
140 @param id: id or name of a label. More often a label name.
141 @param hosts: The hostnames of hosts that need the label.
142
143 @raises models.Label.DoesNotExist: If the label with id doesn't exist.
144 """
145 label = models.Label.smart_get(id)
146 host_objs = models.Host.smart_get_bulk(hosts)
147 if label.platform:
148 models.Host.check_no_platform(host_objs)
Shuqian Zhao40e182b2016-10-11 11:55:11 -0700149 # Ensure a host has no more than one board label with it.
150 if label.name.startswith('board:'):
Dan Shib5b8b4f2016-11-02 14:04:02 -0700151 models.Host.check_board_labels_allowed(host_objs, [label.name])
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800152 label.host_set.add(*host_objs)
153
154
Kevin Chengbdfc57d2016-04-14 13:46:58 -0700155def _create_label_everywhere(id, hosts):
156 """
157 Yet another method to create labels.
158
159 ALERT! This method should be run only on master not shards!
160 DO NOT RUN THIS ON A SHARD!!! Deputies will hate you if you do!!!
161
162 This method exists primarily to serve label_add_hosts() and
163 host_add_labels(). Basically it pulls out the label check/add logic
164 from label_add_hosts() into this nice method that not only creates
165 the label but also tells the shards that service the hosts to also
166 create the label.
167
168 @param id: id or name of a label. More often a label name.
169 @param hosts: A list of hostnames or ids. More often hostnames.
170 """
171 try:
172 label = models.Label.smart_get(id)
173 except models.Label.DoesNotExist:
174 # This matches the type checks in smart_get, which is a hack
175 # in and off itself. The aim here is to create any non-existent
176 # label, which we cannot do if the 'id' specified isn't a label name.
177 if isinstance(id, basestring):
178 label = models.Label.smart_get(add_label(id))
179 else:
180 raise ValueError('Label id (%s) does not exist. Please specify '
181 'the argument, id, as a string (label name).'
182 % id)
183
184 # Make sure the label exists on the shard with the same id
185 # as it is on the master.
186 # It is possible that the label is already in a shard because
187 # we are adding a new label only to shards of hosts that the label
188 # is going to be attached.
189 # For example, we add a label L1 to a host in shard S1.
190 # Master and S1 will have L1 but other shards won't.
191 # Later, when we add the same label L1 to hosts in shards S1 and S2,
192 # S1 already has the label but S2 doesn't.
193 # S2 should have the new label without any problem.
194 # We ignore exception in such a case.
195 host_objs = models.Host.smart_get_bulk(hosts)
196 rpc_utils.fanout_rpc(
197 host_objs, 'add_label', include_hostnames=False,
198 name=label.name, ignore_exception_if_exists=True,
199 id=label.id, platform=label.platform)
200
201
MK Ryufbb002c2015-06-08 14:13:16 -0700202@rpc_utils.route_rpc_to_master
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800203def label_add_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800204 """Adds a label with the given id to the given hosts.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800205
206 This method should be run only on master not shards.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800207 The given label will be created if it doesn't exist, provided the `id`
208 supplied is a label name not an int/long id.
209
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800210 @param id: id or name of a label. More often a label name.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800211 @param hosts: A list of hostnames or ids. More often hostnames.
212
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800213 @raises ValueError: If the id specified is an int/long (label id)
214 while the label does not exist.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800215 """
Kevin Chengbdfc57d2016-04-14 13:46:58 -0700216 # Create the label.
217 _create_label_everywhere(id, hosts)
218
219 # Add it to the master.
MK Ryu8e2c2d02016-01-06 15:24:38 -0800220 add_label_to_hosts(id, hosts)
MK Ryucf027c62015-03-04 12:00:50 -0800221
Kevin Chengbdfc57d2016-04-14 13:46:58 -0700222 # Add it to the shards.
MK Ryucf027c62015-03-04 12:00:50 -0800223 host_objs = models.Host.smart_get_bulk(hosts)
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800224 rpc_utils.fanout_rpc(host_objs, 'add_label_to_hosts', id=id)
showardbbabf502008-06-06 00:02:02 +0000225
226
MK Ryucf027c62015-03-04 12:00:50 -0800227def remove_label_from_hosts(id, hosts):
228 """Removes a label of the given id from the given hosts only in local DB.
229
230 @param id: id or name of a label.
231 @param hosts: The hostnames of hosts that need to remove the label from.
232 """
showardbe3ec042008-11-12 18:16:07 +0000233 host_objs = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000234 models.Label.smart_get(id).host_set.remove(*host_objs)
showardbbabf502008-06-06 00:02:02 +0000235
236
MK Ryufbb002c2015-06-08 14:13:16 -0700237@rpc_utils.route_rpc_to_master
MK Ryucf027c62015-03-04 12:00:50 -0800238def label_remove_hosts(id, hosts):
239 """Removes a label of the given id from the given hosts.
240
241 This method should be run only on master not shards.
242
243 @param id: id or name of a label.
244 @param hosts: A list of hostnames or ids. More often hostnames.
245 """
MK Ryucf027c62015-03-04 12:00:50 -0800246 host_objs = models.Host.smart_get_bulk(hosts)
MK Ryu26f0c932015-05-28 18:14:33 -0700247 remove_label_from_hosts(id, hosts)
248
MK Ryu8e2c2d02016-01-06 15:24:38 -0800249 rpc_utils.fanout_rpc(host_objs, 'remove_label_from_hosts', id=id)
250
MK Ryucf027c62015-03-04 12:00:50 -0800251
Jiaxi Luo31874592014-06-11 10:36:35 -0700252def get_labels(exclude_filters=(), **filter_data):
showardc92da832009-04-07 18:14:34 +0000253 """\
Jiaxi Luo31874592014-06-11 10:36:35 -0700254 @param exclude_filters: A sequence of dictionaries of filters.
255
showardc92da832009-04-07 18:14:34 +0000256 @returns A sequence of nested dictionaries of label information.
257 """
Jiaxi Luo31874592014-06-11 10:36:35 -0700258 labels = models.Label.query_objects(filter_data)
259 for exclude_filter in exclude_filters:
260 labels = labels.exclude(**exclude_filter)
261 return rpc_utils.prepare_rows_as_nested_dicts(labels, ('atomic_group',))
showardc92da832009-04-07 18:14:34 +0000262
263
264# atomic groups
265
showarde9450c92009-06-30 01:58:52 +0000266def add_atomic_group(name, max_number_of_machines=None, description=None):
showardc92da832009-04-07 18:14:34 +0000267 return models.AtomicGroup.add_object(
268 name=name, max_number_of_machines=max_number_of_machines,
269 description=description).id
270
271
272def modify_atomic_group(id, **data):
273 models.AtomicGroup.smart_get(id).update_object(data)
274
275
276def delete_atomic_group(id):
277 models.AtomicGroup.smart_get(id).delete()
278
279
280def atomic_group_add_labels(id, labels):
281 label_objs = models.Label.smart_get_bulk(labels)
282 models.AtomicGroup.smart_get(id).label_set.add(*label_objs)
283
284
285def atomic_group_remove_labels(id, labels):
286 label_objs = models.Label.smart_get_bulk(labels)
287 models.AtomicGroup.smart_get(id).label_set.remove(*label_objs)
288
289
290def get_atomic_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000291 return rpc_utils.prepare_for_serialization(
showardc92da832009-04-07 18:14:34 +0000292 models.AtomicGroup.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000293
294
295# hosts
296
Matthew Sartori68186332015-04-27 17:19:53 -0700297def add_host(hostname, status=None, locked=None, lock_reason='', protection=None):
298 if locked and not lock_reason:
299 raise model_logic.ValidationError(
300 {'locked': 'Please provide a reason for locking when adding host.'})
301
jadmanski0afbb632008-06-06 21:10:57 +0000302 return models.Host.add_object(hostname=hostname, status=status,
Matthew Sartori68186332015-04-27 17:19:53 -0700303 locked=locked, lock_reason=lock_reason,
304 protection=protection).id
mblighe8819cd2008-02-15 16:48:40 +0000305
306
MK Ryu33889612015-09-04 14:32:35 -0700307@rpc_utils.route_rpc_to_master
308def modify_host(id, **kwargs):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700309 """Modify local attributes of a host.
310
311 If this is called on the master, but the host is assigned to a shard, this
MK Ryu33889612015-09-04 14:32:35 -0700312 will call `modify_host_local` RPC to the responsible shard. This means if
313 a host is being locked using this function, this change will also propagate
314 to shards.
315 When this is called on a shard, the shard just routes the RPC to the master
316 and does nothing.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700317
318 @param id: id of the host to modify.
MK Ryu33889612015-09-04 14:32:35 -0700319 @param kwargs: key=value pairs of values to set on the host.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700320 """
MK Ryu33889612015-09-04 14:32:35 -0700321 rpc_utils.check_modify_host(kwargs)
showardce7c0922009-09-11 18:39:24 +0000322 host = models.Host.smart_get(id)
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800323 try:
324 rpc_utils.check_modify_host_locking(host, kwargs)
325 except model_logic.ValidationError as e:
326 if not kwargs.get('force_modify_locking', False):
327 raise
328 logging.exception('The following exception will be ignored and lock '
329 'modification will be enforced. %s', e)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700330
MK Ryud53e1492015-12-15 12:09:03 -0800331 # This is required to make `lock_time` for a host be exactly same
332 # between the master and a shard.
333 if kwargs.get('locked', None) and 'lock_time' not in kwargs:
334 kwargs['lock_time'] = datetime.datetime.now()
MK Ryu8e2c2d02016-01-06 15:24:38 -0800335 host.update_object(kwargs)
MK Ryud53e1492015-12-15 12:09:03 -0800336
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800337 # force_modifying_locking is not an internal field in database, remove.
338 kwargs.pop('force_modify_locking', None)
MK Ryu33889612015-09-04 14:32:35 -0700339 rpc_utils.fanout_rpc([host], 'modify_host_local',
340 include_hostnames=False, id=id, **kwargs)
mblighe8819cd2008-02-15 16:48:40 +0000341
342
MK Ryu33889612015-09-04 14:32:35 -0700343def modify_host_local(id, **kwargs):
344 """Modify host attributes in local DB.
345
346 @param id: Host id.
347 @param kwargs: key=value pairs of values to set on the host.
348 """
349 models.Host.smart_get(id).update_object(kwargs)
350
351
352@rpc_utils.route_rpc_to_master
showard276f9442009-05-20 00:33:16 +0000353def modify_hosts(host_filter_data, update_data):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700354 """Modify local attributes of multiple hosts.
355
356 If this is called on the master, but one of the hosts in that match the
MK Ryu33889612015-09-04 14:32:35 -0700357 filters is assigned to a shard, this will call `modify_hosts_local` RPC
358 to the responsible shard.
359 When this is called on a shard, the shard just routes the RPC to the master
360 and does nothing.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700361
362 The filters are always applied on the master, not on the shards. This means
363 if the states of a host differ on the master and a shard, the state on the
364 master will be used. I.e. this means:
365 A host was synced to Shard 1. On Shard 1 the status of the host was set to
366 'Repair Failed'.
367 - A call to modify_hosts with host_filter_data={'status': 'Ready'} will
368 update the host (both on the shard and on the master), because the state
369 of the host as the master knows it is still 'Ready'.
370 - A call to modify_hosts with host_filter_data={'status': 'Repair failed'
371 will not update the host, because the filter doesn't apply on the master.
372
showardbe0d8692009-08-20 23:42:44 +0000373 @param host_filter_data: Filters out which hosts to modify.
374 @param update_data: A dictionary with the changes to make to the hosts.
showard276f9442009-05-20 00:33:16 +0000375 """
MK Ryu93161712015-12-21 10:41:32 -0800376 update_data = update_data.copy()
showardbe0d8692009-08-20 23:42:44 +0000377 rpc_utils.check_modify_host(update_data)
showard276f9442009-05-20 00:33:16 +0000378 hosts = models.Host.query_objects(host_filter_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700379
380 affected_shard_hostnames = set()
381 affected_host_ids = []
382
Alex Miller9658a952013-05-14 16:40:02 -0700383 # Check all hosts before changing data for exception safety.
384 for host in hosts:
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800385 try:
386 rpc_utils.check_modify_host_locking(host, update_data)
387 except model_logic.ValidationError as e:
388 if not update_data.get('force_modify_locking', False):
389 raise
390 logging.exception('The following exception will be ignored and '
391 'lock modification will be enforced. %s', e)
392
Jakob Juelich50e91f72014-10-01 12:43:23 -0700393 if host.shard:
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800394 affected_shard_hostnames.add(host.shard.rpc_hostname())
Jakob Juelich50e91f72014-10-01 12:43:23 -0700395 affected_host_ids.append(host.id)
396
MK Ryud53e1492015-12-15 12:09:03 -0800397 # This is required to make `lock_time` for a host be exactly same
398 # between the master and a shard.
399 if update_data.get('locked', None) and 'lock_time' not in update_data:
400 update_data['lock_time'] = datetime.datetime.now()
MK Ryu8e2c2d02016-01-06 15:24:38 -0800401 for host in hosts:
402 host.update_object(update_data)
MK Ryud53e1492015-12-15 12:09:03 -0800403
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800404 update_data.pop('force_modify_locking', None)
MK Ryu33889612015-09-04 14:32:35 -0700405 # Caution: Changing the filter from the original here. See docstring.
406 rpc_utils.run_rpc_on_multiple_hostnames(
407 'modify_hosts_local', affected_shard_hostnames,
Jakob Juelich50e91f72014-10-01 12:43:23 -0700408 host_filter_data={'id__in': affected_host_ids},
409 update_data=update_data)
410
showard276f9442009-05-20 00:33:16 +0000411
MK Ryu33889612015-09-04 14:32:35 -0700412def modify_hosts_local(host_filter_data, update_data):
413 """Modify attributes of hosts in local DB.
414
415 @param host_filter_data: Filters out which hosts to modify.
416 @param update_data: A dictionary with the changes to make to the hosts.
417 """
418 for host in models.Host.query_objects(host_filter_data):
419 host.update_object(update_data)
420
421
MK Ryufbb002c2015-06-08 14:13:16 -0700422def add_labels_to_host(id, labels):
423 """Adds labels to a given host only in local DB.
showardcafd16e2009-05-29 18:37:49 +0000424
MK Ryufbb002c2015-06-08 14:13:16 -0700425 @param id: id or hostname for a host.
426 @param labels: ids or names for labels.
427 """
428 label_objs = models.Label.smart_get_bulk(labels)
429 models.Host.smart_get(id).labels.add(*label_objs)
430
431
432@rpc_utils.route_rpc_to_master
433def host_add_labels(id, labels):
434 """Adds labels to a given host.
435
436 @param id: id or hostname for a host.
437 @param labels: ids or names for labels.
438
Shuqian Zhao40e182b2016-10-11 11:55:11 -0700439 @raises ValidationError: If adding more than one platform/board label.
MK Ryufbb002c2015-06-08 14:13:16 -0700440 """
Kevin Chengbdfc57d2016-04-14 13:46:58 -0700441 # Create the labels on the master/shards.
442 for label in labels:
443 _create_label_everywhere(label, [id])
444
MK Ryufbb002c2015-06-08 14:13:16 -0700445 label_objs = models.Label.smart_get_bulk(labels)
446 platforms = [label.name for label in label_objs if label.platform]
Shuqian Zhao40e182b2016-10-11 11:55:11 -0700447 boards = [label.name for label in label_objs
448 if label.name.startswith('board:')]
Dan Shib5b8b4f2016-11-02 14:04:02 -0700449 if len(platforms) > 1 or not utils.board_labels_allowed(boards):
showardcafd16e2009-05-29 18:37:49 +0000450 raise model_logic.ValidationError(
Dan Shib5b8b4f2016-11-02 14:04:02 -0700451 {'labels': ('Adding more than one platform label, or a list of '
452 'non-compatible board labels.: %s %s' %
453 (', '.join(platforms), ', '.join(boards)))})
MK Ryufbb002c2015-06-08 14:13:16 -0700454
455 host_obj = models.Host.smart_get(id)
Dan Shi4a3deb82016-10-27 21:32:30 -0700456 if platforms:
MK Ryufbb002c2015-06-08 14:13:16 -0700457 models.Host.check_no_platform([host_obj])
Dan Shi4a3deb82016-10-27 21:32:30 -0700458 if boards:
Dan Shib5b8b4f2016-11-02 14:04:02 -0700459 models.Host.check_board_labels_allowed([host_obj], labels)
MK Ryu8e2c2d02016-01-06 15:24:38 -0800460 add_labels_to_host(id, labels)
MK Ryufbb002c2015-06-08 14:13:16 -0700461
462 rpc_utils.fanout_rpc([host_obj], 'add_labels_to_host', False,
463 id=id, labels=labels)
mblighe8819cd2008-02-15 16:48:40 +0000464
465
MK Ryufbb002c2015-06-08 14:13:16 -0700466def remove_labels_from_host(id, labels):
467 """Removes labels from a given host only in local DB.
468
469 @param id: id or hostname for a host.
470 @param labels: ids or names for labels.
471 """
472 label_objs = models.Label.smart_get_bulk(labels)
473 models.Host.smart_get(id).labels.remove(*label_objs)
474
475
476@rpc_utils.route_rpc_to_master
mblighe8819cd2008-02-15 16:48:40 +0000477def host_remove_labels(id, labels):
MK Ryufbb002c2015-06-08 14:13:16 -0700478 """Removes labels from a given host.
479
480 @param id: id or hostname for a host.
481 @param labels: ids or names for labels.
482 """
MK Ryu8e2c2d02016-01-06 15:24:38 -0800483 remove_labels_from_host(id, labels)
484
MK Ryufbb002c2015-06-08 14:13:16 -0700485 host_obj = models.Host.smart_get(id)
486 rpc_utils.fanout_rpc([host_obj], 'remove_labels_from_host', False,
487 id=id, labels=labels)
mblighe8819cd2008-02-15 16:48:40 +0000488
489
MK Ryuacf35922014-10-03 14:56:49 -0700490def get_host_attribute(attribute, **host_filter_data):
491 """
492 @param attribute: string name of attribute
493 @param host_filter_data: filter data to apply to Hosts to choose hosts to
494 act upon
495 """
496 hosts = rpc_utils.get_host_query((), False, False, True, host_filter_data)
497 hosts = list(hosts)
498 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
499 'attribute_list')
500 host_attr_dicts = []
501 for host_obj in hosts:
502 for attr_obj in host_obj.attribute_list:
503 if attr_obj.attribute == attribute:
504 host_attr_dicts.append(attr_obj.get_object_dict())
505 return rpc_utils.prepare_for_serialization(host_attr_dicts)
506
507
showard0957a842009-05-11 19:25:08 +0000508def set_host_attribute(attribute, value, **host_filter_data):
509 """
MK Ryu26f0c932015-05-28 18:14:33 -0700510 @param attribute: string name of attribute
511 @param value: string, or None to delete an attribute
512 @param host_filter_data: filter data to apply to Hosts to choose hosts to
513 act upon
showard0957a842009-05-11 19:25:08 +0000514 """
515 assert host_filter_data # disallow accidental actions on all hosts
516 hosts = models.Host.query_objects(host_filter_data)
517 models.AclGroup.check_for_acl_violation_hosts(hosts)
MK Ryu8e2c2d02016-01-06 15:24:38 -0800518 for host in hosts:
519 host.set_or_delete_attribute(attribute, value)
showard0957a842009-05-11 19:25:08 +0000520
MK Ryu26f0c932015-05-28 18:14:33 -0700521 # Master forwards this RPC to shards.
522 if not utils.is_shard():
523 rpc_utils.fanout_rpc(hosts, 'set_host_attribute', False,
524 attribute=attribute, value=value, **host_filter_data)
525
showard0957a842009-05-11 19:25:08 +0000526
Jakob Juelich50e91f72014-10-01 12:43:23 -0700527@rpc_utils.forward_single_host_rpc_to_shard
mblighe8819cd2008-02-15 16:48:40 +0000528def delete_host(id):
jadmanski0afbb632008-06-06 21:10:57 +0000529 models.Host.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000530
531
showard87cc38f2009-08-20 23:37:04 +0000532def get_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
Dan Shi37df54d2015-12-14 11:16:28 -0800533 exclude_atomic_group_hosts=False, valid_only=True,
534 include_current_job=False, **filter_data):
535 """Get a list of dictionaries which contains the information of hosts.
536
showard87cc38f2009-08-20 23:37:04 +0000537 @param multiple_labels: match hosts in all of the labels given. Should
538 be a list of label names.
539 @param exclude_only_if_needed_labels: Exclude hosts with at least one
540 "only_if_needed" label applied.
541 @param exclude_atomic_group_hosts: Exclude hosts that have one or more
542 atomic group labels associated with them.
Dan Shi37df54d2015-12-14 11:16:28 -0800543 @param include_current_job: Set to True to include ids of currently running
544 job and special task.
jadmanski0afbb632008-06-06 21:10:57 +0000545 """
showard43a3d262008-11-12 18:17:05 +0000546 hosts = rpc_utils.get_host_query(multiple_labels,
547 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000548 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000549 valid_only, filter_data)
showard0957a842009-05-11 19:25:08 +0000550 hosts = list(hosts)
551 models.Host.objects.populate_relationships(hosts, models.Label,
552 'label_list')
553 models.Host.objects.populate_relationships(hosts, models.AclGroup,
554 'acl_list')
555 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
556 'attribute_list')
showard43a3d262008-11-12 18:17:05 +0000557 host_dicts = []
558 for host_obj in hosts:
559 host_dict = host_obj.get_object_dict()
showard0957a842009-05-11 19:25:08 +0000560 host_dict['labels'] = [label.name for label in host_obj.label_list]
showard909c9142009-07-07 20:54:42 +0000561 host_dict['platform'], host_dict['atomic_group'] = (rpc_utils.
562 find_platform_and_atomic_group(host_obj))
showard0957a842009-05-11 19:25:08 +0000563 host_dict['acls'] = [acl.name for acl in host_obj.acl_list]
564 host_dict['attributes'] = dict((attribute.attribute, attribute.value)
565 for attribute in host_obj.attribute_list)
Dan Shi37df54d2015-12-14 11:16:28 -0800566 if include_current_job:
567 host_dict['current_job'] = None
568 host_dict['current_special_task'] = None
569 entries = models.HostQueueEntry.objects.filter(
570 host_id=host_dict['id'], active=True, complete=False)
571 if entries:
572 host_dict['current_job'] = (
573 entries[0].get_object_dict()['job'])
574 tasks = models.SpecialTask.objects.filter(
575 host_id=host_dict['id'], is_active=True, is_complete=False)
576 if tasks:
577 host_dict['current_special_task'] = (
578 '%d-%s' % (tasks[0].get_object_dict()['id'],
579 tasks[0].get_object_dict()['task'].lower()))
showard43a3d262008-11-12 18:17:05 +0000580 host_dicts.append(host_dict)
581 return rpc_utils.prepare_for_serialization(host_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000582
583
showard87cc38f2009-08-20 23:37:04 +0000584def get_num_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000585 exclude_atomic_group_hosts=False, valid_only=True,
586 **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000587 """
588 Same parameters as get_hosts().
589
590 @returns The number of matching hosts.
591 """
showard43a3d262008-11-12 18:17:05 +0000592 hosts = rpc_utils.get_host_query(multiple_labels,
593 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000594 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000595 valid_only, filter_data)
showard43a3d262008-11-12 18:17:05 +0000596 return hosts.count()
showard1385b162008-03-13 15:59:40 +0000597
mblighe8819cd2008-02-15 16:48:40 +0000598
599# tests
600
showard909c7a62008-07-15 21:52:38 +0000601def add_test(name, test_type, path, author=None, dependencies=None,
showard3d9899a2008-07-31 02:11:58 +0000602 experimental=True, run_verify=None, test_class=None,
showard909c7a62008-07-15 21:52:38 +0000603 test_time=None, test_category=None, description=None,
604 sync_count=1):
jadmanski0afbb632008-06-06 21:10:57 +0000605 return models.Test.add_object(name=name, test_type=test_type, path=path,
showard909c7a62008-07-15 21:52:38 +0000606 author=author, dependencies=dependencies,
607 experimental=experimental,
608 run_verify=run_verify, test_time=test_time,
609 test_category=test_category,
610 sync_count=sync_count,
jadmanski0afbb632008-06-06 21:10:57 +0000611 test_class=test_class,
612 description=description).id
mblighe8819cd2008-02-15 16:48:40 +0000613
614
615def modify_test(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000616 models.Test.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000617
618
619def delete_test(id):
jadmanski0afbb632008-06-06 21:10:57 +0000620 models.Test.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000621
622
623def get_tests(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000624 return rpc_utils.prepare_for_serialization(
625 models.Test.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000626
627
Moises Osorio2dc7a102014-12-02 18:24:02 -0800628@_timer.decorate
629def get_tests_status_counts_by_job_name_label(job_name_prefix, label_name):
630 """Gets the counts of all passed and failed tests from the matching jobs.
631
632 @param job_name_prefix: Name prefix of the jobs to get the summary from, e.g.,
633 'butterfly-release/R40-6457.21.0/bvt-cq/'.
634 @param label_name: Label that must be set in the jobs, e.g.,
635 'cros-version:butterfly-release/R40-6457.21.0'.
636
637 @returns A summary of the counts of all the passed and failed tests.
638 """
639 job_ids = list(models.Job.objects.filter(
640 name__startswith=job_name_prefix,
641 dependency_labels__name=label_name).values_list(
642 'pk', flat=True))
643 summary = {'passed': 0, 'failed': 0}
644 if not job_ids:
645 return summary
646
647 counts = (tko_models.TestView.objects.filter(
648 afe_job_id__in=job_ids).exclude(
649 test_name='SERVER_JOB').exclude(
650 test_name__startswith='CLIENT_JOB').values(
651 'status').annotate(
652 count=Count('status')))
653 for status in counts:
654 if status['status'] == 'GOOD':
655 summary['passed'] += status['count']
656 else:
657 summary['failed'] += status['count']
658 return summary
659
660
showard2b9a88b2008-06-13 20:55:03 +0000661# profilers
662
663def add_profiler(name, description=None):
664 return models.Profiler.add_object(name=name, description=description).id
665
666
667def modify_profiler(id, **data):
668 models.Profiler.smart_get(id).update_object(data)
669
670
671def delete_profiler(id):
672 models.Profiler.smart_get(id).delete()
673
674
675def get_profilers(**filter_data):
676 return rpc_utils.prepare_for_serialization(
677 models.Profiler.list_objects(filter_data))
678
679
mblighe8819cd2008-02-15 16:48:40 +0000680# users
681
682def add_user(login, access_level=None):
jadmanski0afbb632008-06-06 21:10:57 +0000683 return models.User.add_object(login=login, access_level=access_level).id
mblighe8819cd2008-02-15 16:48:40 +0000684
685
686def modify_user(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000687 models.User.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000688
689
690def delete_user(id):
jadmanski0afbb632008-06-06 21:10:57 +0000691 models.User.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000692
693
694def get_users(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000695 return rpc_utils.prepare_for_serialization(
696 models.User.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000697
698
699# acl groups
700
701def add_acl_group(name, description=None):
showard04f2cd82008-07-25 20:53:31 +0000702 group = models.AclGroup.add_object(name=name, description=description)
showard64a95952010-01-13 21:27:16 +0000703 group.users.add(models.User.current_user())
showard04f2cd82008-07-25 20:53:31 +0000704 return group.id
mblighe8819cd2008-02-15 16:48:40 +0000705
706
707def modify_acl_group(id, **data):
showard04f2cd82008-07-25 20:53:31 +0000708 group = models.AclGroup.smart_get(id)
709 group.check_for_acl_violation_acl_group()
710 group.update_object(data)
711 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000712
713
714def acl_group_add_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000715 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000716 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000717 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000718 group.users.add(*users)
mblighe8819cd2008-02-15 16:48:40 +0000719
720
721def acl_group_remove_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000722 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000723 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000724 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000725 group.users.remove(*users)
showard04f2cd82008-07-25 20:53:31 +0000726 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000727
728
729def acl_group_add_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000730 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000731 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000732 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000733 group.hosts.add(*hosts)
showard08f981b2008-06-24 21:59:03 +0000734 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000735
736
737def acl_group_remove_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000738 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000739 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000740 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000741 group.hosts.remove(*hosts)
showard08f981b2008-06-24 21:59:03 +0000742 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000743
744
745def delete_acl_group(id):
jadmanski0afbb632008-06-06 21:10:57 +0000746 models.AclGroup.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000747
748
749def get_acl_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000750 acl_groups = models.AclGroup.list_objects(filter_data)
751 for acl_group in acl_groups:
752 acl_group_obj = models.AclGroup.objects.get(id=acl_group['id'])
753 acl_group['users'] = [user.login
754 for user in acl_group_obj.users.all()]
755 acl_group['hosts'] = [host.hostname
756 for host in acl_group_obj.hosts.all()]
757 return rpc_utils.prepare_for_serialization(acl_groups)
mblighe8819cd2008-02-15 16:48:40 +0000758
759
760# jobs
761
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700762def generate_control_file(tests=(), profilers=(),
showard91f85102009-10-12 20:34:52 +0000763 client_control_file='', use_container=False,
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700764 profile_only=None, db_tests=True,
765 test_source_build=None):
jadmanski0afbb632008-06-06 21:10:57 +0000766 """
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700767 Generates a client-side control file to run tests.
mbligh120351e2009-01-24 01:40:45 +0000768
Matthew Sartori10438092015-06-24 14:30:18 -0700769 @param tests List of tests to run. See db_tests for more information.
mbligh120351e2009-01-24 01:40:45 +0000770 @param profilers List of profilers to activate during the job.
771 @param client_control_file The contents of a client-side control file to
772 run at the end of all tests. If this is supplied, all tests must be
773 client side.
774 TODO: in the future we should support server control files directly
775 to wrap with a kernel. That'll require changing the parameter
776 name and adding a boolean to indicate if it is a client or server
777 control file.
778 @param use_container unused argument today. TODO: Enable containers
779 on the host during a client side test.
showard91f85102009-10-12 20:34:52 +0000780 @param profile_only A boolean that indicates what default profile_only
781 mode to use in the control file. Passing None will generate a
782 control file that does not explcitly set the default mode at all.
Matthew Sartori10438092015-06-24 14:30:18 -0700783 @param db_tests: if True, the test object can be found in the database
784 backing the test model. In this case, tests is a tuple
785 of test IDs which are used to retrieve the test objects
786 from the database. If False, tests is a tuple of test
787 dictionaries stored client-side in the AFE.
Michael Tang84a2ecf2016-06-07 15:10:53 -0700788 @param test_source_build: Build to be used to retrieve test code. Default
789 to None.
mbligh120351e2009-01-24 01:40:45 +0000790
791 @returns a dict with the following keys:
792 control_file: str, The control file text.
793 is_server: bool, is the control file a server-side control file?
794 synch_count: How many machines the job uses per autoserv execution.
795 synch_count == 1 means the job is asynchronous.
796 dependencies: A list of the names of labels on which the job depends.
797 """
showardd86debe2009-06-10 17:37:56 +0000798 if not tests and not client_control_file:
showard2bab8f42008-11-12 18:15:22 +0000799 return dict(control_file='', is_server=False, synch_count=1,
showard989f25d2008-10-01 11:38:11 +0000800 dependencies=[])
mblighe8819cd2008-02-15 16:48:40 +0000801
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700802 cf_info, test_objects, profiler_objects = (
803 rpc_utils.prepare_generate_control_file(tests, profilers,
804 db_tests))
Allen Lia59b1262016-12-14 12:53:51 -0800805 cf_info['control_file'] = control_file_lib.generate_control(
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700806 tests=test_objects, profilers=profiler_objects,
807 is_server=cf_info['is_server'],
showard232b7ae2009-11-10 00:46:48 +0000808 client_control_file=client_control_file, profile_only=profile_only,
Michael Tang84a2ecf2016-06-07 15:10:53 -0700809 test_source_build=test_source_build)
showard989f25d2008-10-01 11:38:11 +0000810 return cf_info
mblighe8819cd2008-02-15 16:48:40 +0000811
812
Allen Li41e47c12016-12-14 12:43:44 -0800813def create_parameterized_job(
814 name,
815 priority,
816 test,
817 parameters,
818 kernel=None,
819 label=None,
820 profilers=(),
821 profiler_parameters=None,
822 use_container=False,
823 profile_only=None,
824 upload_kernel_config=False,
825 hosts=(),
826 meta_hosts=(),
827 one_time_hosts=(),
828 atomic_group_name=None,
829 synch_count=None,
830 is_template=False,
831 timeout=None,
832 timeout_mins=None,
833 max_runtime_mins=None,
834 run_verify=False,
835 email_list='',
836 dependencies=(),
837 reboot_before=None,
838 reboot_after=None,
839 parse_failed_repair=None,
840 hostless=False,
841 keyvals=None,
842 drone_set=None,
843 run_reset=True,
844 require_ssp=None):
Shuqian Zhao54a5b672016-05-11 22:12:17 +0000845 """
846 Creates and enqueues a parameterized job.
847
848 Most parameters a combination of the parameters for generate_control_file()
849 and create_job(), with the exception of:
850
851 @param test name or ID of the test to run
852 @param parameters a map of parameter name ->
853 tuple of (param value, param type)
854 @param profiler_parameters a dictionary of parameters for the profilers:
855 key: profiler name
856 value: dict of param name -> tuple of
857 (param value,
858 param type)
859 """
Shuqian Zhao54a5b672016-05-11 22:12:17 +0000860 # Set up the parameterized job configs
861 test_obj = models.Test.smart_get(test)
862 control_type = test_obj.test_type
863
864 try:
865 label = models.Label.smart_get(label)
866 except models.Label.DoesNotExist:
867 label = None
868
869 kernel_objs = models.Kernel.create_kernels(kernel)
870 profiler_objs = [models.Profiler.smart_get(profiler)
871 for profiler in profilers]
872
873 parameterized_job = models.ParameterizedJob.objects.create(
874 test=test_obj, label=label, use_container=use_container,
875 profile_only=profile_only,
876 upload_kernel_config=upload_kernel_config)
877 parameterized_job.kernels.add(*kernel_objs)
878
879 for profiler in profiler_objs:
880 parameterized_profiler = models.ParameterizedJobProfiler.objects.create(
881 parameterized_job=parameterized_job,
882 profiler=profiler)
883 profiler_params = profiler_parameters.get(profiler.name, {})
884 for name, (value, param_type) in profiler_params.iteritems():
885 models.ParameterizedJobProfilerParameter.objects.create(
886 parameterized_job_profiler=parameterized_profiler,
887 parameter_name=name,
888 parameter_value=value,
889 parameter_type=param_type)
890
891 try:
892 for parameter in test_obj.testparameter_set.all():
893 if parameter.name in parameters:
894 param_value, param_type = parameters.pop(parameter.name)
895 parameterized_job.parameterizedjobparameter_set.create(
896 test_parameter=parameter, parameter_value=param_value,
897 parameter_type=param_type)
898
899 if parameters:
900 raise Exception('Extra parameters remain: %r' % parameters)
901
902 return rpc_utils.create_job_common(
Allen Li47034ee2016-12-14 12:59:29 -0800903 **rpc_utils.get_create_job_common_args(dict(
904 name=name,
905 priority=priority,
906 control_type=control_type,
907 hosts=hosts,
908 meta_hosts=meta_hosts,
909 one_time_hosts=one_time_hosts,
910 atomic_group_name=atomic_group_name,
911 synch_count=synch_count,
912 is_template=is_template,
913 timeout=timeout,
914 timeout_mins=timeout_mins,
915 max_runtime_mins=max_runtime_mins,
916 run_verify=run_verify,
917 email_list=email_list,
918 dependencies=dependencies,
919 reboot_before=reboot_before,
920 reboot_after=reboot_after,
921 parse_failed_repair=parse_failed_repair,
922 hostless=hostless,
923 keyvals=keyvals,
924 drone_set=drone_set,
925 parameterized_job=parameterized_job.id,
926 run_reset=run_reset,
927 require_ssp=require_ssp)))
Shuqian Zhao54a5b672016-05-11 22:12:17 +0000928 except:
929 parameterized_job.delete()
930 raise
931
932
Simran Basib6ec8ae2014-04-23 12:05:08 -0700933def create_job_page_handler(name, priority, control_file, control_type,
Dan Shid215dbe2015-06-18 16:14:59 -0700934 image=None, hostless=False, firmware_rw_build=None,
935 firmware_ro_build=None, test_source_build=None,
Michael Tang84a2ecf2016-06-07 15:10:53 -0700936 is_cloning=False, **kwargs):
Simran Basib6ec8ae2014-04-23 12:05:08 -0700937 """\
938 Create and enqueue a job.
939
940 @param name name of this job
941 @param priority Integer priority of this job. Higher is more important.
942 @param control_file String contents of the control file.
943 @param control_type Type of control file, Client or Server.
Dan Shid215dbe2015-06-18 16:14:59 -0700944 @param image: ChromeOS build to be installed in the dut. Default to None.
945 @param firmware_rw_build: Firmware build to update RW firmware. Default to
946 None, i.e., RW firmware will not be updated.
947 @param firmware_ro_build: Firmware build to update RO firmware. Default to
948 None, i.e., RO firmware will not be updated.
949 @param test_source_build: Build to be used to retrieve test code. Default
950 to None.
Michael Tang6dc174e2016-05-31 23:13:42 -0700951 @param is_cloning: True if creating a cloning job.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700952 @param kwargs extra args that will be required by create_suite_job or
953 create_job.
954
955 @returns The created Job id number.
956 """
Michael Tang6dc174e2016-05-31 23:13:42 -0700957 if is_cloning:
958 logging.info('Start to clone a new job')
Shuqian Zhao61f5d312016-08-05 17:15:23 -0700959 # When cloning a job, hosts and meta_hosts should not exist together,
960 # which would cause host-scheduler to schedule two hqe jobs to one host
961 # at the same time, and crash itself. Clear meta_hosts for this case.
962 if kwargs.get('hosts') and kwargs.get('meta_hosts'):
963 kwargs['meta_hosts'] = []
Michael Tang6dc174e2016-05-31 23:13:42 -0700964 else:
965 logging.info('Start to create a new job')
Simran Basib6ec8ae2014-04-23 12:05:08 -0700966 control_file = rpc_utils.encode_ascii(control_file)
Jiaxi Luodd67beb2014-07-18 16:28:31 -0700967 if not control_file:
968 raise model_logic.ValidationError({
969 'control_file' : "Control file cannot be empty"})
Simran Basib6ec8ae2014-04-23 12:05:08 -0700970
971 if image and hostless:
Dan Shid215dbe2015-06-18 16:14:59 -0700972 builds = {}
973 builds[provision.CROS_VERSION_PREFIX] = image
974 if firmware_rw_build:
Dan Shi0723bf52015-06-24 10:52:38 -0700975 builds[provision.FW_RW_VERSION_PREFIX] = firmware_rw_build
Dan Shid215dbe2015-06-18 16:14:59 -0700976 if firmware_ro_build:
977 builds[provision.FW_RO_VERSION_PREFIX] = firmware_ro_build
Simran Basib6ec8ae2014-04-23 12:05:08 -0700978 return site_rpc_interface.create_suite_job(
979 name=name, control_file=control_file, priority=priority,
Michael Tang6dc174e2016-05-31 23:13:42 -0700980 builds=builds, test_source_build=test_source_build,
981 is_cloning=is_cloning, **kwargs)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700982 return create_job(name, priority, control_file, control_type, image=image,
Allen Liac199b62016-12-14 12:56:02 -0800983 hostless=hostless, **kwargs)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700984
985
MK Ryue301eb72015-06-25 12:51:02 -0700986@rpc_utils.route_rpc_to_master
Allen Li8af9da02016-12-12 17:32:39 -0800987def create_job(
988 name,
989 priority,
990 control_file,
991 control_type,
992 hosts=(),
993 meta_hosts=(),
994 one_time_hosts=(),
995 atomic_group_name=None,
996 synch_count=None,
997 is_template=False,
998 timeout=None,
999 timeout_mins=None,
1000 max_runtime_mins=None,
1001 run_verify=False,
1002 email_list='',
1003 dependencies=(),
1004 reboot_before=None,
1005 reboot_after=None,
1006 parse_failed_repair=None,
1007 hostless=False,
1008 keyvals=None,
1009 drone_set=None,
1010 image=None,
1011 parent_job_id=None,
1012 test_retry=0,
1013 run_reset=True,
1014 require_ssp=None,
1015 args=(),
Allen Li8af9da02016-12-12 17:32:39 -08001016 **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +00001017 """\
1018 Create and enqueue a job.
mblighe8819cd2008-02-15 16:48:40 +00001019
showarda1e74b32009-05-12 17:32:04 +00001020 @param name name of this job
Alex Miller7d658cf2013-09-04 16:00:35 -07001021 @param priority Integer priority of this job. Higher is more important.
showarda1e74b32009-05-12 17:32:04 +00001022 @param control_file String contents of the control file.
1023 @param control_type Type of control file, Client or Server.
1024 @param synch_count How many machines the job uses per autoserv execution.
Jiaxi Luo90190c92014-06-18 12:35:57 -07001025 synch_count == 1 means the job is asynchronous. If an atomic group is
1026 given this value is treated as a minimum.
showarda1e74b32009-05-12 17:32:04 +00001027 @param is_template If true then create a template job.
1028 @param timeout Hours after this call returns until the job times out.
Simran Basi7e605742013-11-12 13:43:36 -08001029 @param timeout_mins Minutes after this call returns until the job times
Jiaxi Luo90190c92014-06-18 12:35:57 -07001030 out.
Simran Basi34217022012-11-06 13:43:15 -08001031 @param max_runtime_mins Minutes from job starting time until job times out
showarda1e74b32009-05-12 17:32:04 +00001032 @param run_verify Should the host be verified before running the test?
1033 @param email_list String containing emails to mail when the job is done
1034 @param dependencies List of label names on which this job depends
1035 @param reboot_before Never, If dirty, or Always
1036 @param reboot_after Never, If all tests passed, or Always
1037 @param parse_failed_repair if true, results of failed repairs launched by
Jiaxi Luo90190c92014-06-18 12:35:57 -07001038 this job will be parsed as part of the job.
showarda9545c02009-12-18 22:44:26 +00001039 @param hostless if true, create a hostless job
showardc1a98d12010-01-15 00:22:22 +00001040 @param keyvals dict of keyvals to associate with the job
showarda1e74b32009-05-12 17:32:04 +00001041 @param hosts List of hosts to run job on.
1042 @param meta_hosts List where each entry is a label name, and for each entry
Jiaxi Luo90190c92014-06-18 12:35:57 -07001043 one host will be chosen from that label to run the job on.
showarda1e74b32009-05-12 17:32:04 +00001044 @param one_time_hosts List of hosts not in the database to run the job on.
1045 @param atomic_group_name The name of an atomic group to schedule the job on.
jamesren76fcf192010-04-21 20:39:50 +00001046 @param drone_set The name of the drone set to run this test on.
Paul Pendlebury5a8c6ad2011-02-01 07:20:17 -08001047 @param image OS image to install before running job.
Aviv Keshet0b9cfc92013-02-05 11:36:02 -08001048 @param parent_job_id id of a job considered to be parent of created job.
Simran Basib6ec8ae2014-04-23 12:05:08 -07001049 @param test_retry Number of times to retry test if the test did not
Jiaxi Luo90190c92014-06-18 12:35:57 -07001050 complete successfully. (optional, default: 0)
Simran Basib6ec8ae2014-04-23 12:05:08 -07001051 @param run_reset Should the host be reset before running the test?
Dan Shiec1d47d2015-02-13 11:38:13 -08001052 @param require_ssp Set to True to require server-side packaging to run the
1053 test. If it's set to None, drone will still try to run
1054 the server side with server-side packaging. If the
1055 autotest-server package doesn't exist for the build or
1056 image is not set, drone will run the test without server-
1057 side packaging. Default is None.
Jiaxi Luo90190c92014-06-18 12:35:57 -07001058 @param args A list of args to be injected into control file.
Simran Basib6ec8ae2014-04-23 12:05:08 -07001059 @param kwargs extra keyword args. NOT USED.
showardc92da832009-04-07 18:14:34 +00001060
1061 @returns The created Job id number.
jadmanski0afbb632008-06-06 21:10:57 +00001062 """
Jiaxi Luo90190c92014-06-18 12:35:57 -07001063 if args:
1064 control_file = tools.inject_vars({'args': args}, control_file)
1065
Don Garretta06ea082017-01-13 00:04:26 +00001066 if image is None:
1067 return rpc_utils.create_job_common(
Allen Li8e17eb12016-12-13 18:37:18 -08001068 **rpc_utils.get_create_job_common_args(dict(
1069 name=name,
1070 priority=priority,
Allen Li8e17eb12016-12-13 18:37:18 -08001071 control_type=control_type,
Allen Li79752992016-12-14 12:21:33 -08001072 control_file=control_file,
Allen Li8e17eb12016-12-13 18:37:18 -08001073 hosts=hosts,
1074 meta_hosts=meta_hosts,
1075 one_time_hosts=one_time_hosts,
1076 atomic_group_name=atomic_group_name,
1077 synch_count=synch_count,
1078 is_template=is_template,
1079 timeout=timeout,
1080 timeout_mins=timeout_mins,
1081 max_runtime_mins=max_runtime_mins,
1082 run_verify=run_verify,
1083 email_list=email_list,
1084 dependencies=dependencies,
1085 reboot_before=reboot_before,
1086 reboot_after=reboot_after,
1087 parse_failed_repair=parse_failed_repair,
1088 hostless=hostless,
1089 keyvals=keyvals,
1090 drone_set=drone_set,
Allen Li8e17eb12016-12-13 18:37:18 -08001091 parent_job_id=parent_job_id,
1092 test_retry=test_retry,
1093 run_reset=run_reset,
Allen Li79752992016-12-14 12:21:33 -08001094 require_ssp=require_ssp)))
Don Garretta06ea082017-01-13 00:04:26 +00001095
1096 # Translate the image name, in case its a relative build name.
1097 ds = dev_server.ImageServer.resolve(image)
1098 image = ds.translate(image)
1099
1100 # When image is supplied use a known parameterized test already in the
1101 # database to pass the OS image path from the front end, through the
1102 # scheduler, and finally to autoserv as the --image parameter.
1103
1104 # The test autoupdate_ParameterizedJob is in afe_autotests and used to
1105 # instantiate a Test object and from there a ParameterizedJob.
1106 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
1107 known_parameterized_job = models.ParameterizedJob.objects.create(
1108 test=known_test_obj)
1109
1110 # autoupdate_ParameterizedJob has a single parameter, the image parameter,
1111 # stored in the table afe_test_parameters. We retrieve and set this
1112 # instance of the parameter to the OS image path.
1113 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
1114 name='image')
1115 known_parameterized_job.parameterizedjobparameter_set.create(
1116 test_parameter=image_parameter, parameter_value=image,
1117 parameter_type='string')
1118
1119 # TODO(crbug.com/502638): save firmware build etc to parameterized_job.
1120
1121 # By passing a parameterized_job to create_job_common the job entry in
1122 # the afe_jobs table will have the field parameterized_job_id set.
1123 # The scheduler uses this id in the afe_parameterized_jobs table to
1124 # match this job to our known test, and then with the
1125 # afe_parameterized_job_parameters table to get the actual image path.
jamesren4a41e012010-07-16 22:33:48 +00001126 return rpc_utils.create_job_common(
Allen Li7c918d82016-12-14 12:26:05 -08001127 **rpc_utils.get_create_job_common_args(dict(
1128 name=name,
1129 priority=priority,
1130 control_type=control_type,
1131 control_file=control_file,
1132 hosts=hosts,
1133 meta_hosts=meta_hosts,
1134 one_time_hosts=one_time_hosts,
1135 atomic_group_name=atomic_group_name,
1136 synch_count=synch_count,
1137 is_template=is_template,
1138 timeout=timeout,
1139 timeout_mins=timeout_mins,
1140 max_runtime_mins=max_runtime_mins,
1141 run_verify=run_verify,
1142 email_list=email_list,
1143 dependencies=dependencies,
1144 reboot_before=reboot_before,
1145 reboot_after=reboot_after,
1146 parse_failed_repair=parse_failed_repair,
1147 hostless=hostless,
1148 keyvals=keyvals,
1149 drone_set=drone_set,
1150 parameterized_job=known_parameterized_job.id,
1151 parent_job_id=parent_job_id,
1152 test_retry=test_retry,
1153 run_reset=run_reset,
1154 require_ssp=require_ssp)))
mblighe8819cd2008-02-15 16:48:40 +00001155
1156
showard9dbdcda2008-10-14 17:34:36 +00001157def abort_host_queue_entries(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001158 """\
showard9dbdcda2008-10-14 17:34:36 +00001159 Abort a set of host queue entries.
Fang Deng63b0e452014-12-19 14:38:15 -08001160
1161 @return: A list of dictionaries, each contains information
1162 about an aborted HQE.
jadmanski0afbb632008-06-06 21:10:57 +00001163 """
showard9dbdcda2008-10-14 17:34:36 +00001164 query = models.HostQueueEntry.query_objects(filter_data)
beepsfaecbce2013-10-29 11:35:10 -07001165
1166 # Dont allow aborts on:
1167 # 1. Jobs that have already completed (whether or not they were aborted)
1168 # 2. Jobs that we have already been aborted (but may not have completed)
1169 query = query.filter(complete=False).filter(aborted=False)
showarddc817512008-11-12 18:16:41 +00001170 models.AclGroup.check_abort_permissions(query)
showard9dbdcda2008-10-14 17:34:36 +00001171 host_queue_entries = list(query.select_related())
showard2bab8f42008-11-12 18:15:22 +00001172 rpc_utils.check_abort_synchronous_jobs(host_queue_entries)
mblighe8819cd2008-02-15 16:48:40 +00001173
Simran Basic1b26762013-06-26 14:23:21 -07001174 models.HostQueueEntry.abort_host_queue_entries(host_queue_entries)
Fang Deng63b0e452014-12-19 14:38:15 -08001175 hqe_info = [{'HostQueueEntry': hqe.id, 'Job': hqe.job_id,
1176 'Job name': hqe.job.name} for hqe in host_queue_entries]
1177 return hqe_info
showard9d821ab2008-07-11 16:54:29 +00001178
1179
beeps8bb1f7d2013-08-05 01:30:09 -07001180def abort_special_tasks(**filter_data):
1181 """\
1182 Abort the special task, or tasks, specified in the filter.
1183 """
1184 query = models.SpecialTask.query_objects(filter_data)
1185 special_tasks = query.filter(is_active=True)
1186 for task in special_tasks:
1187 task.abort()
1188
1189
Simran Basi73dae552013-02-25 14:57:46 -08001190def _call_special_tasks_on_hosts(task, hosts):
1191 """\
1192 Schedules a set of hosts for a special task.
1193
1194 @returns A list of hostnames that a special task was created for.
1195 """
1196 models.AclGroup.check_for_acl_violation_hosts(hosts)
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -08001197 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -08001198 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -08001199 raise ValueError('The following hosts are on shards, please '
1200 'follow the link to the shards and create jobs '
1201 'there instead. %s.' % shard_host_map)
Simran Basi73dae552013-02-25 14:57:46 -08001202 for host in hosts:
1203 models.SpecialTask.schedule_special_task(host, task)
1204 return list(sorted(host.hostname for host in hosts))
1205
1206
MK Ryu5aa25042015-07-28 16:08:04 -07001207def _forward_special_tasks_on_hosts(task, rpc, **filter_data):
1208 """Forward special tasks to corresponding shards.
mbligh4e545a52009-12-19 05:30:39 +00001209
MK Ryu5aa25042015-07-28 16:08:04 -07001210 For master, when special tasks are fired on hosts that are sharded,
1211 forward the RPC to corresponding shards.
1212
1213 For shard, create special task records in local DB.
1214
1215 @param task: Enum value of frontend.afe.models.SpecialTask.Task
1216 @param rpc: RPC name to forward.
1217 @param filter_data: Filter keywords to be used for DB query.
1218
1219 @return: A list of hostnames that a special task was created for.
showard1ff7b2e2009-05-15 23:17:18 +00001220 """
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001221 hosts = models.Host.query_objects(filter_data)
1222 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts, rpc_hostnames=True)
1223
1224 # Filter out hosts on a shard from those on the master, forward
1225 # rpcs to the shard with an additional hostname__in filter, and
1226 # create a local SpecialTask for each remaining host.
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -08001227 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001228 hosts = [h for h in hosts if h.shard is None]
1229 for shard, hostnames in shard_host_map.iteritems():
1230
1231 # The main client of this module is the frontend website, and
1232 # it invokes it with an 'id' or an 'id__in' filter. Regardless,
1233 # the 'hostname' filter should narrow down the list of hosts on
1234 # each shard even though we supply all the ids in filter_data.
1235 # This method uses hostname instead of id because it fits better
MK Ryu5aa25042015-07-28 16:08:04 -07001236 # with the overall architecture of redirection functions in
1237 # rpc_utils.
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001238 shard_filter = filter_data.copy()
1239 shard_filter['hostname__in'] = hostnames
1240 rpc_utils.run_rpc_on_multiple_hostnames(
MK Ryu5aa25042015-07-28 16:08:04 -07001241 rpc, [shard], **shard_filter)
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001242
1243 # There is a race condition here if someone assigns a shard to one of these
1244 # hosts before we create the task. The host will stay on the master if:
1245 # 1. The host is not Ready
1246 # 2. The host is Ready but has a task
1247 # But if the host is Ready and doesn't have a task yet, it will get sent
1248 # to the shard as we're creating a task here.
1249
1250 # Given that we only rarely verify Ready hosts it isn't worth putting this
1251 # entire method in a transaction. The worst case scenario is that we have
MK Ryu5aa25042015-07-28 16:08:04 -07001252 # a verify running on a Ready host while the shard is using it, if the
1253 # verify fails no subsequent tasks will be created against the host on the
1254 # master, and verifies are safe enough that this is OK.
1255 return _call_special_tasks_on_hosts(task, hosts)
1256
1257
1258def reverify_hosts(**filter_data):
1259 """\
1260 Schedules a set of hosts for verify.
1261
1262 @returns A list of hostnames that a verify task was created for.
1263 """
1264 return _forward_special_tasks_on_hosts(
1265 models.SpecialTask.Task.VERIFY, 'reverify_hosts', **filter_data)
Simran Basi73dae552013-02-25 14:57:46 -08001266
1267
1268def repair_hosts(**filter_data):
1269 """\
1270 Schedules a set of hosts for repair.
1271
1272 @returns A list of hostnames that a repair task was created for.
1273 """
MK Ryu5aa25042015-07-28 16:08:04 -07001274 return _forward_special_tasks_on_hosts(
1275 models.SpecialTask.Task.REPAIR, 'repair_hosts', **filter_data)
showard1ff7b2e2009-05-15 23:17:18 +00001276
1277
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001278def get_jobs(not_yet_run=False, running=False, finished=False,
1279 suite=False, sub=False, standalone=False, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001280 """\
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001281 Extra status filter args for get_jobs:
jadmanski0afbb632008-06-06 21:10:57 +00001282 -not_yet_run: Include only jobs that have not yet started running.
1283 -running: Include only jobs that have start running but for which not
1284 all hosts have completed.
1285 -finished: Include only jobs for which all hosts have completed (or
1286 aborted).
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001287
1288 Extra type filter args for get_jobs:
1289 -suite: Include only jobs with child jobs.
1290 -sub: Include only jobs with a parent job.
1291 -standalone: Inlcude only jobs with no child or parent jobs.
1292 At most one of these three fields should be specified.
jadmanski0afbb632008-06-06 21:10:57 +00001293 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001294 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
1295 running,
1296 finished)
1297 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
1298 suite,
1299 sub,
1300 standalone)
showard0957a842009-05-11 19:25:08 +00001301 job_dicts = []
1302 jobs = list(models.Job.query_objects(filter_data))
1303 models.Job.objects.populate_relationships(jobs, models.Label,
1304 'dependencies')
showardc1a98d12010-01-15 00:22:22 +00001305 models.Job.objects.populate_relationships(jobs, models.JobKeyval, 'keyvals')
showard0957a842009-05-11 19:25:08 +00001306 for job in jobs:
1307 job_dict = job.get_object_dict()
1308 job_dict['dependencies'] = ','.join(label.name
1309 for label in job.dependencies)
showardc1a98d12010-01-15 00:22:22 +00001310 job_dict['keyvals'] = dict((keyval.key, keyval.value)
1311 for keyval in job.keyvals)
Eric Lid23bc192011-02-09 14:38:57 -08001312 if job.parameterized_job:
1313 job_dict['image'] = get_parameterized_autoupdate_image_url(job)
showard0957a842009-05-11 19:25:08 +00001314 job_dicts.append(job_dict)
1315 return rpc_utils.prepare_for_serialization(job_dicts)
mblighe8819cd2008-02-15 16:48:40 +00001316
1317
1318def get_num_jobs(not_yet_run=False, running=False, finished=False,
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001319 suite=False, sub=False, standalone=False,
jadmanski0afbb632008-06-06 21:10:57 +00001320 **filter_data):
Aviv Keshet17660a52016-04-06 18:56:43 +00001321 """\
1322 See get_jobs() for documentation of extra filter parameters.
jadmanski0afbb632008-06-06 21:10:57 +00001323 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001324 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
1325 running,
1326 finished)
1327 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
1328 suite,
1329 sub,
1330 standalone)
Aviv Keshet17660a52016-04-06 18:56:43 +00001331 return models.Job.query_count(filter_data)
mblighe8819cd2008-02-15 16:48:40 +00001332
1333
mblighe8819cd2008-02-15 16:48:40 +00001334def get_jobs_summary(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001335 """\
Jiaxi Luoaac54572014-06-04 13:57:02 -07001336 Like get_jobs(), but adds 'status_counts' and 'result_counts' field.
1337
1338 'status_counts' filed is a dictionary mapping status strings to the number
1339 of hosts currently with that status, i.e. {'Queued' : 4, 'Running' : 2}.
1340
1341 'result_counts' field is piped to tko's rpc_interface and has the return
1342 format specified under get_group_counts.
jadmanski0afbb632008-06-06 21:10:57 +00001343 """
1344 jobs = get_jobs(**filter_data)
1345 ids = [job['id'] for job in jobs]
1346 all_status_counts = models.Job.objects.get_status_counts(ids)
1347 for job in jobs:
1348 job['status_counts'] = all_status_counts[job['id']]
Jiaxi Luoaac54572014-06-04 13:57:02 -07001349 job['result_counts'] = tko_rpc_interface.get_status_counts(
1350 ['afe_job_id', 'afe_job_id'],
1351 header_groups=[['afe_job_id'], ['afe_job_id']],
1352 **{'afe_job_id': job['id']})
jadmanski0afbb632008-06-06 21:10:57 +00001353 return rpc_utils.prepare_for_serialization(jobs)
mblighe8819cd2008-02-15 16:48:40 +00001354
1355
showarda965cef2009-05-15 23:17:41 +00001356def get_info_for_clone(id, preserve_metahosts, queue_entry_filter_data=None):
showarda8709c52008-07-03 19:44:54 +00001357 """\
1358 Retrieves all the information needed to clone a job.
1359 """
showarda8709c52008-07-03 19:44:54 +00001360 job = models.Job.objects.get(id=id)
showard29f7cd22009-04-29 21:16:24 +00001361 job_info = rpc_utils.get_job_info(job,
showarda965cef2009-05-15 23:17:41 +00001362 preserve_metahosts,
1363 queue_entry_filter_data)
showard945072f2008-09-03 20:34:59 +00001364
showardd9992fe2008-07-31 02:15:03 +00001365 host_dicts = []
showard29f7cd22009-04-29 21:16:24 +00001366 for host in job_info['hosts']:
1367 host_dict = get_hosts(id=host.id)[0]
1368 other_labels = host_dict['labels']
1369 if host_dict['platform']:
1370 other_labels.remove(host_dict['platform'])
1371 host_dict['other_labels'] = ', '.join(other_labels)
showardd9992fe2008-07-31 02:15:03 +00001372 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001373
showard29f7cd22009-04-29 21:16:24 +00001374 for host in job_info['one_time_hosts']:
1375 host_dict = dict(hostname=host.hostname,
1376 id=host.id,
1377 platform='(one-time host)',
1378 locked_text='')
1379 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001380
showard4d077562009-05-08 18:24:36 +00001381 # convert keys from Label objects to strings (names of labels)
showard29f7cd22009-04-29 21:16:24 +00001382 meta_host_counts = dict((meta_host.name, count) for meta_host, count
showard4d077562009-05-08 18:24:36 +00001383 in job_info['meta_host_counts'].iteritems())
showard29f7cd22009-04-29 21:16:24 +00001384
1385 info = dict(job=job.get_object_dict(),
1386 meta_host_counts=meta_host_counts,
1387 hosts=host_dicts)
1388 info['job']['dependencies'] = job_info['dependencies']
1389 if job_info['atomic_group']:
1390 info['atomic_group_name'] = (job_info['atomic_group']).name
1391 else:
1392 info['atomic_group_name'] = None
jamesren2275ef12010-04-12 18:25:06 +00001393 info['hostless'] = job_info['hostless']
jamesren76fcf192010-04-21 20:39:50 +00001394 info['drone_set'] = job.drone_set and job.drone_set.name
showarda8709c52008-07-03 19:44:54 +00001395
Michael Tang6dc174e2016-05-31 23:13:42 -07001396 image = _get_image_for_job(job, job_info['hostless'])
1397 if image:
1398 info['job']['image'] = image
Eric Lid23bc192011-02-09 14:38:57 -08001399
showarda8709c52008-07-03 19:44:54 +00001400 return rpc_utils.prepare_for_serialization(info)
1401
1402
Michael Tang6dc174e2016-05-31 23:13:42 -07001403def _get_image_for_job(job, hostless):
1404 """ Gets the image used for a job.
1405
1406 Gets the image used for an AFE job. If the job is a parameterized job, get
1407 the image from the job parameter; otherwise, tries to get the image from
1408 the job's keyvals 'build' or 'builds'. As a last resort, if the job is a
1409 hostless job, tries to get the image from its control file attributes
1410 'build' or 'builds'.
1411
1412 TODO(ntang): Needs to handle FAFT with two builds for ro/rw.
1413
1414 @param job An AFE job object.
1415 @param hostless Boolean on of the job is hostless.
1416
1417 @returns The image build used for the job.
1418 """
1419 image = None
1420 if job.parameterized_job:
1421 image = get_parameterized_autoupdate_image_url(job)
1422 else:
1423 keyvals = job.keyval_dict()
Michael Tang84a2ecf2016-06-07 15:10:53 -07001424 image = keyvals.get('build')
Michael Tang6dc174e2016-05-31 23:13:42 -07001425 if not image:
1426 value = keyvals.get('builds')
1427 builds = None
1428 if isinstance(value, dict):
1429 builds = value
1430 elif isinstance(value, basestring):
1431 builds = ast.literal_eval(value)
1432 if builds:
1433 image = builds.get('cros-version')
1434 if not image and hostless and job.control_file:
1435 try:
1436 control_obj = control_data.parse_control_string(
1437 job.control_file)
1438 if hasattr(control_obj, 'build'):
1439 image = getattr(control_obj, 'build')
1440 if not image and hasattr(control_obj, 'builds'):
1441 builds = getattr(control_obj, 'builds')
1442 image = builds.get('cros-version')
1443 except:
1444 logging.warning('Failed to parse control file for job: %s',
1445 job.name)
1446 return image
1447
showard34dc5fa2008-04-24 20:58:40 +00001448
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001449def get_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001450 """\
showardc92da832009-04-07 18:14:34 +00001451 @returns A sequence of nested dictionaries of host and job information.
jadmanski0afbb632008-06-06 21:10:57 +00001452 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001453 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1454 'started_on__lte',
1455 start_time,
1456 end_time,
1457 **filter_data)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001458 return rpc_utils.prepare_rows_as_nested_dicts(
1459 models.HostQueueEntry.query_objects(filter_data),
1460 ('host', 'atomic_group', 'job'))
showard34dc5fa2008-04-24 20:58:40 +00001461
1462
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001463def get_num_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001464 """\
1465 Get the number of host queue entries associated with this job.
1466 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001467 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1468 'started_on__lte',
1469 start_time,
1470 end_time,
1471 **filter_data)
jadmanski0afbb632008-06-06 21:10:57 +00001472 return models.HostQueueEntry.query_count(filter_data)
showard34dc5fa2008-04-24 20:58:40 +00001473
1474
showard1e935f12008-07-11 00:11:36 +00001475def get_hqe_percentage_complete(**filter_data):
1476 """
showardc92da832009-04-07 18:14:34 +00001477 Computes the fraction of host queue entries matching the given filter data
showard1e935f12008-07-11 00:11:36 +00001478 that are complete.
1479 """
1480 query = models.HostQueueEntry.query_objects(filter_data)
1481 complete_count = query.filter(complete=True).count()
1482 total_count = query.count()
1483 if total_count == 0:
1484 return 1
1485 return float(complete_count) / total_count
1486
1487
showard1a5a4082009-07-28 20:01:37 +00001488# special tasks
1489
1490def get_special_tasks(**filter_data):
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001491 """Get special task entries from the local database.
1492
1493 Query the special tasks table for tasks matching the given
1494 `filter_data`, and return a list of the results. No attempt is
1495 made to forward the call to shards; the buck will stop here.
1496 The caller is expected to know the target shard for such reasons
1497 as:
1498 * The caller is a service (such as gs_offloader) configured
1499 to operate on behalf of one specific shard, and no other.
1500 * The caller has a host as a parameter, and knows that this is
1501 the shard assigned to that host.
1502
1503 @param filter_data Filter keywords to pass to the underlying
1504 database query.
1505
1506 """
J. Richard Barnettefdfcd662015-04-13 17:20:29 -07001507 return rpc_utils.prepare_rows_as_nested_dicts(
1508 models.SpecialTask.query_objects(filter_data),
1509 ('host', 'queue_entry'))
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001510
1511
1512def get_host_special_tasks(host_id, **filter_data):
1513 """Get special task entries for a given host.
1514
1515 Query the special tasks table for tasks that ran on the host
1516 given by `host_id` and matching the given `filter_data`.
1517 Return a list of the results. If the host is assigned to a
1518 shard, forward this call to that shard.
1519
1520 @param host_id Id in the database of the target host.
1521 @param filter_data Filter keywords to pass to the underlying
1522 database query.
1523
1524 """
MK Ryu0c1a37d2015-04-30 12:00:55 -07001525 # Retrieve host data even if the host is in an invalid state.
1526 host = models.Host.smart_get(host_id, False)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001527 if not host.shard:
J. Richard Barnettefdfcd662015-04-13 17:20:29 -07001528 return get_special_tasks(host_id=host_id, **filter_data)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001529 else:
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001530 # The return values from AFE methods are post-processed
1531 # objects that aren't JSON-serializable. So, we have to
1532 # call AFE.run() to get the raw, serializable output from
1533 # the shard.
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001534 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1535 return shard_afe.run('get_special_tasks',
1536 host_id=host_id, **filter_data)
showard1a5a4082009-07-28 20:01:37 +00001537
1538
MK Ryu0c1a37d2015-04-30 12:00:55 -07001539def get_num_special_tasks(**kwargs):
1540 """Get the number of special task entries from the local database.
1541
1542 Query the special tasks table for tasks matching the given 'kwargs',
1543 and return the number of the results. No attempt is made to forward
1544 the call to shards; the buck will stop here.
1545
1546 @param kwargs Filter keywords to pass to the underlying database query.
1547
1548 """
1549 return models.SpecialTask.query_count(kwargs)
1550
1551
1552def get_host_num_special_tasks(host, **kwargs):
1553 """Get special task entries for a given host.
1554
1555 Query the special tasks table for tasks that ran on the host
1556 given by 'host' and matching the given 'kwargs'.
1557 Return a list of the results. If the host is assigned to a
1558 shard, forward this call to that shard.
1559
1560 @param host id or name of a host. More often a hostname.
1561 @param kwargs Filter keywords to pass to the underlying database query.
1562
1563 """
1564 # Retrieve host data even if the host is in an invalid state.
1565 host_model = models.Host.smart_get(host, False)
1566 if not host_model.shard:
1567 return get_num_special_tasks(host=host, **kwargs)
1568 else:
1569 shard_afe = frontend.AFE(server=host_model.shard.rpc_hostname())
1570 return shard_afe.run('get_num_special_tasks', host=host, **kwargs)
1571
1572
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001573def get_status_task(host_id, end_time):
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001574 """Get the "status task" for a host from the local shard.
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001575
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001576 Returns a single special task representing the given host's
1577 "status task". The status task is a completed special task that
1578 identifies whether the corresponding host was working or broken
1579 when it completed. A successful task indicates a working host;
1580 a failed task indicates broken.
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001581
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001582 This call will not be forward to a shard; the receiving server
1583 must be the shard that owns the host.
1584
1585 @param host_id Id in the database of the target host.
1586 @param end_time Time reference for the host's status.
1587
1588 @return A single task; its status (successful or not)
1589 corresponds to the status of the host (working or
1590 broken) at the given time. If no task is found, return
1591 `None`.
1592
1593 """
1594 tasklist = rpc_utils.prepare_rows_as_nested_dicts(
1595 status_history.get_status_task(host_id, end_time),
1596 ('host', 'queue_entry'))
1597 return tasklist[0] if tasklist else None
1598
1599
1600def get_host_status_task(host_id, end_time):
1601 """Get the "status task" for a host from its owning shard.
1602
1603 Finds the given host's owning shard, and forwards to it a call
1604 to `get_status_task()` (see above).
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001605
1606 @param host_id Id in the database of the target host.
1607 @param end_time Time reference for the host's status.
1608
1609 @return A single task; its status (successful or not)
1610 corresponds to the status of the host (working or
1611 broken) at the given time. If no task is found, return
1612 `None`.
1613
1614 """
1615 host = models.Host.smart_get(host_id)
1616 if not host.shard:
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001617 return get_status_task(host_id, end_time)
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001618 else:
1619 # The return values from AFE methods are post-processed
1620 # objects that aren't JSON-serializable. So, we have to
1621 # call AFE.run() to get the raw, serializable output from
1622 # the shard.
1623 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1624 return shard_afe.run('get_status_task',
1625 host_id=host_id, end_time=end_time)
1626
1627
J. Richard Barnette8abbfd62015-06-23 12:46:54 -07001628def get_host_diagnosis_interval(host_id, end_time, success):
1629 """Find a "diagnosis interval" for a given host.
1630
1631 A "diagnosis interval" identifies a start and end time where
1632 the host went from "working" to "broken", or vice versa. The
1633 interval's starting time is the starting time of the last status
1634 task with the old status; the end time is the finish time of the
1635 first status task with the new status.
1636
1637 This routine finds the most recent diagnosis interval for the
1638 given host prior to `end_time`, with a starting status matching
1639 `success`. If `success` is true, the interval will start with a
1640 successful status task; if false the interval will start with a
1641 failed status task.
1642
1643 @param host_id Id in the database of the target host.
1644 @param end_time Time reference for the diagnosis interval.
1645 @param success Whether the diagnosis interval should start
1646 with a successful or failed status task.
1647
1648 @return A list of two strings. The first is the timestamp for
1649 the beginning of the interval; the second is the
1650 timestamp for the end. If the host has never changed
1651 state, the list is empty.
1652
1653 """
1654 host = models.Host.smart_get(host_id)
J. Richard Barnette78f281a2015-06-29 13:24:51 -07001655 if not host.shard or utils.is_shard():
J. Richard Barnette8abbfd62015-06-23 12:46:54 -07001656 return status_history.get_diagnosis_interval(
1657 host_id, end_time, success)
1658 else:
1659 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1660 return shard_afe.get_host_diagnosis_interval(
1661 host_id, end_time, success)
1662
1663
showardc0ac3a72009-07-08 21:14:45 +00001664# support for host detail view
1665
MK Ryu0c1a37d2015-04-30 12:00:55 -07001666def get_host_queue_entries_and_special_tasks(host, query_start=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001667 query_limit=None, start_time=None,
1668 end_time=None):
showardc0ac3a72009-07-08 21:14:45 +00001669 """
1670 @returns an interleaved list of HostQueueEntries and SpecialTasks,
1671 in approximate run order. each dict contains keys for type, host,
1672 job, status, started_on, execution_path, and ID.
1673 """
1674 total_limit = None
1675 if query_limit is not None:
1676 total_limit = query_start + query_limit
MK Ryu0c1a37d2015-04-30 12:00:55 -07001677 filter_data_common = {'host': host,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001678 'query_limit': total_limit,
1679 'sort_by': ['-id']}
showardc0ac3a72009-07-08 21:14:45 +00001680
MK Ryu0c1a37d2015-04-30 12:00:55 -07001681 filter_data_special_tasks = rpc_utils.inject_times_to_filter(
1682 'time_started__gte', 'time_started__lte', start_time, end_time,
1683 **filter_data_common)
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001684
MK Ryu0c1a37d2015-04-30 12:00:55 -07001685 queue_entries = get_host_queue_entries(
1686 start_time, end_time, **filter_data_common)
1687 special_tasks = get_host_special_tasks(host, **filter_data_special_tasks)
showardc0ac3a72009-07-08 21:14:45 +00001688
1689 interleaved_entries = rpc_utils.interleave_entries(queue_entries,
1690 special_tasks)
1691 if query_start is not None:
1692 interleaved_entries = interleaved_entries[query_start:]
1693 if query_limit is not None:
1694 interleaved_entries = interleaved_entries[:query_limit]
MK Ryu0c1a37d2015-04-30 12:00:55 -07001695 return rpc_utils.prepare_host_queue_entries_and_special_tasks(
1696 interleaved_entries, queue_entries)
showardc0ac3a72009-07-08 21:14:45 +00001697
1698
MK Ryu0c1a37d2015-04-30 12:00:55 -07001699def get_num_host_queue_entries_and_special_tasks(host, start_time=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001700 end_time=None):
MK Ryu0c1a37d2015-04-30 12:00:55 -07001701 filter_data_common = {'host': host}
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001702
1703 filter_data_queue_entries, filter_data_special_tasks = (
1704 rpc_utils.inject_times_to_hqe_special_tasks_filters(
1705 filter_data_common, start_time, end_time))
1706
1707 return (models.HostQueueEntry.query_count(filter_data_queue_entries)
MK Ryu0c1a37d2015-04-30 12:00:55 -07001708 + get_host_num_special_tasks(**filter_data_special_tasks))
showardc0ac3a72009-07-08 21:14:45 +00001709
1710
showard29f7cd22009-04-29 21:16:24 +00001711# recurring run
1712
1713def get_recurring(**filter_data):
1714 return rpc_utils.prepare_rows_as_nested_dicts(
1715 models.RecurringRun.query_objects(filter_data),
1716 ('job', 'owner'))
1717
1718
1719def get_num_recurring(**filter_data):
1720 return models.RecurringRun.query_count(filter_data)
1721
1722
1723def delete_recurring_runs(**filter_data):
1724 to_delete = models.RecurringRun.query_objects(filter_data)
1725 to_delete.delete()
1726
1727
1728def create_recurring_run(job_id, start_date, loop_period, loop_count):
showard64a95952010-01-13 21:27:16 +00001729 owner = models.User.current_user().login
showard29f7cd22009-04-29 21:16:24 +00001730 job = models.Job.objects.get(id=job_id)
1731 return job.create_recurring_job(start_date=start_date,
1732 loop_period=loop_period,
1733 loop_count=loop_count,
1734 owner=owner)
1735
1736
mblighe8819cd2008-02-15 16:48:40 +00001737# other
1738
showarde0b63622008-08-04 20:58:47 +00001739def echo(data=""):
1740 """\
1741 Returns a passed in string. For doing a basic test to see if RPC calls
1742 can successfully be made.
1743 """
1744 return data
1745
1746
showardb7a52fd2009-04-27 20:10:56 +00001747def get_motd():
1748 """\
1749 Returns the message of the day as a string.
1750 """
1751 return rpc_utils.get_motd()
1752
1753
mblighe8819cd2008-02-15 16:48:40 +00001754def get_static_data():
jadmanski0afbb632008-06-06 21:10:57 +00001755 """\
1756 Returns a dictionary containing a bunch of data that shouldn't change
1757 often and is otherwise inaccessible. This includes:
showardc92da832009-04-07 18:14:34 +00001758
1759 priorities: List of job priority choices.
1760 default_priority: Default priority value for new jobs.
1761 users: Sorted list of all users.
Jiaxi Luo31874592014-06-11 10:36:35 -07001762 labels: Sorted list of labels not start with 'cros-version' and
1763 'fw-version'.
showardc92da832009-04-07 18:14:34 +00001764 atomic_groups: Sorted list of all atomic groups.
1765 tests: Sorted list of all tests.
1766 profilers: Sorted list of all profilers.
1767 current_user: Logged-in username.
1768 host_statuses: Sorted list of possible Host statuses.
1769 job_statuses: Sorted list of possible HostQueueEntry statuses.
Simran Basi7e605742013-11-12 13:43:36 -08001770 job_timeout_default: The default job timeout length in minutes.
showarda1e74b32009-05-12 17:32:04 +00001771 parse_failed_repair_default: Default value for the parse_failed_repair job
Jiaxi Luo31874592014-06-11 10:36:35 -07001772 option.
showardc92da832009-04-07 18:14:34 +00001773 reboot_before_options: A list of valid RebootBefore string enums.
1774 reboot_after_options: A list of valid RebootAfter string enums.
1775 motd: Server's message of the day.
1776 status_dictionary: A mapping from one word job status names to a more
1777 informative description.
jadmanski0afbb632008-06-06 21:10:57 +00001778 """
showard21baa452008-10-21 00:08:39 +00001779
1780 job_fields = models.Job.get_field_dict()
jamesren76fcf192010-04-21 20:39:50 +00001781 default_drone_set_name = models.DroneSet.default_drone_set_name()
1782 drone_sets = ([default_drone_set_name] +
1783 sorted(drone_set.name for drone_set in
1784 models.DroneSet.objects.exclude(
1785 name=default_drone_set_name)))
showard21baa452008-10-21 00:08:39 +00001786
jadmanski0afbb632008-06-06 21:10:57 +00001787 result = {}
Alex Miller7d658cf2013-09-04 16:00:35 -07001788 result['priorities'] = priorities.Priority.choices()
1789 default_priority = priorities.Priority.DEFAULT
1790 result['default_priority'] = 'Default'
1791 result['max_schedulable_priority'] = priorities.Priority.DEFAULT
jadmanski0afbb632008-06-06 21:10:57 +00001792 result['users'] = get_users(sort_by=['login'])
Jiaxi Luo31874592014-06-11 10:36:35 -07001793
1794 label_exclude_filters = [{'name__startswith': 'cros-version'},
Dan Shi65351d62015-08-03 12:03:23 -07001795 {'name__startswith': 'fw-version'},
1796 {'name__startswith': 'fwrw-version'},
Dan Shi27516972016-03-16 14:03:41 -07001797 {'name__startswith': 'fwro-version'},
1798 {'name__startswith': 'ab-version'},
1799 {'name__startswith': 'testbed-version'}]
Jiaxi Luo31874592014-06-11 10:36:35 -07001800 result['labels'] = get_labels(
1801 label_exclude_filters,
1802 sort_by=['-platform', 'name'])
1803
showardc92da832009-04-07 18:14:34 +00001804 result['atomic_groups'] = get_atomic_groups(sort_by=['name'])
jadmanski0afbb632008-06-06 21:10:57 +00001805 result['tests'] = get_tests(sort_by=['name'])
showard2b9a88b2008-06-13 20:55:03 +00001806 result['profilers'] = get_profilers(sort_by=['name'])
showard0fc38302008-10-23 00:44:07 +00001807 result['current_user'] = rpc_utils.prepare_for_serialization(
showard64a95952010-01-13 21:27:16 +00001808 models.User.current_user().get_object_dict())
showard2b9a88b2008-06-13 20:55:03 +00001809 result['host_statuses'] = sorted(models.Host.Status.names)
mbligh5a198b92008-12-11 19:33:29 +00001810 result['job_statuses'] = sorted(models.HostQueueEntry.Status.names)
Simran Basi7e605742013-11-12 13:43:36 -08001811 result['job_timeout_mins_default'] = models.Job.DEFAULT_TIMEOUT_MINS
Simran Basi34217022012-11-06 13:43:15 -08001812 result['job_max_runtime_mins_default'] = (
1813 models.Job.DEFAULT_MAX_RUNTIME_MINS)
showarda1e74b32009-05-12 17:32:04 +00001814 result['parse_failed_repair_default'] = bool(
1815 models.Job.DEFAULT_PARSE_FAILED_REPAIR)
jamesrendd855242010-03-02 22:23:44 +00001816 result['reboot_before_options'] = model_attributes.RebootBefore.names
1817 result['reboot_after_options'] = model_attributes.RebootAfter.names
showard8fbae652009-01-20 23:23:10 +00001818 result['motd'] = rpc_utils.get_motd()
jamesren76fcf192010-04-21 20:39:50 +00001819 result['drone_sets_enabled'] = models.DroneSet.drone_sets_enabled()
1820 result['drone_sets'] = drone_sets
jamesren4a41e012010-07-16 22:33:48 +00001821 result['parameterized_jobs'] = models.Job.parameterized_jobs_enabled()
showard8ac29b42008-07-17 17:01:55 +00001822
showardd3dc1992009-04-22 21:01:40 +00001823 result['status_dictionary'] = {"Aborted": "Aborted",
showard8ac29b42008-07-17 17:01:55 +00001824 "Verifying": "Verifying Host",
Alex Millerdfff2fd2013-05-28 13:05:06 -07001825 "Provisioning": "Provisioning Host",
showard8ac29b42008-07-17 17:01:55 +00001826 "Pending": "Waiting on other hosts",
1827 "Running": "Running autoserv",
1828 "Completed": "Autoserv completed",
1829 "Failed": "Failed to complete",
showardd823b362008-07-24 16:35:46 +00001830 "Queued": "Queued",
showard5deb6772008-11-04 21:54:33 +00001831 "Starting": "Next in host's queue",
1832 "Stopped": "Other host(s) failed verify",
showardd3dc1992009-04-22 21:01:40 +00001833 "Parsing": "Awaiting parse of final results",
showard29f7cd22009-04-29 21:16:24 +00001834 "Gathering": "Gathering log files",
showard8cc058f2009-09-08 16:26:33 +00001835 "Template": "Template job for recurring run",
mbligh4608b002010-01-05 18:22:35 +00001836 "Waiting": "Waiting for scheduler action",
Dan Shi07e09af2013-04-12 09:31:29 -07001837 "Archiving": "Archiving results",
1838 "Resetting": "Resetting hosts"}
Jiaxi Luo421608e2014-07-07 14:38:00 -07001839
1840 result['wmatrix_url'] = rpc_utils.get_wmatrix_url()
Simran Basi71206ef2014-08-13 13:51:18 -07001841 result['is_moblab'] = bool(utils.is_moblab())
Jiaxi Luo421608e2014-07-07 14:38:00 -07001842
jadmanski0afbb632008-06-06 21:10:57 +00001843 return result
showard29f7cd22009-04-29 21:16:24 +00001844
1845
1846def get_server_time():
1847 return datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
Kevin Cheng19521982016-09-22 12:27:23 -07001848
1849
1850def get_hosts_by_attribute(attribute, value):
1851 """
1852 Get the list of valid hosts that share the same host attribute value.
1853
1854 @param attribute: String of the host attribute to check.
1855 @param value: String of the value that is shared between hosts.
1856
1857 @returns List of hostnames that all have the same host attribute and
1858 value.
1859 """
1860 hosts = models.HostAttribute.query_objects({'attribute': attribute,
1861 'value': value})
1862 return [row.host.hostname for row in hosts if row.host.invalid == 0]