blob: 999cd2201c81631881d64cab38cf6129760a1179 [file] [log] [blame]
Shuqian Zhao54a5b672016-05-11 22:12:17 +00001# pylint: disable-msg=C0111
Aviv Keshet0b9cfc92013-02-05 11:36:02 -08002
mblighe8819cd2008-02-15 16:48:40 +00003"""\
4Functions to expose over the RPC interface.
5
6For all modify* and delete* functions that ask for an 'id' parameter to
7identify the object to operate on, the id may be either
8 * the database row ID
9 * the name of the object (label name, hostname, user login, etc.)
10 * a dictionary containing uniquely identifying field (this option should seldom
11 be used)
12
13When specifying foreign key fields (i.e. adding hosts to a label, or adding
14users to an ACL group), the given value may be either the database row ID or the
15name of the object.
16
17All get* functions return lists of dictionaries. Each dictionary represents one
18object and maps field names to values.
19
20Some examples:
21modify_host(2, hostname='myhost') # modify hostname of host with database ID 2
22modify_host('ipaj2', hostname='myhost') # modify hostname of host 'ipaj2'
23modify_test('sleeptest', test_type='Client', params=', seconds=60')
24delete_acl_group(1) # delete by ID
25delete_acl_group('Everyone') # delete by name
26acl_group_add_users('Everyone', ['mbligh', 'showard'])
27get_jobs(owner='showard', status='Queued')
28
mbligh93c80e62009-02-03 17:48:30 +000029See doctests/001_rpc_test.txt for (lots) more examples.
mblighe8819cd2008-02-15 16:48:40 +000030"""
31
32__author__ = 'showard@google.com (Steve Howard)'
33
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 re
38import sys
MK Ryu9c5fbbe2015-02-11 15:46:22 -080039
Moises Osorio2dc7a102014-12-02 18:24:02 -080040from django.db.models import Count
showardcafd16e2009-05-29 18:37:49 +000041import common
Michael Tang6dc174e2016-05-31 23:13:42 -070042from autotest_lib.client.common_lib import control_data
Simran Basib6ec8ae2014-04-23 12:05:08 -070043from autotest_lib.client.common_lib import priorities
Simran Basi6157e8e2015-12-07 18:22:34 -080044from autotest_lib.client.common_lib.cros import dev_server
Gabe Black1e1c41b2015-02-04 23:55:15 -080045from autotest_lib.client.common_lib.cros.graphite import autotest_stats
showard6d7b2ff2009-06-10 00:16:47 +000046from autotest_lib.frontend.afe import control_file, rpc_utils
J. Richard Barnetteb5164d62015-04-13 12:59:31 -070047from autotest_lib.frontend.afe import models, model_logic, model_attributes
Simran Basib6ec8ae2014-04-23 12:05:08 -070048from autotest_lib.frontend.afe import site_rpc_interface
Moises Osorio2dc7a102014-12-02 18:24:02 -080049from autotest_lib.frontend.tko import models as tko_models
Jiaxi Luoaac54572014-06-04 13:57:02 -070050from autotest_lib.frontend.tko import rpc_interface as tko_rpc_interface
J. Richard Barnetteb5164d62015-04-13 12:59:31 -070051from autotest_lib.server import frontend
Simran Basi71206ef2014-08-13 13:51:18 -070052from autotest_lib.server import utils
Dan Shid215dbe2015-06-18 16:14:59 -070053from autotest_lib.server.cros import provision
Jiaxi Luo90190c92014-06-18 12:35:57 -070054from autotest_lib.server.cros.dynamic_suite import tools
Aviv Keshet7ee95862016-08-30 15:18:27 -070055from autotest_lib.server.lib import status_history
mblighe8819cd2008-02-15 16:48:40 +000056
Moises Osorio2dc7a102014-12-02 18:24:02 -080057
Gabe Black1e1c41b2015-02-04 23:55:15 -080058_timer = autotest_stats.Timer('rpc_interface')
Moises Osorio2dc7a102014-12-02 18:24:02 -080059
Eric Lid23bc192011-02-09 14:38:57 -080060def get_parameterized_autoupdate_image_url(job):
61 """Get the parameterized autoupdate image url from a parameterized job."""
62 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
63 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
beeps8bb1f7d2013-08-05 01:30:09 -070064 name='image')
Eric Lid23bc192011-02-09 14:38:57 -080065 para_set = job.parameterized_job.parameterizedjobparameter_set
66 job_test_para = para_set.get(test_parameter=image_parameter)
67 return job_test_para.parameter_value
68
69
mblighe8819cd2008-02-15 16:48:40 +000070# labels
71
mblighe8819cd2008-02-15 16:48:40 +000072def modify_label(id, **data):
MK Ryu8c554cf2015-06-12 11:45:50 -070073 """Modify a label.
74
75 @param id: id or name of a label. More often a label name.
76 @param data: New data for a label.
77 """
78 label_model = models.Label.smart_get(id)
MK Ryu8e2c2d02016-01-06 15:24:38 -080079 label_model.update_object(data)
MK Ryu8c554cf2015-06-12 11:45:50 -070080
81 # Master forwards the RPC to shards
82 if not utils.is_shard():
83 rpc_utils.fanout_rpc(label_model.host_set.all(), 'modify_label', False,
84 id=id, **data)
85
mblighe8819cd2008-02-15 16:48:40 +000086
87def delete_label(id):
MK Ryu8c554cf2015-06-12 11:45:50 -070088 """Delete a label.
89
90 @param id: id or name of a label. More often a label name.
91 """
92 label_model = models.Label.smart_get(id)
MK Ryu8e2c2d02016-01-06 15:24:38 -080093 # Hosts that have the label to be deleted. Save this info before
94 # the label is deleted to use it later.
95 hosts = []
96 for h in label_model.host_set.all():
97 hosts.append(models.Host.smart_get(h.id))
98 label_model.delete()
MK Ryu8c554cf2015-06-12 11:45:50 -070099
100 # Master forwards the RPC to shards
101 if not utils.is_shard():
MK Ryu8e2c2d02016-01-06 15:24:38 -0800102 rpc_utils.fanout_rpc(hosts, 'delete_label', False, id=id)
mblighe8819cd2008-02-15 16:48:40 +0000103
Prashanth Balasubramanian744898f2015-01-13 05:04:16 -0800104
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800105def add_label(name, ignore_exception_if_exists=False, **kwargs):
MK Ryucf027c62015-03-04 12:00:50 -0800106 """Adds a new label of a given name.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800107
108 @param name: label name.
109 @param ignore_exception_if_exists: If True and the exception was
110 thrown due to the duplicated label name when adding a label,
111 then suppress the exception. Default is False.
112 @param kwargs: keyword args that store more info about a label
113 other than the name.
114 @return: int/long id of a new label.
115 """
116 # models.Label.add_object() throws model_logic.ValidationError
117 # when it is given a label name that already exists.
118 # However, ValidationError can be thrown with different errors,
119 # and those errors should be thrown up to the call chain.
120 try:
121 label = models.Label.add_object(name=name, **kwargs)
122 except:
123 exc_info = sys.exc_info()
124 if ignore_exception_if_exists:
125 label = rpc_utils.get_label(name)
126 # If the exception is raised not because of duplicated
127 # "name", then raise the original exception.
128 if label is None:
129 raise exc_info[0], exc_info[1], exc_info[2]
130 else:
131 raise exc_info[0], exc_info[1], exc_info[2]
132 return label.id
133
134
135def add_label_to_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800136 """Adds a label of the given id to the given hosts only in local DB.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800137
138 @param id: id or name of a label. More often a label name.
139 @param hosts: The hostnames of hosts that need the label.
140
141 @raises models.Label.DoesNotExist: If the label with id doesn't exist.
142 """
143 label = models.Label.smart_get(id)
144 host_objs = models.Host.smart_get_bulk(hosts)
145 if label.platform:
146 models.Host.check_no_platform(host_objs)
Shuqian Zhao40e182b2016-10-11 11:55:11 -0700147 # Ensure a host has no more than one board label with it.
148 if label.name.startswith('board:'):
149 models.Host.check_no_board(host_objs)
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800150 label.host_set.add(*host_objs)
151
152
Kevin Chengbdfc57d2016-04-14 13:46:58 -0700153def _create_label_everywhere(id, hosts):
154 """
155 Yet another method to create labels.
156
157 ALERT! This method should be run only on master not shards!
158 DO NOT RUN THIS ON A SHARD!!! Deputies will hate you if you do!!!
159
160 This method exists primarily to serve label_add_hosts() and
161 host_add_labels(). Basically it pulls out the label check/add logic
162 from label_add_hosts() into this nice method that not only creates
163 the label but also tells the shards that service the hosts to also
164 create the label.
165
166 @param id: id or name of a label. More often a label name.
167 @param hosts: A list of hostnames or ids. More often hostnames.
168 """
169 try:
170 label = models.Label.smart_get(id)
171 except models.Label.DoesNotExist:
172 # This matches the type checks in smart_get, which is a hack
173 # in and off itself. The aim here is to create any non-existent
174 # label, which we cannot do if the 'id' specified isn't a label name.
175 if isinstance(id, basestring):
176 label = models.Label.smart_get(add_label(id))
177 else:
178 raise ValueError('Label id (%s) does not exist. Please specify '
179 'the argument, id, as a string (label name).'
180 % id)
181
182 # Make sure the label exists on the shard with the same id
183 # as it is on the master.
184 # It is possible that the label is already in a shard because
185 # we are adding a new label only to shards of hosts that the label
186 # is going to be attached.
187 # For example, we add a label L1 to a host in shard S1.
188 # Master and S1 will have L1 but other shards won't.
189 # Later, when we add the same label L1 to hosts in shards S1 and S2,
190 # S1 already has the label but S2 doesn't.
191 # S2 should have the new label without any problem.
192 # We ignore exception in such a case.
193 host_objs = models.Host.smart_get_bulk(hosts)
194 rpc_utils.fanout_rpc(
195 host_objs, 'add_label', include_hostnames=False,
196 name=label.name, ignore_exception_if_exists=True,
197 id=label.id, platform=label.platform)
198
199
MK Ryufbb002c2015-06-08 14:13:16 -0700200@rpc_utils.route_rpc_to_master
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800201def label_add_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800202 """Adds a label with the given id to the given hosts.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800203
204 This method should be run only on master not shards.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800205 The given label will be created if it doesn't exist, provided the `id`
206 supplied is a label name not an int/long id.
207
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800208 @param id: id or name of a label. More often a label name.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800209 @param hosts: A list of hostnames or ids. More often hostnames.
210
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800211 @raises ValueError: If the id specified is an int/long (label id)
212 while the label does not exist.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800213 """
Kevin Chengbdfc57d2016-04-14 13:46:58 -0700214 # Create the label.
215 _create_label_everywhere(id, hosts)
216
217 # Add it to the master.
MK Ryu8e2c2d02016-01-06 15:24:38 -0800218 add_label_to_hosts(id, hosts)
MK Ryucf027c62015-03-04 12:00:50 -0800219
Kevin Chengbdfc57d2016-04-14 13:46:58 -0700220 # Add it to the shards.
MK Ryucf027c62015-03-04 12:00:50 -0800221 host_objs = models.Host.smart_get_bulk(hosts)
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800222 rpc_utils.fanout_rpc(host_objs, 'add_label_to_hosts', id=id)
showardbbabf502008-06-06 00:02:02 +0000223
224
MK Ryucf027c62015-03-04 12:00:50 -0800225def remove_label_from_hosts(id, hosts):
226 """Removes a label of the given id from the given hosts only in local DB.
227
228 @param id: id or name of a label.
229 @param hosts: The hostnames of hosts that need to remove the label from.
230 """
showardbe3ec042008-11-12 18:16:07 +0000231 host_objs = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000232 models.Label.smart_get(id).host_set.remove(*host_objs)
showardbbabf502008-06-06 00:02:02 +0000233
234
MK Ryufbb002c2015-06-08 14:13:16 -0700235@rpc_utils.route_rpc_to_master
MK Ryucf027c62015-03-04 12:00:50 -0800236def label_remove_hosts(id, hosts):
237 """Removes a label of the given id from the given hosts.
238
239 This method should be run only on master not shards.
240
241 @param id: id or name of a label.
242 @param hosts: A list of hostnames or ids. More often hostnames.
243 """
MK Ryucf027c62015-03-04 12:00:50 -0800244 host_objs = models.Host.smart_get_bulk(hosts)
MK Ryu26f0c932015-05-28 18:14:33 -0700245 remove_label_from_hosts(id, hosts)
246
MK Ryu8e2c2d02016-01-06 15:24:38 -0800247 rpc_utils.fanout_rpc(host_objs, 'remove_label_from_hosts', id=id)
248
MK Ryucf027c62015-03-04 12:00:50 -0800249
Jiaxi Luo31874592014-06-11 10:36:35 -0700250def get_labels(exclude_filters=(), **filter_data):
showardc92da832009-04-07 18:14:34 +0000251 """\
Jiaxi Luo31874592014-06-11 10:36:35 -0700252 @param exclude_filters: A sequence of dictionaries of filters.
253
showardc92da832009-04-07 18:14:34 +0000254 @returns A sequence of nested dictionaries of label information.
255 """
Jiaxi Luo31874592014-06-11 10:36:35 -0700256 labels = models.Label.query_objects(filter_data)
257 for exclude_filter in exclude_filters:
258 labels = labels.exclude(**exclude_filter)
259 return rpc_utils.prepare_rows_as_nested_dicts(labels, ('atomic_group',))
showardc92da832009-04-07 18:14:34 +0000260
261
262# atomic groups
263
showarde9450c92009-06-30 01:58:52 +0000264def add_atomic_group(name, max_number_of_machines=None, description=None):
showardc92da832009-04-07 18:14:34 +0000265 return models.AtomicGroup.add_object(
266 name=name, max_number_of_machines=max_number_of_machines,
267 description=description).id
268
269
270def modify_atomic_group(id, **data):
271 models.AtomicGroup.smart_get(id).update_object(data)
272
273
274def delete_atomic_group(id):
275 models.AtomicGroup.smart_get(id).delete()
276
277
278def atomic_group_add_labels(id, labels):
279 label_objs = models.Label.smart_get_bulk(labels)
280 models.AtomicGroup.smart_get(id).label_set.add(*label_objs)
281
282
283def atomic_group_remove_labels(id, labels):
284 label_objs = models.Label.smart_get_bulk(labels)
285 models.AtomicGroup.smart_get(id).label_set.remove(*label_objs)
286
287
288def get_atomic_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000289 return rpc_utils.prepare_for_serialization(
showardc92da832009-04-07 18:14:34 +0000290 models.AtomicGroup.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000291
292
293# hosts
294
Matthew Sartori68186332015-04-27 17:19:53 -0700295def add_host(hostname, status=None, locked=None, lock_reason='', protection=None):
296 if locked and not lock_reason:
297 raise model_logic.ValidationError(
298 {'locked': 'Please provide a reason for locking when adding host.'})
299
jadmanski0afbb632008-06-06 21:10:57 +0000300 return models.Host.add_object(hostname=hostname, status=status,
Matthew Sartori68186332015-04-27 17:19:53 -0700301 locked=locked, lock_reason=lock_reason,
302 protection=protection).id
mblighe8819cd2008-02-15 16:48:40 +0000303
304
MK Ryu33889612015-09-04 14:32:35 -0700305@rpc_utils.route_rpc_to_master
306def modify_host(id, **kwargs):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700307 """Modify local attributes of a host.
308
309 If this is called on the master, but the host is assigned to a shard, this
MK Ryu33889612015-09-04 14:32:35 -0700310 will call `modify_host_local` RPC to the responsible shard. This means if
311 a host is being locked using this function, this change will also propagate
312 to shards.
313 When this is called on a shard, the shard just routes the RPC to the master
314 and does nothing.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700315
316 @param id: id of the host to modify.
MK Ryu33889612015-09-04 14:32:35 -0700317 @param kwargs: key=value pairs of values to set on the host.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700318 """
MK Ryu33889612015-09-04 14:32:35 -0700319 rpc_utils.check_modify_host(kwargs)
showardce7c0922009-09-11 18:39:24 +0000320 host = models.Host.smart_get(id)
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800321 try:
322 rpc_utils.check_modify_host_locking(host, kwargs)
323 except model_logic.ValidationError as e:
324 if not kwargs.get('force_modify_locking', False):
325 raise
326 logging.exception('The following exception will be ignored and lock '
327 'modification will be enforced. %s', e)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700328
MK Ryud53e1492015-12-15 12:09:03 -0800329 # This is required to make `lock_time` for a host be exactly same
330 # between the master and a shard.
331 if kwargs.get('locked', None) and 'lock_time' not in kwargs:
332 kwargs['lock_time'] = datetime.datetime.now()
MK Ryu8e2c2d02016-01-06 15:24:38 -0800333 host.update_object(kwargs)
MK Ryud53e1492015-12-15 12:09:03 -0800334
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800335 # force_modifying_locking is not an internal field in database, remove.
336 kwargs.pop('force_modify_locking', None)
MK Ryu33889612015-09-04 14:32:35 -0700337 rpc_utils.fanout_rpc([host], 'modify_host_local',
338 include_hostnames=False, id=id, **kwargs)
mblighe8819cd2008-02-15 16:48:40 +0000339
340
MK Ryu33889612015-09-04 14:32:35 -0700341def modify_host_local(id, **kwargs):
342 """Modify host attributes in local DB.
343
344 @param id: Host id.
345 @param kwargs: key=value pairs of values to set on the host.
346 """
347 models.Host.smart_get(id).update_object(kwargs)
348
349
350@rpc_utils.route_rpc_to_master
showard276f9442009-05-20 00:33:16 +0000351def modify_hosts(host_filter_data, update_data):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700352 """Modify local attributes of multiple hosts.
353
354 If this is called on the master, but one of the hosts in that match the
MK Ryu33889612015-09-04 14:32:35 -0700355 filters is assigned to a shard, this will call `modify_hosts_local` RPC
356 to the responsible shard.
357 When this is called on a shard, the shard just routes the RPC to the master
358 and does nothing.
Jakob Juelich50e91f72014-10-01 12:43:23 -0700359
360 The filters are always applied on the master, not on the shards. This means
361 if the states of a host differ on the master and a shard, the state on the
362 master will be used. I.e. this means:
363 A host was synced to Shard 1. On Shard 1 the status of the host was set to
364 'Repair Failed'.
365 - A call to modify_hosts with host_filter_data={'status': 'Ready'} will
366 update the host (both on the shard and on the master), because the state
367 of the host as the master knows it is still 'Ready'.
368 - A call to modify_hosts with host_filter_data={'status': 'Repair failed'
369 will not update the host, because the filter doesn't apply on the master.
370
showardbe0d8692009-08-20 23:42:44 +0000371 @param host_filter_data: Filters out which hosts to modify.
372 @param update_data: A dictionary with the changes to make to the hosts.
showard276f9442009-05-20 00:33:16 +0000373 """
MK Ryu93161712015-12-21 10:41:32 -0800374 update_data = update_data.copy()
showardbe0d8692009-08-20 23:42:44 +0000375 rpc_utils.check_modify_host(update_data)
showard276f9442009-05-20 00:33:16 +0000376 hosts = models.Host.query_objects(host_filter_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700377
378 affected_shard_hostnames = set()
379 affected_host_ids = []
380
Alex Miller9658a952013-05-14 16:40:02 -0700381 # Check all hosts before changing data for exception safety.
382 for host in hosts:
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800383 try:
384 rpc_utils.check_modify_host_locking(host, update_data)
385 except model_logic.ValidationError as e:
386 if not update_data.get('force_modify_locking', False):
387 raise
388 logging.exception('The following exception will be ignored and '
389 'lock modification will be enforced. %s', e)
390
Jakob Juelich50e91f72014-10-01 12:43:23 -0700391 if host.shard:
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800392 affected_shard_hostnames.add(host.shard.rpc_hostname())
Jakob Juelich50e91f72014-10-01 12:43:23 -0700393 affected_host_ids.append(host.id)
394
MK Ryud53e1492015-12-15 12:09:03 -0800395 # This is required to make `lock_time` for a host be exactly same
396 # between the master and a shard.
397 if update_data.get('locked', None) and 'lock_time' not in update_data:
398 update_data['lock_time'] = datetime.datetime.now()
MK Ryu8e2c2d02016-01-06 15:24:38 -0800399 for host in hosts:
400 host.update_object(update_data)
MK Ryud53e1492015-12-15 12:09:03 -0800401
Shuqian Zhao4c0d2902016-01-12 17:03:15 -0800402 update_data.pop('force_modify_locking', None)
MK Ryu33889612015-09-04 14:32:35 -0700403 # Caution: Changing the filter from the original here. See docstring.
404 rpc_utils.run_rpc_on_multiple_hostnames(
405 'modify_hosts_local', affected_shard_hostnames,
Jakob Juelich50e91f72014-10-01 12:43:23 -0700406 host_filter_data={'id__in': affected_host_ids},
407 update_data=update_data)
408
showard276f9442009-05-20 00:33:16 +0000409
MK Ryu33889612015-09-04 14:32:35 -0700410def modify_hosts_local(host_filter_data, update_data):
411 """Modify attributes of hosts in local DB.
412
413 @param host_filter_data: Filters out which hosts to modify.
414 @param update_data: A dictionary with the changes to make to the hosts.
415 """
416 for host in models.Host.query_objects(host_filter_data):
417 host.update_object(update_data)
418
419
MK Ryufbb002c2015-06-08 14:13:16 -0700420def add_labels_to_host(id, labels):
421 """Adds labels to a given host only in local DB.
showardcafd16e2009-05-29 18:37:49 +0000422
MK Ryufbb002c2015-06-08 14:13:16 -0700423 @param id: id or hostname for a host.
424 @param labels: ids or names for labels.
425 """
426 label_objs = models.Label.smart_get_bulk(labels)
427 models.Host.smart_get(id).labels.add(*label_objs)
428
429
Dan Shi4a3deb82016-10-27 21:32:30 -0700430def boards_allowed(boards):
431 """Check if the list of board labels can be set to a single host.
432
433 The only case multiple board labels can be set to a single host is for
434 testbed, which may have a list of board labels like
435 board:angler-1, board:angler-2, board:angler-3'
436
437 @param boards: A list of board labels.
438
439 @returns True if the the list of boards can be set to a single host.
440 """
441 if len(boards) <= 1:
442 return True
443 match = re.match('(board:[^-]+)(?:-\d+)?', boards[0])
444 if not match:
445 return False
446 pattern = '%s-\d+' % match.group(1)
447 for board in boards:
448 if not re.match(pattern, board):
449 return False
450 return True
451
452
MK Ryufbb002c2015-06-08 14:13:16 -0700453@rpc_utils.route_rpc_to_master
454def host_add_labels(id, labels):
455 """Adds labels to a given host.
456
457 @param id: id or hostname for a host.
458 @param labels: ids or names for labels.
459
Shuqian Zhao40e182b2016-10-11 11:55:11 -0700460 @raises ValidationError: If adding more than one platform/board label.
MK Ryufbb002c2015-06-08 14:13:16 -0700461 """
Kevin Chengbdfc57d2016-04-14 13:46:58 -0700462 # Create the labels on the master/shards.
463 for label in labels:
464 _create_label_everywhere(label, [id])
465
MK Ryufbb002c2015-06-08 14:13:16 -0700466 label_objs = models.Label.smart_get_bulk(labels)
467 platforms = [label.name for label in label_objs if label.platform]
Shuqian Zhao40e182b2016-10-11 11:55:11 -0700468 boards = [label.name for label in label_objs
469 if label.name.startswith('board:')]
Dan Shi4a3deb82016-10-27 21:32:30 -0700470 if len(platforms) > 1 or not boards_allowed(boards):
showardcafd16e2009-05-29 18:37:49 +0000471 raise model_logic.ValidationError(
Shuqian Zhao40e182b2016-10-11 11:55:11 -0700472 {'labels': 'Adding more than one platform/board label: %s %s' %
473 (', '.join(platforms), ', '.join(boards))})
MK Ryufbb002c2015-06-08 14:13:16 -0700474
475 host_obj = models.Host.smart_get(id)
Dan Shi4a3deb82016-10-27 21:32:30 -0700476 if platforms:
MK Ryufbb002c2015-06-08 14:13:16 -0700477 models.Host.check_no_platform([host_obj])
Dan Shi4a3deb82016-10-27 21:32:30 -0700478 if boards:
Shuqian Zhao40e182b2016-10-11 11:55:11 -0700479 models.Host.check_no_board([host_obj])
MK Ryu8e2c2d02016-01-06 15:24:38 -0800480 add_labels_to_host(id, labels)
MK Ryufbb002c2015-06-08 14:13:16 -0700481
482 rpc_utils.fanout_rpc([host_obj], 'add_labels_to_host', False,
483 id=id, labels=labels)
mblighe8819cd2008-02-15 16:48:40 +0000484
485
MK Ryufbb002c2015-06-08 14:13:16 -0700486def remove_labels_from_host(id, labels):
487 """Removes labels from a given host only in local DB.
488
489 @param id: id or hostname for a host.
490 @param labels: ids or names for labels.
491 """
492 label_objs = models.Label.smart_get_bulk(labels)
493 models.Host.smart_get(id).labels.remove(*label_objs)
494
495
496@rpc_utils.route_rpc_to_master
mblighe8819cd2008-02-15 16:48:40 +0000497def host_remove_labels(id, labels):
MK Ryufbb002c2015-06-08 14:13:16 -0700498 """Removes labels from a given host.
499
500 @param id: id or hostname for a host.
501 @param labels: ids or names for labels.
502 """
MK Ryu8e2c2d02016-01-06 15:24:38 -0800503 remove_labels_from_host(id, labels)
504
MK Ryufbb002c2015-06-08 14:13:16 -0700505 host_obj = models.Host.smart_get(id)
506 rpc_utils.fanout_rpc([host_obj], 'remove_labels_from_host', False,
507 id=id, labels=labels)
mblighe8819cd2008-02-15 16:48:40 +0000508
509
MK Ryuacf35922014-10-03 14:56:49 -0700510def get_host_attribute(attribute, **host_filter_data):
511 """
512 @param attribute: string name of attribute
513 @param host_filter_data: filter data to apply to Hosts to choose hosts to
514 act upon
515 """
516 hosts = rpc_utils.get_host_query((), False, False, True, host_filter_data)
517 hosts = list(hosts)
518 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
519 'attribute_list')
520 host_attr_dicts = []
521 for host_obj in hosts:
522 for attr_obj in host_obj.attribute_list:
523 if attr_obj.attribute == attribute:
524 host_attr_dicts.append(attr_obj.get_object_dict())
525 return rpc_utils.prepare_for_serialization(host_attr_dicts)
526
527
showard0957a842009-05-11 19:25:08 +0000528def set_host_attribute(attribute, value, **host_filter_data):
529 """
MK Ryu26f0c932015-05-28 18:14:33 -0700530 @param attribute: string name of attribute
531 @param value: string, or None to delete an attribute
532 @param host_filter_data: filter data to apply to Hosts to choose hosts to
533 act upon
showard0957a842009-05-11 19:25:08 +0000534 """
535 assert host_filter_data # disallow accidental actions on all hosts
536 hosts = models.Host.query_objects(host_filter_data)
537 models.AclGroup.check_for_acl_violation_hosts(hosts)
MK Ryu8e2c2d02016-01-06 15:24:38 -0800538 for host in hosts:
539 host.set_or_delete_attribute(attribute, value)
showard0957a842009-05-11 19:25:08 +0000540
MK Ryu26f0c932015-05-28 18:14:33 -0700541 # Master forwards this RPC to shards.
542 if not utils.is_shard():
543 rpc_utils.fanout_rpc(hosts, 'set_host_attribute', False,
544 attribute=attribute, value=value, **host_filter_data)
545
showard0957a842009-05-11 19:25:08 +0000546
Jakob Juelich50e91f72014-10-01 12:43:23 -0700547@rpc_utils.forward_single_host_rpc_to_shard
mblighe8819cd2008-02-15 16:48:40 +0000548def delete_host(id):
jadmanski0afbb632008-06-06 21:10:57 +0000549 models.Host.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000550
551
showard87cc38f2009-08-20 23:37:04 +0000552def get_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
Dan Shi37df54d2015-12-14 11:16:28 -0800553 exclude_atomic_group_hosts=False, valid_only=True,
554 include_current_job=False, **filter_data):
555 """Get a list of dictionaries which contains the information of hosts.
556
showard87cc38f2009-08-20 23:37:04 +0000557 @param multiple_labels: match hosts in all of the labels given. Should
558 be a list of label names.
559 @param exclude_only_if_needed_labels: Exclude hosts with at least one
560 "only_if_needed" label applied.
561 @param exclude_atomic_group_hosts: Exclude hosts that have one or more
562 atomic group labels associated with them.
Dan Shi37df54d2015-12-14 11:16:28 -0800563 @param include_current_job: Set to True to include ids of currently running
564 job and special task.
jadmanski0afbb632008-06-06 21:10:57 +0000565 """
showard43a3d262008-11-12 18:17:05 +0000566 hosts = rpc_utils.get_host_query(multiple_labels,
567 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000568 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000569 valid_only, filter_data)
showard0957a842009-05-11 19:25:08 +0000570 hosts = list(hosts)
571 models.Host.objects.populate_relationships(hosts, models.Label,
572 'label_list')
573 models.Host.objects.populate_relationships(hosts, models.AclGroup,
574 'acl_list')
575 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
576 'attribute_list')
showard43a3d262008-11-12 18:17:05 +0000577 host_dicts = []
578 for host_obj in hosts:
579 host_dict = host_obj.get_object_dict()
showard0957a842009-05-11 19:25:08 +0000580 host_dict['labels'] = [label.name for label in host_obj.label_list]
showard909c9142009-07-07 20:54:42 +0000581 host_dict['platform'], host_dict['atomic_group'] = (rpc_utils.
582 find_platform_and_atomic_group(host_obj))
showard0957a842009-05-11 19:25:08 +0000583 host_dict['acls'] = [acl.name for acl in host_obj.acl_list]
584 host_dict['attributes'] = dict((attribute.attribute, attribute.value)
585 for attribute in host_obj.attribute_list)
Dan Shi37df54d2015-12-14 11:16:28 -0800586 if include_current_job:
587 host_dict['current_job'] = None
588 host_dict['current_special_task'] = None
589 entries = models.HostQueueEntry.objects.filter(
590 host_id=host_dict['id'], active=True, complete=False)
591 if entries:
592 host_dict['current_job'] = (
593 entries[0].get_object_dict()['job'])
594 tasks = models.SpecialTask.objects.filter(
595 host_id=host_dict['id'], is_active=True, is_complete=False)
596 if tasks:
597 host_dict['current_special_task'] = (
598 '%d-%s' % (tasks[0].get_object_dict()['id'],
599 tasks[0].get_object_dict()['task'].lower()))
showard43a3d262008-11-12 18:17:05 +0000600 host_dicts.append(host_dict)
601 return rpc_utils.prepare_for_serialization(host_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000602
603
showard87cc38f2009-08-20 23:37:04 +0000604def get_num_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000605 exclude_atomic_group_hosts=False, valid_only=True,
606 **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000607 """
608 Same parameters as get_hosts().
609
610 @returns The number of matching hosts.
611 """
showard43a3d262008-11-12 18:17:05 +0000612 hosts = rpc_utils.get_host_query(multiple_labels,
613 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000614 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000615 valid_only, filter_data)
showard43a3d262008-11-12 18:17:05 +0000616 return hosts.count()
showard1385b162008-03-13 15:59:40 +0000617
mblighe8819cd2008-02-15 16:48:40 +0000618
619# tests
620
showard909c7a62008-07-15 21:52:38 +0000621def add_test(name, test_type, path, author=None, dependencies=None,
showard3d9899a2008-07-31 02:11:58 +0000622 experimental=True, run_verify=None, test_class=None,
showard909c7a62008-07-15 21:52:38 +0000623 test_time=None, test_category=None, description=None,
624 sync_count=1):
jadmanski0afbb632008-06-06 21:10:57 +0000625 return models.Test.add_object(name=name, test_type=test_type, path=path,
showard909c7a62008-07-15 21:52:38 +0000626 author=author, dependencies=dependencies,
627 experimental=experimental,
628 run_verify=run_verify, test_time=test_time,
629 test_category=test_category,
630 sync_count=sync_count,
jadmanski0afbb632008-06-06 21:10:57 +0000631 test_class=test_class,
632 description=description).id
mblighe8819cd2008-02-15 16:48:40 +0000633
634
635def modify_test(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000636 models.Test.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000637
638
639def delete_test(id):
jadmanski0afbb632008-06-06 21:10:57 +0000640 models.Test.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000641
642
643def get_tests(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000644 return rpc_utils.prepare_for_serialization(
645 models.Test.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000646
647
Moises Osorio2dc7a102014-12-02 18:24:02 -0800648@_timer.decorate
649def get_tests_status_counts_by_job_name_label(job_name_prefix, label_name):
650 """Gets the counts of all passed and failed tests from the matching jobs.
651
652 @param job_name_prefix: Name prefix of the jobs to get the summary from, e.g.,
653 'butterfly-release/R40-6457.21.0/bvt-cq/'.
654 @param label_name: Label that must be set in the jobs, e.g.,
655 'cros-version:butterfly-release/R40-6457.21.0'.
656
657 @returns A summary of the counts of all the passed and failed tests.
658 """
659 job_ids = list(models.Job.objects.filter(
660 name__startswith=job_name_prefix,
661 dependency_labels__name=label_name).values_list(
662 'pk', flat=True))
663 summary = {'passed': 0, 'failed': 0}
664 if not job_ids:
665 return summary
666
667 counts = (tko_models.TestView.objects.filter(
668 afe_job_id__in=job_ids).exclude(
669 test_name='SERVER_JOB').exclude(
670 test_name__startswith='CLIENT_JOB').values(
671 'status').annotate(
672 count=Count('status')))
673 for status in counts:
674 if status['status'] == 'GOOD':
675 summary['passed'] += status['count']
676 else:
677 summary['failed'] += status['count']
678 return summary
679
680
showard2b9a88b2008-06-13 20:55:03 +0000681# profilers
682
683def add_profiler(name, description=None):
684 return models.Profiler.add_object(name=name, description=description).id
685
686
687def modify_profiler(id, **data):
688 models.Profiler.smart_get(id).update_object(data)
689
690
691def delete_profiler(id):
692 models.Profiler.smart_get(id).delete()
693
694
695def get_profilers(**filter_data):
696 return rpc_utils.prepare_for_serialization(
697 models.Profiler.list_objects(filter_data))
698
699
mblighe8819cd2008-02-15 16:48:40 +0000700# users
701
702def add_user(login, access_level=None):
jadmanski0afbb632008-06-06 21:10:57 +0000703 return models.User.add_object(login=login, access_level=access_level).id
mblighe8819cd2008-02-15 16:48:40 +0000704
705
706def modify_user(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000707 models.User.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000708
709
710def delete_user(id):
jadmanski0afbb632008-06-06 21:10:57 +0000711 models.User.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000712
713
714def get_users(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000715 return rpc_utils.prepare_for_serialization(
716 models.User.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000717
718
719# acl groups
720
721def add_acl_group(name, description=None):
showard04f2cd82008-07-25 20:53:31 +0000722 group = models.AclGroup.add_object(name=name, description=description)
showard64a95952010-01-13 21:27:16 +0000723 group.users.add(models.User.current_user())
showard04f2cd82008-07-25 20:53:31 +0000724 return group.id
mblighe8819cd2008-02-15 16:48:40 +0000725
726
727def modify_acl_group(id, **data):
showard04f2cd82008-07-25 20:53:31 +0000728 group = models.AclGroup.smart_get(id)
729 group.check_for_acl_violation_acl_group()
730 group.update_object(data)
731 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000732
733
734def acl_group_add_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000735 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000736 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000737 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000738 group.users.add(*users)
mblighe8819cd2008-02-15 16:48:40 +0000739
740
741def acl_group_remove_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000742 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000743 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000744 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000745 group.users.remove(*users)
showard04f2cd82008-07-25 20:53:31 +0000746 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000747
748
749def acl_group_add_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000750 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000751 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000752 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000753 group.hosts.add(*hosts)
showard08f981b2008-06-24 21:59:03 +0000754 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000755
756
757def acl_group_remove_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000758 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000759 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000760 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000761 group.hosts.remove(*hosts)
showard08f981b2008-06-24 21:59:03 +0000762 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000763
764
765def delete_acl_group(id):
jadmanski0afbb632008-06-06 21:10:57 +0000766 models.AclGroup.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000767
768
769def get_acl_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000770 acl_groups = models.AclGroup.list_objects(filter_data)
771 for acl_group in acl_groups:
772 acl_group_obj = models.AclGroup.objects.get(id=acl_group['id'])
773 acl_group['users'] = [user.login
774 for user in acl_group_obj.users.all()]
775 acl_group['hosts'] = [host.hostname
776 for host in acl_group_obj.hosts.all()]
777 return rpc_utils.prepare_for_serialization(acl_groups)
mblighe8819cd2008-02-15 16:48:40 +0000778
779
780# jobs
781
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700782def generate_control_file(tests=(), profilers=(),
showard91f85102009-10-12 20:34:52 +0000783 client_control_file='', use_container=False,
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700784 profile_only=None, db_tests=True,
785 test_source_build=None):
jadmanski0afbb632008-06-06 21:10:57 +0000786 """
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700787 Generates a client-side control file to run tests.
mbligh120351e2009-01-24 01:40:45 +0000788
Matthew Sartori10438092015-06-24 14:30:18 -0700789 @param tests List of tests to run. See db_tests for more information.
mbligh120351e2009-01-24 01:40:45 +0000790 @param profilers List of profilers to activate during the job.
791 @param client_control_file The contents of a client-side control file to
792 run at the end of all tests. If this is supplied, all tests must be
793 client side.
794 TODO: in the future we should support server control files directly
795 to wrap with a kernel. That'll require changing the parameter
796 name and adding a boolean to indicate if it is a client or server
797 control file.
798 @param use_container unused argument today. TODO: Enable containers
799 on the host during a client side test.
showard91f85102009-10-12 20:34:52 +0000800 @param profile_only A boolean that indicates what default profile_only
801 mode to use in the control file. Passing None will generate a
802 control file that does not explcitly set the default mode at all.
Matthew Sartori10438092015-06-24 14:30:18 -0700803 @param db_tests: if True, the test object can be found in the database
804 backing the test model. In this case, tests is a tuple
805 of test IDs which are used to retrieve the test objects
806 from the database. If False, tests is a tuple of test
807 dictionaries stored client-side in the AFE.
Michael Tang84a2ecf2016-06-07 15:10:53 -0700808 @param test_source_build: Build to be used to retrieve test code. Default
809 to None.
mbligh120351e2009-01-24 01:40:45 +0000810
811 @returns a dict with the following keys:
812 control_file: str, The control file text.
813 is_server: bool, is the control file a server-side control file?
814 synch_count: How many machines the job uses per autoserv execution.
815 synch_count == 1 means the job is asynchronous.
816 dependencies: A list of the names of labels on which the job depends.
817 """
showardd86debe2009-06-10 17:37:56 +0000818 if not tests and not client_control_file:
showard2bab8f42008-11-12 18:15:22 +0000819 return dict(control_file='', is_server=False, synch_count=1,
showard989f25d2008-10-01 11:38:11 +0000820 dependencies=[])
mblighe8819cd2008-02-15 16:48:40 +0000821
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700822 cf_info, test_objects, profiler_objects = (
823 rpc_utils.prepare_generate_control_file(tests, profilers,
824 db_tests))
showard989f25d2008-10-01 11:38:11 +0000825 cf_info['control_file'] = control_file.generate_control(
Richard Barnette8e33b4e2016-05-21 12:12:26 -0700826 tests=test_objects, profilers=profiler_objects,
827 is_server=cf_info['is_server'],
showard232b7ae2009-11-10 00:46:48 +0000828 client_control_file=client_control_file, profile_only=profile_only,
Michael Tang84a2ecf2016-06-07 15:10:53 -0700829 test_source_build=test_source_build)
showard989f25d2008-10-01 11:38:11 +0000830 return cf_info
mblighe8819cd2008-02-15 16:48:40 +0000831
832
Shuqian Zhao54a5b672016-05-11 22:12:17 +0000833def create_parameterized_job(name, priority, test, parameters, kernel=None,
834 label=None, profilers=(), profiler_parameters=None,
835 use_container=False, profile_only=None,
836 upload_kernel_config=False, hosts=(),
837 meta_hosts=(), one_time_hosts=(),
838 atomic_group_name=None, synch_count=None,
839 is_template=False, timeout=None,
840 timeout_mins=None, max_runtime_mins=None,
841 run_verify=False, email_list='', dependencies=(),
842 reboot_before=None, reboot_after=None,
843 parse_failed_repair=None, hostless=False,
844 keyvals=None, drone_set=None, run_reset=True,
845 require_ssp=None):
846 """
847 Creates and enqueues a parameterized job.
848
849 Most parameters a combination of the parameters for generate_control_file()
850 and create_job(), with the exception of:
851
852 @param test name or ID of the test to run
853 @param parameters a map of parameter name ->
854 tuple of (param value, param type)
855 @param profiler_parameters a dictionary of parameters for the profilers:
856 key: profiler name
857 value: dict of param name -> tuple of
858 (param value,
859 param type)
860 """
861 # Save the values of the passed arguments here. What we're going to do with
862 # them is pass them all to rpc_utils.get_create_job_common_args(), which
863 # will extract the subset of these arguments that apply for
864 # rpc_utils.create_job_common(), which we then pass in to that function.
865 args = locals()
866
867 # Set up the parameterized job configs
868 test_obj = models.Test.smart_get(test)
869 control_type = test_obj.test_type
870
871 try:
872 label = models.Label.smart_get(label)
873 except models.Label.DoesNotExist:
874 label = None
875
876 kernel_objs = models.Kernel.create_kernels(kernel)
877 profiler_objs = [models.Profiler.smart_get(profiler)
878 for profiler in profilers]
879
880 parameterized_job = models.ParameterizedJob.objects.create(
881 test=test_obj, label=label, use_container=use_container,
882 profile_only=profile_only,
883 upload_kernel_config=upload_kernel_config)
884 parameterized_job.kernels.add(*kernel_objs)
885
886 for profiler in profiler_objs:
887 parameterized_profiler = models.ParameterizedJobProfiler.objects.create(
888 parameterized_job=parameterized_job,
889 profiler=profiler)
890 profiler_params = profiler_parameters.get(profiler.name, {})
891 for name, (value, param_type) in profiler_params.iteritems():
892 models.ParameterizedJobProfilerParameter.objects.create(
893 parameterized_job_profiler=parameterized_profiler,
894 parameter_name=name,
895 parameter_value=value,
896 parameter_type=param_type)
897
898 try:
899 for parameter in test_obj.testparameter_set.all():
900 if parameter.name in parameters:
901 param_value, param_type = parameters.pop(parameter.name)
902 parameterized_job.parameterizedjobparameter_set.create(
903 test_parameter=parameter, parameter_value=param_value,
904 parameter_type=param_type)
905
906 if parameters:
907 raise Exception('Extra parameters remain: %r' % parameters)
908
909 return rpc_utils.create_job_common(
910 parameterized_job=parameterized_job.id,
911 control_type=control_type,
912 **rpc_utils.get_create_job_common_args(args))
913 except:
914 parameterized_job.delete()
915 raise
916
917
Simran Basib6ec8ae2014-04-23 12:05:08 -0700918def create_job_page_handler(name, priority, control_file, control_type,
Dan Shid215dbe2015-06-18 16:14:59 -0700919 image=None, hostless=False, firmware_rw_build=None,
920 firmware_ro_build=None, test_source_build=None,
Michael Tang84a2ecf2016-06-07 15:10:53 -0700921 is_cloning=False, **kwargs):
Simran Basib6ec8ae2014-04-23 12:05:08 -0700922 """\
923 Create and enqueue a job.
924
925 @param name name of this job
926 @param priority Integer priority of this job. Higher is more important.
927 @param control_file String contents of the control file.
928 @param control_type Type of control file, Client or Server.
Dan Shid215dbe2015-06-18 16:14:59 -0700929 @param image: ChromeOS build to be installed in the dut. Default to None.
930 @param firmware_rw_build: Firmware build to update RW firmware. Default to
931 None, i.e., RW firmware will not be updated.
932 @param firmware_ro_build: Firmware build to update RO firmware. Default to
933 None, i.e., RO firmware will not be updated.
934 @param test_source_build: Build to be used to retrieve test code. Default
935 to None.
Michael Tang6dc174e2016-05-31 23:13:42 -0700936 @param is_cloning: True if creating a cloning job.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700937 @param kwargs extra args that will be required by create_suite_job or
938 create_job.
939
940 @returns The created Job id number.
941 """
Michael Tang6dc174e2016-05-31 23:13:42 -0700942 if is_cloning:
943 logging.info('Start to clone a new job')
Shuqian Zhao61f5d312016-08-05 17:15:23 -0700944 # When cloning a job, hosts and meta_hosts should not exist together,
945 # which would cause host-scheduler to schedule two hqe jobs to one host
946 # at the same time, and crash itself. Clear meta_hosts for this case.
947 if kwargs.get('hosts') and kwargs.get('meta_hosts'):
948 kwargs['meta_hosts'] = []
Michael Tang6dc174e2016-05-31 23:13:42 -0700949 else:
950 logging.info('Start to create a new job')
Simran Basib6ec8ae2014-04-23 12:05:08 -0700951 control_file = rpc_utils.encode_ascii(control_file)
Jiaxi Luodd67beb2014-07-18 16:28:31 -0700952 if not control_file:
953 raise model_logic.ValidationError({
954 'control_file' : "Control file cannot be empty"})
Simran Basib6ec8ae2014-04-23 12:05:08 -0700955
956 if image and hostless:
Dan Shid215dbe2015-06-18 16:14:59 -0700957 builds = {}
958 builds[provision.CROS_VERSION_PREFIX] = image
959 if firmware_rw_build:
Dan Shi0723bf52015-06-24 10:52:38 -0700960 builds[provision.FW_RW_VERSION_PREFIX] = firmware_rw_build
Dan Shid215dbe2015-06-18 16:14:59 -0700961 if firmware_ro_build:
962 builds[provision.FW_RO_VERSION_PREFIX] = firmware_ro_build
Simran Basib6ec8ae2014-04-23 12:05:08 -0700963 return site_rpc_interface.create_suite_job(
964 name=name, control_file=control_file, priority=priority,
Michael Tang6dc174e2016-05-31 23:13:42 -0700965 builds=builds, test_source_build=test_source_build,
966 is_cloning=is_cloning, **kwargs)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700967 return create_job(name, priority, control_file, control_type, image=image,
Michael Tang6dc174e2016-05-31 23:13:42 -0700968 hostless=hostless, is_cloning=is_cloning, **kwargs)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700969
970
MK Ryue301eb72015-06-25 12:51:02 -0700971@rpc_utils.route_rpc_to_master
showard12f3e322009-05-13 21:27:42 +0000972def create_job(name, priority, control_file, control_type,
973 hosts=(), meta_hosts=(), one_time_hosts=(),
974 atomic_group_name=None, synch_count=None, is_template=False,
Simran Basi7e605742013-11-12 13:43:36 -0800975 timeout=None, timeout_mins=None, max_runtime_mins=None,
976 run_verify=False, email_list='', dependencies=(),
977 reboot_before=None, reboot_after=None, parse_failed_repair=None,
978 hostless=False, keyvals=None, drone_set=None, image=None,
Dan Shiec1d47d2015-02-13 11:38:13 -0800979 parent_job_id=None, test_retry=0, run_reset=True,
Michael Tang6dc174e2016-05-31 23:13:42 -0700980 require_ssp=None, args=(), is_cloning=False, **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +0000981 """\
982 Create and enqueue a job.
mblighe8819cd2008-02-15 16:48:40 +0000983
showarda1e74b32009-05-12 17:32:04 +0000984 @param name name of this job
Alex Miller7d658cf2013-09-04 16:00:35 -0700985 @param priority Integer priority of this job. Higher is more important.
showarda1e74b32009-05-12 17:32:04 +0000986 @param control_file String contents of the control file.
987 @param control_type Type of control file, Client or Server.
988 @param synch_count How many machines the job uses per autoserv execution.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700989 synch_count == 1 means the job is asynchronous. If an atomic group is
990 given this value is treated as a minimum.
showarda1e74b32009-05-12 17:32:04 +0000991 @param is_template If true then create a template job.
992 @param timeout Hours after this call returns until the job times out.
Simran Basi7e605742013-11-12 13:43:36 -0800993 @param timeout_mins Minutes after this call returns until the job times
Jiaxi Luo90190c92014-06-18 12:35:57 -0700994 out.
Simran Basi34217022012-11-06 13:43:15 -0800995 @param max_runtime_mins Minutes from job starting time until job times out
showarda1e74b32009-05-12 17:32:04 +0000996 @param run_verify Should the host be verified before running the test?
997 @param email_list String containing emails to mail when the job is done
998 @param dependencies List of label names on which this job depends
999 @param reboot_before Never, If dirty, or Always
1000 @param reboot_after Never, If all tests passed, or Always
1001 @param parse_failed_repair if true, results of failed repairs launched by
Jiaxi Luo90190c92014-06-18 12:35:57 -07001002 this job will be parsed as part of the job.
showarda9545c02009-12-18 22:44:26 +00001003 @param hostless if true, create a hostless job
showardc1a98d12010-01-15 00:22:22 +00001004 @param keyvals dict of keyvals to associate with the job
showarda1e74b32009-05-12 17:32:04 +00001005 @param hosts List of hosts to run job on.
1006 @param meta_hosts List where each entry is a label name, and for each entry
Jiaxi Luo90190c92014-06-18 12:35:57 -07001007 one host will be chosen from that label to run the job on.
showarda1e74b32009-05-12 17:32:04 +00001008 @param one_time_hosts List of hosts not in the database to run the job on.
1009 @param atomic_group_name The name of an atomic group to schedule the job on.
jamesren76fcf192010-04-21 20:39:50 +00001010 @param drone_set The name of the drone set to run this test on.
Paul Pendlebury5a8c6ad2011-02-01 07:20:17 -08001011 @param image OS image to install before running job.
Aviv Keshet0b9cfc92013-02-05 11:36:02 -08001012 @param parent_job_id id of a job considered to be parent of created job.
Simran Basib6ec8ae2014-04-23 12:05:08 -07001013 @param test_retry Number of times to retry test if the test did not
Jiaxi Luo90190c92014-06-18 12:35:57 -07001014 complete successfully. (optional, default: 0)
Simran Basib6ec8ae2014-04-23 12:05:08 -07001015 @param run_reset Should the host be reset before running the test?
Dan Shiec1d47d2015-02-13 11:38:13 -08001016 @param require_ssp Set to True to require server-side packaging to run the
1017 test. If it's set to None, drone will still try to run
1018 the server side with server-side packaging. If the
1019 autotest-server package doesn't exist for the build or
1020 image is not set, drone will run the test without server-
1021 side packaging. Default is None.
Jiaxi Luo90190c92014-06-18 12:35:57 -07001022 @param args A list of args to be injected into control file.
Michael Tang6dc174e2016-05-31 23:13:42 -07001023 @param is_cloning: True if creating a cloning job.
Simran Basib6ec8ae2014-04-23 12:05:08 -07001024 @param kwargs extra keyword args. NOT USED.
showardc92da832009-04-07 18:14:34 +00001025
1026 @returns The created Job id number.
jadmanski0afbb632008-06-06 21:10:57 +00001027 """
Jiaxi Luo90190c92014-06-18 12:35:57 -07001028 if args:
1029 control_file = tools.inject_vars({'args': args}, control_file)
1030
Simran Basiab5a1bf2014-05-28 15:39:44 -07001031 if image is None:
1032 return rpc_utils.create_job_common(
1033 **rpc_utils.get_create_job_common_args(locals()))
1034
Simran Basi6157e8e2015-12-07 18:22:34 -08001035 # Translate the image name, in case its a relative build name.
1036 ds = dev_server.ImageServer.resolve(image)
1037 image = ds.translate(image)
1038
Simran Basiab5a1bf2014-05-28 15:39:44 -07001039 # When image is supplied use a known parameterized test already in the
1040 # database to pass the OS image path from the front end, through the
1041 # scheduler, and finally to autoserv as the --image parameter.
1042
1043 # The test autoupdate_ParameterizedJob is in afe_autotests and used to
1044 # instantiate a Test object and from there a ParameterizedJob.
1045 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
1046 known_parameterized_job = models.ParameterizedJob.objects.create(
1047 test=known_test_obj)
1048
1049 # autoupdate_ParameterizedJob has a single parameter, the image parameter,
1050 # stored in the table afe_test_parameters. We retrieve and set this
1051 # instance of the parameter to the OS image path.
1052 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
1053 name='image')
1054 known_parameterized_job.parameterizedjobparameter_set.create(
1055 test_parameter=image_parameter, parameter_value=image,
1056 parameter_type='string')
1057
Dan Shid215dbe2015-06-18 16:14:59 -07001058 # TODO(crbug.com/502638): save firmware build etc to parameterized_job.
1059
Simran Basiab5a1bf2014-05-28 15:39:44 -07001060 # By passing a parameterized_job to create_job_common the job entry in
1061 # the afe_jobs table will have the field parameterized_job_id set.
1062 # The scheduler uses this id in the afe_parameterized_jobs table to
1063 # match this job to our known test, and then with the
1064 # afe_parameterized_job_parameters table to get the actual image path.
jamesren4a41e012010-07-16 22:33:48 +00001065 return rpc_utils.create_job_common(
Simran Basiab5a1bf2014-05-28 15:39:44 -07001066 parameterized_job=known_parameterized_job.id,
jamesren4a41e012010-07-16 22:33:48 +00001067 **rpc_utils.get_create_job_common_args(locals()))
mblighe8819cd2008-02-15 16:48:40 +00001068
1069
showard9dbdcda2008-10-14 17:34:36 +00001070def abort_host_queue_entries(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001071 """\
showard9dbdcda2008-10-14 17:34:36 +00001072 Abort a set of host queue entries.
Fang Deng63b0e452014-12-19 14:38:15 -08001073
1074 @return: A list of dictionaries, each contains information
1075 about an aborted HQE.
jadmanski0afbb632008-06-06 21:10:57 +00001076 """
showard9dbdcda2008-10-14 17:34:36 +00001077 query = models.HostQueueEntry.query_objects(filter_data)
beepsfaecbce2013-10-29 11:35:10 -07001078
1079 # Dont allow aborts on:
1080 # 1. Jobs that have already completed (whether or not they were aborted)
1081 # 2. Jobs that we have already been aborted (but may not have completed)
1082 query = query.filter(complete=False).filter(aborted=False)
showarddc817512008-11-12 18:16:41 +00001083 models.AclGroup.check_abort_permissions(query)
showard9dbdcda2008-10-14 17:34:36 +00001084 host_queue_entries = list(query.select_related())
showard2bab8f42008-11-12 18:15:22 +00001085 rpc_utils.check_abort_synchronous_jobs(host_queue_entries)
mblighe8819cd2008-02-15 16:48:40 +00001086
Simran Basic1b26762013-06-26 14:23:21 -07001087 models.HostQueueEntry.abort_host_queue_entries(host_queue_entries)
Fang Deng63b0e452014-12-19 14:38:15 -08001088 hqe_info = [{'HostQueueEntry': hqe.id, 'Job': hqe.job_id,
1089 'Job name': hqe.job.name} for hqe in host_queue_entries]
1090 return hqe_info
showard9d821ab2008-07-11 16:54:29 +00001091
1092
beeps8bb1f7d2013-08-05 01:30:09 -07001093def abort_special_tasks(**filter_data):
1094 """\
1095 Abort the special task, or tasks, specified in the filter.
1096 """
1097 query = models.SpecialTask.query_objects(filter_data)
1098 special_tasks = query.filter(is_active=True)
1099 for task in special_tasks:
1100 task.abort()
1101
1102
Simran Basi73dae552013-02-25 14:57:46 -08001103def _call_special_tasks_on_hosts(task, hosts):
1104 """\
1105 Schedules a set of hosts for a special task.
1106
1107 @returns A list of hostnames that a special task was created for.
1108 """
1109 models.AclGroup.check_for_acl_violation_hosts(hosts)
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -08001110 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -08001111 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -08001112 raise ValueError('The following hosts are on shards, please '
1113 'follow the link to the shards and create jobs '
1114 'there instead. %s.' % shard_host_map)
Simran Basi73dae552013-02-25 14:57:46 -08001115 for host in hosts:
1116 models.SpecialTask.schedule_special_task(host, task)
1117 return list(sorted(host.hostname for host in hosts))
1118
1119
MK Ryu5aa25042015-07-28 16:08:04 -07001120def _forward_special_tasks_on_hosts(task, rpc, **filter_data):
1121 """Forward special tasks to corresponding shards.
mbligh4e545a52009-12-19 05:30:39 +00001122
MK Ryu5aa25042015-07-28 16:08:04 -07001123 For master, when special tasks are fired on hosts that are sharded,
1124 forward the RPC to corresponding shards.
1125
1126 For shard, create special task records in local DB.
1127
1128 @param task: Enum value of frontend.afe.models.SpecialTask.Task
1129 @param rpc: RPC name to forward.
1130 @param filter_data: Filter keywords to be used for DB query.
1131
1132 @return: A list of hostnames that a special task was created for.
showard1ff7b2e2009-05-15 23:17:18 +00001133 """
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001134 hosts = models.Host.query_objects(filter_data)
1135 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts, rpc_hostnames=True)
1136
1137 # Filter out hosts on a shard from those on the master, forward
1138 # rpcs to the shard with an additional hostname__in filter, and
1139 # create a local SpecialTask for each remaining host.
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -08001140 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001141 hosts = [h for h in hosts if h.shard is None]
1142 for shard, hostnames in shard_host_map.iteritems():
1143
1144 # The main client of this module is the frontend website, and
1145 # it invokes it with an 'id' or an 'id__in' filter. Regardless,
1146 # the 'hostname' filter should narrow down the list of hosts on
1147 # each shard even though we supply all the ids in filter_data.
1148 # This method uses hostname instead of id because it fits better
MK Ryu5aa25042015-07-28 16:08:04 -07001149 # with the overall architecture of redirection functions in
1150 # rpc_utils.
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001151 shard_filter = filter_data.copy()
1152 shard_filter['hostname__in'] = hostnames
1153 rpc_utils.run_rpc_on_multiple_hostnames(
MK Ryu5aa25042015-07-28 16:08:04 -07001154 rpc, [shard], **shard_filter)
Prashanth Balasubramanian40981232014-12-16 19:01:58 -08001155
1156 # There is a race condition here if someone assigns a shard to one of these
1157 # hosts before we create the task. The host will stay on the master if:
1158 # 1. The host is not Ready
1159 # 2. The host is Ready but has a task
1160 # But if the host is Ready and doesn't have a task yet, it will get sent
1161 # to the shard as we're creating a task here.
1162
1163 # Given that we only rarely verify Ready hosts it isn't worth putting this
1164 # entire method in a transaction. The worst case scenario is that we have
MK Ryu5aa25042015-07-28 16:08:04 -07001165 # a verify running on a Ready host while the shard is using it, if the
1166 # verify fails no subsequent tasks will be created against the host on the
1167 # master, and verifies are safe enough that this is OK.
1168 return _call_special_tasks_on_hosts(task, hosts)
1169
1170
1171def reverify_hosts(**filter_data):
1172 """\
1173 Schedules a set of hosts for verify.
1174
1175 @returns A list of hostnames that a verify task was created for.
1176 """
1177 return _forward_special_tasks_on_hosts(
1178 models.SpecialTask.Task.VERIFY, 'reverify_hosts', **filter_data)
Simran Basi73dae552013-02-25 14:57:46 -08001179
1180
1181def repair_hosts(**filter_data):
1182 """\
1183 Schedules a set of hosts for repair.
1184
1185 @returns A list of hostnames that a repair task was created for.
1186 """
MK Ryu5aa25042015-07-28 16:08:04 -07001187 return _forward_special_tasks_on_hosts(
1188 models.SpecialTask.Task.REPAIR, 'repair_hosts', **filter_data)
showard1ff7b2e2009-05-15 23:17:18 +00001189
1190
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001191def get_jobs(not_yet_run=False, running=False, finished=False,
1192 suite=False, sub=False, standalone=False, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001193 """\
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001194 Extra status filter args for get_jobs:
jadmanski0afbb632008-06-06 21:10:57 +00001195 -not_yet_run: Include only jobs that have not yet started running.
1196 -running: Include only jobs that have start running but for which not
1197 all hosts have completed.
1198 -finished: Include only jobs for which all hosts have completed (or
1199 aborted).
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001200
1201 Extra type filter args for get_jobs:
1202 -suite: Include only jobs with child jobs.
1203 -sub: Include only jobs with a parent job.
1204 -standalone: Inlcude only jobs with no child or parent jobs.
1205 At most one of these three fields should be specified.
jadmanski0afbb632008-06-06 21:10:57 +00001206 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001207 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
1208 running,
1209 finished)
1210 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
1211 suite,
1212 sub,
1213 standalone)
showard0957a842009-05-11 19:25:08 +00001214 job_dicts = []
1215 jobs = list(models.Job.query_objects(filter_data))
1216 models.Job.objects.populate_relationships(jobs, models.Label,
1217 'dependencies')
showardc1a98d12010-01-15 00:22:22 +00001218 models.Job.objects.populate_relationships(jobs, models.JobKeyval, 'keyvals')
showard0957a842009-05-11 19:25:08 +00001219 for job in jobs:
1220 job_dict = job.get_object_dict()
1221 job_dict['dependencies'] = ','.join(label.name
1222 for label in job.dependencies)
showardc1a98d12010-01-15 00:22:22 +00001223 job_dict['keyvals'] = dict((keyval.key, keyval.value)
1224 for keyval in job.keyvals)
Eric Lid23bc192011-02-09 14:38:57 -08001225 if job.parameterized_job:
1226 job_dict['image'] = get_parameterized_autoupdate_image_url(job)
showard0957a842009-05-11 19:25:08 +00001227 job_dicts.append(job_dict)
1228 return rpc_utils.prepare_for_serialization(job_dicts)
mblighe8819cd2008-02-15 16:48:40 +00001229
1230
1231def get_num_jobs(not_yet_run=False, running=False, finished=False,
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001232 suite=False, sub=False, standalone=False,
jadmanski0afbb632008-06-06 21:10:57 +00001233 **filter_data):
Aviv Keshet17660a52016-04-06 18:56:43 +00001234 """\
1235 See get_jobs() for documentation of extra filter parameters.
jadmanski0afbb632008-06-06 21:10:57 +00001236 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -07001237 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
1238 running,
1239 finished)
1240 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
1241 suite,
1242 sub,
1243 standalone)
Aviv Keshet17660a52016-04-06 18:56:43 +00001244 return models.Job.query_count(filter_data)
mblighe8819cd2008-02-15 16:48:40 +00001245
1246
mblighe8819cd2008-02-15 16:48:40 +00001247def get_jobs_summary(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001248 """\
Jiaxi Luoaac54572014-06-04 13:57:02 -07001249 Like get_jobs(), but adds 'status_counts' and 'result_counts' field.
1250
1251 'status_counts' filed is a dictionary mapping status strings to the number
1252 of hosts currently with that status, i.e. {'Queued' : 4, 'Running' : 2}.
1253
1254 'result_counts' field is piped to tko's rpc_interface and has the return
1255 format specified under get_group_counts.
jadmanski0afbb632008-06-06 21:10:57 +00001256 """
1257 jobs = get_jobs(**filter_data)
1258 ids = [job['id'] for job in jobs]
1259 all_status_counts = models.Job.objects.get_status_counts(ids)
1260 for job in jobs:
1261 job['status_counts'] = all_status_counts[job['id']]
Jiaxi Luoaac54572014-06-04 13:57:02 -07001262 job['result_counts'] = tko_rpc_interface.get_status_counts(
1263 ['afe_job_id', 'afe_job_id'],
1264 header_groups=[['afe_job_id'], ['afe_job_id']],
1265 **{'afe_job_id': job['id']})
jadmanski0afbb632008-06-06 21:10:57 +00001266 return rpc_utils.prepare_for_serialization(jobs)
mblighe8819cd2008-02-15 16:48:40 +00001267
1268
showarda965cef2009-05-15 23:17:41 +00001269def get_info_for_clone(id, preserve_metahosts, queue_entry_filter_data=None):
showarda8709c52008-07-03 19:44:54 +00001270 """\
1271 Retrieves all the information needed to clone a job.
1272 """
showarda8709c52008-07-03 19:44:54 +00001273 job = models.Job.objects.get(id=id)
showard29f7cd22009-04-29 21:16:24 +00001274 job_info = rpc_utils.get_job_info(job,
showarda965cef2009-05-15 23:17:41 +00001275 preserve_metahosts,
1276 queue_entry_filter_data)
showard945072f2008-09-03 20:34:59 +00001277
showardd9992fe2008-07-31 02:15:03 +00001278 host_dicts = []
showard29f7cd22009-04-29 21:16:24 +00001279 for host in job_info['hosts']:
1280 host_dict = get_hosts(id=host.id)[0]
1281 other_labels = host_dict['labels']
1282 if host_dict['platform']:
1283 other_labels.remove(host_dict['platform'])
1284 host_dict['other_labels'] = ', '.join(other_labels)
showardd9992fe2008-07-31 02:15:03 +00001285 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001286
showard29f7cd22009-04-29 21:16:24 +00001287 for host in job_info['one_time_hosts']:
1288 host_dict = dict(hostname=host.hostname,
1289 id=host.id,
1290 platform='(one-time host)',
1291 locked_text='')
1292 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001293
showard4d077562009-05-08 18:24:36 +00001294 # convert keys from Label objects to strings (names of labels)
showard29f7cd22009-04-29 21:16:24 +00001295 meta_host_counts = dict((meta_host.name, count) for meta_host, count
showard4d077562009-05-08 18:24:36 +00001296 in job_info['meta_host_counts'].iteritems())
showard29f7cd22009-04-29 21:16:24 +00001297
1298 info = dict(job=job.get_object_dict(),
1299 meta_host_counts=meta_host_counts,
1300 hosts=host_dicts)
1301 info['job']['dependencies'] = job_info['dependencies']
1302 if job_info['atomic_group']:
1303 info['atomic_group_name'] = (job_info['atomic_group']).name
1304 else:
1305 info['atomic_group_name'] = None
jamesren2275ef12010-04-12 18:25:06 +00001306 info['hostless'] = job_info['hostless']
jamesren76fcf192010-04-21 20:39:50 +00001307 info['drone_set'] = job.drone_set and job.drone_set.name
showarda8709c52008-07-03 19:44:54 +00001308
Michael Tang6dc174e2016-05-31 23:13:42 -07001309 image = _get_image_for_job(job, job_info['hostless'])
1310 if image:
1311 info['job']['image'] = image
Eric Lid23bc192011-02-09 14:38:57 -08001312
showarda8709c52008-07-03 19:44:54 +00001313 return rpc_utils.prepare_for_serialization(info)
1314
1315
Michael Tang6dc174e2016-05-31 23:13:42 -07001316def _get_image_for_job(job, hostless):
1317 """ Gets the image used for a job.
1318
1319 Gets the image used for an AFE job. If the job is a parameterized job, get
1320 the image from the job parameter; otherwise, tries to get the image from
1321 the job's keyvals 'build' or 'builds'. As a last resort, if the job is a
1322 hostless job, tries to get the image from its control file attributes
1323 'build' or 'builds'.
1324
1325 TODO(ntang): Needs to handle FAFT with two builds for ro/rw.
1326
1327 @param job An AFE job object.
1328 @param hostless Boolean on of the job is hostless.
1329
1330 @returns The image build used for the job.
1331 """
1332 image = None
1333 if job.parameterized_job:
1334 image = get_parameterized_autoupdate_image_url(job)
1335 else:
1336 keyvals = job.keyval_dict()
Michael Tang84a2ecf2016-06-07 15:10:53 -07001337 image = keyvals.get('build')
Michael Tang6dc174e2016-05-31 23:13:42 -07001338 if not image:
1339 value = keyvals.get('builds')
1340 builds = None
1341 if isinstance(value, dict):
1342 builds = value
1343 elif isinstance(value, basestring):
1344 builds = ast.literal_eval(value)
1345 if builds:
1346 image = builds.get('cros-version')
1347 if not image and hostless and job.control_file:
1348 try:
1349 control_obj = control_data.parse_control_string(
1350 job.control_file)
1351 if hasattr(control_obj, 'build'):
1352 image = getattr(control_obj, 'build')
1353 if not image and hasattr(control_obj, 'builds'):
1354 builds = getattr(control_obj, 'builds')
1355 image = builds.get('cros-version')
1356 except:
1357 logging.warning('Failed to parse control file for job: %s',
1358 job.name)
1359 return image
1360
showard34dc5fa2008-04-24 20:58:40 +00001361
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001362def get_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001363 """\
showardc92da832009-04-07 18:14:34 +00001364 @returns A sequence of nested dictionaries of host and job information.
jadmanski0afbb632008-06-06 21:10:57 +00001365 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001366 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1367 'started_on__lte',
1368 start_time,
1369 end_time,
1370 **filter_data)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001371 return rpc_utils.prepare_rows_as_nested_dicts(
1372 models.HostQueueEntry.query_objects(filter_data),
1373 ('host', 'atomic_group', 'job'))
showard34dc5fa2008-04-24 20:58:40 +00001374
1375
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001376def get_num_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001377 """\
1378 Get the number of host queue entries associated with this job.
1379 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001380 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1381 'started_on__lte',
1382 start_time,
1383 end_time,
1384 **filter_data)
jadmanski0afbb632008-06-06 21:10:57 +00001385 return models.HostQueueEntry.query_count(filter_data)
showard34dc5fa2008-04-24 20:58:40 +00001386
1387
showard1e935f12008-07-11 00:11:36 +00001388def get_hqe_percentage_complete(**filter_data):
1389 """
showardc92da832009-04-07 18:14:34 +00001390 Computes the fraction of host queue entries matching the given filter data
showard1e935f12008-07-11 00:11:36 +00001391 that are complete.
1392 """
1393 query = models.HostQueueEntry.query_objects(filter_data)
1394 complete_count = query.filter(complete=True).count()
1395 total_count = query.count()
1396 if total_count == 0:
1397 return 1
1398 return float(complete_count) / total_count
1399
1400
showard1a5a4082009-07-28 20:01:37 +00001401# special tasks
1402
1403def get_special_tasks(**filter_data):
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001404 """Get special task entries from the local database.
1405
1406 Query the special tasks table for tasks matching the given
1407 `filter_data`, and return a list of the results. No attempt is
1408 made to forward the call to shards; the buck will stop here.
1409 The caller is expected to know the target shard for such reasons
1410 as:
1411 * The caller is a service (such as gs_offloader) configured
1412 to operate on behalf of one specific shard, and no other.
1413 * The caller has a host as a parameter, and knows that this is
1414 the shard assigned to that host.
1415
1416 @param filter_data Filter keywords to pass to the underlying
1417 database query.
1418
1419 """
J. Richard Barnettefdfcd662015-04-13 17:20:29 -07001420 return rpc_utils.prepare_rows_as_nested_dicts(
1421 models.SpecialTask.query_objects(filter_data),
1422 ('host', 'queue_entry'))
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001423
1424
1425def get_host_special_tasks(host_id, **filter_data):
1426 """Get special task entries for a given host.
1427
1428 Query the special tasks table for tasks that ran on the host
1429 given by `host_id` and matching the given `filter_data`.
1430 Return a list of the results. If the host is assigned to a
1431 shard, forward this call to that shard.
1432
1433 @param host_id Id in the database of the target host.
1434 @param filter_data Filter keywords to pass to the underlying
1435 database query.
1436
1437 """
MK Ryu0c1a37d2015-04-30 12:00:55 -07001438 # Retrieve host data even if the host is in an invalid state.
1439 host = models.Host.smart_get(host_id, False)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001440 if not host.shard:
J. Richard Barnettefdfcd662015-04-13 17:20:29 -07001441 return get_special_tasks(host_id=host_id, **filter_data)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001442 else:
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001443 # The return values from AFE methods are post-processed
1444 # objects that aren't JSON-serializable. So, we have to
1445 # call AFE.run() to get the raw, serializable output from
1446 # the shard.
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001447 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1448 return shard_afe.run('get_special_tasks',
1449 host_id=host_id, **filter_data)
showard1a5a4082009-07-28 20:01:37 +00001450
1451
MK Ryu0c1a37d2015-04-30 12:00:55 -07001452def get_num_special_tasks(**kwargs):
1453 """Get the number of special task entries from the local database.
1454
1455 Query the special tasks table for tasks matching the given 'kwargs',
1456 and return the number of the results. No attempt is made to forward
1457 the call to shards; the buck will stop here.
1458
1459 @param kwargs Filter keywords to pass to the underlying database query.
1460
1461 """
1462 return models.SpecialTask.query_count(kwargs)
1463
1464
1465def get_host_num_special_tasks(host, **kwargs):
1466 """Get special task entries for a given host.
1467
1468 Query the special tasks table for tasks that ran on the host
1469 given by 'host' and matching the given 'kwargs'.
1470 Return a list of the results. If the host is assigned to a
1471 shard, forward this call to that shard.
1472
1473 @param host id or name of a host. More often a hostname.
1474 @param kwargs Filter keywords to pass to the underlying database query.
1475
1476 """
1477 # Retrieve host data even if the host is in an invalid state.
1478 host_model = models.Host.smart_get(host, False)
1479 if not host_model.shard:
1480 return get_num_special_tasks(host=host, **kwargs)
1481 else:
1482 shard_afe = frontend.AFE(server=host_model.shard.rpc_hostname())
1483 return shard_afe.run('get_num_special_tasks', host=host, **kwargs)
1484
1485
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001486def get_status_task(host_id, end_time):
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001487 """Get the "status task" for a host from the local shard.
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001488
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001489 Returns a single special task representing the given host's
1490 "status task". The status task is a completed special task that
1491 identifies whether the corresponding host was working or broken
1492 when it completed. A successful task indicates a working host;
1493 a failed task indicates broken.
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001494
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001495 This call will not be forward to a shard; the receiving server
1496 must be the shard that owns the host.
1497
1498 @param host_id Id in the database of the target host.
1499 @param end_time Time reference for the host's status.
1500
1501 @return A single task; its status (successful or not)
1502 corresponds to the status of the host (working or
1503 broken) at the given time. If no task is found, return
1504 `None`.
1505
1506 """
1507 tasklist = rpc_utils.prepare_rows_as_nested_dicts(
1508 status_history.get_status_task(host_id, end_time),
1509 ('host', 'queue_entry'))
1510 return tasklist[0] if tasklist else None
1511
1512
1513def get_host_status_task(host_id, end_time):
1514 """Get the "status task" for a host from its owning shard.
1515
1516 Finds the given host's owning shard, and forwards to it a call
1517 to `get_status_task()` (see above).
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001518
1519 @param host_id Id in the database of the target host.
1520 @param end_time Time reference for the host's status.
1521
1522 @return A single task; its status (successful or not)
1523 corresponds to the status of the host (working or
1524 broken) at the given time. If no task is found, return
1525 `None`.
1526
1527 """
1528 host = models.Host.smart_get(host_id)
1529 if not host.shard:
J. Richard Barnette4d7e6e62015-05-01 10:47:34 -07001530 return get_status_task(host_id, end_time)
J. Richard Barnette39255fa2015-04-14 17:23:41 -07001531 else:
1532 # The return values from AFE methods are post-processed
1533 # objects that aren't JSON-serializable. So, we have to
1534 # call AFE.run() to get the raw, serializable output from
1535 # the shard.
1536 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1537 return shard_afe.run('get_status_task',
1538 host_id=host_id, end_time=end_time)
1539
1540
J. Richard Barnette8abbfd62015-06-23 12:46:54 -07001541def get_host_diagnosis_interval(host_id, end_time, success):
1542 """Find a "diagnosis interval" for a given host.
1543
1544 A "diagnosis interval" identifies a start and end time where
1545 the host went from "working" to "broken", or vice versa. The
1546 interval's starting time is the starting time of the last status
1547 task with the old status; the end time is the finish time of the
1548 first status task with the new status.
1549
1550 This routine finds the most recent diagnosis interval for the
1551 given host prior to `end_time`, with a starting status matching
1552 `success`. If `success` is true, the interval will start with a
1553 successful status task; if false the interval will start with a
1554 failed status task.
1555
1556 @param host_id Id in the database of the target host.
1557 @param end_time Time reference for the diagnosis interval.
1558 @param success Whether the diagnosis interval should start
1559 with a successful or failed status task.
1560
1561 @return A list of two strings. The first is the timestamp for
1562 the beginning of the interval; the second is the
1563 timestamp for the end. If the host has never changed
1564 state, the list is empty.
1565
1566 """
1567 host = models.Host.smart_get(host_id)
J. Richard Barnette78f281a2015-06-29 13:24:51 -07001568 if not host.shard or utils.is_shard():
J. Richard Barnette8abbfd62015-06-23 12:46:54 -07001569 return status_history.get_diagnosis_interval(
1570 host_id, end_time, success)
1571 else:
1572 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1573 return shard_afe.get_host_diagnosis_interval(
1574 host_id, end_time, success)
1575
1576
showardc0ac3a72009-07-08 21:14:45 +00001577# support for host detail view
1578
MK Ryu0c1a37d2015-04-30 12:00:55 -07001579def get_host_queue_entries_and_special_tasks(host, query_start=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001580 query_limit=None, start_time=None,
1581 end_time=None):
showardc0ac3a72009-07-08 21:14:45 +00001582 """
1583 @returns an interleaved list of HostQueueEntries and SpecialTasks,
1584 in approximate run order. each dict contains keys for type, host,
1585 job, status, started_on, execution_path, and ID.
1586 """
1587 total_limit = None
1588 if query_limit is not None:
1589 total_limit = query_start + query_limit
MK Ryu0c1a37d2015-04-30 12:00:55 -07001590 filter_data_common = {'host': host,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001591 'query_limit': total_limit,
1592 'sort_by': ['-id']}
showardc0ac3a72009-07-08 21:14:45 +00001593
MK Ryu0c1a37d2015-04-30 12:00:55 -07001594 filter_data_special_tasks = rpc_utils.inject_times_to_filter(
1595 'time_started__gte', 'time_started__lte', start_time, end_time,
1596 **filter_data_common)
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001597
MK Ryu0c1a37d2015-04-30 12:00:55 -07001598 queue_entries = get_host_queue_entries(
1599 start_time, end_time, **filter_data_common)
1600 special_tasks = get_host_special_tasks(host, **filter_data_special_tasks)
showardc0ac3a72009-07-08 21:14:45 +00001601
1602 interleaved_entries = rpc_utils.interleave_entries(queue_entries,
1603 special_tasks)
1604 if query_start is not None:
1605 interleaved_entries = interleaved_entries[query_start:]
1606 if query_limit is not None:
1607 interleaved_entries = interleaved_entries[:query_limit]
MK Ryu0c1a37d2015-04-30 12:00:55 -07001608 return rpc_utils.prepare_host_queue_entries_and_special_tasks(
1609 interleaved_entries, queue_entries)
showardc0ac3a72009-07-08 21:14:45 +00001610
1611
MK Ryu0c1a37d2015-04-30 12:00:55 -07001612def get_num_host_queue_entries_and_special_tasks(host, start_time=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001613 end_time=None):
MK Ryu0c1a37d2015-04-30 12:00:55 -07001614 filter_data_common = {'host': host}
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001615
1616 filter_data_queue_entries, filter_data_special_tasks = (
1617 rpc_utils.inject_times_to_hqe_special_tasks_filters(
1618 filter_data_common, start_time, end_time))
1619
1620 return (models.HostQueueEntry.query_count(filter_data_queue_entries)
MK Ryu0c1a37d2015-04-30 12:00:55 -07001621 + get_host_num_special_tasks(**filter_data_special_tasks))
showardc0ac3a72009-07-08 21:14:45 +00001622
1623
showard29f7cd22009-04-29 21:16:24 +00001624# recurring run
1625
1626def get_recurring(**filter_data):
1627 return rpc_utils.prepare_rows_as_nested_dicts(
1628 models.RecurringRun.query_objects(filter_data),
1629 ('job', 'owner'))
1630
1631
1632def get_num_recurring(**filter_data):
1633 return models.RecurringRun.query_count(filter_data)
1634
1635
1636def delete_recurring_runs(**filter_data):
1637 to_delete = models.RecurringRun.query_objects(filter_data)
1638 to_delete.delete()
1639
1640
1641def create_recurring_run(job_id, start_date, loop_period, loop_count):
showard64a95952010-01-13 21:27:16 +00001642 owner = models.User.current_user().login
showard29f7cd22009-04-29 21:16:24 +00001643 job = models.Job.objects.get(id=job_id)
1644 return job.create_recurring_job(start_date=start_date,
1645 loop_period=loop_period,
1646 loop_count=loop_count,
1647 owner=owner)
1648
1649
mblighe8819cd2008-02-15 16:48:40 +00001650# other
1651
showarde0b63622008-08-04 20:58:47 +00001652def echo(data=""):
1653 """\
1654 Returns a passed in string. For doing a basic test to see if RPC calls
1655 can successfully be made.
1656 """
1657 return data
1658
1659
showardb7a52fd2009-04-27 20:10:56 +00001660def get_motd():
1661 """\
1662 Returns the message of the day as a string.
1663 """
1664 return rpc_utils.get_motd()
1665
1666
mblighe8819cd2008-02-15 16:48:40 +00001667def get_static_data():
jadmanski0afbb632008-06-06 21:10:57 +00001668 """\
1669 Returns a dictionary containing a bunch of data that shouldn't change
1670 often and is otherwise inaccessible. This includes:
showardc92da832009-04-07 18:14:34 +00001671
1672 priorities: List of job priority choices.
1673 default_priority: Default priority value for new jobs.
1674 users: Sorted list of all users.
Jiaxi Luo31874592014-06-11 10:36:35 -07001675 labels: Sorted list of labels not start with 'cros-version' and
1676 'fw-version'.
showardc92da832009-04-07 18:14:34 +00001677 atomic_groups: Sorted list of all atomic groups.
1678 tests: Sorted list of all tests.
1679 profilers: Sorted list of all profilers.
1680 current_user: Logged-in username.
1681 host_statuses: Sorted list of possible Host statuses.
1682 job_statuses: Sorted list of possible HostQueueEntry statuses.
Simran Basi7e605742013-11-12 13:43:36 -08001683 job_timeout_default: The default job timeout length in minutes.
showarda1e74b32009-05-12 17:32:04 +00001684 parse_failed_repair_default: Default value for the parse_failed_repair job
Jiaxi Luo31874592014-06-11 10:36:35 -07001685 option.
showardc92da832009-04-07 18:14:34 +00001686 reboot_before_options: A list of valid RebootBefore string enums.
1687 reboot_after_options: A list of valid RebootAfter string enums.
1688 motd: Server's message of the day.
1689 status_dictionary: A mapping from one word job status names to a more
1690 informative description.
jadmanski0afbb632008-06-06 21:10:57 +00001691 """
showard21baa452008-10-21 00:08:39 +00001692
1693 job_fields = models.Job.get_field_dict()
jamesren76fcf192010-04-21 20:39:50 +00001694 default_drone_set_name = models.DroneSet.default_drone_set_name()
1695 drone_sets = ([default_drone_set_name] +
1696 sorted(drone_set.name for drone_set in
1697 models.DroneSet.objects.exclude(
1698 name=default_drone_set_name)))
showard21baa452008-10-21 00:08:39 +00001699
jadmanski0afbb632008-06-06 21:10:57 +00001700 result = {}
Alex Miller7d658cf2013-09-04 16:00:35 -07001701 result['priorities'] = priorities.Priority.choices()
1702 default_priority = priorities.Priority.DEFAULT
1703 result['default_priority'] = 'Default'
1704 result['max_schedulable_priority'] = priorities.Priority.DEFAULT
jadmanski0afbb632008-06-06 21:10:57 +00001705 result['users'] = get_users(sort_by=['login'])
Jiaxi Luo31874592014-06-11 10:36:35 -07001706
1707 label_exclude_filters = [{'name__startswith': 'cros-version'},
Dan Shi65351d62015-08-03 12:03:23 -07001708 {'name__startswith': 'fw-version'},
1709 {'name__startswith': 'fwrw-version'},
Dan Shi27516972016-03-16 14:03:41 -07001710 {'name__startswith': 'fwro-version'},
1711 {'name__startswith': 'ab-version'},
1712 {'name__startswith': 'testbed-version'}]
Jiaxi Luo31874592014-06-11 10:36:35 -07001713 result['labels'] = get_labels(
1714 label_exclude_filters,
1715 sort_by=['-platform', 'name'])
1716
showardc92da832009-04-07 18:14:34 +00001717 result['atomic_groups'] = get_atomic_groups(sort_by=['name'])
jadmanski0afbb632008-06-06 21:10:57 +00001718 result['tests'] = get_tests(sort_by=['name'])
showard2b9a88b2008-06-13 20:55:03 +00001719 result['profilers'] = get_profilers(sort_by=['name'])
showard0fc38302008-10-23 00:44:07 +00001720 result['current_user'] = rpc_utils.prepare_for_serialization(
showard64a95952010-01-13 21:27:16 +00001721 models.User.current_user().get_object_dict())
showard2b9a88b2008-06-13 20:55:03 +00001722 result['host_statuses'] = sorted(models.Host.Status.names)
mbligh5a198b92008-12-11 19:33:29 +00001723 result['job_statuses'] = sorted(models.HostQueueEntry.Status.names)
Simran Basi7e605742013-11-12 13:43:36 -08001724 result['job_timeout_mins_default'] = models.Job.DEFAULT_TIMEOUT_MINS
Simran Basi34217022012-11-06 13:43:15 -08001725 result['job_max_runtime_mins_default'] = (
1726 models.Job.DEFAULT_MAX_RUNTIME_MINS)
showarda1e74b32009-05-12 17:32:04 +00001727 result['parse_failed_repair_default'] = bool(
1728 models.Job.DEFAULT_PARSE_FAILED_REPAIR)
jamesrendd855242010-03-02 22:23:44 +00001729 result['reboot_before_options'] = model_attributes.RebootBefore.names
1730 result['reboot_after_options'] = model_attributes.RebootAfter.names
showard8fbae652009-01-20 23:23:10 +00001731 result['motd'] = rpc_utils.get_motd()
jamesren76fcf192010-04-21 20:39:50 +00001732 result['drone_sets_enabled'] = models.DroneSet.drone_sets_enabled()
1733 result['drone_sets'] = drone_sets
jamesren4a41e012010-07-16 22:33:48 +00001734 result['parameterized_jobs'] = models.Job.parameterized_jobs_enabled()
showard8ac29b42008-07-17 17:01:55 +00001735
showardd3dc1992009-04-22 21:01:40 +00001736 result['status_dictionary'] = {"Aborted": "Aborted",
showard8ac29b42008-07-17 17:01:55 +00001737 "Verifying": "Verifying Host",
Alex Millerdfff2fd2013-05-28 13:05:06 -07001738 "Provisioning": "Provisioning Host",
showard8ac29b42008-07-17 17:01:55 +00001739 "Pending": "Waiting on other hosts",
1740 "Running": "Running autoserv",
1741 "Completed": "Autoserv completed",
1742 "Failed": "Failed to complete",
showardd823b362008-07-24 16:35:46 +00001743 "Queued": "Queued",
showard5deb6772008-11-04 21:54:33 +00001744 "Starting": "Next in host's queue",
1745 "Stopped": "Other host(s) failed verify",
showardd3dc1992009-04-22 21:01:40 +00001746 "Parsing": "Awaiting parse of final results",
showard29f7cd22009-04-29 21:16:24 +00001747 "Gathering": "Gathering log files",
showard8cc058f2009-09-08 16:26:33 +00001748 "Template": "Template job for recurring run",
mbligh4608b002010-01-05 18:22:35 +00001749 "Waiting": "Waiting for scheduler action",
Dan Shi07e09af2013-04-12 09:31:29 -07001750 "Archiving": "Archiving results",
1751 "Resetting": "Resetting hosts"}
Jiaxi Luo421608e2014-07-07 14:38:00 -07001752
1753 result['wmatrix_url'] = rpc_utils.get_wmatrix_url()
Simran Basi71206ef2014-08-13 13:51:18 -07001754 result['is_moblab'] = bool(utils.is_moblab())
Jiaxi Luo421608e2014-07-07 14:38:00 -07001755
jadmanski0afbb632008-06-06 21:10:57 +00001756 return result
showard29f7cd22009-04-29 21:16:24 +00001757
1758
1759def get_server_time():
1760 return datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
Kevin Cheng19521982016-09-22 12:27:23 -07001761
1762
1763def get_hosts_by_attribute(attribute, value):
1764 """
1765 Get the list of valid hosts that share the same host attribute value.
1766
1767 @param attribute: String of the host attribute to check.
1768 @param value: String of the value that is shared between hosts.
1769
1770 @returns List of hostnames that all have the same host attribute and
1771 value.
1772 """
1773 hosts = models.HostAttribute.query_objects({'attribute': attribute,
1774 'value': value})
1775 return [row.host.hostname for row in hosts if row.host.invalid == 0]