blob: 16dd0173d9ff55e94db0f8e281d54e9f9e850442 [file] [log] [blame]
Aviv Keshet0b9cfc92013-02-05 11:36:02 -08001# pylint: disable-msg=C0111
2
mblighe8819cd2008-02-15 16:48:40 +00003"""\
4Functions to expose over the RPC interface.
5
6For all modify* and delete* functions that ask for an 'id' parameter to
7identify the object to operate on, the id may be either
8 * the database row ID
9 * the name of the object (label name, hostname, user login, etc.)
10 * a dictionary containing uniquely identifying field (this option should seldom
11 be used)
12
13When specifying foreign key fields (i.e. adding hosts to a label, or adding
14users to an ACL group), the given value may be either the database row ID or the
15name of the object.
16
17All get* functions return lists of dictionaries. Each dictionary represents one
18object and maps field names to values.
19
20Some examples:
21modify_host(2, hostname='myhost') # modify hostname of host with database ID 2
22modify_host('ipaj2', hostname='myhost') # modify hostname of host 'ipaj2'
23modify_test('sleeptest', test_type='Client', params=', seconds=60')
24delete_acl_group(1) # delete by ID
25delete_acl_group('Everyone') # delete by name
26acl_group_add_users('Everyone', ['mbligh', 'showard'])
27get_jobs(owner='showard', status='Queued')
28
mbligh93c80e62009-02-03 17:48:30 +000029See doctests/001_rpc_test.txt for (lots) more examples.
mblighe8819cd2008-02-15 16:48:40 +000030"""
31
32__author__ = 'showard@google.com (Steve Howard)'
33
MK Ryu9c5fbbe2015-02-11 15:46:22 -080034import sys
showard29f7cd22009-04-29 21:16:24 +000035import datetime
MK Ryu9c5fbbe2015-02-11 15:46:22 -080036
Moises Osorio2dc7a102014-12-02 18:24:02 -080037from django.db.models import Count
showardcafd16e2009-05-29 18:37:49 +000038import common
Simran Basib6ec8ae2014-04-23 12:05:08 -070039from autotest_lib.client.common_lib import priorities
Gabe Black1e1c41b2015-02-04 23:55:15 -080040from autotest_lib.client.common_lib.cros.graphite import autotest_stats
showard6d7b2ff2009-06-10 00:16:47 +000041from autotest_lib.frontend.afe import control_file, rpc_utils
J. Richard Barnetteb5164d62015-04-13 12:59:31 -070042from autotest_lib.frontend.afe import models, model_logic, model_attributes
Simran Basib6ec8ae2014-04-23 12:05:08 -070043from autotest_lib.frontend.afe import site_rpc_interface
Moises Osorio2dc7a102014-12-02 18:24:02 -080044from autotest_lib.frontend.tko import models as tko_models
Jiaxi Luoaac54572014-06-04 13:57:02 -070045from autotest_lib.frontend.tko import rpc_interface as tko_rpc_interface
J. Richard Barnetteb5164d62015-04-13 12:59:31 -070046from autotest_lib.server import frontend
Simran Basi71206ef2014-08-13 13:51:18 -070047from autotest_lib.server import utils
Jiaxi Luo90190c92014-06-18 12:35:57 -070048from autotest_lib.server.cros.dynamic_suite import tools
mblighe8819cd2008-02-15 16:48:40 +000049
Moises Osorio2dc7a102014-12-02 18:24:02 -080050
Gabe Black1e1c41b2015-02-04 23:55:15 -080051_timer = autotest_stats.Timer('rpc_interface')
Moises Osorio2dc7a102014-12-02 18:24:02 -080052
Eric Lid23bc192011-02-09 14:38:57 -080053def get_parameterized_autoupdate_image_url(job):
54 """Get the parameterized autoupdate image url from a parameterized job."""
55 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
56 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
beeps8bb1f7d2013-08-05 01:30:09 -070057 name='image')
Eric Lid23bc192011-02-09 14:38:57 -080058 para_set = job.parameterized_job.parameterizedjobparameter_set
59 job_test_para = para_set.get(test_parameter=image_parameter)
60 return job_test_para.parameter_value
61
62
mblighe8819cd2008-02-15 16:48:40 +000063# labels
64
mblighe8819cd2008-02-15 16:48:40 +000065def modify_label(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +000066 models.Label.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +000067
68
69def delete_label(id):
jadmanski0afbb632008-06-06 21:10:57 +000070 models.Label.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +000071
Prashanth Balasubramanian744898f2015-01-13 05:04:16 -080072
MK Ryu9c5fbbe2015-02-11 15:46:22 -080073def add_label(name, ignore_exception_if_exists=False, **kwargs):
MK Ryucf027c62015-03-04 12:00:50 -080074 """Adds a new label of a given name.
MK Ryu9c5fbbe2015-02-11 15:46:22 -080075
76 @param name: label name.
77 @param ignore_exception_if_exists: If True and the exception was
78 thrown due to the duplicated label name when adding a label,
79 then suppress the exception. Default is False.
80 @param kwargs: keyword args that store more info about a label
81 other than the name.
82 @return: int/long id of a new label.
83 """
84 # models.Label.add_object() throws model_logic.ValidationError
85 # when it is given a label name that already exists.
86 # However, ValidationError can be thrown with different errors,
87 # and those errors should be thrown up to the call chain.
88 try:
89 label = models.Label.add_object(name=name, **kwargs)
90 except:
91 exc_info = sys.exc_info()
92 if ignore_exception_if_exists:
93 label = rpc_utils.get_label(name)
94 # If the exception is raised not because of duplicated
95 # "name", then raise the original exception.
96 if label is None:
97 raise exc_info[0], exc_info[1], exc_info[2]
98 else:
99 raise exc_info[0], exc_info[1], exc_info[2]
100 return label.id
101
102
103def add_label_to_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800104 """Adds a label of the given id to the given hosts only in local DB.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800105
106 @param id: id or name of a label. More often a label name.
107 @param hosts: The hostnames of hosts that need the label.
108
109 @raises models.Label.DoesNotExist: If the label with id doesn't exist.
110 """
111 label = models.Label.smart_get(id)
112 host_objs = models.Host.smart_get_bulk(hosts)
113 if label.platform:
114 models.Host.check_no_platform(host_objs)
115 label.host_set.add(*host_objs)
116
117
118def label_add_hosts(id, hosts):
MK Ryucf027c62015-03-04 12:00:50 -0800119 """Adds a label with the given id to the given hosts.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800120
121 This method should be run only on master not shards.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800122 The given label will be created if it doesn't exist, provided the `id`
123 supplied is a label name not an int/long id.
124
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800125 @param id: id or name of a label. More often a label name.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800126 @param hosts: A list of hostnames or ids. More often hostnames.
127
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800128 @raises ValueError: If the id specified is an int/long (label id)
129 while the label does not exist.
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800130 """
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800131 # This RPC call should be accepted only by master.
132 if utils.is_shard():
133 rpc_utils.route_rpc_to_master('label_add_hosts', id=id, hosts=hosts)
134 return
135
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800136 try:
Prashanth Balasubramanian5949b4a2014-11-23 12:58:30 -0800137 label = models.Label.smart_get(id)
138 except models.Label.DoesNotExist:
139 # This matches the type checks in smart_get, which is a hack
140 # in and off itself. The aim here is to create any non-existent
141 # label, which we cannot do if the 'id' specified isn't a label name.
142 if isinstance(id, basestring):
143 label = models.Label.smart_get(add_label(id))
144 else:
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800145 raise ValueError('Label id (%s) does not exist. Please specify '
146 'the argument, id, as a string (label name).'
147 % id)
148 add_label_to_hosts(id, hosts)
MK Ryucf027c62015-03-04 12:00:50 -0800149
150 host_objs = models.Host.smart_get_bulk(hosts)
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800151 # Make sure the label exists on the shard with the same id
152 # as it is on the master.
MK Ryucf027c62015-03-04 12:00:50 -0800153 # It is possible that the label is already in a shard because
154 # we are adding a new label only to shards of hosts that the label
155 # is going to be attached.
156 # For example, we add a label L1 to a host in shard S1.
157 # Master and S1 will have L1 but other shards won't.
158 # Later, when we add the same label L1 to hosts in shards S1 and S2,
159 # S1 already has the label but S2 doesn't.
160 # S2 should have the new label without any problem.
MK Ryu9c5fbbe2015-02-11 15:46:22 -0800161 # We ignore exception in such a case.
162 rpc_utils.fanout_rpc(
163 host_objs, 'add_label', name=label.name, id=label.id,
164 include_hostnames=False, ignore_exception_if_exists=True)
165 rpc_utils.fanout_rpc(host_objs, 'add_label_to_hosts', id=id)
showardbbabf502008-06-06 00:02:02 +0000166
167
MK Ryucf027c62015-03-04 12:00:50 -0800168def remove_label_from_hosts(id, hosts):
169 """Removes a label of the given id from the given hosts only in local DB.
170
171 @param id: id or name of a label.
172 @param hosts: The hostnames of hosts that need to remove the label from.
173 """
showardbe3ec042008-11-12 18:16:07 +0000174 host_objs = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000175 models.Label.smart_get(id).host_set.remove(*host_objs)
showardbbabf502008-06-06 00:02:02 +0000176
177
MK Ryucf027c62015-03-04 12:00:50 -0800178def label_remove_hosts(id, hosts):
179 """Removes a label of the given id from the given hosts.
180
181 This method should be run only on master not shards.
182
183 @param id: id or name of a label.
184 @param hosts: A list of hostnames or ids. More often hostnames.
185 """
186 # This RPC call should be accepted only by master.
187 if utils.is_shard():
188 rpc_utils.route_rpc_to_master('label_remove_hosts', id=id, hosts=hosts)
189 return
190
191 remove_label_from_hosts(id, hosts)
192 host_objs = models.Host.smart_get_bulk(hosts)
193 rpc_utils.fanout_rpc(host_objs, 'remove_label_from_hosts', id=id)
194
195
Jiaxi Luo31874592014-06-11 10:36:35 -0700196def get_labels(exclude_filters=(), **filter_data):
showardc92da832009-04-07 18:14:34 +0000197 """\
Jiaxi Luo31874592014-06-11 10:36:35 -0700198 @param exclude_filters: A sequence of dictionaries of filters.
199
showardc92da832009-04-07 18:14:34 +0000200 @returns A sequence of nested dictionaries of label information.
201 """
Jiaxi Luo31874592014-06-11 10:36:35 -0700202 labels = models.Label.query_objects(filter_data)
203 for exclude_filter in exclude_filters:
204 labels = labels.exclude(**exclude_filter)
205 return rpc_utils.prepare_rows_as_nested_dicts(labels, ('atomic_group',))
showardc92da832009-04-07 18:14:34 +0000206
207
208# atomic groups
209
showarde9450c92009-06-30 01:58:52 +0000210def add_atomic_group(name, max_number_of_machines=None, description=None):
showardc92da832009-04-07 18:14:34 +0000211 return models.AtomicGroup.add_object(
212 name=name, max_number_of_machines=max_number_of_machines,
213 description=description).id
214
215
216def modify_atomic_group(id, **data):
217 models.AtomicGroup.smart_get(id).update_object(data)
218
219
220def delete_atomic_group(id):
221 models.AtomicGroup.smart_get(id).delete()
222
223
224def atomic_group_add_labels(id, labels):
225 label_objs = models.Label.smart_get_bulk(labels)
226 models.AtomicGroup.smart_get(id).label_set.add(*label_objs)
227
228
229def atomic_group_remove_labels(id, labels):
230 label_objs = models.Label.smart_get_bulk(labels)
231 models.AtomicGroup.smart_get(id).label_set.remove(*label_objs)
232
233
234def get_atomic_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000235 return rpc_utils.prepare_for_serialization(
showardc92da832009-04-07 18:14:34 +0000236 models.AtomicGroup.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000237
238
239# hosts
240
showarddf062562008-07-03 19:56:37 +0000241def add_host(hostname, status=None, locked=None, protection=None):
jadmanski0afbb632008-06-06 21:10:57 +0000242 return models.Host.add_object(hostname=hostname, status=status,
showarddf062562008-07-03 19:56:37 +0000243 locked=locked, protection=protection).id
mblighe8819cd2008-02-15 16:48:40 +0000244
245
Jakob Juelich50e91f72014-10-01 12:43:23 -0700246@rpc_utils.forward_single_host_rpc_to_shard
mblighe8819cd2008-02-15 16:48:40 +0000247def modify_host(id, **data):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700248 """Modify local attributes of a host.
249
250 If this is called on the master, but the host is assigned to a shard, this
251 will also forward the call to the responsible shard. This means i.e. if a
252 host is being locked using this function, this change will also propagate to
253 shards.
254
255 @param id: id of the host to modify.
256 @param **data: key=value pairs of values to set on the host.
257 """
showardbe0d8692009-08-20 23:42:44 +0000258 rpc_utils.check_modify_host(data)
showardce7c0922009-09-11 18:39:24 +0000259 host = models.Host.smart_get(id)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700260
showardce7c0922009-09-11 18:39:24 +0000261 rpc_utils.check_modify_host_locking(host, data)
262 host.update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000263
264
showard276f9442009-05-20 00:33:16 +0000265def modify_hosts(host_filter_data, update_data):
Jakob Juelich50e91f72014-10-01 12:43:23 -0700266 """Modify local attributes of multiple hosts.
267
268 If this is called on the master, but one of the hosts in that match the
269 filters is assigned to a shard, this will also forward the call to the
270 responsible shard.
271
272 The filters are always applied on the master, not on the shards. This means
273 if the states of a host differ on the master and a shard, the state on the
274 master will be used. I.e. this means:
275 A host was synced to Shard 1. On Shard 1 the status of the host was set to
276 'Repair Failed'.
277 - A call to modify_hosts with host_filter_data={'status': 'Ready'} will
278 update the host (both on the shard and on the master), because the state
279 of the host as the master knows it is still 'Ready'.
280 - A call to modify_hosts with host_filter_data={'status': 'Repair failed'
281 will not update the host, because the filter doesn't apply on the master.
282
showardbe0d8692009-08-20 23:42:44 +0000283 @param host_filter_data: Filters out which hosts to modify.
284 @param update_data: A dictionary with the changes to make to the hosts.
showard276f9442009-05-20 00:33:16 +0000285 """
showardbe0d8692009-08-20 23:42:44 +0000286 rpc_utils.check_modify_host(update_data)
showard276f9442009-05-20 00:33:16 +0000287 hosts = models.Host.query_objects(host_filter_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700288
289 affected_shard_hostnames = set()
290 affected_host_ids = []
291
Alex Miller9658a952013-05-14 16:40:02 -0700292 # Check all hosts before changing data for exception safety.
293 for host in hosts:
294 rpc_utils.check_modify_host_locking(host, update_data)
Jakob Juelich50e91f72014-10-01 12:43:23 -0700295 if host.shard:
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800296 affected_shard_hostnames.add(host.shard.rpc_hostname())
Jakob Juelich50e91f72014-10-01 12:43:23 -0700297 affected_host_ids.append(host.id)
298
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800299 if not utils.is_shard():
Jakob Juelich50e91f72014-10-01 12:43:23 -0700300 # Caution: Changing the filter from the original here. See docstring.
301 rpc_utils.run_rpc_on_multiple_hostnames(
302 'modify_hosts', affected_shard_hostnames,
303 host_filter_data={'id__in': affected_host_ids},
304 update_data=update_data)
305
showard276f9442009-05-20 00:33:16 +0000306 for host in hosts:
307 host.update_object(update_data)
308
309
mblighe8819cd2008-02-15 16:48:40 +0000310def host_add_labels(id, labels):
showardbe3ec042008-11-12 18:16:07 +0000311 labels = models.Label.smart_get_bulk(labels)
showardcafd16e2009-05-29 18:37:49 +0000312 host = models.Host.smart_get(id)
313
314 platforms = [label.name for label in labels if label.platform]
315 if len(platforms) > 1:
316 raise model_logic.ValidationError(
317 {'labels': 'Adding more than one platform label: %s' %
318 ', '.join(platforms)})
319 if len(platforms) == 1:
320 models.Host.check_no_platform([host])
321 host.labels.add(*labels)
mblighe8819cd2008-02-15 16:48:40 +0000322
323
324def host_remove_labels(id, labels):
showardbe3ec042008-11-12 18:16:07 +0000325 labels = models.Label.smart_get_bulk(labels)
jadmanski0afbb632008-06-06 21:10:57 +0000326 models.Host.smart_get(id).labels.remove(*labels)
mblighe8819cd2008-02-15 16:48:40 +0000327
328
MK Ryuacf35922014-10-03 14:56:49 -0700329def get_host_attribute(attribute, **host_filter_data):
330 """
331 @param attribute: string name of attribute
332 @param host_filter_data: filter data to apply to Hosts to choose hosts to
333 act upon
334 """
335 hosts = rpc_utils.get_host_query((), False, False, True, host_filter_data)
336 hosts = list(hosts)
337 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
338 'attribute_list')
339 host_attr_dicts = []
340 for host_obj in hosts:
341 for attr_obj in host_obj.attribute_list:
342 if attr_obj.attribute == attribute:
343 host_attr_dicts.append(attr_obj.get_object_dict())
344 return rpc_utils.prepare_for_serialization(host_attr_dicts)
345
346
showard0957a842009-05-11 19:25:08 +0000347def set_host_attribute(attribute, value, **host_filter_data):
348 """
349 @param attribute string name of attribute
350 @param value string, or None to delete an attribute
351 @param host_filter_data filter data to apply to Hosts to choose hosts to act
352 upon
353 """
354 assert host_filter_data # disallow accidental actions on all hosts
355 hosts = models.Host.query_objects(host_filter_data)
356 models.AclGroup.check_for_acl_violation_hosts(hosts)
357
358 for host in hosts:
showardf8b19042009-05-12 17:22:49 +0000359 host.set_or_delete_attribute(attribute, value)
showard0957a842009-05-11 19:25:08 +0000360
361
Jakob Juelich50e91f72014-10-01 12:43:23 -0700362@rpc_utils.forward_single_host_rpc_to_shard
mblighe8819cd2008-02-15 16:48:40 +0000363def delete_host(id):
jadmanski0afbb632008-06-06 21:10:57 +0000364 models.Host.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000365
366
showard87cc38f2009-08-20 23:37:04 +0000367def get_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000368 exclude_atomic_group_hosts=False, valid_only=True, **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000369 """
370 @param multiple_labels: match hosts in all of the labels given. Should
371 be a list of label names.
372 @param exclude_only_if_needed_labels: Exclude hosts with at least one
373 "only_if_needed" label applied.
374 @param exclude_atomic_group_hosts: Exclude hosts that have one or more
375 atomic group labels associated with them.
jadmanski0afbb632008-06-06 21:10:57 +0000376 """
showard43a3d262008-11-12 18:17:05 +0000377 hosts = rpc_utils.get_host_query(multiple_labels,
378 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000379 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000380 valid_only, filter_data)
showard0957a842009-05-11 19:25:08 +0000381 hosts = list(hosts)
382 models.Host.objects.populate_relationships(hosts, models.Label,
383 'label_list')
384 models.Host.objects.populate_relationships(hosts, models.AclGroup,
385 'acl_list')
386 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
387 'attribute_list')
showard43a3d262008-11-12 18:17:05 +0000388 host_dicts = []
389 for host_obj in hosts:
390 host_dict = host_obj.get_object_dict()
showard0957a842009-05-11 19:25:08 +0000391 host_dict['labels'] = [label.name for label in host_obj.label_list]
showard909c9142009-07-07 20:54:42 +0000392 host_dict['platform'], host_dict['atomic_group'] = (rpc_utils.
393 find_platform_and_atomic_group(host_obj))
showard0957a842009-05-11 19:25:08 +0000394 host_dict['acls'] = [acl.name for acl in host_obj.acl_list]
395 host_dict['attributes'] = dict((attribute.attribute, attribute.value)
396 for attribute in host_obj.attribute_list)
showard43a3d262008-11-12 18:17:05 +0000397 host_dicts.append(host_dict)
398 return rpc_utils.prepare_for_serialization(host_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000399
400
showard87cc38f2009-08-20 23:37:04 +0000401def get_num_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000402 exclude_atomic_group_hosts=False, valid_only=True,
403 **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000404 """
405 Same parameters as get_hosts().
406
407 @returns The number of matching hosts.
408 """
showard43a3d262008-11-12 18:17:05 +0000409 hosts = rpc_utils.get_host_query(multiple_labels,
410 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000411 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000412 valid_only, filter_data)
showard43a3d262008-11-12 18:17:05 +0000413 return hosts.count()
showard1385b162008-03-13 15:59:40 +0000414
mblighe8819cd2008-02-15 16:48:40 +0000415
416# tests
417
showard909c7a62008-07-15 21:52:38 +0000418def add_test(name, test_type, path, author=None, dependencies=None,
showard3d9899a2008-07-31 02:11:58 +0000419 experimental=True, run_verify=None, test_class=None,
showard909c7a62008-07-15 21:52:38 +0000420 test_time=None, test_category=None, description=None,
421 sync_count=1):
jadmanski0afbb632008-06-06 21:10:57 +0000422 return models.Test.add_object(name=name, test_type=test_type, path=path,
showard909c7a62008-07-15 21:52:38 +0000423 author=author, dependencies=dependencies,
424 experimental=experimental,
425 run_verify=run_verify, test_time=test_time,
426 test_category=test_category,
427 sync_count=sync_count,
jadmanski0afbb632008-06-06 21:10:57 +0000428 test_class=test_class,
429 description=description).id
mblighe8819cd2008-02-15 16:48:40 +0000430
431
432def modify_test(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000433 models.Test.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000434
435
436def delete_test(id):
jadmanski0afbb632008-06-06 21:10:57 +0000437 models.Test.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000438
439
440def get_tests(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000441 return rpc_utils.prepare_for_serialization(
442 models.Test.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000443
444
Moises Osorio2dc7a102014-12-02 18:24:02 -0800445@_timer.decorate
446def get_tests_status_counts_by_job_name_label(job_name_prefix, label_name):
447 """Gets the counts of all passed and failed tests from the matching jobs.
448
449 @param job_name_prefix: Name prefix of the jobs to get the summary from, e.g.,
450 'butterfly-release/R40-6457.21.0/bvt-cq/'.
451 @param label_name: Label that must be set in the jobs, e.g.,
452 'cros-version:butterfly-release/R40-6457.21.0'.
453
454 @returns A summary of the counts of all the passed and failed tests.
455 """
456 job_ids = list(models.Job.objects.filter(
457 name__startswith=job_name_prefix,
458 dependency_labels__name=label_name).values_list(
459 'pk', flat=True))
460 summary = {'passed': 0, 'failed': 0}
461 if not job_ids:
462 return summary
463
464 counts = (tko_models.TestView.objects.filter(
465 afe_job_id__in=job_ids).exclude(
466 test_name='SERVER_JOB').exclude(
467 test_name__startswith='CLIENT_JOB').values(
468 'status').annotate(
469 count=Count('status')))
470 for status in counts:
471 if status['status'] == 'GOOD':
472 summary['passed'] += status['count']
473 else:
474 summary['failed'] += status['count']
475 return summary
476
477
showard2b9a88b2008-06-13 20:55:03 +0000478# profilers
479
480def add_profiler(name, description=None):
481 return models.Profiler.add_object(name=name, description=description).id
482
483
484def modify_profiler(id, **data):
485 models.Profiler.smart_get(id).update_object(data)
486
487
488def delete_profiler(id):
489 models.Profiler.smart_get(id).delete()
490
491
492def get_profilers(**filter_data):
493 return rpc_utils.prepare_for_serialization(
494 models.Profiler.list_objects(filter_data))
495
496
mblighe8819cd2008-02-15 16:48:40 +0000497# users
498
499def add_user(login, access_level=None):
jadmanski0afbb632008-06-06 21:10:57 +0000500 return models.User.add_object(login=login, access_level=access_level).id
mblighe8819cd2008-02-15 16:48:40 +0000501
502
503def modify_user(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000504 models.User.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000505
506
507def delete_user(id):
jadmanski0afbb632008-06-06 21:10:57 +0000508 models.User.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000509
510
511def get_users(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000512 return rpc_utils.prepare_for_serialization(
513 models.User.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000514
515
516# acl groups
517
518def add_acl_group(name, description=None):
showard04f2cd82008-07-25 20:53:31 +0000519 group = models.AclGroup.add_object(name=name, description=description)
showard64a95952010-01-13 21:27:16 +0000520 group.users.add(models.User.current_user())
showard04f2cd82008-07-25 20:53:31 +0000521 return group.id
mblighe8819cd2008-02-15 16:48:40 +0000522
523
524def modify_acl_group(id, **data):
showard04f2cd82008-07-25 20:53:31 +0000525 group = models.AclGroup.smart_get(id)
526 group.check_for_acl_violation_acl_group()
527 group.update_object(data)
528 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000529
530
531def acl_group_add_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000532 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000533 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000534 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000535 group.users.add(*users)
mblighe8819cd2008-02-15 16:48:40 +0000536
537
538def acl_group_remove_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000539 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000540 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000541 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000542 group.users.remove(*users)
showard04f2cd82008-07-25 20:53:31 +0000543 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000544
545
546def acl_group_add_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000547 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000548 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000549 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000550 group.hosts.add(*hosts)
showard08f981b2008-06-24 21:59:03 +0000551 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000552
553
554def acl_group_remove_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000555 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000556 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000557 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000558 group.hosts.remove(*hosts)
showard08f981b2008-06-24 21:59:03 +0000559 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000560
561
562def delete_acl_group(id):
jadmanski0afbb632008-06-06 21:10:57 +0000563 models.AclGroup.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000564
565
566def get_acl_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000567 acl_groups = models.AclGroup.list_objects(filter_data)
568 for acl_group in acl_groups:
569 acl_group_obj = models.AclGroup.objects.get(id=acl_group['id'])
570 acl_group['users'] = [user.login
571 for user in acl_group_obj.users.all()]
572 acl_group['hosts'] = [host.hostname
573 for host in acl_group_obj.hosts.all()]
574 return rpc_utils.prepare_for_serialization(acl_groups)
mblighe8819cd2008-02-15 16:48:40 +0000575
576
577# jobs
578
mbligh120351e2009-01-24 01:40:45 +0000579def generate_control_file(tests=(), kernel=None, label=None, profilers=(),
showard91f85102009-10-12 20:34:52 +0000580 client_control_file='', use_container=False,
showard232b7ae2009-11-10 00:46:48 +0000581 profile_only=None, upload_kernel_config=False):
jadmanski0afbb632008-06-06 21:10:57 +0000582 """
mbligh120351e2009-01-24 01:40:45 +0000583 Generates a client-side control file to load a kernel and run tests.
584
585 @param tests List of tests to run.
mbligha3c58d22009-08-24 22:01:51 +0000586 @param kernel A list of kernel info dictionaries configuring which kernels
587 to boot for this job and other options for them
mbligh120351e2009-01-24 01:40:45 +0000588 @param label Name of label to grab kernel config from.
589 @param profilers List of profilers to activate during the job.
590 @param client_control_file The contents of a client-side control file to
591 run at the end of all tests. If this is supplied, all tests must be
592 client side.
593 TODO: in the future we should support server control files directly
594 to wrap with a kernel. That'll require changing the parameter
595 name and adding a boolean to indicate if it is a client or server
596 control file.
597 @param use_container unused argument today. TODO: Enable containers
598 on the host during a client side test.
showard91f85102009-10-12 20:34:52 +0000599 @param profile_only A boolean that indicates what default profile_only
600 mode to use in the control file. Passing None will generate a
601 control file that does not explcitly set the default mode at all.
showard232b7ae2009-11-10 00:46:48 +0000602 @param upload_kernel_config: if enabled it will generate server control
603 file code that uploads the kernel config file to the client and
604 tells the client of the new (local) path when compiling the kernel;
605 the tests must be server side tests
mbligh120351e2009-01-24 01:40:45 +0000606
607 @returns a dict with the following keys:
608 control_file: str, The control file text.
609 is_server: bool, is the control file a server-side control file?
610 synch_count: How many machines the job uses per autoserv execution.
611 synch_count == 1 means the job is asynchronous.
612 dependencies: A list of the names of labels on which the job depends.
613 """
showardd86debe2009-06-10 17:37:56 +0000614 if not tests and not client_control_file:
showard2bab8f42008-11-12 18:15:22 +0000615 return dict(control_file='', is_server=False, synch_count=1,
showard989f25d2008-10-01 11:38:11 +0000616 dependencies=[])
mblighe8819cd2008-02-15 16:48:40 +0000617
showard989f25d2008-10-01 11:38:11 +0000618 cf_info, test_objects, profiler_objects, label = (
showard2b9a88b2008-06-13 20:55:03 +0000619 rpc_utils.prepare_generate_control_file(tests, kernel, label,
620 profilers))
showard989f25d2008-10-01 11:38:11 +0000621 cf_info['control_file'] = control_file.generate_control(
mbligha3c58d22009-08-24 22:01:51 +0000622 tests=test_objects, kernels=kernel, platform=label,
mbligh120351e2009-01-24 01:40:45 +0000623 profilers=profiler_objects, is_server=cf_info['is_server'],
showard232b7ae2009-11-10 00:46:48 +0000624 client_control_file=client_control_file, profile_only=profile_only,
625 upload_kernel_config=upload_kernel_config)
showard989f25d2008-10-01 11:38:11 +0000626 return cf_info
mblighe8819cd2008-02-15 16:48:40 +0000627
628
jamesren4a41e012010-07-16 22:33:48 +0000629def create_parameterized_job(name, priority, test, parameters, kernel=None,
630 label=None, profilers=(), profiler_parameters=None,
631 use_container=False, profile_only=None,
632 upload_kernel_config=False, hosts=(),
633 meta_hosts=(), one_time_hosts=(),
634 atomic_group_name=None, synch_count=None,
635 is_template=False, timeout=None,
Simran Basi7e605742013-11-12 13:43:36 -0800636 timeout_mins=None, max_runtime_mins=None,
637 run_verify=False, email_list='', dependencies=(),
638 reboot_before=None, reboot_after=None,
639 parse_failed_repair=None, hostless=False,
Dan Shiec1d47d2015-02-13 11:38:13 -0800640 keyvals=None, drone_set=None, run_reset=True,
641 require_ssq=None):
jamesren4a41e012010-07-16 22:33:48 +0000642 """
643 Creates and enqueues a parameterized job.
644
645 Most parameters a combination of the parameters for generate_control_file()
646 and create_job(), with the exception of:
647
648 @param test name or ID of the test to run
649 @param parameters a map of parameter name ->
650 tuple of (param value, param type)
651 @param profiler_parameters a dictionary of parameters for the profilers:
652 key: profiler name
653 value: dict of param name -> tuple of
654 (param value,
655 param type)
656 """
657 # Save the values of the passed arguments here. What we're going to do with
658 # them is pass them all to rpc_utils.get_create_job_common_args(), which
659 # will extract the subset of these arguments that apply for
660 # rpc_utils.create_job_common(), which we then pass in to that function.
661 args = locals()
662
663 # Set up the parameterized job configs
664 test_obj = models.Test.smart_get(test)
Aviv Keshet3dd8beb2013-05-13 17:36:04 -0700665 control_type = test_obj.test_type
jamesren4a41e012010-07-16 22:33:48 +0000666
667 try:
668 label = models.Label.smart_get(label)
669 except models.Label.DoesNotExist:
670 label = None
671
672 kernel_objs = models.Kernel.create_kernels(kernel)
673 profiler_objs = [models.Profiler.smart_get(profiler)
674 for profiler in profilers]
675
676 parameterized_job = models.ParameterizedJob.objects.create(
677 test=test_obj, label=label, use_container=use_container,
678 profile_only=profile_only,
679 upload_kernel_config=upload_kernel_config)
680 parameterized_job.kernels.add(*kernel_objs)
681
682 for profiler in profiler_objs:
683 parameterized_profiler = models.ParameterizedJobProfiler.objects.create(
684 parameterized_job=parameterized_job,
685 profiler=profiler)
686 profiler_params = profiler_parameters.get(profiler.name, {})
687 for name, (value, param_type) in profiler_params.iteritems():
688 models.ParameterizedJobProfilerParameter.objects.create(
689 parameterized_job_profiler=parameterized_profiler,
690 parameter_name=name,
691 parameter_value=value,
692 parameter_type=param_type)
693
694 try:
695 for parameter in test_obj.testparameter_set.all():
696 if parameter.name in parameters:
697 param_value, param_type = parameters.pop(parameter.name)
698 parameterized_job.parameterizedjobparameter_set.create(
699 test_parameter=parameter, parameter_value=param_value,
700 parameter_type=param_type)
701
702 if parameters:
703 raise Exception('Extra parameters remain: %r' % parameters)
704
705 return rpc_utils.create_job_common(
706 parameterized_job=parameterized_job.id,
707 control_type=control_type,
708 **rpc_utils.get_create_job_common_args(args))
709 except:
710 parameterized_job.delete()
711 raise
712
713
Simran Basib6ec8ae2014-04-23 12:05:08 -0700714def create_job_page_handler(name, priority, control_file, control_type,
715 image=None, hostless=False, **kwargs):
716 """\
717 Create and enqueue a job.
718
719 @param name name of this job
720 @param priority Integer priority of this job. Higher is more important.
721 @param control_file String contents of the control file.
722 @param control_type Type of control file, Client or Server.
723 @param kwargs extra args that will be required by create_suite_job or
724 create_job.
725
726 @returns The created Job id number.
727 """
728 control_file = rpc_utils.encode_ascii(control_file)
Jiaxi Luodd67beb2014-07-18 16:28:31 -0700729 if not control_file:
730 raise model_logic.ValidationError({
731 'control_file' : "Control file cannot be empty"})
Simran Basib6ec8ae2014-04-23 12:05:08 -0700732
733 if image and hostless:
734 return site_rpc_interface.create_suite_job(
735 name=name, control_file=control_file, priority=priority,
736 build=image, **kwargs)
737 return create_job(name, priority, control_file, control_type, image=image,
738 hostless=hostless, **kwargs)
739
740
showard12f3e322009-05-13 21:27:42 +0000741def create_job(name, priority, control_file, control_type,
742 hosts=(), meta_hosts=(), one_time_hosts=(),
743 atomic_group_name=None, synch_count=None, is_template=False,
Simran Basi7e605742013-11-12 13:43:36 -0800744 timeout=None, timeout_mins=None, max_runtime_mins=None,
745 run_verify=False, email_list='', dependencies=(),
746 reboot_before=None, reboot_after=None, parse_failed_repair=None,
747 hostless=False, keyvals=None, drone_set=None, image=None,
Dan Shiec1d47d2015-02-13 11:38:13 -0800748 parent_job_id=None, test_retry=0, run_reset=True,
749 require_ssp=None, args=(), **kwargs):
jadmanski0afbb632008-06-06 21:10:57 +0000750 """\
751 Create and enqueue a job.
mblighe8819cd2008-02-15 16:48:40 +0000752
showarda1e74b32009-05-12 17:32:04 +0000753 @param name name of this job
Alex Miller7d658cf2013-09-04 16:00:35 -0700754 @param priority Integer priority of this job. Higher is more important.
showarda1e74b32009-05-12 17:32:04 +0000755 @param control_file String contents of the control file.
756 @param control_type Type of control file, Client or Server.
757 @param synch_count How many machines the job uses per autoserv execution.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700758 synch_count == 1 means the job is asynchronous. If an atomic group is
759 given this value is treated as a minimum.
showarda1e74b32009-05-12 17:32:04 +0000760 @param is_template If true then create a template job.
761 @param timeout Hours after this call returns until the job times out.
Simran Basi7e605742013-11-12 13:43:36 -0800762 @param timeout_mins Minutes after this call returns until the job times
Jiaxi Luo90190c92014-06-18 12:35:57 -0700763 out.
Simran Basi34217022012-11-06 13:43:15 -0800764 @param max_runtime_mins Minutes from job starting time until job times out
showarda1e74b32009-05-12 17:32:04 +0000765 @param run_verify Should the host be verified before running the test?
766 @param email_list String containing emails to mail when the job is done
767 @param dependencies List of label names on which this job depends
768 @param reboot_before Never, If dirty, or Always
769 @param reboot_after Never, If all tests passed, or Always
770 @param parse_failed_repair if true, results of failed repairs launched by
Jiaxi Luo90190c92014-06-18 12:35:57 -0700771 this job will be parsed as part of the job.
showarda9545c02009-12-18 22:44:26 +0000772 @param hostless if true, create a hostless job
showardc1a98d12010-01-15 00:22:22 +0000773 @param keyvals dict of keyvals to associate with the job
showarda1e74b32009-05-12 17:32:04 +0000774 @param hosts List of hosts to run job on.
775 @param meta_hosts List where each entry is a label name, and for each entry
Jiaxi Luo90190c92014-06-18 12:35:57 -0700776 one host will be chosen from that label to run the job on.
showarda1e74b32009-05-12 17:32:04 +0000777 @param one_time_hosts List of hosts not in the database to run the job on.
778 @param atomic_group_name The name of an atomic group to schedule the job on.
jamesren76fcf192010-04-21 20:39:50 +0000779 @param drone_set The name of the drone set to run this test on.
Paul Pendlebury5a8c6ad2011-02-01 07:20:17 -0800780 @param image OS image to install before running job.
Aviv Keshet0b9cfc92013-02-05 11:36:02 -0800781 @param parent_job_id id of a job considered to be parent of created job.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700782 @param test_retry Number of times to retry test if the test did not
Jiaxi Luo90190c92014-06-18 12:35:57 -0700783 complete successfully. (optional, default: 0)
Simran Basib6ec8ae2014-04-23 12:05:08 -0700784 @param run_reset Should the host be reset before running the test?
Dan Shiec1d47d2015-02-13 11:38:13 -0800785 @param require_ssp Set to True to require server-side packaging to run the
786 test. If it's set to None, drone will still try to run
787 the server side with server-side packaging. If the
788 autotest-server package doesn't exist for the build or
789 image is not set, drone will run the test without server-
790 side packaging. Default is None.
Jiaxi Luo90190c92014-06-18 12:35:57 -0700791 @param args A list of args to be injected into control file.
Simran Basib6ec8ae2014-04-23 12:05:08 -0700792 @param kwargs extra keyword args. NOT USED.
showardc92da832009-04-07 18:14:34 +0000793
794 @returns The created Job id number.
jadmanski0afbb632008-06-06 21:10:57 +0000795 """
Jiaxi Luo90190c92014-06-18 12:35:57 -0700796 if args:
797 control_file = tools.inject_vars({'args': args}, control_file)
798
Simran Basiab5a1bf2014-05-28 15:39:44 -0700799 if image is None:
800 return rpc_utils.create_job_common(
801 **rpc_utils.get_create_job_common_args(locals()))
802
803 # When image is supplied use a known parameterized test already in the
804 # database to pass the OS image path from the front end, through the
805 # scheduler, and finally to autoserv as the --image parameter.
806
807 # The test autoupdate_ParameterizedJob is in afe_autotests and used to
808 # instantiate a Test object and from there a ParameterizedJob.
809 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
810 known_parameterized_job = models.ParameterizedJob.objects.create(
811 test=known_test_obj)
812
813 # autoupdate_ParameterizedJob has a single parameter, the image parameter,
814 # stored in the table afe_test_parameters. We retrieve and set this
815 # instance of the parameter to the OS image path.
816 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
817 name='image')
818 known_parameterized_job.parameterizedjobparameter_set.create(
819 test_parameter=image_parameter, parameter_value=image,
820 parameter_type='string')
821
822 # By passing a parameterized_job to create_job_common the job entry in
823 # the afe_jobs table will have the field parameterized_job_id set.
824 # The scheduler uses this id in the afe_parameterized_jobs table to
825 # match this job to our known test, and then with the
826 # afe_parameterized_job_parameters table to get the actual image path.
jamesren4a41e012010-07-16 22:33:48 +0000827 return rpc_utils.create_job_common(
Simran Basiab5a1bf2014-05-28 15:39:44 -0700828 parameterized_job=known_parameterized_job.id,
jamesren4a41e012010-07-16 22:33:48 +0000829 **rpc_utils.get_create_job_common_args(locals()))
mblighe8819cd2008-02-15 16:48:40 +0000830
831
showard9dbdcda2008-10-14 17:34:36 +0000832def abort_host_queue_entries(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000833 """\
showard9dbdcda2008-10-14 17:34:36 +0000834 Abort a set of host queue entries.
Fang Deng63b0e452014-12-19 14:38:15 -0800835
836 @return: A list of dictionaries, each contains information
837 about an aborted HQE.
jadmanski0afbb632008-06-06 21:10:57 +0000838 """
showard9dbdcda2008-10-14 17:34:36 +0000839 query = models.HostQueueEntry.query_objects(filter_data)
beepsfaecbce2013-10-29 11:35:10 -0700840
841 # Dont allow aborts on:
842 # 1. Jobs that have already completed (whether or not they were aborted)
843 # 2. Jobs that we have already been aborted (but may not have completed)
844 query = query.filter(complete=False).filter(aborted=False)
showarddc817512008-11-12 18:16:41 +0000845 models.AclGroup.check_abort_permissions(query)
showard9dbdcda2008-10-14 17:34:36 +0000846 host_queue_entries = list(query.select_related())
showard2bab8f42008-11-12 18:15:22 +0000847 rpc_utils.check_abort_synchronous_jobs(host_queue_entries)
mblighe8819cd2008-02-15 16:48:40 +0000848
Simran Basic1b26762013-06-26 14:23:21 -0700849 models.HostQueueEntry.abort_host_queue_entries(host_queue_entries)
Fang Deng63b0e452014-12-19 14:38:15 -0800850 hqe_info = [{'HostQueueEntry': hqe.id, 'Job': hqe.job_id,
851 'Job name': hqe.job.name} for hqe in host_queue_entries]
852 return hqe_info
showard9d821ab2008-07-11 16:54:29 +0000853
854
beeps8bb1f7d2013-08-05 01:30:09 -0700855def abort_special_tasks(**filter_data):
856 """\
857 Abort the special task, or tasks, specified in the filter.
858 """
859 query = models.SpecialTask.query_objects(filter_data)
860 special_tasks = query.filter(is_active=True)
861 for task in special_tasks:
862 task.abort()
863
864
Simran Basi73dae552013-02-25 14:57:46 -0800865def _call_special_tasks_on_hosts(task, hosts):
866 """\
867 Schedules a set of hosts for a special task.
868
869 @returns A list of hostnames that a special task was created for.
870 """
871 models.AclGroup.check_for_acl_violation_hosts(hosts)
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -0800872 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts)
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800873 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian6edaaf92014-11-24 16:36:25 -0800874 raise ValueError('The following hosts are on shards, please '
875 'follow the link to the shards and create jobs '
876 'there instead. %s.' % shard_host_map)
Simran Basi73dae552013-02-25 14:57:46 -0800877 for host in hosts:
878 models.SpecialTask.schedule_special_task(host, task)
879 return list(sorted(host.hostname for host in hosts))
880
881
showard1ff7b2e2009-05-15 23:17:18 +0000882def reverify_hosts(**filter_data):
883 """\
884 Schedules a set of hosts for verify.
mbligh4e545a52009-12-19 05:30:39 +0000885
886 @returns A list of hostnames that a verify task was created for.
showard1ff7b2e2009-05-15 23:17:18 +0000887 """
Prashanth Balasubramanian40981232014-12-16 19:01:58 -0800888 hosts = models.Host.query_objects(filter_data)
889 shard_host_map = rpc_utils.bucket_hosts_by_shard(hosts, rpc_hostnames=True)
890
891 # Filter out hosts on a shard from those on the master, forward
892 # rpcs to the shard with an additional hostname__in filter, and
893 # create a local SpecialTask for each remaining host.
Prashanth Balasubramanian8c98ac12014-12-23 11:26:44 -0800894 if shard_host_map and not utils.is_shard():
Prashanth Balasubramanian40981232014-12-16 19:01:58 -0800895 hosts = [h for h in hosts if h.shard is None]
896 for shard, hostnames in shard_host_map.iteritems():
897
898 # The main client of this module is the frontend website, and
899 # it invokes it with an 'id' or an 'id__in' filter. Regardless,
900 # the 'hostname' filter should narrow down the list of hosts on
901 # each shard even though we supply all the ids in filter_data.
902 # This method uses hostname instead of id because it fits better
903 # with the overall architecture of redirection functions in rpc_utils.
904 shard_filter = filter_data.copy()
905 shard_filter['hostname__in'] = hostnames
906 rpc_utils.run_rpc_on_multiple_hostnames(
907 'reverify_hosts', [shard], **shard_filter)
908
909 # There is a race condition here if someone assigns a shard to one of these
910 # hosts before we create the task. The host will stay on the master if:
911 # 1. The host is not Ready
912 # 2. The host is Ready but has a task
913 # But if the host is Ready and doesn't have a task yet, it will get sent
914 # to the shard as we're creating a task here.
915
916 # Given that we only rarely verify Ready hosts it isn't worth putting this
917 # entire method in a transaction. The worst case scenario is that we have
918 # a verify running on a Ready host while the shard is using it, if the verify
919 # fails no subsequent tasks will be created against the host on the master,
920 # and verifies are safe enough that this is OK.
921 return _call_special_tasks_on_hosts(models.SpecialTask.Task.VERIFY, hosts)
Simran Basi73dae552013-02-25 14:57:46 -0800922
923
924def repair_hosts(**filter_data):
925 """\
926 Schedules a set of hosts for repair.
927
928 @returns A list of hostnames that a repair task was created for.
929 """
930 return _call_special_tasks_on_hosts(models.SpecialTask.Task.REPAIR,
931 models.Host.query_objects(filter_data))
showard1ff7b2e2009-05-15 23:17:18 +0000932
933
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700934def get_jobs(not_yet_run=False, running=False, finished=False,
935 suite=False, sub=False, standalone=False, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000936 """\
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700937 Extra status filter args for get_jobs:
jadmanski0afbb632008-06-06 21:10:57 +0000938 -not_yet_run: Include only jobs that have not yet started running.
939 -running: Include only jobs that have start running but for which not
940 all hosts have completed.
941 -finished: Include only jobs for which all hosts have completed (or
942 aborted).
943 At most one of these three fields should be specified.
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700944
945 Extra type filter args for get_jobs:
946 -suite: Include only jobs with child jobs.
947 -sub: Include only jobs with a parent job.
948 -standalone: Inlcude only jobs with no child or parent jobs.
949 At most one of these three fields should be specified.
jadmanski0afbb632008-06-06 21:10:57 +0000950 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700951 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
952 running,
953 finished)
954 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
955 suite,
956 sub,
957 standalone)
showard0957a842009-05-11 19:25:08 +0000958 job_dicts = []
959 jobs = list(models.Job.query_objects(filter_data))
960 models.Job.objects.populate_relationships(jobs, models.Label,
961 'dependencies')
showardc1a98d12010-01-15 00:22:22 +0000962 models.Job.objects.populate_relationships(jobs, models.JobKeyval, 'keyvals')
showard0957a842009-05-11 19:25:08 +0000963 for job in jobs:
964 job_dict = job.get_object_dict()
965 job_dict['dependencies'] = ','.join(label.name
966 for label in job.dependencies)
showardc1a98d12010-01-15 00:22:22 +0000967 job_dict['keyvals'] = dict((keyval.key, keyval.value)
968 for keyval in job.keyvals)
Eric Lid23bc192011-02-09 14:38:57 -0800969 if job.parameterized_job:
970 job_dict['image'] = get_parameterized_autoupdate_image_url(job)
showard0957a842009-05-11 19:25:08 +0000971 job_dicts.append(job_dict)
972 return rpc_utils.prepare_for_serialization(job_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000973
974
975def get_num_jobs(not_yet_run=False, running=False, finished=False,
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700976 suite=False, sub=False, standalone=False,
jadmanski0afbb632008-06-06 21:10:57 +0000977 **filter_data):
978 """\
979 See get_jobs() for documentation of extra filter parameters.
980 """
Jiaxi Luo15cbf372014-07-01 19:20:20 -0700981 extra_args = rpc_utils.extra_job_status_filters(not_yet_run,
982 running,
983 finished)
984 filter_data['extra_args'] = rpc_utils.extra_job_type_filters(extra_args,
985 suite,
986 sub,
987 standalone)
jadmanski0afbb632008-06-06 21:10:57 +0000988 return models.Job.query_count(filter_data)
mblighe8819cd2008-02-15 16:48:40 +0000989
990
mblighe8819cd2008-02-15 16:48:40 +0000991def get_jobs_summary(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000992 """\
Jiaxi Luoaac54572014-06-04 13:57:02 -0700993 Like get_jobs(), but adds 'status_counts' and 'result_counts' field.
994
995 'status_counts' filed is a dictionary mapping status strings to the number
996 of hosts currently with that status, i.e. {'Queued' : 4, 'Running' : 2}.
997
998 'result_counts' field is piped to tko's rpc_interface and has the return
999 format specified under get_group_counts.
jadmanski0afbb632008-06-06 21:10:57 +00001000 """
1001 jobs = get_jobs(**filter_data)
1002 ids = [job['id'] for job in jobs]
1003 all_status_counts = models.Job.objects.get_status_counts(ids)
1004 for job in jobs:
1005 job['status_counts'] = all_status_counts[job['id']]
Jiaxi Luoaac54572014-06-04 13:57:02 -07001006 job['result_counts'] = tko_rpc_interface.get_status_counts(
1007 ['afe_job_id', 'afe_job_id'],
1008 header_groups=[['afe_job_id'], ['afe_job_id']],
1009 **{'afe_job_id': job['id']})
jadmanski0afbb632008-06-06 21:10:57 +00001010 return rpc_utils.prepare_for_serialization(jobs)
mblighe8819cd2008-02-15 16:48:40 +00001011
1012
showarda965cef2009-05-15 23:17:41 +00001013def get_info_for_clone(id, preserve_metahosts, queue_entry_filter_data=None):
showarda8709c52008-07-03 19:44:54 +00001014 """\
1015 Retrieves all the information needed to clone a job.
1016 """
showarda8709c52008-07-03 19:44:54 +00001017 job = models.Job.objects.get(id=id)
showard29f7cd22009-04-29 21:16:24 +00001018 job_info = rpc_utils.get_job_info(job,
showarda965cef2009-05-15 23:17:41 +00001019 preserve_metahosts,
1020 queue_entry_filter_data)
showard945072f2008-09-03 20:34:59 +00001021
showardd9992fe2008-07-31 02:15:03 +00001022 host_dicts = []
showard29f7cd22009-04-29 21:16:24 +00001023 for host in job_info['hosts']:
1024 host_dict = get_hosts(id=host.id)[0]
1025 other_labels = host_dict['labels']
1026 if host_dict['platform']:
1027 other_labels.remove(host_dict['platform'])
1028 host_dict['other_labels'] = ', '.join(other_labels)
showardd9992fe2008-07-31 02:15:03 +00001029 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001030
showard29f7cd22009-04-29 21:16:24 +00001031 for host in job_info['one_time_hosts']:
1032 host_dict = dict(hostname=host.hostname,
1033 id=host.id,
1034 platform='(one-time host)',
1035 locked_text='')
1036 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +00001037
showard4d077562009-05-08 18:24:36 +00001038 # convert keys from Label objects to strings (names of labels)
showard29f7cd22009-04-29 21:16:24 +00001039 meta_host_counts = dict((meta_host.name, count) for meta_host, count
showard4d077562009-05-08 18:24:36 +00001040 in job_info['meta_host_counts'].iteritems())
showard29f7cd22009-04-29 21:16:24 +00001041
1042 info = dict(job=job.get_object_dict(),
1043 meta_host_counts=meta_host_counts,
1044 hosts=host_dicts)
1045 info['job']['dependencies'] = job_info['dependencies']
1046 if job_info['atomic_group']:
1047 info['atomic_group_name'] = (job_info['atomic_group']).name
1048 else:
1049 info['atomic_group_name'] = None
jamesren2275ef12010-04-12 18:25:06 +00001050 info['hostless'] = job_info['hostless']
jamesren76fcf192010-04-21 20:39:50 +00001051 info['drone_set'] = job.drone_set and job.drone_set.name
showarda8709c52008-07-03 19:44:54 +00001052
Eric Lid23bc192011-02-09 14:38:57 -08001053 if job.parameterized_job:
1054 info['job']['image'] = get_parameterized_autoupdate_image_url(job)
1055
showarda8709c52008-07-03 19:44:54 +00001056 return rpc_utils.prepare_for_serialization(info)
1057
1058
showard34dc5fa2008-04-24 20:58:40 +00001059# host queue entries
1060
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001061def get_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001062 """\
showardc92da832009-04-07 18:14:34 +00001063 @returns A sequence of nested dictionaries of host and job information.
jadmanski0afbb632008-06-06 21:10:57 +00001064 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001065 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1066 'started_on__lte',
1067 start_time,
1068 end_time,
1069 **filter_data)
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001070 return rpc_utils.prepare_rows_as_nested_dicts(
1071 models.HostQueueEntry.query_objects(filter_data),
1072 ('host', 'atomic_group', 'job'))
showard34dc5fa2008-04-24 20:58:40 +00001073
1074
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001075def get_num_host_queue_entries(start_time=None, end_time=None, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +00001076 """\
1077 Get the number of host queue entries associated with this job.
1078 """
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001079 filter_data = rpc_utils.inject_times_to_filter('started_on__gte',
1080 'started_on__lte',
1081 start_time,
1082 end_time,
1083 **filter_data)
jadmanski0afbb632008-06-06 21:10:57 +00001084 return models.HostQueueEntry.query_count(filter_data)
showard34dc5fa2008-04-24 20:58:40 +00001085
1086
showard1e935f12008-07-11 00:11:36 +00001087def get_hqe_percentage_complete(**filter_data):
1088 """
showardc92da832009-04-07 18:14:34 +00001089 Computes the fraction of host queue entries matching the given filter data
showard1e935f12008-07-11 00:11:36 +00001090 that are complete.
1091 """
1092 query = models.HostQueueEntry.query_objects(filter_data)
1093 complete_count = query.filter(complete=True).count()
1094 total_count = query.count()
1095 if total_count == 0:
1096 return 1
1097 return float(complete_count) / total_count
1098
1099
showard1a5a4082009-07-28 20:01:37 +00001100# special tasks
1101
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001102def _get_local_special_tasks(**filter_data):
1103 # TODO(jrbarnette): This function represents the correct
1104 # implementation of get_special_tasks(). It's being kept
1105 # separate until we can fix get_special_tasks() (see below).
1106 return rpc_utils.prepare_rows_as_nested_dicts(
1107 models.SpecialTask.query_objects(filter_data),
1108 ('host', 'queue_entry'))
1109
1110
showard1a5a4082009-07-28 20:01:37 +00001111def get_special_tasks(**filter_data):
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001112 """Get special task entries from the local database.
1113
1114 Query the special tasks table for tasks matching the given
1115 `filter_data`, and return a list of the results. No attempt is
1116 made to forward the call to shards; the buck will stop here.
1117 The caller is expected to know the target shard for such reasons
1118 as:
1119 * The caller is a service (such as gs_offloader) configured
1120 to operate on behalf of one specific shard, and no other.
1121 * The caller has a host as a parameter, and knows that this is
1122 the shard assigned to that host.
1123
1124 @param filter_data Filter keywords to pass to the underlying
1125 database query.
1126
1127 """
1128 # TODO(jrbarnette): Currently, this code still forwards to
1129 # shards despite the specification that says we don't. This is
1130 # a temporary measure to provide compatibility to dut-status
1131 # clients that haven't been updated to use
1132 # get_host_special_tasks(). This code must be fixed/deleted
1133 # once all those clients have been updated.
MK Ryu2d107562015-02-24 17:45:02 -08001134 if 'id' in filter_data or 'id__in' in filter_data:
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001135 return _get_local_special_tasks(**filter_data)
MK Ryu2d107562015-02-24 17:45:02 -08001136 else:
J. Richard Barnetteb5164d62015-04-13 12:59:31 -07001137 return rpc_utils.get_data(_get_local_special_tasks,
1138 'get_special_tasks', **filter_data)
1139
1140
1141def get_host_special_tasks(host_id, **filter_data):
1142 """Get special task entries for a given host.
1143
1144 Query the special tasks table for tasks that ran on the host
1145 given by `host_id` and matching the given `filter_data`.
1146 Return a list of the results. If the host is assigned to a
1147 shard, forward this call to that shard.
1148
1149 @param host_id Id in the database of the target host.
1150 @param filter_data Filter keywords to pass to the underlying
1151 database query.
1152
1153 """
1154 host = models.Host.smart_get(host_id)
1155 if not host.shard:
1156 # TODO(jrbarnette): change to get_special_tasks() once
1157 # all dut-status clients are updated.
1158 return _get_local_special_tasks(host_id=host_id, **filter_data)
1159 else:
1160 # The return value from AFE.get_special_tasks() is a list of
1161 # post-processed objects that aren't JSON-serializable. So,
1162 # we have to call AFE.run() to get the raw, serializable
1163 # output from the shard.
1164 shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
1165 return shard_afe.run('get_special_tasks',
1166 host_id=host_id, **filter_data)
showard1a5a4082009-07-28 20:01:37 +00001167
1168
showardc0ac3a72009-07-08 21:14:45 +00001169# support for host detail view
1170
Jiaxi Luo79ce6422014-06-13 17:08:09 -07001171def get_host_queue_entries_and_special_tasks(host_id, query_start=None,
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001172 query_limit=None, start_time=None,
1173 end_time=None):
showardc0ac3a72009-07-08 21:14:45 +00001174 """
1175 @returns an interleaved list of HostQueueEntries and SpecialTasks,
1176 in approximate run order. each dict contains keys for type, host,
1177 job, status, started_on, execution_path, and ID.
1178 """
1179 total_limit = None
1180 if query_limit is not None:
1181 total_limit = query_start + query_limit
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001182 filter_data_common = {'host': host_id,
1183 'query_limit': total_limit,
1184 'sort_by': ['-id']}
showardc0ac3a72009-07-08 21:14:45 +00001185
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001186 filter_data_queue_entries, filter_data_special_tasks = (
1187 rpc_utils.inject_times_to_hqe_special_tasks_filters(
1188 filter_data_common, start_time, end_time))
1189
1190 queue_entries = list(models.HostQueueEntry.query_objects(
1191 filter_data_queue_entries))
1192 special_tasks = list(models.SpecialTask.query_objects(
1193 filter_data_special_tasks))
showardc0ac3a72009-07-08 21:14:45 +00001194
1195 interleaved_entries = rpc_utils.interleave_entries(queue_entries,
1196 special_tasks)
1197 if query_start is not None:
1198 interleaved_entries = interleaved_entries[query_start:]
1199 if query_limit is not None:
1200 interleaved_entries = interleaved_entries[:query_limit]
1201 return rpc_utils.prepare_for_serialization(interleaved_entries)
1202
1203
Jiaxi Luo57bc1952014-07-22 15:27:30 -07001204def get_num_host_queue_entries_and_special_tasks(host_id, start_time=None,
1205 end_time=None):
1206 filter_data_common = {'host': host_id}
1207
1208 filter_data_queue_entries, filter_data_special_tasks = (
1209 rpc_utils.inject_times_to_hqe_special_tasks_filters(
1210 filter_data_common, start_time, end_time))
1211
1212 return (models.HostQueueEntry.query_count(filter_data_queue_entries)
1213 + models.SpecialTask.query_count(filter_data_special_tasks))
showardc0ac3a72009-07-08 21:14:45 +00001214
1215
showard29f7cd22009-04-29 21:16:24 +00001216# recurring run
1217
1218def get_recurring(**filter_data):
1219 return rpc_utils.prepare_rows_as_nested_dicts(
1220 models.RecurringRun.query_objects(filter_data),
1221 ('job', 'owner'))
1222
1223
1224def get_num_recurring(**filter_data):
1225 return models.RecurringRun.query_count(filter_data)
1226
1227
1228def delete_recurring_runs(**filter_data):
1229 to_delete = models.RecurringRun.query_objects(filter_data)
1230 to_delete.delete()
1231
1232
1233def create_recurring_run(job_id, start_date, loop_period, loop_count):
showard64a95952010-01-13 21:27:16 +00001234 owner = models.User.current_user().login
showard29f7cd22009-04-29 21:16:24 +00001235 job = models.Job.objects.get(id=job_id)
1236 return job.create_recurring_job(start_date=start_date,
1237 loop_period=loop_period,
1238 loop_count=loop_count,
1239 owner=owner)
1240
1241
mblighe8819cd2008-02-15 16:48:40 +00001242# other
1243
showarde0b63622008-08-04 20:58:47 +00001244def echo(data=""):
1245 """\
1246 Returns a passed in string. For doing a basic test to see if RPC calls
1247 can successfully be made.
1248 """
1249 return data
1250
1251
showardb7a52fd2009-04-27 20:10:56 +00001252def get_motd():
1253 """\
1254 Returns the message of the day as a string.
1255 """
1256 return rpc_utils.get_motd()
1257
1258
mblighe8819cd2008-02-15 16:48:40 +00001259def get_static_data():
jadmanski0afbb632008-06-06 21:10:57 +00001260 """\
1261 Returns a dictionary containing a bunch of data that shouldn't change
1262 often and is otherwise inaccessible. This includes:
showardc92da832009-04-07 18:14:34 +00001263
1264 priorities: List of job priority choices.
1265 default_priority: Default priority value for new jobs.
1266 users: Sorted list of all users.
Jiaxi Luo31874592014-06-11 10:36:35 -07001267 labels: Sorted list of labels not start with 'cros-version' and
1268 'fw-version'.
showardc92da832009-04-07 18:14:34 +00001269 atomic_groups: Sorted list of all atomic groups.
1270 tests: Sorted list of all tests.
1271 profilers: Sorted list of all profilers.
1272 current_user: Logged-in username.
1273 host_statuses: Sorted list of possible Host statuses.
1274 job_statuses: Sorted list of possible HostQueueEntry statuses.
Simran Basi7e605742013-11-12 13:43:36 -08001275 job_timeout_default: The default job timeout length in minutes.
showarda1e74b32009-05-12 17:32:04 +00001276 parse_failed_repair_default: Default value for the parse_failed_repair job
Jiaxi Luo31874592014-06-11 10:36:35 -07001277 option.
showardc92da832009-04-07 18:14:34 +00001278 reboot_before_options: A list of valid RebootBefore string enums.
1279 reboot_after_options: A list of valid RebootAfter string enums.
1280 motd: Server's message of the day.
1281 status_dictionary: A mapping from one word job status names to a more
1282 informative description.
jadmanski0afbb632008-06-06 21:10:57 +00001283 """
showard21baa452008-10-21 00:08:39 +00001284
1285 job_fields = models.Job.get_field_dict()
jamesren76fcf192010-04-21 20:39:50 +00001286 default_drone_set_name = models.DroneSet.default_drone_set_name()
1287 drone_sets = ([default_drone_set_name] +
1288 sorted(drone_set.name for drone_set in
1289 models.DroneSet.objects.exclude(
1290 name=default_drone_set_name)))
showard21baa452008-10-21 00:08:39 +00001291
jadmanski0afbb632008-06-06 21:10:57 +00001292 result = {}
Alex Miller7d658cf2013-09-04 16:00:35 -07001293 result['priorities'] = priorities.Priority.choices()
1294 default_priority = priorities.Priority.DEFAULT
1295 result['default_priority'] = 'Default'
1296 result['max_schedulable_priority'] = priorities.Priority.DEFAULT
jadmanski0afbb632008-06-06 21:10:57 +00001297 result['users'] = get_users(sort_by=['login'])
Jiaxi Luo31874592014-06-11 10:36:35 -07001298
1299 label_exclude_filters = [{'name__startswith': 'cros-version'},
1300 {'name__startswith': 'fw-version'}]
1301 result['labels'] = get_labels(
1302 label_exclude_filters,
1303 sort_by=['-platform', 'name'])
1304
showardc92da832009-04-07 18:14:34 +00001305 result['atomic_groups'] = get_atomic_groups(sort_by=['name'])
jadmanski0afbb632008-06-06 21:10:57 +00001306 result['tests'] = get_tests(sort_by=['name'])
showard2b9a88b2008-06-13 20:55:03 +00001307 result['profilers'] = get_profilers(sort_by=['name'])
showard0fc38302008-10-23 00:44:07 +00001308 result['current_user'] = rpc_utils.prepare_for_serialization(
showard64a95952010-01-13 21:27:16 +00001309 models.User.current_user().get_object_dict())
showard2b9a88b2008-06-13 20:55:03 +00001310 result['host_statuses'] = sorted(models.Host.Status.names)
mbligh5a198b92008-12-11 19:33:29 +00001311 result['job_statuses'] = sorted(models.HostQueueEntry.Status.names)
Simran Basi7e605742013-11-12 13:43:36 -08001312 result['job_timeout_mins_default'] = models.Job.DEFAULT_TIMEOUT_MINS
Simran Basi34217022012-11-06 13:43:15 -08001313 result['job_max_runtime_mins_default'] = (
1314 models.Job.DEFAULT_MAX_RUNTIME_MINS)
showarda1e74b32009-05-12 17:32:04 +00001315 result['parse_failed_repair_default'] = bool(
1316 models.Job.DEFAULT_PARSE_FAILED_REPAIR)
jamesrendd855242010-03-02 22:23:44 +00001317 result['reboot_before_options'] = model_attributes.RebootBefore.names
1318 result['reboot_after_options'] = model_attributes.RebootAfter.names
showard8fbae652009-01-20 23:23:10 +00001319 result['motd'] = rpc_utils.get_motd()
jamesren76fcf192010-04-21 20:39:50 +00001320 result['drone_sets_enabled'] = models.DroneSet.drone_sets_enabled()
1321 result['drone_sets'] = drone_sets
jamesren4a41e012010-07-16 22:33:48 +00001322 result['parameterized_jobs'] = models.Job.parameterized_jobs_enabled()
showard8ac29b42008-07-17 17:01:55 +00001323
showardd3dc1992009-04-22 21:01:40 +00001324 result['status_dictionary'] = {"Aborted": "Aborted",
showard8ac29b42008-07-17 17:01:55 +00001325 "Verifying": "Verifying Host",
Alex Millerdfff2fd2013-05-28 13:05:06 -07001326 "Provisioning": "Provisioning Host",
showard8ac29b42008-07-17 17:01:55 +00001327 "Pending": "Waiting on other hosts",
1328 "Running": "Running autoserv",
1329 "Completed": "Autoserv completed",
1330 "Failed": "Failed to complete",
showardd823b362008-07-24 16:35:46 +00001331 "Queued": "Queued",
showard5deb6772008-11-04 21:54:33 +00001332 "Starting": "Next in host's queue",
1333 "Stopped": "Other host(s) failed verify",
showardd3dc1992009-04-22 21:01:40 +00001334 "Parsing": "Awaiting parse of final results",
showard29f7cd22009-04-29 21:16:24 +00001335 "Gathering": "Gathering log files",
showard8cc058f2009-09-08 16:26:33 +00001336 "Template": "Template job for recurring run",
mbligh4608b002010-01-05 18:22:35 +00001337 "Waiting": "Waiting for scheduler action",
Dan Shi07e09af2013-04-12 09:31:29 -07001338 "Archiving": "Archiving results",
1339 "Resetting": "Resetting hosts"}
Jiaxi Luo421608e2014-07-07 14:38:00 -07001340
1341 result['wmatrix_url'] = rpc_utils.get_wmatrix_url()
Simran Basi71206ef2014-08-13 13:51:18 -07001342 result['is_moblab'] = bool(utils.is_moblab())
Jiaxi Luo421608e2014-07-07 14:38:00 -07001343
jadmanski0afbb632008-06-06 21:10:57 +00001344 return result
showard29f7cd22009-04-29 21:16:24 +00001345
1346
1347def get_server_time():
1348 return datetime.datetime.now().strftime("%Y-%m-%d %H:%M")