blob: 9bbacdf80735f728eb5afa78e571ea2d8f37632c [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
showard6d7b2ff2009-06-10 00:16:47 +000047from autotest_lib.frontend.afe import control_file, rpc_utils
J. Richard Barnetteb5164d62015-04-13 12:59:31 -070048from autotest_lib.frontend.afe import models, model_logic, model_attributes
Simran Basib6ec8ae2014-04-23 12:05:08 -070049from autotest_lib.frontend.afe import site_rpc_interface
Moises Osorio2dc7a102014-12-02 18:24:02 -080050from autotest_lib.frontend.tko import models as tko_models
Jiaxi Luoaac54572014-06-04 13:57:02 -070051from autotest_lib.frontend.tko import rpc_interface as tko_rpc_interface
J. Richard Barnetteb5164d62015-04-13 12:59:31 -070052from autotest_lib.server import frontend
Simran Basi71206ef2014-08-13 13:51:18 -070053from autotest_lib.server import utils
Dan Shid215dbe2015-06-18 16:14:59 -070054from autotest_lib.server.cros import provision
Jiaxi Luo90190c92014-06-18 12:35:57 -070055from autotest_lib.server.cros.dynamic_suite import tools
Aviv Keshet7ee95862016-08-30 15:18:27 -070056from autotest_lib.server.lib import status_history
mblighe8819cd2008-02-15 16:48:40 +000057
Moises Osorio2dc7a102014-12-02 18:24:02 -080058
Gabe Black1e1c41b2015-02-04 23:55:15 -080059_timer = autotest_stats.Timer('rpc_interface')
Moises Osorio2dc7a102014-12-02 18:24:02 -080060
Eric Lid23bc192011-02-09 14:38:57 -080061def get_parameterized_autoupdate_image_url(job):
62 """Get the parameterized autoupdate image url from a parameterized job."""
63 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
64 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
beeps8bb1f7d2013-08-05 01:30:09 -070065 name='image')
Eric Lid23bc192011-02-09 14:38:57 -080066 para_set = job.parameterized_job.parameterizedjobparameter_set
67 job_test_para = para_set.get(test_parameter=image_parameter)
68 return job_test_para.parameter_value
69
70
mblighe8819cd2008-02-15 16:48:40 +000071# labels
72
mblighe8819cd2008-02-15 16:48:40 +000073def modify_label(id, **data):
MK Ryu8c554cf2015-06-12 11:45:50 -070074 """Modify a label.
75
76 @param id: id or name of a label. More often a label name.
77 @param data: New data for a label.
78 """
79 label_model = models.Label.smart_get(id)
MK Ryu8e2c2d02016-01-06 15:24:38 -080080 label_model.update_object(data)
MK Ryu8c554cf2015-06-12 11:45:50 -070081
82 # Master forwards the RPC to shards
83 if not utils.is_shard():
84 rpc_utils.fanout_rpc(label_model.host_set.all(), 'modify_label', False,
85 id=id, **data)
86
mblighe8819cd2008-02-15 16:48:40 +000087
88def delete_label(id):
MK Ryu8c554cf2015-06-12 11:45:50 -070089 """Delete a label.
90
91 @param id: id or name of a label. More often a label name.
92 """
93 label_model = models.Label.smart_get(id)
MK Ryu8e2c2d02016-01-06 15:24:38 -080094 # Hosts that have the label to be deleted. Save this info before
95 # the label is deleted to use it later.
96 hosts = []
97 for h in label_model.host_set.all():
98 hosts.append(models.Host.smart_get(h.id))
99 label_model.delete()
MK Ryu8c554cf2015-06-12 11:45:50 -0700100
101 # Master forwards the RPC to shards
102 if not utils.is_shard():
MK Ryu8e2c2d02016-01-06 15:24:38 -0800103 rpc_utils.fanout_rpc(hosts, 'delete_label', False, id=id)
mblighe8819cd2008-02-15 16:48:40 +0000104
Prashanth Balasubramanian744898f2015-01-13 05:04:16 -0800105
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800106def add_label(name, ignore_exception_if_exists=False, **kwargs):
MK Ryucf027c62015-03-04 12:00:50 -0800107 """Adds a new label of a given name.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800108
109 @param name: label name.
110 @param ignore_exception_if_exists: If True and the exception was
111 thrown due to the duplicated label name when adding a label,
112 then suppress the exception. Default is False.
113 @param kwargs: keyword args that store more info about a label
114 other than the name.
115 @return: int/long id of a new label.
116 """
117 # models.Label.add_object() throws model_logic.ValidationError
118 # when it is given a label name that already exists.
119 # However, ValidationError can be thrown with different errors,
120 # and those errors should be thrown up to the call chain.
121 try:
122 label = models.Label.add_object(name=name, **kwargs)
123 except:
124 exc_info = sys.exc_info()
125 if ignore_exception_if_exists:
126 label = rpc_utils.get_label(name)
127 # If the exception is raised not because of duplicated
128 # "name", then raise the original exception.
129 if label is None:
130 raise exc_info[0], exc_info[1], exc_info[2]
131 else:
132 raise exc_info[0], exc_info[1], exc_info[2]
133 return label.id
134
135
136def add_label_to_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800137 """Adds a label of the given id to the given hosts only in local DB.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800138
139 @param id: id or name of a label. More often a label name.
140 @param hosts: The hostnames of hosts that need the label.
141
142 @raises models.Label.DoesNotExist: If the label with id doesn't exist.
143 """
144 label = models.Label.smart_get(id)
145 host_objs = models.Host.smart_get_bulk(hosts)
146 if label.platform:
147 models.Host.check_no_platform(host_objs)
Shuqian Zhao40e182b2016-10-11 11:55:11 -0700148 # Ensure a host has no more than one board label with it.
149 if label.name.startswith('board:'):
Dan Shib5b8b4f2016-11-02 14:04:02 -0700150 models.Host.check_board_labels_allowed(host_objs, [label.name])
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800151 label.host_set.add(*host_objs)
152
153
Kevin Chengbdfc57d2016-04-14 13:46:58 -0700154def _create_label_everywhere(id, hosts):
155 """
156 Yet another method to create labels.
157
158 ALERT! This method should be run only on master not shards!
159 DO NOT RUN THIS ON A SHARD!!! Deputies will hate you if you do!!!
160
161 This method exists primarily to serve label_add_hosts() and
162 host_add_labels(). Basically it pulls out the label check/add logic
163 from label_add_hosts() into this nice method that not only creates
164 the label but also tells the shards that service the hosts to also
165 create the label.
166
167 @param id: id or name of a label. More often a label name.
168 @param hosts: A list of hostnames or ids. More often hostnames.
169 """
170 try:
171 label = models.Label.smart_get(id)
172 except models.Label.DoesNotExist:
173 # This matches the type checks in smart_get, which is a hack
174 # in and off itself. The aim here is to create any non-existent
175 # label, which we cannot do if the 'id' specified isn't a label name.
176 if isinstance(id, basestring):
177 label = models.Label.smart_get(add_label(id))
178 else:
179 raise ValueError('Label id (%s) does not exist. Please specify '
180 'the argument, id, as a string (label name).'
181 % id)
182
183 # Make sure the label exists on the shard with the same id
184 # as it is on the master.
185 # It is possible that the label is already in a shard because
186 # we are adding a new label only to shards of hosts that the label
187 # is going to be attached.
188 # For example, we add a label L1 to a host in shard S1.
189 # Master and S1 will have L1 but other shards won't.
190 # Later, when we add the same label L1 to hosts in shards S1 and S2,
191 # S1 already has the label but S2 doesn't.
192 # S2 should have the new label without any problem.
193 # We ignore exception in such a case.
194 host_objs = models.Host.smart_get_bulk(hosts)
195 rpc_utils.fanout_rpc(
196 host_objs, 'add_label', include_hostnames=False,
197 name=label.name, ignore_exception_if_exists=True,
198 id=label.id, platform=label.platform)
199
200
MK Ryufbb002c2015-06-08 14:13:16 -0700201@rpc_utils.route_rpc_to_master
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800202def label_add_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800203 """Adds a label with the given id to the given hosts.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800204
205 This method should be run only on master not shards.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800206 The given label will be created if it doesn't exist, provided the `id`
207 supplied is a label name not an int/long id.
208
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800209 @param id: id or name of a label. More often a label name.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800210 @param hosts: A list of hostnames or ids. More often hostnames.
211
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800212 @raises ValueError: If the id specified is an int/long (label id)
213 while the label does not exist.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800214 """
Kevin Chengbdfc57d2016-04-14 13:46:58 -0700215 # Create the label.
216 _create_label_everywhere(id, hosts)
217
218 # Add it to the master.
MK Ryu8e2c2d02016-01-06 15:24:38 -0800219 add_label_to_hosts(id, hosts)
MK Ryucf027c62015-03-04 12:00:50 -0800220
Kevin Chengbdfc57d2016-04-14 13:46:58 -0700221 # Add it to the shards.
MK Ryucf027c62015-03-04 12:00:50 -0800222 host_objs = models.Host.smart_get_bulk(hosts)
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800223 rpc_utils.fanout_rpc(host_objs, 'add_label_to_hosts', id=id)
showardbbabf502008-06-06 00:02:02 +0000224
225
MK Ryucf027c62015-03-04 12:00:50 -0800226def remove_label_from_hosts(id, hosts):
227 """Removes a label of the given id from the given hosts only in local DB.
228
229 @param id: id or name of a label.
230 @param hosts: The hostnames of hosts that need to remove the label from.
231 """
showardbe3ec042008-11-12 18:16:07 +0000232 host_objs = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000233 models.Label.smart_get(id).host_set.remove(*host_objs)
showardbbabf502008-06-06 00:02:02 +0000234
235
MK Ryufbb002c2015-06-08 14:13:16 -0700236@rpc_utils.route_rpc_to_master
MK Ryucf027c62015-03-04 12:00:50 -0800237def label_remove_hosts(id, hosts):
238 """Removes a label of the given id from the given hosts.
239
240 This method should be run only on master not shards.
241
242 @param id: id or name of a label.
243 @param hosts: A list of hostnames or ids. More often hostnames.
244 """
MK Ryucf027c62015-03-04 12:00:50 -0800245 host_objs = models.Host.smart_get_bulk(hosts)
MK Ryu26f0c932015-05-28 18:14:33 -0700246 remove_label_from_hosts(id, hosts)
247
MK Ryu8e2c2d02016-01-06 15:24:38 -0800248 rpc_utils.fanout_rpc(host_objs, 'remove_label_from_hosts', id=id)
249
MK Ryucf027c62015-03-04 12:00:50 -0800250
Jiaxi Luo31874592014-06-11 10:36:35 -0700251def get_labels(exclude_filters=(), **filter_data):
showardc92da832009-04-07 18:14:34 +0000252 """\
Jiaxi Luo31874592014-06-11 10:36:35 -0700253 @param exclude_filters: A sequence of dictionaries of filters.
254
showardc92da832009-04-07 18:14:34 +0000255 @returns A sequence of nested dictionaries of label information.
256 """
Jiaxi Luo31874592014-06-11 10:36:35 -0700257 labels = models.Label.query_objects(filter_data)
258 for exclude_filter in exclude_filters:
259 labels = labels.exclude(**exclude_filter)
260 return rpc_utils.prepare_rows_as_nested_dicts(labels, ('atomic_group',))
showardc92da832009-04-07 18:14:34 +0000261
262
263# atomic groups
264
showarde9450c92009-06-30 01:58:52 +0000265def add_atomic_group(name, max_number_of_machines=None, description=None):
showardc92da832009-04-07 18:14:34 +0000266 return models.AtomicGroup.add_object(
267 name=name, max_number_of_machines=max_number_of_machines,
268 description=description).id
269
270
271def modify_atomic_group(id, **data):
272 models.AtomicGroup.smart_get(id).update_object(data)
273
274
275def delete_atomic_group(id):
276 models.AtomicGroup.smart_get(id).delete()
277
278
279def atomic_group_add_labels(id, labels):
280 label_objs = models.Label.smart_get_bulk(labels)
281 models.AtomicGroup.smart_get(id).label_set.add(*label_objs)
282
283
284def atomic_group_remove_labels(id, labels):
285 label_objs = models.Label.smart_get_bulk(labels)
286 models.AtomicGroup.smart_get(id).label_set.remove(*label_objs)
287
288
289def get_atomic_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000290 return rpc_utils.prepare_for_serialization(
showardc92da832009-04-07 18:14:34 +0000291 models.AtomicGroup.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000292
293
294# hosts
295
Matthew Sartori68186332015-04-27 17:19:53 -0700296def add_host(hostname, status=None, locked=None, lock_reason='', protection=None):
297 if locked and not lock_reason:
298 raise model_logic.ValidationError(
299 {'locked': 'Please provide a reason for locking when adding host.'})
300
jadmanski0afbb632008-06-06 21:10:57 +0000301 return models.Host.add_object(hostname=hostname, status=status,
Matthew Sartori68186332015-04-27 17:19:53 -0700302 locked=locked, lock_reason=lock_reason,
303 protection=protection).id
mblighe8819cd2008-02-15 16:48:40 +0000304
305
MK Ryu33889612015-09-04 14:32:35 -0700306@rpc_utils.route_rpc_to_master
307def modify_host(id, **kwargs):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700308 """Modify local attributes of a host.
309
310 If this is called on the master, but the host is assigned to a shard, this
MK Ryu33889612015-09-04 14:32:35 -0700311 will call `modify_host_local` RPC to the responsible shard. This means if
312 a host is being locked using this function, this change will also propagate
313 to shards.
314 When this is called on a shard, the shard just routes the RPC to the master
315 and does nothing.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700316
317 @param id: id of the host to modify.
MK Ryu33889612015-09-04 14:32:35 -0700318 @param kwargs: key=value pairs of values to set on the host.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700319 """
MK Ryu33889612015-09-04 14:32:35 -0700320 rpc_utils.check_modify_host(kwargs)
showardce7c0922009-09-11 18:39:24 +0000321 host = models.Host.smart_get(id)
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800322 try:
323 rpc_utils.check_modify_host_locking(host, kwargs)
324 except model_logic.ValidationError as e:
325 if not kwargs.get('force_modify_locking', False):
326 raise
327 logging.exception('The following exception will be ignored and lock '
328 'modification will be enforced. %s', e)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700329
MK Ryud53e1492015-12-15 12:09:03 -0800330 # This is required to make `lock_time` for a host be exactly same
331 # between the master and a shard.
332 if kwargs.get('locked', None) and 'lock_time' not in kwargs:
333 kwargs['lock_time'] = datetime.datetime.now()
MK Ryu8e2c2d02016-01-06 15:24:38 -0800334 host.update_object(kwargs)
MK Ryud53e1492015-12-15 12:09:03 -0800335
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800336 # force_modifying_locking is not an internal field in database, remove.
337 kwargs.pop('force_modify_locking', None)
MK Ryu33889612015-09-04 14:32:35 -0700338 rpc_utils.fanout_rpc([host], 'modify_host_local',
339 include_hostnames=False, id=id, **kwargs)
mblighe8819cd2008-02-15 16:48:40 +0000340
341
MK Ryu33889612015-09-04 14:32:35 -0700342def modify_host_local(id, **kwargs):
343 """Modify host attributes in local DB.
344
345 @param id: Host id.
346 @param kwargs: key=value pairs of values to set on the host.
347 """
348 models.Host.smart_get(id).update_object(kwargs)
349
350
351@rpc_utils.route_rpc_to_master
showard276f9442009-05-20 00:33:16 +0000352def modify_hosts(host_filter_data, update_data):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700353 """Modify local attributes of multiple hosts.
354
355 If this is called on the master, but one of the hosts in that match the
MK Ryu33889612015-09-04 14:32:35 -0700356 filters is assigned to a shard, this will call `modify_hosts_local` RPC
357 to the responsible shard.
358 When this is called on a shard, the shard just routes the RPC to the master
359 and does nothing.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700360
361 The filters are always applied on the master, not on the shards. This means
362 if the states of a host differ on the master and a shard, the state on the
363 master will be used. I.e. this means:
364 A host was synced to Shard 1. On Shard 1 the status of the host was set to
365 'Repair Failed'.
366 - A call to modify_hosts with host_filter_data={'status': 'Ready'} will
367 update the host (both on the shard and on the master), because the state
368 of the host as the master knows it is still 'Ready'.
369 - A call to modify_hosts with host_filter_data={'status': 'Repair failed'
370 will not update the host, because the filter doesn't apply on the master.
371
showardbe0d8692009-08-20 23:42:44 +0000372 @param host_filter_data: Filters out which hosts to modify.
373 @param update_data: A dictionary with the changes to make to the hosts.
showard276f9442009-05-20 00:33:16 +0000374 """
MK Ryu93161712015-12-21 10:41:32 -0800375 update_data = update_data.copy()
showardbe0d8692009-08-20 23:42:44 +0000376 rpc_utils.check_modify_host(update_data)
showard276f9442009-05-20 00:33:16 +0000377 hosts = models.Host.query_objects(host_filter_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700378
379 affected_shard_hostnames = set()
380 affected_host_ids = []
381
Alex Miller9658a952013-05-14 16:40:02 -0700382 # Check all hosts before changing data for exception safety.
383 for host in hosts:
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800384 try:
385 rpc_utils.check_modify_host_locking(host, update_data)
386 except model_logic.ValidationError as e:
387 if not update_data.get('force_modify_locking', False):
388 raise
389 logging.exception('The following exception will be ignored and '
390 'lock modification will be enforced. %s', e)
391
Jakob Juelich50e91f72014-10-01 12:43:23 -0700392 if host.shard:
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800393 affected_shard_hostnames.add(host.shard.rpc_hostname())
Jakob Juelich50e91f72014-10-01 12:43:23 -0700394 affected_host_ids.append(host.id)
395
MK Ryud53e1492015-12-15 12:09:03 -0800396 # This is required to make `lock_time` for a host be exactly same
397 # between the master and a shard.
398 if update_data.get('locked', None) and 'lock_time' not in update_data:
399 update_data['lock_time'] = datetime.datetime.now()
MK Ryu8e2c2d02016-01-06 15:24:38 -0800400 for host in hosts:
401 host.update_object(update_data)
MK Ryud53e1492015-12-15 12:09:03 -0800402
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800403 update_data.pop('force_modify_locking', None)
MK Ryu33889612015-09-04 14:32:35 -0700404 # Caution: Changing the filter from the original here. See docstring.
405 rpc_utils.run_rpc_on_multiple_hostnames(
406 'modify_hosts_local', affected_shard_hostnames,
Jakob Juelich50e91f72014-10-01 12:43:23 -0700407 host_filter_data={'id__in': affected_host_ids},
408 update_data=update_data)
409
showard276f9442009-05-20 00:33:16 +0000410
MK Ryu33889612015-09-04 14:32:35 -0700411def modify_hosts_local(host_filter_data, update_data):
412 """Modify attributes of hosts in local DB.
413
414 @param host_filter_data: Filters out which hosts to modify.
415 @param update_data: A dictionary with the changes to make to the hosts.
416 """
417 for host in models.Host.query_objects(host_filter_data):
418 host.update_object(update_data)
419
420
MK Ryufbb002c2015-06-08 14:13:16 -0700421def add_labels_to_host(id, labels):
422 """Adds labels to a given host only in local DB.
showardcafd16e2009-05-29 18:37:49 +0000423
MK Ryufbb002c2015-06-08 14:13:16 -0700424 @param id: id or hostname for a host.
425 @param labels: ids or names for labels.
426 """
427 label_objs = models.Label.smart_get_bulk(labels)
428 models.Host.smart_get(id).labels.add(*label_objs)
429
430
431@rpc_utils.route_rpc_to_master
432def host_add_labels(id, labels):
433 """Adds labels to a given host.
434
435 @param id: id or hostname for a host.
436 @param labels: ids or names for labels.
437
Shuqian Zhao40e182b2016-10-11 11:55:11 -0700438 @raises ValidationError: If adding more than one platform/board label.
MK Ryufbb002c2015-06-08 14:13:16 -0700439 """
Kevin Chengbdfc57d2016-04-14 13:46:58 -0700440 # Create the labels on the master/shards.
441 for label in labels:
442 _create_label_everywhere(label, [id])
443
MK Ryufbb002c2015-06-08 14:13:16 -0700444 label_objs = models.Label.smart_get_bulk(labels)
445 platforms = [label.name for label in label_objs if label.platform]
Shuqian Zhao40e182b2016-10-11 11:55:11 -0700446 boards = [label.name for label in label_objs
447 if label.name.startswith('board:')]
Dan Shib5b8b4f2016-11-02 14:04:02 -0700448 if len(platforms) > 1 or not utils.board_labels_allowed(boards):
showardcafd16e2009-05-29 18:37:49 +0000449 raise model_logic.ValidationError(
Dan Shib5b8b4f2016-11-02 14:04:02 -0700450 {'labels': ('Adding more than one platform label, or a list of '
451 'non-compatible board labels.: %s %s' %
452 (', '.join(platforms), ', '.join(boards)))})
MK Ryufbb002c2015-06-08 14:13:16 -0700453
454 host_obj = models.Host.smart_get(id)
Dan Shi4a3deb82016-10-27 21:32:30 -0700455 if platforms:
MK Ryufbb002c2015-06-08 14:13:16 -0700456 models.Host.check_no_platform([host_obj])
Dan Shi4a3deb82016-10-27 21:32:30 -0700457 if boards:
Dan Shib5b8b4f2016-11-02 14:04:02 -0700458 models.Host.check_board_labels_allowed([host_obj], labels)
MK Ryu8e2c2d02016-01-06 15:24:38 -0800459 add_labels_to_host(id, labels)
MK Ryufbb002c2015-06-08 14:13:16 -0700460
461 rpc_utils.fanout_rpc([host_obj], 'add_labels_to_host', False,
462 id=id, labels=labels)
mblighe8819cd2008-02-15 16:48:40 +0000463
464
MK Ryufbb002c2015-06-08 14:13:16 -0700465def remove_labels_from_host(id, labels):
466 """Removes labels from a given host only in local DB.
467
468 @param id: id or hostname for a host.
469 @param labels: ids or names for labels.
470 """
471 label_objs = models.Label.smart_get_bulk(labels)
472 models.Host.smart_get(id).labels.remove(*label_objs)
473
474
475@rpc_utils.route_rpc_to_master
mblighe8819cd2008-02-15 16:48:40 +0000476def host_remove_labels(id, labels):
MK Ryufbb002c2015-06-08 14:13:16 -0700477 """Removes labels from a given host.
478
479 @param id: id or hostname for a host.
480 @param labels: ids or names for labels.
481 """
MK Ryu8e2c2d02016-01-06 15:24:38 -0800482 remove_labels_from_host(id, labels)
483
MK Ryufbb002c2015-06-08 14:13:16 -0700484 host_obj = models.Host.smart_get(id)
485 rpc_utils.fanout_rpc([host_obj], 'remove_labels_from_host', False,
486 id=id, labels=labels)
mblighe8819cd2008-02-15 16:48:40 +0000487
488
MK Ryuacf35922014-10-03 14:56:49 -0700489def get_host_attribute(attribute, **host_filter_data):
490 """
491 @param attribute: string name of attribute
492 @param host_filter_data: filter data to apply to Hosts to choose hosts to
493 act upon
494 """
495 hosts = rpc_utils.get_host_query((), False, False, True, host_filter_data)
496 hosts = list(hosts)
497 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
498 'attribute_list')
499 host_attr_dicts = []
500 for host_obj in hosts:
501 for attr_obj in host_obj.attribute_list:
502 if attr_obj.attribute == attribute:
503 host_attr_dicts.append(attr_obj.get_object_dict())
504 return rpc_utils.prepare_for_serialization(host_attr_dicts)
505
506
showard0957a842009-05-11 19:25:08 +0000507def set_host_attribute(attribute, value, **host_filter_data):
508 """
MK Ryu26f0c932015-05-28 18:14:33 -0700509 @param attribute: string name of attribute
510 @param value: string, or None to delete an attribute
511 @param host_filter_data: filter data to apply to Hosts to choose hosts to
512 act upon
showard0957a842009-05-11 19:25:08 +0000513 """
514 assert host_filter_data # disallow accidental actions on all hosts
515 hosts = models.Host.query_objects(host_filter_data)
516 models.AclGroup.check_for_acl_violation_hosts(hosts)
MK Ryu8e2c2d02016-01-06 15:24:38 -0800517 for host in hosts:
518 host.set_or_delete_attribute(attribute, value)
showard0957a842009-05-11 19:25:08 +0000519
MK Ryu26f0c932015-05-28 18:14:33 -0700520 # Master forwards this RPC to shards.
521 if not utils.is_shard():
522 rpc_utils.fanout_rpc(hosts, 'set_host_attribute', False,
523 attribute=attribute, value=value, **host_filter_data)
524
showard0957a842009-05-11 19:25:08 +0000525
Jakob Juelich50e91f72014-10-01 12:43:23 -0700526@rpc_utils.forward_single_host_rpc_to_shard
mblighe8819cd2008-02-15 16:48:40 +0000527def delete_host(id):
jadmanski0afbb632008-06-06 21:10:57 +0000528 models.Host.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000529
530
showard87cc38f2009-08-20 23:37:04 +0000531def get_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
Dan Shi37df54d2015-12-14 11:16:28 -0800532 exclude_atomic_group_hosts=False, valid_only=True,
533 include_current_job=False, **filter_data):
534 """Get a list of dictionaries which contains the information of hosts.
535
showard87cc38f2009-08-20 23:37:04 +0000536 @param multiple_labels: match hosts in all of the labels given. Should
537 be a list of label names.
538 @param exclude_only_if_needed_labels: Exclude hosts with at least one
539 "only_if_needed" label applied.
540 @param exclude_atomic_group_hosts: Exclude hosts that have one or more
541 atomic group labels associated with them.
Dan Shi37df54d2015-12-14 11:16:28 -0800542 @param include_current_job: Set to True to include ids of currently running
543 job and special task.
jadmanski0afbb632008-06-06 21:10:57 +0000544 """
showard43a3d262008-11-12 18:17:05 +0000545 hosts = rpc_utils.get_host_query(multiple_labels,
546 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000547 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000548 valid_only, filter_data)
showard0957a842009-05-11 19:25:08 +0000549 hosts = list(hosts)
550 models.Host.objects.populate_relationships(hosts, models.Label,
551 'label_list')
552 models.Host.objects.populate_relationships(hosts, models.AclGroup,
553 'acl_list')
554 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
555 'attribute_list')
showard43a3d262008-11-12 18:17:05 +0000556 host_dicts = []
557 for host_obj in hosts:
558 host_dict = host_obj.get_object_dict()
showard0957a842009-05-11 19:25:08 +0000559 host_dict['labels'] = [label.name for label in host_obj.label_list]
showard909c9142009-07-07 20:54:42 +0000560 host_dict['platform'], host_dict['atomic_group'] = (rpc_utils.
561 find_platform_and_atomic_group(host_obj))
showard0957a842009-05-11 19:25:08 +0000562 host_dict['acls'] = [acl.name for acl in host_obj.acl_list]
563 host_dict['attributes'] = dict((attribute.attribute, attribute.value)
564 for attribute in host_obj.attribute_list)
Dan Shi37df54d2015-12-14 11:16:28 -0800565 if include_current_job:
566 host_dict['current_job'] = None
567 host_dict['current_special_task'] = None
568 entries = models.HostQueueEntry.objects.filter(
569 host_id=host_dict['id'], active=True, complete=False)
570 if entries:
571 host_dict['current_job'] = (
572 entries[0].get_object_dict()['job'])
573 tasks = models.SpecialTask.objects.filter(
574 host_id=host_dict['id'], is_active=True, is_complete=False)
575 if tasks:
576 host_dict['current_special_task'] = (
577 '%d-%s' % (tasks[0].get_object_dict()['id'],
578 tasks[0].get_object_dict()['task'].lower()))
showard43a3d262008-11-12 18:17:05 +0000579 host_dicts.append(host_dict)
580 return rpc_utils.prepare_for_serialization(host_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000581
582
showard87cc38f2009-08-20 23:37:04 +0000583def get_num_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000584 exclude_atomic_group_hosts=False, valid_only=True,
585 **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000586 """
587 Same parameters as get_hosts().
588
589 @returns The number of matching hosts.
590 """
showard43a3d262008-11-12 18:17:05 +0000591 hosts = rpc_utils.get_host_query(multiple_labels,
592 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000593 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000594 valid_only, filter_data)
showard43a3d262008-11-12 18:17:05 +0000595 return hosts.count()
showard1385b162008-03-13 15:59:40 +0000596
mblighe8819cd2008-02-15 16:48:40 +0000597
598# tests
599
showard909c7a62008-07-15 21:52:38 +0000600def add_test(name, test_type, path, author=None, dependencies=None,
showard3d9899a2008-07-31 02:11:58 +0000601 experimental=True, run_verify=None, test_class=None,
showard909c7a62008-07-15 21:52:38 +0000602 test_time=None, test_category=None, description=None,
603 sync_count=1):
jadmanski0afbb632008-06-06 21:10:57 +0000604 return models.Test.add_object(name=name, test_type=test_type, path=path,
showard909c7a62008-07-15 21:52:38 +0000605 author=author, dependencies=dependencies,
606 experimental=experimental,
607 run_verify=run_verify, test_time=test_time,
608 test_category=test_category,
609 sync_count=sync_count,
jadmanski0afbb632008-06-06 21:10:57 +0000610 test_class=test_class,
611 description=description).id
mblighe8819cd2008-02-15 16:48:40 +0000612
613
614def modify_test(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000615 models.Test.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000616
617
618def delete_test(id):
jadmanski0afbb632008-06-06 21:10:57 +0000619 models.Test.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000620
621
622def get_tests(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000623 return rpc_utils.prepare_for_serialization(
624 models.Test.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000625
626
Moises Osorio2dc7a102014-12-02 18:24:02 -0800627@_timer.decorate
628def get_tests_status_counts_by_job_name_label(job_name_prefix, label_name):
629 """Gets the counts of all passed and failed tests from the matching jobs.
630
631 @param job_name_prefix: Name prefix of the jobs to get the summary from, e.g.,
632 'butterfly-release/R40-6457.21.0/bvt-cq/'.
633 @param label_name: Label that must be set in the jobs, e.g.,
634 'cros-version:butterfly-release/R40-6457.21.0'.
635
636 @returns A summary of the counts of all the passed and failed tests.
637 """
638 job_ids = list(models.Job.objects.filter(
639 name__startswith=job_name_prefix,
640 dependency_labels__name=label_name).values_list(
641 'pk', flat=True))
642 summary = {'passed': 0, 'failed': 0}
643 if not job_ids:
644 return summary
645
646 counts = (tko_models.TestView.objects.filter(
647 afe_job_id__in=job_ids).exclude(
648 test_name='SERVER_JOB').exclude(
649 test_name__startswith='CLIENT_JOB').values(
650 'status').annotate(
651 count=Count('status')))
652 for status in counts:
653 if status['status'] == 'GOOD':
654 summary['passed'] += status['count']
655 else:
656 summary['failed'] += status['count']
657 return summary
658
659
showard2b9a88b2008-06-13 20:55:03 +0000660# profilers
661
662def add_profiler(name, description=None):
663 return models.Profiler.add_object(name=name, description=description).id
664
665
666def modify_profiler(id, **data):
667 models.Profiler.smart_get(id).update_object(data)
668
669
670def delete_profiler(id):
671 models.Profiler.smart_get(id).delete()
672
673
674def get_profilers(**filter_data):
675 return rpc_utils.prepare_for_serialization(
676 models.Profiler.list_objects(filter_data))
677
678
mblighe8819cd2008-02-15 16:48:40 +0000679# users
680
681def add_user(login, access_level=None):
jadmanski0afbb632008-06-06 21:10:57 +0000682 return models.User.add_object(login=login, access_level=access_level).id
mblighe8819cd2008-02-15 16:48:40 +0000683
684
685def modify_user(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000686 models.User.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000687
688
689def delete_user(id):
jadmanski0afbb632008-06-06 21:10:57 +0000690 models.User.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000691
692
693def get_users(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000694 return rpc_utils.prepare_for_serialization(
695 models.User.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000696
697
698# acl groups
699
700def add_acl_group(name, description=None):
showard04f2cd82008-07-25 20:53:31 +0000701 group = models.AclGroup.add_object(name=name, description=description)
showard64a95952010-01-13 21:27:16 +0000702 group.users.add(models.User.current_user())
showard04f2cd82008-07-25 20:53:31 +0000703 return group.id
mblighe8819cd2008-02-15 16:48:40 +0000704
705
706def modify_acl_group(id, **data):
showard04f2cd82008-07-25 20:53:31 +0000707 group = models.AclGroup.smart_get(id)
708 group.check_for_acl_violation_acl_group()
709 group.update_object(data)
710 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000711
712
713def acl_group_add_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000714 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000715 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000716 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000717 group.users.add(*users)
mblighe8819cd2008-02-15 16:48:40 +0000718
719
720def acl_group_remove_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000721 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000722 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000723 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000724 group.users.remove(*users)
showard04f2cd82008-07-25 20:53:31 +0000725 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000726
727
728def acl_group_add_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000729 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000730 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000731 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000732 group.hosts.add(*hosts)
showard08f981b2008-06-24 21:59:03 +0000733 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000734
735
736def acl_group_remove_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000737 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000738 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000739 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000740 group.hosts.remove(*hosts)
showard08f981b2008-06-24 21:59:03 +0000741 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000742
743
744def delete_acl_group(id):
jadmanski0afbb632008-06-06 21:10:57 +0000745 models.AclGroup.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000746
747
748def get_acl_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000749 acl_groups = models.AclGroup.list_objects(filter_data)
750 for acl_group in acl_groups:
751 acl_group_obj = models.AclGroup.objects.get(id=acl_group['id'])
752 acl_group['users'] = [user.login
753 for user in acl_group_obj.users.all()]
754 acl_group['hosts'] = [host.hostname
755 for host in acl_group_obj.hosts.all()]
756 return rpc_utils.prepare_for_serialization(acl_groups)
mblighe8819cd2008-02-15 16:48:40 +0000757
758
759# jobs
760
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700761def generate_control_file(tests=(), profilers=(),
showard91f85102009-10-12 20:34:52 +0000762 client_control_file='', use_container=False,
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700763 profile_only=None, db_tests=True,
764 test_source_build=None):
jadmanski0afbb632008-06-06 21:10:57 +0000765 """
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700766 Generates a client-side control file to run tests.
mbligh120351e2009-01-24 01:40:45 +0000767
Matthew Sartori10438092015-06-24 14:30:18 -0700768 @param tests List of tests to run. See db_tests for more information.
mbligh120351e2009-01-24 01:40:45 +0000769 @param profilers List of profilers to activate during the job.
770 @param client_control_file The contents of a client-side control file to
771 run at the end of all tests. If this is supplied, all tests must be
772 client side.
773 TODO: in the future we should support server control files directly
774 to wrap with a kernel. That'll require changing the parameter
775 name and adding a boolean to indicate if it is a client or server
776 control file.
777 @param use_container unused argument today. TODO: Enable containers
778 on the host during a client side test.
showard91f85102009-10-12 20:34:52 +0000779 @param profile_only A boolean that indicates what default profile_only
780 mode to use in the control file. Passing None will generate a
781 control file that does not explcitly set the default mode at all.
Matthew Sartori10438092015-06-24 14:30:18 -0700782 @param db_tests: if True, the test object can be found in the database
783 backing the test model. In this case, tests is a tuple
784 of test IDs which are used to retrieve the test objects
785 from the database. If False, tests is a tuple of test
786 dictionaries stored client-side in the AFE.
Michael Tang84a2ecf2016-06-07 15:10:53 -0700787 @param test_source_build: Build to be used to retrieve test code. Default
788 to None.
mbligh120351e2009-01-24 01:40:45 +0000789
790 @returns a dict with the following keys:
791 control_file: str, The control file text.
792 is_server: bool, is the control file a server-side control file?
793 synch_count: How many machines the job uses per autoserv execution.
794 synch_count == 1 means the job is asynchronous.
795 dependencies: A list of the names of labels on which the job depends.
796 """
showardd86debe2009-06-10 17:37:56 +0000797 if not tests and not client_control_file:
showard2bab8f42008-11-12 18:15:22 +0000798 return dict(control_file='', is_server=False, synch_count=1,
showard989f25d2008-10-01 11:38:11 +0000799 dependencies=[])
mblighe8819cd2008-02-15 16:48:40 +0000800
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700801 cf_info, test_objects, profiler_objects = (
802 rpc_utils.prepare_generate_control_file(tests, profilers,
803 db_tests))
showard989f25d2008-10-01 11:38:11 +0000804 cf_info['control_file'] = control_file.generate_control(
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700805 tests=test_objects, profilers=profiler_objects,
806 is_server=cf_info['is_server'],
showard232b7ae2009-11-10 00:46:48 +0000807 client_control_file=client_control_file, profile_only=profile_only,
Michael Tang84a2ecf2016-06-07 15:10:53 -0700808 test_source_build=test_source_build)
showard989f25d2008-10-01 11:38:11 +0000809 return cf_info
mblighe8819cd2008-02-15 16:48:40 +0000810
811
Shuqian Zhao54a5b672016-05-11 22:12:17 +0000812def create_parameterized_job(name, priority, test, parameters, kernel=None,
813 label=None, profilers=(), profiler_parameters=None,
814 use_container=False, profile_only=None,
815 upload_kernel_config=False, hosts=(),
816 meta_hosts=(), one_time_hosts=(),
817 atomic_group_name=None, synch_count=None,
818 is_template=False, timeout=None,
819 timeout_mins=None, max_runtime_mins=None,
820 run_verify=False, email_list='', dependencies=(),
821 reboot_before=None, reboot_after=None,
822 parse_failed_repair=None, hostless=False,
823 keyvals=None, drone_set=None, run_reset=True,
824 require_ssp=None):
825 """
826 Creates and enqueues a parameterized job.
827
828 Most parameters a combination of the parameters for generate_control_file()
829 and create_job(), with the exception of:
830
831 @param test name or ID of the test to run
832 @param parameters a map of parameter name ->
833 tuple of (param value, param type)
834 @param profiler_parameters a dictionary of parameters for the profilers:
835 key: profiler name
836 value: dict of param name -> tuple of
837 (param value,
838 param type)
839 """
840 # Save the values of the passed arguments here. What we're going to do with
841 # them is pass them all to rpc_utils.get_create_job_common_args(), which
842 # will extract the subset of these arguments that apply for
843 # rpc_utils.create_job_common(), which we then pass in to that function.
844 args = locals()
845
846 # Set up the parameterized job configs
847 test_obj = models.Test.smart_get(test)
848 control_type = test_obj.test_type
849
850 try:
851 label = models.Label.smart_get(label)
852 except models.Label.DoesNotExist:
853 label = None
854
855 kernel_objs = models.Kernel.create_kernels(kernel)
856 profiler_objs = [models.Profiler.smart_get(profiler)
857 for profiler in profilers]
858
859 parameterized_job = models.ParameterizedJob.objects.create(
860 test=test_obj, label=label, use_container=use_container,
861 profile_only=profile_only,
862 upload_kernel_config=upload_kernel_config)
863 parameterized_job.kernels.add(*kernel_objs)
864
865 for profiler in profiler_objs:
866 parameterized_profiler = models.ParameterizedJobProfiler.objects.create(
867 parameterized_job=parameterized_job,
868 profiler=profiler)
869 profiler_params = profiler_parameters.get(profiler.name, {})
870 for name, (value, param_type) in profiler_params.iteritems():
871 models.ParameterizedJobProfilerParameter.objects.create(
872 parameterized_job_profiler=parameterized_profiler,
873 parameter_name=name,
874 parameter_value=value,
875 parameter_type=param_type)
876
877 try:
878 for parameter in test_obj.testparameter_set.all():
879 if parameter.name in parameters:
880 param_value, param_type = parameters.pop(parameter.name)
881 parameterized_job.parameterizedjobparameter_set.create(
882 test_parameter=parameter, parameter_value=param_value,
883 parameter_type=param_type)
884
885 if parameters:
886 raise Exception('Extra parameters remain: %r' % parameters)
887
888 return rpc_utils.create_job_common(
889 parameterized_job=parameterized_job.id,
890 control_type=control_type,
891 **rpc_utils.get_create_job_common_args(args))
892 except:
893 parameterized_job.delete()
894 raise
895
896
Simran Basib6ec8ae2014-04-23 12:05:08 -0700897def create_job_page_handler(name, priority, control_file, control_type,
Dan Shid215dbe2015-06-18 16:14:59 -0700898 image=None, hostless=False, firmware_rw_build=None,
899 firmware_ro_build=None, test_source_build=None,
Michael Tang84a2ecf2016-06-07 15:10:53 -0700900 is_cloning=False, **kwargs):
Simran Basib6ec8ae2014-04-23 12:05:08 -0700901 """\
902 Create and enqueue a job.
903
904 @param name name of this job
905 @param priority Integer priority of this job. Higher is more important.
906 @param control_file String contents of the control file.
907 @param control_type Type of control file, Client or Server.
Dan Shid215dbe2015-06-18 16:14:59 -0700908 @param image: ChromeOS build to be installed in the dut. Default to None.
909 @param firmware_rw_build: Firmware build to update RW firmware. Default to
910 None, i.e., RW firmware will not be updated.
911 @param firmware_ro_build: Firmware build to update RO firmware. Default to
912 None, i.e., RO firmware will not be updated.
913 @param test_source_build: Build to be used to retrieve test code. Default
914 to None.
Michael Tang6dc174e2016-05-31 23:13:42 -0700915 @param is_cloning: True if creating a cloning job.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700916 @param kwargs extra args that will be required by create_suite_job or
917 create_job.
918
919 @returns The created Job id number.
920 """
Michael Tang6dc174e2016-05-31 23:13:42 -0700921 if is_cloning:
922 logging.info('Start to clone a new job')
Shuqian Zhao61f5d312016-08-05 17:15:23 -0700923 # When cloning a job, hosts and meta_hosts should not exist together,
924 # which would cause host-scheduler to schedule two hqe jobs to one host
925 # at the same time, and crash itself. Clear meta_hosts for this case.
926 if kwargs.get('hosts') and kwargs.get('meta_hosts'):
927 kwargs['meta_hosts'] = []
Michael Tang6dc174e2016-05-31 23:13:42 -0700928 else:
929 logging.info('Start to create a new job')
Simran Basib6ec8ae2014-04-23 12:05:08 -0700930 control_file = rpc_utils.encode_ascii(control_file)
Jiaxi Luodd67beb2014-07-18 16:28:31 -0700931 if not control_file:
932 raise model_logic.ValidationError({
933 'control_file' : "Control file cannot be empty"})
Simran Basib6ec8ae2014-04-23 12:05:08 -0700934
935 if image and hostless:
Dan Shid215dbe2015-06-18 16:14:59 -0700936 builds = {}
937 builds[provision.CROS_VERSION_PREFIX] = image
938 if firmware_rw_build:
Dan Shi0723bf52015-06-24 10:52:38 -0700939 builds[provision.FW_RW_VERSION_PREFIX] = firmware_rw_build
Dan Shid215dbe2015-06-18 16:14:59 -0700940 if firmware_ro_build:
941 builds[provision.FW_RO_VERSION_PREFIX] = firmware_ro_build
Simran Basib6ec8ae2014-04-23 12:05:08 -0700942 return site_rpc_interface.create_suite_job(
943 name=name, control_file=control_file, priority=priority,
Michael Tang6dc174e2016-05-31 23:13:42 -0700944 builds=builds, test_source_build=test_source_build,
945 is_cloning=is_cloning, **kwargs)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700946 return create_job(name, priority, control_file, control_type, image=image,
Michael Tang6dc174e2016-05-31 23:13:42 -0700947 hostless=hostless, is_cloning=is_cloning, **kwargs)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700948
949
MK Ryue301eb72015-06-25 12:51:02 -0700950@rpc_utils.route_rpc_to_master
Allen Li8af9da02016-12-12 17:32:39 -0800951def create_job(
952 name,
953 priority,
954 control_file,
955 control_type,
956 hosts=(),
957 meta_hosts=(),
958 one_time_hosts=(),
959 atomic_group_name=None,
960 synch_count=None,
961 is_template=False,
962 timeout=None,
963 timeout_mins=None,
964 max_runtime_mins=None,
965 run_verify=False,
966 email_list='',
967 dependencies=(),
968 reboot_before=None,
969 reboot_after=None,
970 parse_failed_repair=None,
971 hostless=False,
972 keyvals=None,
973 drone_set=None,
974 image=None,
975 parent_job_id=None,
976 test_retry=0,
977 run_reset=True,
978 require_ssp=None,
979 args=(),
980 is_cloning=False,
981 **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +0000982 """\
983 Create and enqueue a job.
mblighe8819cd2008-02-15 16:48:40 +0000984
showarda1e74b32009-05-12 17:32:04 +0000985 @param name name of this job
Alex Miller7d658cf2013-09-04 16:00:35 -0700986 @param priority Integer priority of this job. Higher is more important.
showarda1e74b32009-05-12 17:32:04 +0000987 @param control_file String contents of the control file.
988 @param control_type Type of control file, Client or Server.
989 @param synch_count How many machines the job uses per autoserv execution.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700990 synch_count == 1 means the job is asynchronous. If an atomic group is
991 given this value is treated as a minimum.
showarda1e74b32009-05-12 17:32:04 +0000992 @param is_template If true then create a template job.
993 @param timeout Hours after this call returns until the job times out.
Simran Basi7e605742013-11-12 13:43:36 -0800994 @param timeout_mins Minutes after this call returns until the job times
Jiaxi Luo90190c92014-06-18 12:35:57 -0700995 out.
Simran Basi34217022012-11-06 13:43:15 -0800996 @param max_runtime_mins Minutes from job starting time until job times out
showarda1e74b32009-05-12 17:32:04 +0000997 @param run_verify Should the host be verified before running the test?
998 @param email_list String containing emails to mail when the job is done
999 @param dependencies List of label names on which this job depends
1000 @param reboot_before Never, If dirty, or Always
1001 @param reboot_after Never, If all tests passed, or Always
1002 @param parse_failed_repair if true, results of failed repairs launched by
Jiaxi Luo90190c92014-06-18 12:35:57 -07001003 this job will be parsed as part of the job.
showarda9545c02009-12-18 22:44:26 +00001004 @param hostless if true, create a hostless job
showardc1a98d12010-01-15 00:22:22 +00001005 @param keyvals dict of keyvals to associate with the job
showarda1e74b32009-05-12 17:32:04 +00001006 @param hosts List of hosts to run job on.
1007 @param meta_hosts List where each entry is a label name, and for each entry
Jiaxi Luo90190c92014-06-18 12:35:57 -07001008 one host will be chosen from that label to run the job on.
showarda1e74b32009-05-12 17:32:04 +00001009 @param one_time_hosts List of hosts not in the database to run the job on.
1010 @param atomic_group_name The name of an atomic group to schedule the job on.
jamesren76fcf192010-04-21 20:39:50 +00001011 @param drone_set The name of the drone set to run this test on.
Paul Pendlebury5a8c6ad2011-02-01 07:20:17 -08001012 @param image OS image to install before running job.
Aviv Keshet0b9cfc92013-02-05 11:36:02 -08001013 @param parent_job_id id of a job considered to be parent of created job.
Simran Basib6ec8ae2014-04-23 12:05:08 -07001014 @param test_retry Number of times to retry test if the test did not
Jiaxi Luo90190c92014-06-18 12:35:57 -07001015 complete successfully. (optional, default: 0)
Simran Basib6ec8ae2014-04-23 12:05:08 -07001016 @param run_reset Should the host be reset before running the test?
Dan Shiec1d47d2015-02-13 11:38:13 -08001017 @param require_ssp Set to True to require server-side packaging to run the
1018 test. If it's set to None, drone will still try to run
1019 the server side with server-side packaging. If the
1020 autotest-server package doesn't exist for the build or
1021 image is not set, drone will run the test without server-
1022 side packaging. Default is None.
Jiaxi Luo90190c92014-06-18 12:35:57 -07001023 @param args A list of args to be injected into control file.
Michael Tang6dc174e2016-05-31 23:13:42 -07001024 @param is_cloning: True if creating a cloning job.
Simran Basib6ec8ae2014-04-23 12:05:08 -07001025 @param kwargs extra keyword args. NOT USED.
showardc92da832009-04-07 18:14:34 +00001026
1027 @returns The created Job id number.
jadmanski0afbb632008-06-06 21:10:57 +00001028 """
Jiaxi Luo90190c92014-06-18 12:35:57 -07001029 if args:
1030 control_file = tools.inject_vars({'args': args}, control_file)
1031
Don Garretta06ea082017-01-13 00:04:26 +00001032 if image is None:
1033 return rpc_utils.create_job_common(
1034 **rpc_utils.get_create_job_common_args(locals()))
1035
1036 # Translate the image name, in case its a relative build name.
1037 ds = dev_server.ImageServer.resolve(image)
1038 image = ds.translate(image)
1039
1040 # When image is supplied use a known parameterized test already in the
1041 # database to pass the OS image path from the front end, through the
1042 # scheduler, and finally to autoserv as the --image parameter.
1043
1044 # The test autoupdate_ParameterizedJob is in afe_autotests and used to
1045 # instantiate a Test object and from there a ParameterizedJob.
1046 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
1047 known_parameterized_job = models.ParameterizedJob.objects.create(
1048 test=known_test_obj)
1049
1050 # autoupdate_ParameterizedJob has a single parameter, the image parameter,
1051 # stored in the table afe_test_parameters. We retrieve and set this
1052 # instance of the parameter to the OS image path.
1053 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
1054 name='image')
1055 known_parameterized_job.parameterizedjobparameter_set.create(
1056 test_parameter=image_parameter, parameter_value=image,
1057 parameter_type='string')
1058
1059 # TODO(crbug.com/502638): save firmware build etc to parameterized_job.
1060
1061 # By passing a parameterized_job to create_job_common the job entry in
1062 # the afe_jobs table will have the field parameterized_job_id set.
1063 # The scheduler uses this id in the afe_parameterized_jobs table to
1064 # match this job to our known test, and then with the
1065 # afe_parameterized_job_parameters table to get the actual image path.
jamesren4a41e012010-07-16 22:33:48 +00001066 return rpc_utils.create_job_common(
Allen Li7c918d82016-12-14 12:26:05 -08001067 **rpc_utils.get_create_job_common_args(dict(
1068 name=name,
1069 priority=priority,
1070 control_type=control_type,
1071 control_file=control_file,
1072 hosts=hosts,
1073 meta_hosts=meta_hosts,
1074 one_time_hosts=one_time_hosts,
1075 atomic_group_name=atomic_group_name,
1076 synch_count=synch_count,
1077 is_template=is_template,
1078 timeout=timeout,
1079 timeout_mins=timeout_mins,
1080 max_runtime_mins=max_runtime_mins,
1081 run_verify=run_verify,
1082 email_list=email_list,
1083 dependencies=dependencies,
1084 reboot_before=reboot_before,
1085 reboot_after=reboot_after,
1086 parse_failed_repair=parse_failed_repair,
1087 hostless=hostless,
1088 keyvals=keyvals,
1089 drone_set=drone_set,
1090 parameterized_job=known_parameterized_job.id,
1091 parent_job_id=parent_job_id,
1092 test_retry=test_retry,
1093 run_reset=run_reset,
1094 require_ssp=require_ssp)))
mblighe8819cd2008-02-15 16:48:40 +00001095
1096
showard9dbdcda2008-10-14 17:34:36 +00001097def abort_host_queue_entries(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001098 """\
showard9dbdcda2008-10-14 17:34:36 +00001099 Abort a set of host queue entries.
Fang Deng63b0e452014-12-19 14:38:15 -08001100
1101 @return: A list of dictionaries, each contains information
1102 about an aborted HQE.
jadmanski0afbb632008-06-06 21:10:57 +00001103 """
showard9dbdcda2008-10-14 17:34:36 +00001104 query = models.HostQueueEntry.query_objects(filter_data)
beepsfaecbce2013-10-29 11:35:10 -07001105
1106 # Dont allow aborts on:
1107 # 1. Jobs that have already completed (whether or not they were aborted)
1108 # 2. Jobs that we have already been aborted (but may not have completed)
1109 query = query.filter(complete=False).filter(aborted=False)
showarddc817512008-11-12 18:16:41 +00001110 models.AclGroup.check_abort_permissions(query)
showard9dbdcda2008-10-14 17:34:36 +00001111 host_queue_entries = list(query.select_related())
showard2bab8f42008-11-12 18:15:22 +00001112 rpc_utils.check_abort_synchronous_jobs(host_queue_entries)
mblighe8819cd2008-02-15 16:48:40 +00001113
Simran Basic1b26762013-06-26 14:23:21 -07001114 models.HostQueueEntry.abort_host_queue_entries(host_queue_entries)
Fang Deng63b0e452014-12-19 14:38:15 -08001115 hqe_info = [{'HostQueueEntry': hqe.id, 'Job': hqe.job_id,
1116 'Job name': hqe.job.name} for hqe in host_queue_entries]
1117 return hqe_info
showard9d821ab2008-07-11 16:54:29 +00001118
1119
beeps8bb1f7d2013-08-05 01:30:09 -07001120def abort_special_tasks(**filter_data):
1121 """\
1122 Abort the special task, or tasks, specified in the filter.
1123 """
1124 query = models.SpecialTask.query_objects(filter_data)
1125 special_tasks = query.filter(is_active=True)
1126 for task in special_tasks:
1127 task.abort()
1128
1129
Simran Basi73dae552013-02-25 14:57:46 -08001130def _call_special_tasks_on_hosts(task, hosts):
1131 """\
1132 Schedules a set of hosts for a special task.
1133
1134 @returns A list of hostnames that a special task was created for.
1135 """
1136 models.AclGroup.check_for_acl_violation_hosts(hosts)
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -08001137 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -08001138 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -08001139 raise ValueError('The following hosts are on shards, please '
1140 'follow the link to the shards and create jobs '
1141 'there instead. %s.' % shard_host_map)
Simran Basi73dae552013-02-25 14:57:46 -08001142 for host in hosts:
1143 models.SpecialTask.schedule_special_task(host, task)
1144 return list(sorted(host.hostname for host in hosts))
1145
1146
MK Ryu5aa25042015-07-28 16:08:04 -07001147def _forward_special_tasks_on_hosts(task, rpc, **filter_data):
1148 """Forward special tasks to corresponding shards.
mbligh4e545a52009-12-19 05:30:39 +00001149
MK Ryu5aa25042015-07-28 16:08:04 -07001150 For master, when special tasks are fired on hosts that are sharded,
1151 forward the RPC to corresponding shards.
1152
1153 For shard, create special task records in local DB.
1154
1155 @param task: Enum value of frontend.afe.models.SpecialTask.Task
1156 @param rpc: RPC name to forward.
1157 @param filter_data: Filter keywords to be used for DB query.
1158
1159 @return: A list of hostnames that a special task was created for.
showard1ff7b2e2009-05-15 23:17:18 +00001160 """
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001161 hosts = models.Host.query_objects(filter_data)
1162 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts, rpc_hostnames=True)
1163
1164 # Filter out hosts on a shard from those on the master, forward
1165 # rpcs to the shard with an additional hostname__in filter, and
1166 # create a local SpecialTask for each remaining host.
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -08001167 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001168 hosts = [h for h in hosts if h.shard is None]
1169 for shard, hostnames in shard_host_map.iteritems():
1170
1171 # The main client of this module is the frontend website, and
1172 # it invokes it with an 'id' or an 'id__in' filter. Regardless,
1173 # the 'hostname' filter should narrow down the list of hosts on
1174 # each shard even though we supply all the ids in filter_data.
1175 # This method uses hostname instead of id because it fits better
MK Ryu5aa25042015-07-28 16:08:04 -07001176 # with the overall architecture of redirection functions in
1177 # rpc_utils.
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001178 shard_filter = filter_data.copy()
1179 shard_filter['hostname__in'] = hostnames
1180 rpc_utils.run_rpc_on_multiple_hostnames(
MK Ryu5aa25042015-07-28 16:08:04 -07001181 rpc, [shard], **shard_filter)
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001182
1183 # There is a race condition here if someone assigns a shard to one of these
1184 # hosts before we create the task. The host will stay on the master if:
1185 # 1. The host is not Ready
1186 # 2. The host is Ready but has a task
1187 # But if the host is Ready and doesn't have a task yet, it will get sent
1188 # to the shard as we're creating a task here.
1189
1190 # Given that we only rarely verify Ready hosts it isn't worth putting this
1191 # entire method in a transaction. The worst case scenario is that we have
MK Ryu5aa25042015-07-28 16:08:04 -07001192 # a verify running on a Ready host while the shard is using it, if the
1193 # verify fails no subsequent tasks will be created against the host on the
1194 # master, and verifies are safe enough that this is OK.
1195 return _call_special_tasks_on_hosts(task, hosts)
1196
1197
1198def reverify_hosts(**filter_data):
1199 """\
1200 Schedules a set of hosts for verify.
1201
1202 @returns A list of hostnames that a verify task was created for.
1203 """
1204 return _forward_special_tasks_on_hosts(
1205 models.SpecialTask.Task.VERIFY, 'reverify_hosts', **filter_data)
Simran Basi73dae552013-02-25 14:57:46 -08001206
1207
1208def repair_hosts(**filter_data):
1209 """\
1210 Schedules a set of hosts for repair.
1211
1212 @returns A list of hostnames that a repair task was created for.
1213 """
MK Ryu5aa25042015-07-28 16:08:04 -07001214 return _forward_special_tasks_on_hosts(
1215 models.SpecialTask.Task.REPAIR, 'repair_hosts', **filter_data)
showard1ff7b2e2009-05-15 23:17:18 +00001216
1217
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001218def get_jobs(not_yet_run=False, running=False, finished=False,
1219 suite=False, sub=False, standalone=False, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001220 """\
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001221 Extra status filter args for get_jobs:
jadmanski0afbb632008-06-06 21:10:57 +00001222 -not_yet_run: Include only jobs that have not yet started running.
1223 -running: Include only jobs that have start running but for which not
1224 all hosts have completed.
1225 -finished: Include only jobs for which all hosts have completed (or
1226 aborted).
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001227
1228 Extra type filter args for get_jobs:
1229 -suite: Include only jobs with child jobs.
1230 -sub: Include only jobs with a parent job.
1231 -standalone: Inlcude only jobs with no child or parent jobs.
1232 At most one of these three fields should be specified.
jadmanski0afbb632008-06-06 21:10:57 +00001233 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001234 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
1235 running,
1236 finished)
1237 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
1238 suite,
1239 sub,
1240 standalone)
showard0957a842009-05-11 19:25:08 +00001241 job_dicts = []
1242 jobs = list(models.Job.query_objects(filter_data))
1243 models.Job.objects.populate_relationships(jobs, models.Label,
1244 'dependencies')
showardc1a98d12010-01-15 00:22:22 +00001245 models.Job.objects.populate_relationships(jobs, models.JobKeyval, 'keyvals')
showard0957a842009-05-11 19:25:08 +00001246 for job in jobs:
1247 job_dict = job.get_object_dict()
1248 job_dict['dependencies'] = ','.join(label.name
1249 for label in job.dependencies)
showardc1a98d12010-01-15 00:22:22 +00001250 job_dict['keyvals'] = dict((keyval.key, keyval.value)
1251 for keyval in job.keyvals)
Eric Lid23bc192011-02-09 14:38:57 -08001252 if job.parameterized_job:
1253 job_dict['image'] = get_parameterized_autoupdate_image_url(job)
showard0957a842009-05-11 19:25:08 +00001254 job_dicts.append(job_dict)
1255 return rpc_utils.prepare_for_serialization(job_dicts)
mblighe8819cd2008-02-15 16:48:40 +00001256
1257
1258def get_num_jobs(not_yet_run=False, running=False, finished=False,
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001259 suite=False, sub=False, standalone=False,
jadmanski0afbb632008-06-06 21:10:57 +00001260 **filter_data):
Aviv Keshet17660a52016-04-06 18:56:43 +00001261 """\
1262 See get_jobs() for documentation of extra filter parameters.
jadmanski0afbb632008-06-06 21:10:57 +00001263 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001264 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
1265 running,
1266 finished)
1267 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
1268 suite,
1269 sub,
1270 standalone)
Aviv Keshet17660a52016-04-06 18:56:43 +00001271 return models.Job.query_count(filter_data)
mblighe8819cd2008-02-15 16:48:40 +00001272
1273
mblighe8819cd2008-02-15 16:48:40 +00001274def get_jobs_summary(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001275 """\
Jiaxi Luoaac54572014-06-04 13:57:02 -07001276 Like get_jobs(), but adds 'status_counts' and 'result_counts' field.
1277
1278 'status_counts' filed is a dictionary mapping status strings to the number
1279 of hosts currently with that status, i.e. {'Queued' : 4, 'Running' : 2}.
1280
1281 'result_counts' field is piped to tko's rpc_interface and has the return
1282 format specified under get_group_counts.
jadmanski0afbb632008-06-06 21:10:57 +00001283 """
1284 jobs = get_jobs(**filter_data)
1285 ids = [job['id'] for job in jobs]
1286 all_status_counts = models.Job.objects.get_status_counts(ids)
1287 for job in jobs:
1288 job['status_counts'] = all_status_counts[job['id']]
Jiaxi Luoaac54572014-06-04 13:57:02 -07001289 job['result_counts'] = tko_rpc_interface.get_status_counts(
1290 ['afe_job_id', 'afe_job_id'],
1291 header_groups=[['afe_job_id'], ['afe_job_id']],
1292 **{'afe_job_id': job['id']})
jadmanski0afbb632008-06-06 21:10:57 +00001293 return rpc_utils.prepare_for_serialization(jobs)
mblighe8819cd2008-02-15 16:48:40 +00001294
1295
showarda965cef2009-05-15 23:17:41 +00001296def get_info_for_clone(id, preserve_metahosts, queue_entry_filter_data=None):
showarda8709c52008-07-03 19:44:54 +00001297 """\
1298 Retrieves all the information needed to clone a job.
1299 """
showarda8709c52008-07-03 19:44:54 +00001300 job = models.Job.objects.get(id=id)
showard29f7cd22009-04-29 21:16:24 +00001301 job_info = rpc_utils.get_job_info(job,
showarda965cef2009-05-15 23:17:41 +00001302 preserve_metahosts,
1303 queue_entry_filter_data)
showard945072f2008-09-03 20:34:59 +00001304
showardd9992fe2008-07-31 02:15:03 +00001305 host_dicts = []
showard29f7cd22009-04-29 21:16:24 +00001306 for host in job_info['hosts']:
1307 host_dict = get_hosts(id=host.id)[0]
1308 other_labels = host_dict['labels']
1309 if host_dict['platform']:
1310 other_labels.remove(host_dict['platform'])
1311 host_dict['other_labels'] = ', '.join(other_labels)
showardd9992fe2008-07-31 02:15:03 +00001312 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001313
showard29f7cd22009-04-29 21:16:24 +00001314 for host in job_info['one_time_hosts']:
1315 host_dict = dict(hostname=host.hostname,
1316 id=host.id,
1317 platform='(one-time host)',
1318 locked_text='')
1319 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001320
showard4d077562009-05-08 18:24:36 +00001321 # convert keys from Label objects to strings (names of labels)
showard29f7cd22009-04-29 21:16:24 +00001322 meta_host_counts = dict((meta_host.name, count) for meta_host, count
showard4d077562009-05-08 18:24:36 +00001323 in job_info['meta_host_counts'].iteritems())
showard29f7cd22009-04-29 21:16:24 +00001324
1325 info = dict(job=job.get_object_dict(),
1326 meta_host_counts=meta_host_counts,
1327 hosts=host_dicts)
1328 info['job']['dependencies'] = job_info['dependencies']
1329 if job_info['atomic_group']:
1330 info['atomic_group_name'] = (job_info['atomic_group']).name
1331 else:
1332 info['atomic_group_name'] = None
jamesren2275ef12010-04-12 18:25:06 +00001333 info['hostless'] = job_info['hostless']
jamesren76fcf192010-04-21 20:39:50 +00001334 info['drone_set'] = job.drone_set and job.drone_set.name
showarda8709c52008-07-03 19:44:54 +00001335
Michael Tang6dc174e2016-05-31 23:13:42 -07001336 image = _get_image_for_job(job, job_info['hostless'])
1337 if image:
1338 info['job']['image'] = image
Eric Lid23bc192011-02-09 14:38:57 -08001339
showarda8709c52008-07-03 19:44:54 +00001340 return rpc_utils.prepare_for_serialization(info)
1341
1342
Michael Tang6dc174e2016-05-31 23:13:42 -07001343def _get_image_for_job(job, hostless):
1344 """ Gets the image used for a job.
1345
1346 Gets the image used for an AFE job. If the job is a parameterized job, get
1347 the image from the job parameter; otherwise, tries to get the image from
1348 the job's keyvals 'build' or 'builds'. As a last resort, if the job is a
1349 hostless job, tries to get the image from its control file attributes
1350 'build' or 'builds'.
1351
1352 TODO(ntang): Needs to handle FAFT with two builds for ro/rw.
1353
1354 @param job An AFE job object.
1355 @param hostless Boolean on of the job is hostless.
1356
1357 @returns The image build used for the job.
1358 """
1359 image = None
1360 if job.parameterized_job:
1361 image = get_parameterized_autoupdate_image_url(job)
1362 else:
1363 keyvals = job.keyval_dict()
Michael Tang84a2ecf2016-06-07 15:10:53 -07001364 image = keyvals.get('build')
Michael Tang6dc174e2016-05-31 23:13:42 -07001365 if not image:
1366 value = keyvals.get('builds')
1367 builds = None
1368 if isinstance(value, dict):
1369 builds = value
1370 elif isinstance(value, basestring):
1371 builds = ast.literal_eval(value)
1372 if builds:
1373 image = builds.get('cros-version')
1374 if not image and hostless and job.control_file:
1375 try:
1376 control_obj = control_data.parse_control_string(
1377 job.control_file)
1378 if hasattr(control_obj, 'build'):
1379 image = getattr(control_obj, 'build')
1380 if not image and hasattr(control_obj, 'builds'):
1381 builds = getattr(control_obj, 'builds')
1382 image = builds.get('cros-version')
1383 except:
1384 logging.warning('Failed to parse control file for job: %s',
1385 job.name)
1386 return image
1387
showard34dc5fa2008-04-24 20:58:40 +00001388
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001389def get_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001390 """\
showardc92da832009-04-07 18:14:34 +00001391 @returns A sequence of nested dictionaries of host and job information.
jadmanski0afbb632008-06-06 21:10:57 +00001392 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001393 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1394 'started_on__lte',
1395 start_time,
1396 end_time,
1397 **filter_data)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001398 return rpc_utils.prepare_rows_as_nested_dicts(
1399 models.HostQueueEntry.query_objects(filter_data),
1400 ('host', 'atomic_group', 'job'))
showard34dc5fa2008-04-24 20:58:40 +00001401
1402
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001403def get_num_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001404 """\
1405 Get the number of host queue entries associated with this job.
1406 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001407 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1408 'started_on__lte',
1409 start_time,
1410 end_time,
1411 **filter_data)
jadmanski0afbb632008-06-06 21:10:57 +00001412 return models.HostQueueEntry.query_count(filter_data)
showard34dc5fa2008-04-24 20:58:40 +00001413
1414
showard1e935f12008-07-11 00:11:36 +00001415def get_hqe_percentage_complete(**filter_data):
1416 """
showardc92da832009-04-07 18:14:34 +00001417 Computes the fraction of host queue entries matching the given filter data
showard1e935f12008-07-11 00:11:36 +00001418 that are complete.
1419 """
1420 query = models.HostQueueEntry.query_objects(filter_data)
1421 complete_count = query.filter(complete=True).count()
1422 total_count = query.count()
1423 if total_count == 0:
1424 return 1
1425 return float(complete_count) / total_count
1426
1427
showard1a5a4082009-07-28 20:01:37 +00001428# special tasks
1429
1430def get_special_tasks(**filter_data):
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001431 """Get special task entries from the local database.
1432
1433 Query the special tasks table for tasks matching the given
1434 `filter_data`, and return a list of the results. No attempt is
1435 made to forward the call to shards; the buck will stop here.
1436 The caller is expected to know the target shard for such reasons
1437 as:
1438 * The caller is a service (such as gs_offloader) configured
1439 to operate on behalf of one specific shard, and no other.
1440 * The caller has a host as a parameter, and knows that this is
1441 the shard assigned to that host.
1442
1443 @param filter_data Filter keywords to pass to the underlying
1444 database query.
1445
1446 """
J. Richard Barnettefdfcd662015-04-13 17:20:29 -07001447 return rpc_utils.prepare_rows_as_nested_dicts(
1448 models.SpecialTask.query_objects(filter_data),
1449 ('host', 'queue_entry'))
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001450
1451
1452def get_host_special_tasks(host_id, **filter_data):
1453 """Get special task entries for a given host.
1454
1455 Query the special tasks table for tasks that ran on the host
1456 given by `host_id` and matching the given `filter_data`.
1457 Return a list of the results. If the host is assigned to a
1458 shard, forward this call to that shard.
1459
1460 @param host_id Id in the database of the target host.
1461 @param filter_data Filter keywords to pass to the underlying
1462 database query.
1463
1464 """
MK Ryu0c1a37d2015-04-30 12:00:55 -07001465 # Retrieve host data even if the host is in an invalid state.
1466 host = models.Host.smart_get(host_id, False)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001467 if not host.shard:
J. Richard Barnettefdfcd662015-04-13 17:20:29 -07001468 return get_special_tasks(host_id=host_id, **filter_data)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001469 else:
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001470 # The return values from AFE methods are post-processed
1471 # objects that aren't JSON-serializable. So, we have to
1472 # call AFE.run() to get the raw, serializable output from
1473 # the shard.
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001474 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1475 return shard_afe.run('get_special_tasks',
1476 host_id=host_id, **filter_data)
showard1a5a4082009-07-28 20:01:37 +00001477
1478
MK Ryu0c1a37d2015-04-30 12:00:55 -07001479def get_num_special_tasks(**kwargs):
1480 """Get the number of special task entries from the local database.
1481
1482 Query the special tasks table for tasks matching the given 'kwargs',
1483 and return the number of the results. No attempt is made to forward
1484 the call to shards; the buck will stop here.
1485
1486 @param kwargs Filter keywords to pass to the underlying database query.
1487
1488 """
1489 return models.SpecialTask.query_count(kwargs)
1490
1491
1492def get_host_num_special_tasks(host, **kwargs):
1493 """Get special task entries for a given host.
1494
1495 Query the special tasks table for tasks that ran on the host
1496 given by 'host' and matching the given 'kwargs'.
1497 Return a list of the results. If the host is assigned to a
1498 shard, forward this call to that shard.
1499
1500 @param host id or name of a host. More often a hostname.
1501 @param kwargs Filter keywords to pass to the underlying database query.
1502
1503 """
1504 # Retrieve host data even if the host is in an invalid state.
1505 host_model = models.Host.smart_get(host, False)
1506 if not host_model.shard:
1507 return get_num_special_tasks(host=host, **kwargs)
1508 else:
1509 shard_afe = frontend.AFE(server=host_model.shard.rpc_hostname())
1510 return shard_afe.run('get_num_special_tasks', host=host, **kwargs)
1511
1512
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001513def get_status_task(host_id, end_time):
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001514 """Get the "status task" for a host from the local shard.
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001515
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001516 Returns a single special task representing the given host's
1517 "status task". The status task is a completed special task that
1518 identifies whether the corresponding host was working or broken
1519 when it completed. A successful task indicates a working host;
1520 a failed task indicates broken.
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001521
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001522 This call will not be forward to a shard; the receiving server
1523 must be the shard that owns the host.
1524
1525 @param host_id Id in the database of the target host.
1526 @param end_time Time reference for the host's status.
1527
1528 @return A single task; its status (successful or not)
1529 corresponds to the status of the host (working or
1530 broken) at the given time. If no task is found, return
1531 `None`.
1532
1533 """
1534 tasklist = rpc_utils.prepare_rows_as_nested_dicts(
1535 status_history.get_status_task(host_id, end_time),
1536 ('host', 'queue_entry'))
1537 return tasklist[0] if tasklist else None
1538
1539
1540def get_host_status_task(host_id, end_time):
1541 """Get the "status task" for a host from its owning shard.
1542
1543 Finds the given host's owning shard, and forwards to it a call
1544 to `get_status_task()` (see above).
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001545
1546 @param host_id Id in the database of the target host.
1547 @param end_time Time reference for the host's status.
1548
1549 @return A single task; its status (successful or not)
1550 corresponds to the status of the host (working or
1551 broken) at the given time. If no task is found, return
1552 `None`.
1553
1554 """
1555 host = models.Host.smart_get(host_id)
1556 if not host.shard:
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001557 return get_status_task(host_id, end_time)
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001558 else:
1559 # The return values from AFE methods are post-processed
1560 # objects that aren't JSON-serializable. So, we have to
1561 # call AFE.run() to get the raw, serializable output from
1562 # the shard.
1563 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1564 return shard_afe.run('get_status_task',
1565 host_id=host_id, end_time=end_time)
1566
1567
J. Richard Barnette8abbfd62015-06-23 12:46:54 -07001568def get_host_diagnosis_interval(host_id, end_time, success):
1569 """Find a "diagnosis interval" for a given host.
1570
1571 A "diagnosis interval" identifies a start and end time where
1572 the host went from "working" to "broken", or vice versa. The
1573 interval's starting time is the starting time of the last status
1574 task with the old status; the end time is the finish time of the
1575 first status task with the new status.
1576
1577 This routine finds the most recent diagnosis interval for the
1578 given host prior to `end_time`, with a starting status matching
1579 `success`. If `success` is true, the interval will start with a
1580 successful status task; if false the interval will start with a
1581 failed status task.
1582
1583 @param host_id Id in the database of the target host.
1584 @param end_time Time reference for the diagnosis interval.
1585 @param success Whether the diagnosis interval should start
1586 with a successful or failed status task.
1587
1588 @return A list of two strings. The first is the timestamp for
1589 the beginning of the interval; the second is the
1590 timestamp for the end. If the host has never changed
1591 state, the list is empty.
1592
1593 """
1594 host = models.Host.smart_get(host_id)
J. Richard Barnette78f281a2015-06-29 13:24:51 -07001595 if not host.shard or utils.is_shard():
J. Richard Barnette8abbfd62015-06-23 12:46:54 -07001596 return status_history.get_diagnosis_interval(
1597 host_id, end_time, success)
1598 else:
1599 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1600 return shard_afe.get_host_diagnosis_interval(
1601 host_id, end_time, success)
1602
1603
showardc0ac3a72009-07-08 21:14:45 +00001604# support for host detail view
1605
MK Ryu0c1a37d2015-04-30 12:00:55 -07001606def get_host_queue_entries_and_special_tasks(host, query_start=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001607 query_limit=None, start_time=None,
1608 end_time=None):
showardc0ac3a72009-07-08 21:14:45 +00001609 """
1610 @returns an interleaved list of HostQueueEntries and SpecialTasks,
1611 in approximate run order. each dict contains keys for type, host,
1612 job, status, started_on, execution_path, and ID.
1613 """
1614 total_limit = None
1615 if query_limit is not None:
1616 total_limit = query_start + query_limit
MK Ryu0c1a37d2015-04-30 12:00:55 -07001617 filter_data_common = {'host': host,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001618 'query_limit': total_limit,
1619 'sort_by': ['-id']}
showardc0ac3a72009-07-08 21:14:45 +00001620
MK Ryu0c1a37d2015-04-30 12:00:55 -07001621 filter_data_special_tasks = rpc_utils.inject_times_to_filter(
1622 'time_started__gte', 'time_started__lte', start_time, end_time,
1623 **filter_data_common)
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001624
MK Ryu0c1a37d2015-04-30 12:00:55 -07001625 queue_entries = get_host_queue_entries(
1626 start_time, end_time, **filter_data_common)
1627 special_tasks = get_host_special_tasks(host, **filter_data_special_tasks)
showardc0ac3a72009-07-08 21:14:45 +00001628
1629 interleaved_entries = rpc_utils.interleave_entries(queue_entries,
1630 special_tasks)
1631 if query_start is not None:
1632 interleaved_entries = interleaved_entries[query_start:]
1633 if query_limit is not None:
1634 interleaved_entries = interleaved_entries[:query_limit]
MK Ryu0c1a37d2015-04-30 12:00:55 -07001635 return rpc_utils.prepare_host_queue_entries_and_special_tasks(
1636 interleaved_entries, queue_entries)
showardc0ac3a72009-07-08 21:14:45 +00001637
1638
MK Ryu0c1a37d2015-04-30 12:00:55 -07001639def get_num_host_queue_entries_and_special_tasks(host, start_time=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001640 end_time=None):
MK Ryu0c1a37d2015-04-30 12:00:55 -07001641 filter_data_common = {'host': host}
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001642
1643 filter_data_queue_entries, filter_data_special_tasks = (
1644 rpc_utils.inject_times_to_hqe_special_tasks_filters(
1645 filter_data_common, start_time, end_time))
1646
1647 return (models.HostQueueEntry.query_count(filter_data_queue_entries)
MK Ryu0c1a37d2015-04-30 12:00:55 -07001648 + get_host_num_special_tasks(**filter_data_special_tasks))
showardc0ac3a72009-07-08 21:14:45 +00001649
1650
showard29f7cd22009-04-29 21:16:24 +00001651# recurring run
1652
1653def get_recurring(**filter_data):
1654 return rpc_utils.prepare_rows_as_nested_dicts(
1655 models.RecurringRun.query_objects(filter_data),
1656 ('job', 'owner'))
1657
1658
1659def get_num_recurring(**filter_data):
1660 return models.RecurringRun.query_count(filter_data)
1661
1662
1663def delete_recurring_runs(**filter_data):
1664 to_delete = models.RecurringRun.query_objects(filter_data)
1665 to_delete.delete()
1666
1667
1668def create_recurring_run(job_id, start_date, loop_period, loop_count):
showard64a95952010-01-13 21:27:16 +00001669 owner = models.User.current_user().login
showard29f7cd22009-04-29 21:16:24 +00001670 job = models.Job.objects.get(id=job_id)
1671 return job.create_recurring_job(start_date=start_date,
1672 loop_period=loop_period,
1673 loop_count=loop_count,
1674 owner=owner)
1675
1676
mblighe8819cd2008-02-15 16:48:40 +00001677# other
1678
showarde0b63622008-08-04 20:58:47 +00001679def echo(data=""):
1680 """\
1681 Returns a passed in string. For doing a basic test to see if RPC calls
1682 can successfully be made.
1683 """
1684 return data
1685
1686
showardb7a52fd2009-04-27 20:10:56 +00001687def get_motd():
1688 """\
1689 Returns the message of the day as a string.
1690 """
1691 return rpc_utils.get_motd()
1692
1693
mblighe8819cd2008-02-15 16:48:40 +00001694def get_static_data():
jadmanski0afbb632008-06-06 21:10:57 +00001695 """\
1696 Returns a dictionary containing a bunch of data that shouldn't change
1697 often and is otherwise inaccessible. This includes:
showardc92da832009-04-07 18:14:34 +00001698
1699 priorities: List of job priority choices.
1700 default_priority: Default priority value for new jobs.
1701 users: Sorted list of all users.
Jiaxi Luo31874592014-06-11 10:36:35 -07001702 labels: Sorted list of labels not start with 'cros-version' and
1703 'fw-version'.
showardc92da832009-04-07 18:14:34 +00001704 atomic_groups: Sorted list of all atomic groups.
1705 tests: Sorted list of all tests.
1706 profilers: Sorted list of all profilers.
1707 current_user: Logged-in username.
1708 host_statuses: Sorted list of possible Host statuses.
1709 job_statuses: Sorted list of possible HostQueueEntry statuses.
Simran Basi7e605742013-11-12 13:43:36 -08001710 job_timeout_default: The default job timeout length in minutes.
showarda1e74b32009-05-12 17:32:04 +00001711 parse_failed_repair_default: Default value for the parse_failed_repair job
Jiaxi Luo31874592014-06-11 10:36:35 -07001712 option.
showardc92da832009-04-07 18:14:34 +00001713 reboot_before_options: A list of valid RebootBefore string enums.
1714 reboot_after_options: A list of valid RebootAfter string enums.
1715 motd: Server's message of the day.
1716 status_dictionary: A mapping from one word job status names to a more
1717 informative description.
jadmanski0afbb632008-06-06 21:10:57 +00001718 """
showard21baa452008-10-21 00:08:39 +00001719
1720 job_fields = models.Job.get_field_dict()
jamesren76fcf192010-04-21 20:39:50 +00001721 default_drone_set_name = models.DroneSet.default_drone_set_name()
1722 drone_sets = ([default_drone_set_name] +
1723 sorted(drone_set.name for drone_set in
1724 models.DroneSet.objects.exclude(
1725 name=default_drone_set_name)))
showard21baa452008-10-21 00:08:39 +00001726
jadmanski0afbb632008-06-06 21:10:57 +00001727 result = {}
Alex Miller7d658cf2013-09-04 16:00:35 -07001728 result['priorities'] = priorities.Priority.choices()
1729 default_priority = priorities.Priority.DEFAULT
1730 result['default_priority'] = 'Default'
1731 result['max_schedulable_priority'] = priorities.Priority.DEFAULT
jadmanski0afbb632008-06-06 21:10:57 +00001732 result['users'] = get_users(sort_by=['login'])
Jiaxi Luo31874592014-06-11 10:36:35 -07001733
1734 label_exclude_filters = [{'name__startswith': 'cros-version'},
Dan Shi65351d62015-08-03 12:03:23 -07001735 {'name__startswith': 'fw-version'},
1736 {'name__startswith': 'fwrw-version'},
Dan Shi27516972016-03-16 14:03:41 -07001737 {'name__startswith': 'fwro-version'},
1738 {'name__startswith': 'ab-version'},
1739 {'name__startswith': 'testbed-version'}]
Jiaxi Luo31874592014-06-11 10:36:35 -07001740 result['labels'] = get_labels(
1741 label_exclude_filters,
1742 sort_by=['-platform', 'name'])
1743
showardc92da832009-04-07 18:14:34 +00001744 result['atomic_groups'] = get_atomic_groups(sort_by=['name'])
jadmanski0afbb632008-06-06 21:10:57 +00001745 result['tests'] = get_tests(sort_by=['name'])
showard2b9a88b2008-06-13 20:55:03 +00001746 result['profilers'] = get_profilers(sort_by=['name'])
showard0fc38302008-10-23 00:44:07 +00001747 result['current_user'] = rpc_utils.prepare_for_serialization(
showard64a95952010-01-13 21:27:16 +00001748 models.User.current_user().get_object_dict())
showard2b9a88b2008-06-13 20:55:03 +00001749 result['host_statuses'] = sorted(models.Host.Status.names)
mbligh5a198b92008-12-11 19:33:29 +00001750 result['job_statuses'] = sorted(models.HostQueueEntry.Status.names)
Simran Basi7e605742013-11-12 13:43:36 -08001751 result['job_timeout_mins_default'] = models.Job.DEFAULT_TIMEOUT_MINS
Simran Basi34217022012-11-06 13:43:15 -08001752 result['job_max_runtime_mins_default'] = (
1753 models.Job.DEFAULT_MAX_RUNTIME_MINS)
showarda1e74b32009-05-12 17:32:04 +00001754 result['parse_failed_repair_default'] = bool(
1755 models.Job.DEFAULT_PARSE_FAILED_REPAIR)
jamesrendd855242010-03-02 22:23:44 +00001756 result['reboot_before_options'] = model_attributes.RebootBefore.names
1757 result['reboot_after_options'] = model_attributes.RebootAfter.names
showard8fbae652009-01-20 23:23:10 +00001758 result['motd'] = rpc_utils.get_motd()
jamesren76fcf192010-04-21 20:39:50 +00001759 result['drone_sets_enabled'] = models.DroneSet.drone_sets_enabled()
1760 result['drone_sets'] = drone_sets
jamesren4a41e012010-07-16 22:33:48 +00001761 result['parameterized_jobs'] = models.Job.parameterized_jobs_enabled()
showard8ac29b42008-07-17 17:01:55 +00001762
showardd3dc1992009-04-22 21:01:40 +00001763 result['status_dictionary'] = {"Aborted": "Aborted",
showard8ac29b42008-07-17 17:01:55 +00001764 "Verifying": "Verifying Host",
Alex Millerdfff2fd2013-05-28 13:05:06 -07001765 "Provisioning": "Provisioning Host",
showard8ac29b42008-07-17 17:01:55 +00001766 "Pending": "Waiting on other hosts",
1767 "Running": "Running autoserv",
1768 "Completed": "Autoserv completed",
1769 "Failed": "Failed to complete",
showardd823b362008-07-24 16:35:46 +00001770 "Queued": "Queued",
showard5deb6772008-11-04 21:54:33 +00001771 "Starting": "Next in host's queue",
1772 "Stopped": "Other host(s) failed verify",
showardd3dc1992009-04-22 21:01:40 +00001773 "Parsing": "Awaiting parse of final results",
showard29f7cd22009-04-29 21:16:24 +00001774 "Gathering": "Gathering log files",
showard8cc058f2009-09-08 16:26:33 +00001775 "Template": "Template job for recurring run",
mbligh4608b002010-01-05 18:22:35 +00001776 "Waiting": "Waiting for scheduler action",
Dan Shi07e09af2013-04-12 09:31:29 -07001777 "Archiving": "Archiving results",
1778 "Resetting": "Resetting hosts"}
Jiaxi Luo421608e2014-07-07 14:38:00 -07001779
1780 result['wmatrix_url'] = rpc_utils.get_wmatrix_url()
Simran Basi71206ef2014-08-13 13:51:18 -07001781 result['is_moblab'] = bool(utils.is_moblab())
Jiaxi Luo421608e2014-07-07 14:38:00 -07001782
jadmanski0afbb632008-06-06 21:10:57 +00001783 return result
showard29f7cd22009-04-29 21:16:24 +00001784
1785
1786def get_server_time():
1787 return datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
Kevin Cheng19521982016-09-22 12:27:23 -07001788
1789
1790def get_hosts_by_attribute(attribute, value):
1791 """
1792 Get the list of valid hosts that share the same host attribute value.
1793
1794 @param attribute: String of the host attribute to check.
1795 @param value: String of the value that is shared between hosts.
1796
1797 @returns List of hostnames that all have the same host attribute and
1798 value.
1799 """
1800 hosts = models.HostAttribute.query_objects({'attribute': attribute,
1801 'value': value})
1802 return [row.host.hostname for row in hosts if row.host.invalid == 0]