blob: 55cd2177a27138f6df896f9badfa5901f3a38772 [file] [log] [blame]
showard26b7ec72009-12-21 22:43:57 +00001"""\
2Functions to expose over the RPC interface.
3"""
4
5__author__ = 'jamesren@google.com (James Ren)'
6
7
jamesrenc3940222010-02-19 21:57:37 +00008import os
9import common
10from django.db import models as django_models
11from autotest_lib.frontend import thread_local
12from autotest_lib.frontend.afe import model_logic, models as afe_models
13from autotest_lib.frontend.afe import rpc_utils as afe_rpc_utils
jamesren3e9f6092010-03-11 21:32:10 +000014from autotest_lib.frontend.tko import models as tko_models
jamesrenb852bce2010-04-07 20:36:13 +000015from autotest_lib.frontend.planner import models, rpc_utils, model_attributes
jamesrenc3940222010-02-19 21:57:37 +000016from autotest_lib.client.common_lib import utils
17
18# basic getter/setter calls
19# TODO: deprecate the basic calls and reimplement them in the REST framework
20
21def get_plan(id):
22 return afe_rpc_utils.prepare_for_serialization(
23 models.Plan.smart_get(id).get_object_dict())
24
25
26def modify_plan(id, **data):
27 models.Plan.smart_get(id).update_object(data)
28
29
jamesren3e9f6092010-03-11 21:32:10 +000030def modify_test_run(id, **data):
31 models.TestRun.objects.get(id=id).update_object(data)
32
33
34def modify_host(id, **data):
35 models.Host.objects.get(id=id).update_object(data)
36
37
38def get_test_config(id):
39 return afe_rpc_utils.prepare_rows_as_nested_dicts(
40 models.TestConfig.objects.filter(id=id), ('control_file',))[0]
41
42
43def add_job(plan_id, test_config_id, afe_job_id):
44 models.Job.objects.create(
45 plan=models.Plan.objects.get(id=plan_id),
46 test_config=models.TestConfig.objects.get(id=test_config_id),
47 afe_job=afe_models.Job.objects.get(id=afe_job_id))
48
49
jamesrenc3940222010-02-19 21:57:37 +000050# more advanced calls
51
52def submit_plan(name, hosts, host_labels, tests,
53 support=None, label_override=None):
54 """
55 Submits a plan to the Test Planner
56
57 @param name: the name of the plan
58 @param hosts: a list of hostnames
59 @param host_labels: a list of host labels. The hosts under test will update
60 to reflect changes in the label
jamesren3e9f6092010-03-11 21:32:10 +000061 @param tests: an ordered list of dictionaries:
62 alias: an alias for the test
63 control_file: the test control file
64 is_server: True if is a server-side control file
65 estimated_runtime: estimated number of hours this test
66 will run
jamesrendbeebf82010-04-08 22:58:26 +000067 @param support: the global support script
jamesrenc3940222010-02-19 21:57:37 +000068 @param label_override: label to prepend to all AFE jobs for this test plan.
69 Defaults to the plan name.
70 """
71 host_objects = []
72 label_objects = []
73
74 for host in hosts or []:
75 try:
76 host_objects.append(
77 afe_models.Host.valid_objects.get(hostname=host))
78 except afe_models.Host.DoesNotExist:
79 raise model_logic.ValidationError(
80 {'hosts': 'host %s does not exist' % host})
81
82 for label in host_labels or []:
83 try:
84 label_objects.append(afe_models.Label.valid_objects.get(name=label))
85 except afe_models.Label.DoesNotExist:
86 raise model_logic.ValidationError(
87 {'host_labels': 'host label %s does not exist' % label})
88
jamesren3e9f6092010-03-11 21:32:10 +000089 aliases_seen = set()
90 test_required_fields = (
91 'alias', 'control_file', 'is_server', 'estimated_runtime')
92 for test in tests:
93 for field in test_required_fields:
94 if field not in test:
95 raise model_logic.ValidationError(
96 {'tests': 'field %s is required' % field})
97
98 alias = test['alias']
99 if alias in aliases_seen:
100 raise model_logic.Validationerror(
101 {'tests': 'alias %s occurs more than once' % alias})
102 aliases_seen.add(alias)
103
jamesrenc3940222010-02-19 21:57:37 +0000104 plan, created = models.Plan.objects.get_or_create(name=name)
105 if not created:
106 raise model_logic.ValidationError(
107 {'name': 'Plan name %s already exists' % name})
108
109 try:
110 label = rpc_utils.create_plan_label(plan)
jamesren3e9f6092010-03-11 21:32:10 +0000111 try:
112 for i, test in enumerate(tests):
113 control, _ = models.ControlFile.objects.get_or_create(
114 contents=test['control_file'])
115 models.TestConfig.objects.create(
116 plan=plan, alias=test['alias'], control_file=control,
117 is_server=test['is_server'], execution_order=i,
118 estimated_runtime=test['estimated_runtime'])
119
120 plan.label_override = label_override
121 plan.support = support or ''
122 plan.save()
123
124 plan.owners.add(afe_models.User.current_user())
125
126 for host in host_objects:
127 planner_host = models.Host.objects.create(plan=plan, host=host)
128
129 plan.host_labels.add(*label_objects)
130
131 rpc_utils.start_plan(plan, label)
132
133 return plan.id
134 except:
135 label.delete()
136 raise
jamesrenc3940222010-02-19 21:57:37 +0000137 except:
138 plan.delete()
139 raise
140
jamesrenc3940222010-02-19 21:57:37 +0000141
142def get_hosts(plan_id):
143 """
144 Gets the hostnames of all the hosts in this test plan.
145
146 Resolves host labels in the plan.
147 """
148 plan = models.Plan.smart_get(plan_id)
149
150 hosts = set(plan.hosts.all().values_list('hostname', flat=True))
151 for label in plan.host_labels.all():
152 hosts.update(label.host_set.all().values_list('hostname', flat=True))
153
154 return afe_rpc_utils.prepare_for_serialization(hosts)
155
156
157def get_atomic_group_control_file():
158 """
159 Gets the control file to apply the atomic group for a set of machines
160 """
161 return rpc_utils.lazy_load(os.path.join(os.path.dirname(__file__),
162 'set_atomic_group_control.srv'))
jamesren3e9f6092010-03-11 21:32:10 +0000163
164
165def get_next_test_configs(plan_id):
166 """
167 Gets information about the next planner test configs that need to be run
168
169 @param plan_id: the ID or name of the test plan
170 @return a dictionary:
171 complete: True or False, shows test plan completion
172 next_configs: a list of dictionaries:
173 host: ID of the host
174 next_test_config_id: ID of the next Planner test to run
175 """
176 plan = models.Plan.smart_get(plan_id)
177
178 result = {'next_configs': []}
179
180 rpc_utils.update_hosts_table(plan)
181 for host in models.Host.objects.filter(plan=plan):
jamesrendbeebf82010-04-08 22:58:26 +0000182 next_test_config = rpc_utils.compute_next_test_config(plan, host)
183 if next_test_config:
184 config = {'next_test_config_id': next_test_config.id,
185 'next_test_config_alias': next_test_config.alias,
jamesren3e9f6092010-03-11 21:32:10 +0000186 'host': host.host.hostname}
187 result['next_configs'].append(config)
188
189 rpc_utils.check_for_completion(plan)
190 result['complete'] = plan.complete
191
192 return result
193
194
195def update_test_runs(plan_id):
196 """
197 Add all applicable TKO jobs to the Planner DB tables
198
199 Looks for tests in the TKO tables that were started as a part of the test
200 plan, and add them to the Planner tables.
201
202 Also updates the status of the test run if the underlying TKO test move from
203 an active status to a completed status.
204
205 @return a list of dictionaries:
206 status: the status of the new (or updated) test run
207 tko_test_idx: the ID of the TKO test added
208 hostname: the host added
209 """
210 plan = models.Plan.objects.get(id=plan_id)
211 updated = []
212
213 for planner_job in plan.job_set.all():
214 known_statuses = dict((test_run.tko_test.test_idx, test_run.status)
215 for test_run in planner_job.testrun_set.all())
216 tko_tests_for_job = tko_models.Test.objects.filter(
217 job__afe_job_id=planner_job.afe_job.id)
218
219 for tko_test in tko_tests_for_job:
220 status = rpc_utils.compute_test_run_status(tko_test.status.word)
221 needs_update = (tko_test.test_idx not in known_statuses or
222 status != known_statuses[tko_test.test_idx])
223 if needs_update:
224 hostnames = tko_test.machine.hostname.split(',')
225 for hostname in hostnames:
226 rpc_utils.add_test_run(
227 plan, planner_job, tko_test, hostname, status)
228 updated.append({'status': status,
229 'tko_test_idx': tko_test.test_idx,
230 'hostname': hostname})
231
232 return updated
jamesrenb852bce2010-04-07 20:36:13 +0000233
234
235def get_failures(plan_id):
236 """
237 Gets a list of the untriaged failures associated with this plan
238
239 @return a list of dictionaries:
240 id: the failure ID, for passing back to triage the failure
241 group: the group for the failure. Normally the same as the
242 reason, but can be different for custom queries
243 machine: the failed machine
244 blocked: True if the failure caused the machine to block
245 test_name: Concatenation of the Planner alias and the TKO test
246 name for the failed test
247 reason: test failure reason
248 seen: True if the failure is marked as "seen"
249 """
250 plan = models.Plan.smart_get(plan_id)
251 result = {}
252
253 failures = plan.testrun_set.filter(
254 finalized=True, triaged=False,
255 status=model_attributes.TestRunStatus.FAILED)
256 failures = failures.select_related('test_job__test', 'host__host',
257 'tko_test')
258 for failure in failures:
259 test_name = '%s:%s' % (
260 failure.test_job.test_config.alias, failure.tko_test.test)
261
262 group_failures = result.setdefault(failure.tko_test.reason, [])
263 failure_dict = {'id': failure.id,
264 'machine': failure.host.host.hostname,
265 'blocked': bool(failure.host.blocked),
266 'test_name': test_name,
267 'reason': failure.tko_test.reason,
268 'seen': bool(failure.seen)}
269 group_failures.append(failure_dict)
270
271 return result
272
273
jamesrendbeebf82010-04-08 22:58:26 +0000274def get_test_runs(**filter_data):
275 """
276 Gets a list of test runs that match the filter data.
277
278 Returns a list of expanded TestRun object dictionaries. Specifically, the
279 "host" and "test_job" fields are expanded. Additionally, the "test_config"
280 field of the "test_job" expansion is also expanded.
281 """
282 result = []
283 for test_run in models.TestRun.objects.filter(**filter_data):
284 test_run_dict = test_run.get_object_dict()
285 test_run_dict['host'] = test_run.host.get_object_dict()
286 test_run_dict['test_job'] = test_run.test_job.get_object_dict()
287 test_run_dict['test_job']['test_config'] = (
288 test_run.test_job.test_config.get_object_dict())
289 result.append(test_run_dict)
290 return result
291
292
293def skip_test(test_config_id, hostname):
294 """
295 Marks a test config as "skipped" for a given host
296 """
297 config = models.TestConfig.objects.get(id=test_config_id)
298 config.skipped_hosts.add(afe_models.Host.objects.get(hostname=hostname))
299
300
jamesrenb852bce2010-04-07 20:36:13 +0000301def get_static_data():
302 result = {'motd': afe_rpc_utils.get_motd()}
303 return result