blob: 57eea88b536ab3dd4090d530d44926f2f61ca41d [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
showard29f7cd22009-04-29 21:16:24 +000034import datetime
showardcafd16e2009-05-29 18:37:49 +000035import common
Alex Millera713e252013-03-01 10:45:44 -080036from autotest_lib.client.common_lib import control_data
37from autotest_lib.client.common_lib import error
jamesrendd855242010-03-02 22:23:44 +000038from autotest_lib.frontend.afe import models, model_logic, model_attributes
showard6d7b2ff2009-06-10 00:16:47 +000039from autotest_lib.frontend.afe import control_file, rpc_utils
showard3bb499f2008-07-03 19:42:20 +000040
mblighe8819cd2008-02-15 16:48:40 +000041
Eric Lid23bc192011-02-09 14:38:57 -080042def get_parameterized_autoupdate_image_url(job):
43 """Get the parameterized autoupdate image url from a parameterized job."""
44 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
45 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
46 name='image')
47 para_set = job.parameterized_job.parameterizedjobparameter_set
48 job_test_para = para_set.get(test_parameter=image_parameter)
49 return job_test_para.parameter_value
50
51
mblighe8819cd2008-02-15 16:48:40 +000052# labels
53
showard989f25d2008-10-01 11:38:11 +000054def add_label(name, kernel_config=None, platform=None, only_if_needed=None):
showardc92da832009-04-07 18:14:34 +000055 return models.Label.add_object(
56 name=name, kernel_config=kernel_config, platform=platform,
57 only_if_needed=only_if_needed).id
mblighe8819cd2008-02-15 16:48:40 +000058
59
60def modify_label(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +000061 models.Label.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +000062
63
64def delete_label(id):
jadmanski0afbb632008-06-06 21:10:57 +000065 models.Label.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +000066
67
showardbbabf502008-06-06 00:02:02 +000068def label_add_hosts(id, hosts):
showardbe3ec042008-11-12 18:16:07 +000069 host_objs = models.Host.smart_get_bulk(hosts)
showardcafd16e2009-05-29 18:37:49 +000070 label = models.Label.smart_get(id)
71 if label.platform:
72 models.Host.check_no_platform(host_objs)
73 label.host_set.add(*host_objs)
showardbbabf502008-06-06 00:02:02 +000074
75
76def label_remove_hosts(id, hosts):
showardbe3ec042008-11-12 18:16:07 +000077 host_objs = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +000078 models.Label.smart_get(id).host_set.remove(*host_objs)
showardbbabf502008-06-06 00:02:02 +000079
80
mblighe8819cd2008-02-15 16:48:40 +000081def get_labels(**filter_data):
showardc92da832009-04-07 18:14:34 +000082 """\
83 @returns A sequence of nested dictionaries of label information.
84 """
85 return rpc_utils.prepare_rows_as_nested_dicts(
86 models.Label.query_objects(filter_data),
87 ('atomic_group',))
88
89
90# atomic groups
91
showarde9450c92009-06-30 01:58:52 +000092def add_atomic_group(name, max_number_of_machines=None, description=None):
showardc92da832009-04-07 18:14:34 +000093 return models.AtomicGroup.add_object(
94 name=name, max_number_of_machines=max_number_of_machines,
95 description=description).id
96
97
98def modify_atomic_group(id, **data):
99 models.AtomicGroup.smart_get(id).update_object(data)
100
101
102def delete_atomic_group(id):
103 models.AtomicGroup.smart_get(id).delete()
104
105
106def atomic_group_add_labels(id, labels):
107 label_objs = models.Label.smart_get_bulk(labels)
108 models.AtomicGroup.smart_get(id).label_set.add(*label_objs)
109
110
111def atomic_group_remove_labels(id, labels):
112 label_objs = models.Label.smart_get_bulk(labels)
113 models.AtomicGroup.smart_get(id).label_set.remove(*label_objs)
114
115
116def get_atomic_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000117 return rpc_utils.prepare_for_serialization(
showardc92da832009-04-07 18:14:34 +0000118 models.AtomicGroup.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000119
120
121# hosts
122
showarddf062562008-07-03 19:56:37 +0000123def add_host(hostname, status=None, locked=None, protection=None):
jadmanski0afbb632008-06-06 21:10:57 +0000124 return models.Host.add_object(hostname=hostname, status=status,
showarddf062562008-07-03 19:56:37 +0000125 locked=locked, protection=protection).id
mblighe8819cd2008-02-15 16:48:40 +0000126
127
128def modify_host(id, **data):
showardbe0d8692009-08-20 23:42:44 +0000129 rpc_utils.check_modify_host(data)
showardce7c0922009-09-11 18:39:24 +0000130 host = models.Host.smart_get(id)
131 rpc_utils.check_modify_host_locking(host, data)
132 host.update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000133
134
showard276f9442009-05-20 00:33:16 +0000135def modify_hosts(host_filter_data, update_data):
136 """
showardbe0d8692009-08-20 23:42:44 +0000137 @param host_filter_data: Filters out which hosts to modify.
138 @param update_data: A dictionary with the changes to make to the hosts.
showard276f9442009-05-20 00:33:16 +0000139 """
showardbe0d8692009-08-20 23:42:44 +0000140 rpc_utils.check_modify_host(update_data)
showard276f9442009-05-20 00:33:16 +0000141 hosts = models.Host.query_objects(host_filter_data)
142 for host in hosts:
143 host.update_object(update_data)
144
145
mblighe8819cd2008-02-15 16:48:40 +0000146def host_add_labels(id, labels):
showardbe3ec042008-11-12 18:16:07 +0000147 labels = models.Label.smart_get_bulk(labels)
showardcafd16e2009-05-29 18:37:49 +0000148 host = models.Host.smart_get(id)
149
150 platforms = [label.name for label in labels if label.platform]
151 if len(platforms) > 1:
152 raise model_logic.ValidationError(
153 {'labels': 'Adding more than one platform label: %s' %
154 ', '.join(platforms)})
155 if len(platforms) == 1:
156 models.Host.check_no_platform([host])
157 host.labels.add(*labels)
mblighe8819cd2008-02-15 16:48:40 +0000158
159
160def host_remove_labels(id, labels):
showardbe3ec042008-11-12 18:16:07 +0000161 labels = models.Label.smart_get_bulk(labels)
jadmanski0afbb632008-06-06 21:10:57 +0000162 models.Host.smart_get(id).labels.remove(*labels)
mblighe8819cd2008-02-15 16:48:40 +0000163
164
showard0957a842009-05-11 19:25:08 +0000165def set_host_attribute(attribute, value, **host_filter_data):
166 """
167 @param attribute string name of attribute
168 @param value string, or None to delete an attribute
169 @param host_filter_data filter data to apply to Hosts to choose hosts to act
170 upon
171 """
172 assert host_filter_data # disallow accidental actions on all hosts
173 hosts = models.Host.query_objects(host_filter_data)
174 models.AclGroup.check_for_acl_violation_hosts(hosts)
175
176 for host in hosts:
showardf8b19042009-05-12 17:22:49 +0000177 host.set_or_delete_attribute(attribute, value)
showard0957a842009-05-11 19:25:08 +0000178
179
mblighe8819cd2008-02-15 16:48:40 +0000180def delete_host(id):
jadmanski0afbb632008-06-06 21:10:57 +0000181 models.Host.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000182
183
showard87cc38f2009-08-20 23:37:04 +0000184def get_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000185 exclude_atomic_group_hosts=False, valid_only=True, **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000186 """
187 @param multiple_labels: match hosts in all of the labels given. Should
188 be a list of label names.
189 @param exclude_only_if_needed_labels: Exclude hosts with at least one
190 "only_if_needed" label applied.
191 @param exclude_atomic_group_hosts: Exclude hosts that have one or more
192 atomic group labels associated with them.
jadmanski0afbb632008-06-06 21:10:57 +0000193 """
showard43a3d262008-11-12 18:17:05 +0000194 hosts = rpc_utils.get_host_query(multiple_labels,
195 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000196 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000197 valid_only, filter_data)
showard0957a842009-05-11 19:25:08 +0000198 hosts = list(hosts)
199 models.Host.objects.populate_relationships(hosts, models.Label,
200 'label_list')
201 models.Host.objects.populate_relationships(hosts, models.AclGroup,
202 'acl_list')
203 models.Host.objects.populate_relationships(hosts, models.HostAttribute,
204 'attribute_list')
showard43a3d262008-11-12 18:17:05 +0000205 host_dicts = []
206 for host_obj in hosts:
207 host_dict = host_obj.get_object_dict()
showard0957a842009-05-11 19:25:08 +0000208 host_dict['labels'] = [label.name for label in host_obj.label_list]
showard909c9142009-07-07 20:54:42 +0000209 host_dict['platform'], host_dict['atomic_group'] = (rpc_utils.
210 find_platform_and_atomic_group(host_obj))
showard0957a842009-05-11 19:25:08 +0000211 host_dict['acls'] = [acl.name for acl in host_obj.acl_list]
212 host_dict['attributes'] = dict((attribute.attribute, attribute.value)
213 for attribute in host_obj.attribute_list)
showard43a3d262008-11-12 18:17:05 +0000214 host_dicts.append(host_dict)
215 return rpc_utils.prepare_for_serialization(host_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000216
217
showard87cc38f2009-08-20 23:37:04 +0000218def get_num_hosts(multiple_labels=(), exclude_only_if_needed_labels=False,
showard8aa84fc2009-09-16 17:17:55 +0000219 exclude_atomic_group_hosts=False, valid_only=True,
220 **filter_data):
showard87cc38f2009-08-20 23:37:04 +0000221 """
222 Same parameters as get_hosts().
223
224 @returns The number of matching hosts.
225 """
showard43a3d262008-11-12 18:17:05 +0000226 hosts = rpc_utils.get_host_query(multiple_labels,
227 exclude_only_if_needed_labels,
showard87cc38f2009-08-20 23:37:04 +0000228 exclude_atomic_group_hosts,
showard8aa84fc2009-09-16 17:17:55 +0000229 valid_only, filter_data)
showard43a3d262008-11-12 18:17:05 +0000230 return hosts.count()
showard1385b162008-03-13 15:59:40 +0000231
mblighe8819cd2008-02-15 16:48:40 +0000232
233# tests
234
showard909c7a62008-07-15 21:52:38 +0000235def add_test(name, test_type, path, author=None, dependencies=None,
showard3d9899a2008-07-31 02:11:58 +0000236 experimental=True, run_verify=None, test_class=None,
showard909c7a62008-07-15 21:52:38 +0000237 test_time=None, test_category=None, description=None,
238 sync_count=1):
jadmanski0afbb632008-06-06 21:10:57 +0000239 return models.Test.add_object(name=name, test_type=test_type, path=path,
showard909c7a62008-07-15 21:52:38 +0000240 author=author, dependencies=dependencies,
241 experimental=experimental,
242 run_verify=run_verify, test_time=test_time,
243 test_category=test_category,
244 sync_count=sync_count,
jadmanski0afbb632008-06-06 21:10:57 +0000245 test_class=test_class,
246 description=description).id
mblighe8819cd2008-02-15 16:48:40 +0000247
248
249def modify_test(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000250 models.Test.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000251
252
253def delete_test(id):
jadmanski0afbb632008-06-06 21:10:57 +0000254 models.Test.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000255
256
257def get_tests(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000258 return rpc_utils.prepare_for_serialization(
259 models.Test.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000260
261
showard2b9a88b2008-06-13 20:55:03 +0000262# profilers
263
264def add_profiler(name, description=None):
265 return models.Profiler.add_object(name=name, description=description).id
266
267
268def modify_profiler(id, **data):
269 models.Profiler.smart_get(id).update_object(data)
270
271
272def delete_profiler(id):
273 models.Profiler.smart_get(id).delete()
274
275
276def get_profilers(**filter_data):
277 return rpc_utils.prepare_for_serialization(
278 models.Profiler.list_objects(filter_data))
279
280
mblighe8819cd2008-02-15 16:48:40 +0000281# users
282
283def add_user(login, access_level=None):
jadmanski0afbb632008-06-06 21:10:57 +0000284 return models.User.add_object(login=login, access_level=access_level).id
mblighe8819cd2008-02-15 16:48:40 +0000285
286
287def modify_user(id, **data):
jadmanski0afbb632008-06-06 21:10:57 +0000288 models.User.smart_get(id).update_object(data)
mblighe8819cd2008-02-15 16:48:40 +0000289
290
291def delete_user(id):
jadmanski0afbb632008-06-06 21:10:57 +0000292 models.User.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000293
294
295def get_users(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000296 return rpc_utils.prepare_for_serialization(
297 models.User.list_objects(filter_data))
mblighe8819cd2008-02-15 16:48:40 +0000298
299
300# acl groups
301
302def add_acl_group(name, description=None):
showard04f2cd82008-07-25 20:53:31 +0000303 group = models.AclGroup.add_object(name=name, description=description)
showard64a95952010-01-13 21:27:16 +0000304 group.users.add(models.User.current_user())
showard04f2cd82008-07-25 20:53:31 +0000305 return group.id
mblighe8819cd2008-02-15 16:48:40 +0000306
307
308def modify_acl_group(id, **data):
showard04f2cd82008-07-25 20:53:31 +0000309 group = models.AclGroup.smart_get(id)
310 group.check_for_acl_violation_acl_group()
311 group.update_object(data)
312 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000313
314
315def acl_group_add_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000316 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000317 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000318 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000319 group.users.add(*users)
mblighe8819cd2008-02-15 16:48:40 +0000320
321
322def acl_group_remove_users(id, users):
jadmanski0afbb632008-06-06 21:10:57 +0000323 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000324 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000325 users = models.User.smart_get_bulk(users)
jadmanski0afbb632008-06-06 21:10:57 +0000326 group.users.remove(*users)
showard04f2cd82008-07-25 20:53:31 +0000327 group.add_current_user_if_empty()
mblighe8819cd2008-02-15 16:48:40 +0000328
329
330def acl_group_add_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000331 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000332 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000333 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000334 group.hosts.add(*hosts)
showard08f981b2008-06-24 21:59:03 +0000335 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000336
337
338def acl_group_remove_hosts(id, hosts):
jadmanski0afbb632008-06-06 21:10:57 +0000339 group = models.AclGroup.smart_get(id)
showard04f2cd82008-07-25 20:53:31 +0000340 group.check_for_acl_violation_acl_group()
showardbe3ec042008-11-12 18:16:07 +0000341 hosts = models.Host.smart_get_bulk(hosts)
jadmanski0afbb632008-06-06 21:10:57 +0000342 group.hosts.remove(*hosts)
showard08f981b2008-06-24 21:59:03 +0000343 group.on_host_membership_change()
mblighe8819cd2008-02-15 16:48:40 +0000344
345
346def delete_acl_group(id):
jadmanski0afbb632008-06-06 21:10:57 +0000347 models.AclGroup.smart_get(id).delete()
mblighe8819cd2008-02-15 16:48:40 +0000348
349
350def get_acl_groups(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000351 acl_groups = models.AclGroup.list_objects(filter_data)
352 for acl_group in acl_groups:
353 acl_group_obj = models.AclGroup.objects.get(id=acl_group['id'])
354 acl_group['users'] = [user.login
355 for user in acl_group_obj.users.all()]
356 acl_group['hosts'] = [host.hostname
357 for host in acl_group_obj.hosts.all()]
358 return rpc_utils.prepare_for_serialization(acl_groups)
mblighe8819cd2008-02-15 16:48:40 +0000359
360
361# jobs
362
mbligh120351e2009-01-24 01:40:45 +0000363def generate_control_file(tests=(), kernel=None, label=None, profilers=(),
showard91f85102009-10-12 20:34:52 +0000364 client_control_file='', use_container=False,
showard232b7ae2009-11-10 00:46:48 +0000365 profile_only=None, upload_kernel_config=False):
jadmanski0afbb632008-06-06 21:10:57 +0000366 """
mbligh120351e2009-01-24 01:40:45 +0000367 Generates a client-side control file to load a kernel and run tests.
368
369 @param tests List of tests to run.
mbligha3c58d22009-08-24 22:01:51 +0000370 @param kernel A list of kernel info dictionaries configuring which kernels
371 to boot for this job and other options for them
mbligh120351e2009-01-24 01:40:45 +0000372 @param label Name of label to grab kernel config from.
373 @param profilers List of profilers to activate during the job.
374 @param client_control_file The contents of a client-side control file to
375 run at the end of all tests. If this is supplied, all tests must be
376 client side.
377 TODO: in the future we should support server control files directly
378 to wrap with a kernel. That'll require changing the parameter
379 name and adding a boolean to indicate if it is a client or server
380 control file.
381 @param use_container unused argument today. TODO: Enable containers
382 on the host during a client side test.
showard91f85102009-10-12 20:34:52 +0000383 @param profile_only A boolean that indicates what default profile_only
384 mode to use in the control file. Passing None will generate a
385 control file that does not explcitly set the default mode at all.
showard232b7ae2009-11-10 00:46:48 +0000386 @param upload_kernel_config: if enabled it will generate server control
387 file code that uploads the kernel config file to the client and
388 tells the client of the new (local) path when compiling the kernel;
389 the tests must be server side tests
mbligh120351e2009-01-24 01:40:45 +0000390
391 @returns a dict with the following keys:
392 control_file: str, The control file text.
393 is_server: bool, is the control file a server-side control file?
394 synch_count: How many machines the job uses per autoserv execution.
395 synch_count == 1 means the job is asynchronous.
396 dependencies: A list of the names of labels on which the job depends.
397 """
showardd86debe2009-06-10 17:37:56 +0000398 if not tests and not client_control_file:
showard2bab8f42008-11-12 18:15:22 +0000399 return dict(control_file='', is_server=False, synch_count=1,
showard989f25d2008-10-01 11:38:11 +0000400 dependencies=[])
mblighe8819cd2008-02-15 16:48:40 +0000401
showard989f25d2008-10-01 11:38:11 +0000402 cf_info, test_objects, profiler_objects, label = (
showard2b9a88b2008-06-13 20:55:03 +0000403 rpc_utils.prepare_generate_control_file(tests, kernel, label,
404 profilers))
showard989f25d2008-10-01 11:38:11 +0000405 cf_info['control_file'] = control_file.generate_control(
mbligha3c58d22009-08-24 22:01:51 +0000406 tests=test_objects, kernels=kernel, platform=label,
mbligh120351e2009-01-24 01:40:45 +0000407 profilers=profiler_objects, is_server=cf_info['is_server'],
showard232b7ae2009-11-10 00:46:48 +0000408 client_control_file=client_control_file, profile_only=profile_only,
409 upload_kernel_config=upload_kernel_config)
showard989f25d2008-10-01 11:38:11 +0000410 return cf_info
mblighe8819cd2008-02-15 16:48:40 +0000411
412
jamesren4a41e012010-07-16 22:33:48 +0000413def create_parameterized_job(name, priority, test, parameters, kernel=None,
414 label=None, profilers=(), profiler_parameters=None,
415 use_container=False, profile_only=None,
416 upload_kernel_config=False, hosts=(),
417 meta_hosts=(), one_time_hosts=(),
418 atomic_group_name=None, synch_count=None,
419 is_template=False, timeout=None,
Simran Basi34217022012-11-06 13:43:15 -0800420 max_runtime_mins=None, run_verify=True,
jamesren4a41e012010-07-16 22:33:48 +0000421 email_list='', dependencies=(), reboot_before=None,
422 reboot_after=None, parse_failed_repair=None,
423 hostless=False, keyvals=None, drone_set=None):
424 """
425 Creates and enqueues a parameterized job.
426
427 Most parameters a combination of the parameters for generate_control_file()
428 and create_job(), with the exception of:
429
430 @param test name or ID of the test to run
431 @param parameters a map of parameter name ->
432 tuple of (param value, param type)
433 @param profiler_parameters a dictionary of parameters for the profilers:
434 key: profiler name
435 value: dict of param name -> tuple of
436 (param value,
437 param type)
438 """
439 # Save the values of the passed arguments here. What we're going to do with
440 # them is pass them all to rpc_utils.get_create_job_common_args(), which
441 # will extract the subset of these arguments that apply for
442 # rpc_utils.create_job_common(), which we then pass in to that function.
443 args = locals()
444
445 # Set up the parameterized job configs
446 test_obj = models.Test.smart_get(test)
447 if test_obj.test_type == model_attributes.TestTypes.SERVER:
448 control_type = models.Job.ControlType.SERVER
449 else:
450 control_type = models.Job.ControlType.CLIENT
451
452 try:
453 label = models.Label.smart_get(label)
454 except models.Label.DoesNotExist:
455 label = None
456
457 kernel_objs = models.Kernel.create_kernels(kernel)
458 profiler_objs = [models.Profiler.smart_get(profiler)
459 for profiler in profilers]
460
461 parameterized_job = models.ParameterizedJob.objects.create(
462 test=test_obj, label=label, use_container=use_container,
463 profile_only=profile_only,
464 upload_kernel_config=upload_kernel_config)
465 parameterized_job.kernels.add(*kernel_objs)
466
467 for profiler in profiler_objs:
468 parameterized_profiler = models.ParameterizedJobProfiler.objects.create(
469 parameterized_job=parameterized_job,
470 profiler=profiler)
471 profiler_params = profiler_parameters.get(profiler.name, {})
472 for name, (value, param_type) in profiler_params.iteritems():
473 models.ParameterizedJobProfilerParameter.objects.create(
474 parameterized_job_profiler=parameterized_profiler,
475 parameter_name=name,
476 parameter_value=value,
477 parameter_type=param_type)
478
479 try:
480 for parameter in test_obj.testparameter_set.all():
481 if parameter.name in parameters:
482 param_value, param_type = parameters.pop(parameter.name)
483 parameterized_job.parameterizedjobparameter_set.create(
484 test_parameter=parameter, parameter_value=param_value,
485 parameter_type=param_type)
486
487 if parameters:
488 raise Exception('Extra parameters remain: %r' % parameters)
489
490 return rpc_utils.create_job_common(
491 parameterized_job=parameterized_job.id,
492 control_type=control_type,
493 **rpc_utils.get_create_job_common_args(args))
494 except:
495 parameterized_job.delete()
496 raise
497
498
showard12f3e322009-05-13 21:27:42 +0000499def create_job(name, priority, control_file, control_type,
500 hosts=(), meta_hosts=(), one_time_hosts=(),
501 atomic_group_name=None, synch_count=None, is_template=False,
Simran Basi34217022012-11-06 13:43:15 -0800502 timeout=None, max_runtime_mins=None, run_verify=True,
showard12f3e322009-05-13 21:27:42 +0000503 email_list='', dependencies=(), reboot_before=None,
showardc1a98d12010-01-15 00:22:22 +0000504 reboot_after=None, parse_failed_repair=None, hostless=False,
Aviv Keshet0b9cfc92013-02-05 11:36:02 -0800505 keyvals=None, drone_set=None, image=None, parent_job_id=None):
jadmanski0afbb632008-06-06 21:10:57 +0000506 """\
507 Create and enqueue a job.
mblighe8819cd2008-02-15 16:48:40 +0000508
showarda1e74b32009-05-12 17:32:04 +0000509 @param name name of this job
510 @param priority Low, Medium, High, Urgent
511 @param control_file String contents of the control file.
512 @param control_type Type of control file, Client or Server.
513 @param synch_count How many machines the job uses per autoserv execution.
514 synch_count == 1 means the job is asynchronous. If an atomic group is
515 given this value is treated as a minimum.
516 @param is_template If true then create a template job.
517 @param timeout Hours after this call returns until the job times out.
Simran Basi34217022012-11-06 13:43:15 -0800518 @param max_runtime_mins Minutes from job starting time until job times out
showarda1e74b32009-05-12 17:32:04 +0000519 @param run_verify Should the host be verified before running the test?
520 @param email_list String containing emails to mail when the job is done
521 @param dependencies List of label names on which this job depends
522 @param reboot_before Never, If dirty, or Always
523 @param reboot_after Never, If all tests passed, or Always
524 @param parse_failed_repair if true, results of failed repairs launched by
525 this job will be parsed as part of the job.
showarda9545c02009-12-18 22:44:26 +0000526 @param hostless if true, create a hostless job
showardc1a98d12010-01-15 00:22:22 +0000527 @param keyvals dict of keyvals to associate with the job
showarda1e74b32009-05-12 17:32:04 +0000528
529 @param hosts List of hosts to run job on.
530 @param meta_hosts List where each entry is a label name, and for each entry
531 one host will be chosen from that label to run the job on.
532 @param one_time_hosts List of hosts not in the database to run the job on.
533 @param atomic_group_name The name of an atomic group to schedule the job on.
jamesren76fcf192010-04-21 20:39:50 +0000534 @param drone_set The name of the drone set to run this test on.
Paul Pendlebury5a8c6ad2011-02-01 07:20:17 -0800535 @param image OS image to install before running job.
Aviv Keshet0b9cfc92013-02-05 11:36:02 -0800536 @param parent_job_id id of a job considered to be parent of created job.
showarda1e74b32009-05-12 17:32:04 +0000537
showardc92da832009-04-07 18:14:34 +0000538
539 @returns The created Job id number.
jadmanski0afbb632008-06-06 21:10:57 +0000540 """
Paul Pendlebury5a8c6ad2011-02-01 07:20:17 -0800541
Alex Millera713e252013-03-01 10:45:44 -0800542 # Force control files to only contain ascii characters.
543 try:
544 control_file.encode('ascii')
545 except UnicodeDecodeError as e:
546 raise error.ControlFileMalformed(str(e))
547
Paul Pendlebury5a8c6ad2011-02-01 07:20:17 -0800548 if image is None:
549 return rpc_utils.create_job_common(
550 **rpc_utils.get_create_job_common_args(locals()))
551
552 # When image is supplied use a known parameterized test already in the
553 # database to pass the OS image path from the front end, through the
554 # scheduler, and finally to autoserv as the --image parameter.
555
556 # The test autoupdate_ParameterizedJob is in afe_autotests and used to
557 # instantiate a Test object and from there a ParameterizedJob.
558 known_test_obj = models.Test.smart_get('autoupdate_ParameterizedJob')
559 known_parameterized_job = models.ParameterizedJob.objects.create(
560 test=known_test_obj)
561
562 # autoupdate_ParameterizedJob has a single parameter, the image parameter,
563 # stored in the table afe_test_parameters. We retrieve and set this
564 # instance of the parameter to the OS image path.
Eric Lid23bc192011-02-09 14:38:57 -0800565 image_parameter = known_test_obj.testparameter_set.get(test=known_test_obj,
566 name='image')
Paul Pendlebury5a8c6ad2011-02-01 07:20:17 -0800567 known_parameterized_job.parameterizedjobparameter_set.create(
568 test_parameter=image_parameter, parameter_value=image,
569 parameter_type='string')
570
571 # By passing a parameterized_job to create_job_common the job entry in
572 # the afe_jobs table will have the field parameterized_job_id set.
573 # The scheduler uses this id in the afe_parameterized_jobs table to
574 # match this job to our known test, and then with the
575 # afe_parameterized_job_parameters table to get the actual image path.
jamesren4a41e012010-07-16 22:33:48 +0000576 return rpc_utils.create_job_common(
Paul Pendlebury5a8c6ad2011-02-01 07:20:17 -0800577 parameterized_job=known_parameterized_job.id,
jamesren4a41e012010-07-16 22:33:48 +0000578 **rpc_utils.get_create_job_common_args(locals()))
mblighe8819cd2008-02-15 16:48:40 +0000579
580
showard9dbdcda2008-10-14 17:34:36 +0000581def abort_host_queue_entries(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000582 """\
showard9dbdcda2008-10-14 17:34:36 +0000583 Abort a set of host queue entries.
jadmanski0afbb632008-06-06 21:10:57 +0000584 """
showard9dbdcda2008-10-14 17:34:36 +0000585 query = models.HostQueueEntry.query_objects(filter_data)
showard0c185192009-01-16 03:07:57 +0000586 query = query.filter(complete=False)
showarddc817512008-11-12 18:16:41 +0000587 models.AclGroup.check_abort_permissions(query)
showard9dbdcda2008-10-14 17:34:36 +0000588 host_queue_entries = list(query.select_related())
showard2bab8f42008-11-12 18:15:22 +0000589 rpc_utils.check_abort_synchronous_jobs(host_queue_entries)
mblighe8819cd2008-02-15 16:48:40 +0000590
showard9dbdcda2008-10-14 17:34:36 +0000591 for queue_entry in host_queue_entries:
showard64a95952010-01-13 21:27:16 +0000592 queue_entry.abort()
showard9d821ab2008-07-11 16:54:29 +0000593
594
Simran Basi73dae552013-02-25 14:57:46 -0800595def _call_special_tasks_on_hosts(task, hosts):
596 """\
597 Schedules a set of hosts for a special task.
598
599 @returns A list of hostnames that a special task was created for.
600 """
601 models.AclGroup.check_for_acl_violation_hosts(hosts)
602 for host in hosts:
603 models.SpecialTask.schedule_special_task(host, task)
604 return list(sorted(host.hostname for host in hosts))
605
606
showard1ff7b2e2009-05-15 23:17:18 +0000607def reverify_hosts(**filter_data):
608 """\
609 Schedules a set of hosts for verify.
mbligh4e545a52009-12-19 05:30:39 +0000610
611 @returns A list of hostnames that a verify task was created for.
showard1ff7b2e2009-05-15 23:17:18 +0000612 """
Simran Basi73dae552013-02-25 14:57:46 -0800613 return _call_special_tasks_on_hosts(models.SpecialTask.Task.VERIFY,
614 models.Host.query_objects(filter_data))
615
616
617def repair_hosts(**filter_data):
618 """\
619 Schedules a set of hosts for repair.
620
621 @returns A list of hostnames that a repair task was created for.
622 """
623 return _call_special_tasks_on_hosts(models.SpecialTask.Task.REPAIR,
624 models.Host.query_objects(filter_data))
showard1ff7b2e2009-05-15 23:17:18 +0000625
626
mblighe8819cd2008-02-15 16:48:40 +0000627def get_jobs(not_yet_run=False, running=False, finished=False, **filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000628 """\
629 Extra filter args for get_jobs:
630 -not_yet_run: Include only jobs that have not yet started running.
631 -running: Include only jobs that have start running but for which not
632 all hosts have completed.
633 -finished: Include only jobs for which all hosts have completed (or
634 aborted).
635 At most one of these three fields should be specified.
636 """
637 filter_data['extra_args'] = rpc_utils.extra_job_filters(not_yet_run,
638 running,
639 finished)
showard0957a842009-05-11 19:25:08 +0000640 job_dicts = []
641 jobs = list(models.Job.query_objects(filter_data))
642 models.Job.objects.populate_relationships(jobs, models.Label,
643 'dependencies')
showardc1a98d12010-01-15 00:22:22 +0000644 models.Job.objects.populate_relationships(jobs, models.JobKeyval, 'keyvals')
showard0957a842009-05-11 19:25:08 +0000645 for job in jobs:
646 job_dict = job.get_object_dict()
647 job_dict['dependencies'] = ','.join(label.name
648 for label in job.dependencies)
showardc1a98d12010-01-15 00:22:22 +0000649 job_dict['keyvals'] = dict((keyval.key, keyval.value)
650 for keyval in job.keyvals)
Eric Lid23bc192011-02-09 14:38:57 -0800651 if job.parameterized_job:
652 job_dict['image'] = get_parameterized_autoupdate_image_url(job)
showard0957a842009-05-11 19:25:08 +0000653 job_dicts.append(job_dict)
654 return rpc_utils.prepare_for_serialization(job_dicts)
mblighe8819cd2008-02-15 16:48:40 +0000655
656
657def get_num_jobs(not_yet_run=False, running=False, finished=False,
jadmanski0afbb632008-06-06 21:10:57 +0000658 **filter_data):
659 """\
660 See get_jobs() for documentation of extra filter parameters.
661 """
662 filter_data['extra_args'] = rpc_utils.extra_job_filters(not_yet_run,
663 running,
664 finished)
665 return models.Job.query_count(filter_data)
mblighe8819cd2008-02-15 16:48:40 +0000666
667
mblighe8819cd2008-02-15 16:48:40 +0000668def get_jobs_summary(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000669 """\
showarda8709c52008-07-03 19:44:54 +0000670 Like get_jobs(), but adds a 'status_counts' field, which is a dictionary
jadmanski0afbb632008-06-06 21:10:57 +0000671 mapping status strings to the number of hosts currently with that
672 status, i.e. {'Queued' : 4, 'Running' : 2}.
673 """
674 jobs = get_jobs(**filter_data)
675 ids = [job['id'] for job in jobs]
676 all_status_counts = models.Job.objects.get_status_counts(ids)
677 for job in jobs:
678 job['status_counts'] = all_status_counts[job['id']]
679 return rpc_utils.prepare_for_serialization(jobs)
mblighe8819cd2008-02-15 16:48:40 +0000680
681
showarda965cef2009-05-15 23:17:41 +0000682def get_info_for_clone(id, preserve_metahosts, queue_entry_filter_data=None):
showarda8709c52008-07-03 19:44:54 +0000683 """\
684 Retrieves all the information needed to clone a job.
685 """
showarda8709c52008-07-03 19:44:54 +0000686 job = models.Job.objects.get(id=id)
showard29f7cd22009-04-29 21:16:24 +0000687 job_info = rpc_utils.get_job_info(job,
showarda965cef2009-05-15 23:17:41 +0000688 preserve_metahosts,
689 queue_entry_filter_data)
showard945072f2008-09-03 20:34:59 +0000690
showardd9992fe2008-07-31 02:15:03 +0000691 host_dicts = []
showard29f7cd22009-04-29 21:16:24 +0000692 for host in job_info['hosts']:
693 host_dict = get_hosts(id=host.id)[0]
694 other_labels = host_dict['labels']
695 if host_dict['platform']:
696 other_labels.remove(host_dict['platform'])
697 host_dict['other_labels'] = ', '.join(other_labels)
showardd9992fe2008-07-31 02:15:03 +0000698 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +0000699
showard29f7cd22009-04-29 21:16:24 +0000700 for host in job_info['one_time_hosts']:
701 host_dict = dict(hostname=host.hostname,
702 id=host.id,
703 platform='(one-time host)',
704 locked_text='')
705 host_dicts.append(host_dict)
showarda8709c52008-07-03 19:44:54 +0000706
showard4d077562009-05-08 18:24:36 +0000707 # convert keys from Label objects to strings (names of labels)
showard29f7cd22009-04-29 21:16:24 +0000708 meta_host_counts = dict((meta_host.name, count) for meta_host, count
showard4d077562009-05-08 18:24:36 +0000709 in job_info['meta_host_counts'].iteritems())
showard29f7cd22009-04-29 21:16:24 +0000710
711 info = dict(job=job.get_object_dict(),
712 meta_host_counts=meta_host_counts,
713 hosts=host_dicts)
714 info['job']['dependencies'] = job_info['dependencies']
715 if job_info['atomic_group']:
716 info['atomic_group_name'] = (job_info['atomic_group']).name
717 else:
718 info['atomic_group_name'] = None
jamesren2275ef12010-04-12 18:25:06 +0000719 info['hostless'] = job_info['hostless']
jamesren76fcf192010-04-21 20:39:50 +0000720 info['drone_set'] = job.drone_set and job.drone_set.name
showarda8709c52008-07-03 19:44:54 +0000721
Eric Lid23bc192011-02-09 14:38:57 -0800722 if job.parameterized_job:
723 info['job']['image'] = get_parameterized_autoupdate_image_url(job)
724
showarda8709c52008-07-03 19:44:54 +0000725 return rpc_utils.prepare_for_serialization(info)
726
727
showard34dc5fa2008-04-24 20:58:40 +0000728# host queue entries
729
730def get_host_queue_entries(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000731 """\
showardc92da832009-04-07 18:14:34 +0000732 @returns A sequence of nested dictionaries of host and job information.
jadmanski0afbb632008-06-06 21:10:57 +0000733 """
showardc92da832009-04-07 18:14:34 +0000734 return rpc_utils.prepare_rows_as_nested_dicts(
735 models.HostQueueEntry.query_objects(filter_data),
736 ('host', 'atomic_group', 'job'))
showard34dc5fa2008-04-24 20:58:40 +0000737
738
739def get_num_host_queue_entries(**filter_data):
jadmanski0afbb632008-06-06 21:10:57 +0000740 """\
741 Get the number of host queue entries associated with this job.
742 """
743 return models.HostQueueEntry.query_count(filter_data)
showard34dc5fa2008-04-24 20:58:40 +0000744
745
showard1e935f12008-07-11 00:11:36 +0000746def get_hqe_percentage_complete(**filter_data):
747 """
showardc92da832009-04-07 18:14:34 +0000748 Computes the fraction of host queue entries matching the given filter data
showard1e935f12008-07-11 00:11:36 +0000749 that are complete.
750 """
751 query = models.HostQueueEntry.query_objects(filter_data)
752 complete_count = query.filter(complete=True).count()
753 total_count = query.count()
754 if total_count == 0:
755 return 1
756 return float(complete_count) / total_count
757
758
showard1a5a4082009-07-28 20:01:37 +0000759# special tasks
760
761def get_special_tasks(**filter_data):
762 return rpc_utils.prepare_rows_as_nested_dicts(
763 models.SpecialTask.query_objects(filter_data),
764 ('host', 'queue_entry'))
765
766
showardc0ac3a72009-07-08 21:14:45 +0000767# support for host detail view
768
769def get_host_queue_entries_and_special_tasks(hostname, query_start=None,
770 query_limit=None):
771 """
772 @returns an interleaved list of HostQueueEntries and SpecialTasks,
773 in approximate run order. each dict contains keys for type, host,
774 job, status, started_on, execution_path, and ID.
775 """
776 total_limit = None
777 if query_limit is not None:
778 total_limit = query_start + query_limit
779 filter_data = {'host__hostname': hostname,
780 'query_limit': total_limit,
781 'sort_by': ['-id']}
782
783 queue_entries = list(models.HostQueueEntry.query_objects(filter_data))
784 special_tasks = list(models.SpecialTask.query_objects(filter_data))
785
786 interleaved_entries = rpc_utils.interleave_entries(queue_entries,
787 special_tasks)
788 if query_start is not None:
789 interleaved_entries = interleaved_entries[query_start:]
790 if query_limit is not None:
791 interleaved_entries = interleaved_entries[:query_limit]
792 return rpc_utils.prepare_for_serialization(interleaved_entries)
793
794
795def get_num_host_queue_entries_and_special_tasks(hostname):
796 filter_data = {'host__hostname': hostname}
797 return (models.HostQueueEntry.query_count(filter_data)
798 + models.SpecialTask.query_count(filter_data))
799
800
showard29f7cd22009-04-29 21:16:24 +0000801# recurring run
802
803def get_recurring(**filter_data):
804 return rpc_utils.prepare_rows_as_nested_dicts(
805 models.RecurringRun.query_objects(filter_data),
806 ('job', 'owner'))
807
808
809def get_num_recurring(**filter_data):
810 return models.RecurringRun.query_count(filter_data)
811
812
813def delete_recurring_runs(**filter_data):
814 to_delete = models.RecurringRun.query_objects(filter_data)
815 to_delete.delete()
816
817
818def create_recurring_run(job_id, start_date, loop_period, loop_count):
showard64a95952010-01-13 21:27:16 +0000819 owner = models.User.current_user().login
showard29f7cd22009-04-29 21:16:24 +0000820 job = models.Job.objects.get(id=job_id)
821 return job.create_recurring_job(start_date=start_date,
822 loop_period=loop_period,
823 loop_count=loop_count,
824 owner=owner)
825
826
mblighe8819cd2008-02-15 16:48:40 +0000827# other
828
showarde0b63622008-08-04 20:58:47 +0000829def echo(data=""):
830 """\
831 Returns a passed in string. For doing a basic test to see if RPC calls
832 can successfully be made.
833 """
834 return data
835
836
showardb7a52fd2009-04-27 20:10:56 +0000837def get_motd():
838 """\
839 Returns the message of the day as a string.
840 """
841 return rpc_utils.get_motd()
842
843
mblighe8819cd2008-02-15 16:48:40 +0000844def get_static_data():
jadmanski0afbb632008-06-06 21:10:57 +0000845 """\
846 Returns a dictionary containing a bunch of data that shouldn't change
847 often and is otherwise inaccessible. This includes:
showardc92da832009-04-07 18:14:34 +0000848
849 priorities: List of job priority choices.
850 default_priority: Default priority value for new jobs.
851 users: Sorted list of all users.
852 labels: Sorted list of all labels.
853 atomic_groups: Sorted list of all atomic groups.
854 tests: Sorted list of all tests.
855 profilers: Sorted list of all profilers.
856 current_user: Logged-in username.
857 host_statuses: Sorted list of possible Host statuses.
858 job_statuses: Sorted list of possible HostQueueEntry statuses.
859 job_timeout_default: The default job timeout length in hours.
showarda1e74b32009-05-12 17:32:04 +0000860 parse_failed_repair_default: Default value for the parse_failed_repair job
861 option.
showardc92da832009-04-07 18:14:34 +0000862 reboot_before_options: A list of valid RebootBefore string enums.
863 reboot_after_options: A list of valid RebootAfter string enums.
864 motd: Server's message of the day.
865 status_dictionary: A mapping from one word job status names to a more
866 informative description.
jadmanski0afbb632008-06-06 21:10:57 +0000867 """
showard21baa452008-10-21 00:08:39 +0000868
869 job_fields = models.Job.get_field_dict()
jamesren76fcf192010-04-21 20:39:50 +0000870 default_drone_set_name = models.DroneSet.default_drone_set_name()
871 drone_sets = ([default_drone_set_name] +
872 sorted(drone_set.name for drone_set in
873 models.DroneSet.objects.exclude(
874 name=default_drone_set_name)))
showard21baa452008-10-21 00:08:39 +0000875
jadmanski0afbb632008-06-06 21:10:57 +0000876 result = {}
877 result['priorities'] = models.Job.Priority.choices()
showard21baa452008-10-21 00:08:39 +0000878 default_priority = job_fields['priority'].default
jadmanski0afbb632008-06-06 21:10:57 +0000879 default_string = models.Job.Priority.get_string(default_priority)
880 result['default_priority'] = default_string
881 result['users'] = get_users(sort_by=['login'])
882 result['labels'] = get_labels(sort_by=['-platform', 'name'])
showardc92da832009-04-07 18:14:34 +0000883 result['atomic_groups'] = get_atomic_groups(sort_by=['name'])
jadmanski0afbb632008-06-06 21:10:57 +0000884 result['tests'] = get_tests(sort_by=['name'])
showard2b9a88b2008-06-13 20:55:03 +0000885 result['profilers'] = get_profilers(sort_by=['name'])
showard0fc38302008-10-23 00:44:07 +0000886 result['current_user'] = rpc_utils.prepare_for_serialization(
showard64a95952010-01-13 21:27:16 +0000887 models.User.current_user().get_object_dict())
showard2b9a88b2008-06-13 20:55:03 +0000888 result['host_statuses'] = sorted(models.Host.Status.names)
mbligh5a198b92008-12-11 19:33:29 +0000889 result['job_statuses'] = sorted(models.HostQueueEntry.Status.names)
showardb1e51872008-10-07 11:08:18 +0000890 result['job_timeout_default'] = models.Job.DEFAULT_TIMEOUT
Simran Basi34217022012-11-06 13:43:15 -0800891 result['job_max_runtime_mins_default'] = (
892 models.Job.DEFAULT_MAX_RUNTIME_MINS)
showarda1e74b32009-05-12 17:32:04 +0000893 result['parse_failed_repair_default'] = bool(
894 models.Job.DEFAULT_PARSE_FAILED_REPAIR)
jamesrendd855242010-03-02 22:23:44 +0000895 result['reboot_before_options'] = model_attributes.RebootBefore.names
896 result['reboot_after_options'] = model_attributes.RebootAfter.names
showard8fbae652009-01-20 23:23:10 +0000897 result['motd'] = rpc_utils.get_motd()
jamesren76fcf192010-04-21 20:39:50 +0000898 result['drone_sets_enabled'] = models.DroneSet.drone_sets_enabled()
899 result['drone_sets'] = drone_sets
jamesren4a41e012010-07-16 22:33:48 +0000900 result['parameterized_jobs'] = models.Job.parameterized_jobs_enabled()
showard8ac29b42008-07-17 17:01:55 +0000901
showardd3dc1992009-04-22 21:01:40 +0000902 result['status_dictionary'] = {"Aborted": "Aborted",
showard8ac29b42008-07-17 17:01:55 +0000903 "Verifying": "Verifying Host",
904 "Pending": "Waiting on other hosts",
905 "Running": "Running autoserv",
906 "Completed": "Autoserv completed",
907 "Failed": "Failed to complete",
showardd823b362008-07-24 16:35:46 +0000908 "Queued": "Queued",
showard5deb6772008-11-04 21:54:33 +0000909 "Starting": "Next in host's queue",
910 "Stopped": "Other host(s) failed verify",
showardd3dc1992009-04-22 21:01:40 +0000911 "Parsing": "Awaiting parse of final results",
showard29f7cd22009-04-29 21:16:24 +0000912 "Gathering": "Gathering log files",
showard8cc058f2009-09-08 16:26:33 +0000913 "Template": "Template job for recurring run",
mbligh4608b002010-01-05 18:22:35 +0000914 "Waiting": "Waiting for scheduler action",
915 "Archiving": "Archiving results"}
jadmanski0afbb632008-06-06 21:10:57 +0000916 return result
showard29f7cd22009-04-29 21:16:24 +0000917
918
919def get_server_time():
920 return datetime.datetime.now().strftime("%Y-%m-%d %H:%M")