blob: ff04b2fd96be0074680e0315fe4187309bd41343 [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 get_test_runs(**filter_data):
31 return afe_rpc_utils.prepare_for_serialization(
32 [test_run.get_object_dict() for test_run
33 in models.TestRun.objects.filter(**filter_data)])
34
35
36def modify_test_run(id, **data):
37 models.TestRun.objects.get(id=id).update_object(data)
38
39
40def modify_host(id, **data):
41 models.Host.objects.get(id=id).update_object(data)
42
43
44def get_test_config(id):
45 return afe_rpc_utils.prepare_rows_as_nested_dicts(
46 models.TestConfig.objects.filter(id=id), ('control_file',))[0]
47
48
49def add_job(plan_id, test_config_id, afe_job_id):
50 models.Job.objects.create(
51 plan=models.Plan.objects.get(id=plan_id),
52 test_config=models.TestConfig.objects.get(id=test_config_id),
53 afe_job=afe_models.Job.objects.get(id=afe_job_id))
54
55
jamesrenc3940222010-02-19 21:57:37 +000056# more advanced calls
57
58def submit_plan(name, hosts, host_labels, tests,
59 support=None, label_override=None):
60 """
61 Submits a plan to the Test Planner
62
63 @param name: the name of the plan
64 @param hosts: a list of hostnames
65 @param host_labels: a list of host labels. The hosts under test will update
66 to reflect changes in the label
jamesren3e9f6092010-03-11 21:32:10 +000067 @param tests: an ordered list of dictionaries:
68 alias: an alias for the test
69 control_file: the test control file
70 is_server: True if is a server-side control file
71 estimated_runtime: estimated number of hours this test
72 will run
jamesrenc3940222010-02-19 21:57:37 +000073 @param support: the global support object
74 @param label_override: label to prepend to all AFE jobs for this test plan.
75 Defaults to the plan name.
76 """
77 host_objects = []
78 label_objects = []
79
80 for host in hosts or []:
81 try:
82 host_objects.append(
83 afe_models.Host.valid_objects.get(hostname=host))
84 except afe_models.Host.DoesNotExist:
85 raise model_logic.ValidationError(
86 {'hosts': 'host %s does not exist' % host})
87
88 for label in host_labels or []:
89 try:
90 label_objects.append(afe_models.Label.valid_objects.get(name=label))
91 except afe_models.Label.DoesNotExist:
92 raise model_logic.ValidationError(
93 {'host_labels': 'host label %s does not exist' % label})
94
jamesren3e9f6092010-03-11 21:32:10 +000095 aliases_seen = set()
96 test_required_fields = (
97 'alias', 'control_file', 'is_server', 'estimated_runtime')
98 for test in tests:
99 for field in test_required_fields:
100 if field not in test:
101 raise model_logic.ValidationError(
102 {'tests': 'field %s is required' % field})
103
104 alias = test['alias']
105 if alias in aliases_seen:
106 raise model_logic.Validationerror(
107 {'tests': 'alias %s occurs more than once' % alias})
108 aliases_seen.add(alias)
109
jamesrenc3940222010-02-19 21:57:37 +0000110 plan, created = models.Plan.objects.get_or_create(name=name)
111 if not created:
112 raise model_logic.ValidationError(
113 {'name': 'Plan name %s already exists' % name})
114
115 try:
116 label = rpc_utils.create_plan_label(plan)
jamesren3e9f6092010-03-11 21:32:10 +0000117 try:
118 for i, test in enumerate(tests):
119 control, _ = models.ControlFile.objects.get_or_create(
120 contents=test['control_file'])
121 models.TestConfig.objects.create(
122 plan=plan, alias=test['alias'], control_file=control,
123 is_server=test['is_server'], execution_order=i,
124 estimated_runtime=test['estimated_runtime'])
125
126 plan.label_override = label_override
127 plan.support = support or ''
128 plan.save()
129
130 plan.owners.add(afe_models.User.current_user())
131
132 for host in host_objects:
133 planner_host = models.Host.objects.create(plan=plan, host=host)
134
135 plan.host_labels.add(*label_objects)
136
137 rpc_utils.start_plan(plan, label)
138
139 return plan.id
140 except:
141 label.delete()
142 raise
jamesrenc3940222010-02-19 21:57:37 +0000143 except:
144 plan.delete()
145 raise
146
jamesrenc3940222010-02-19 21:57:37 +0000147
148def get_hosts(plan_id):
149 """
150 Gets the hostnames of all the hosts in this test plan.
151
152 Resolves host labels in the plan.
153 """
154 plan = models.Plan.smart_get(plan_id)
155
156 hosts = set(plan.hosts.all().values_list('hostname', flat=True))
157 for label in plan.host_labels.all():
158 hosts.update(label.host_set.all().values_list('hostname', flat=True))
159
160 return afe_rpc_utils.prepare_for_serialization(hosts)
161
162
163def get_atomic_group_control_file():
164 """
165 Gets the control file to apply the atomic group for a set of machines
166 """
167 return rpc_utils.lazy_load(os.path.join(os.path.dirname(__file__),
168 'set_atomic_group_control.srv'))
jamesren3e9f6092010-03-11 21:32:10 +0000169
170
171def get_next_test_configs(plan_id):
172 """
173 Gets information about the next planner test configs that need to be run
174
175 @param plan_id: the ID or name of the test plan
176 @return a dictionary:
177 complete: True or False, shows test plan completion
178 next_configs: a list of dictionaries:
179 host: ID of the host
180 next_test_config_id: ID of the next Planner test to run
181 """
182 plan = models.Plan.smart_get(plan_id)
183
184 result = {'next_configs': []}
185
186 rpc_utils.update_hosts_table(plan)
187 for host in models.Host.objects.filter(plan=plan):
188 next_test_config_id = rpc_utils.compute_next_test_config(plan, host)
189 if next_test_config_id:
190 config = {'next_test_config_id': next_test_config_id,
191 'host': host.host.hostname}
192 result['next_configs'].append(config)
193
194 rpc_utils.check_for_completion(plan)
195 result['complete'] = plan.complete
196
197 return result
198
199
200def update_test_runs(plan_id):
201 """
202 Add all applicable TKO jobs to the Planner DB tables
203
204 Looks for tests in the TKO tables that were started as a part of the test
205 plan, and add them to the Planner tables.
206
207 Also updates the status of the test run if the underlying TKO test move from
208 an active status to a completed status.
209
210 @return a list of dictionaries:
211 status: the status of the new (or updated) test run
212 tko_test_idx: the ID of the TKO test added
213 hostname: the host added
214 """
215 plan = models.Plan.objects.get(id=plan_id)
216 updated = []
217
218 for planner_job in plan.job_set.all():
219 known_statuses = dict((test_run.tko_test.test_idx, test_run.status)
220 for test_run in planner_job.testrun_set.all())
221 tko_tests_for_job = tko_models.Test.objects.filter(
222 job__afe_job_id=planner_job.afe_job.id)
223
224 for tko_test in tko_tests_for_job:
225 status = rpc_utils.compute_test_run_status(tko_test.status.word)
226 needs_update = (tko_test.test_idx not in known_statuses or
227 status != known_statuses[tko_test.test_idx])
228 if needs_update:
229 hostnames = tko_test.machine.hostname.split(',')
230 for hostname in hostnames:
231 rpc_utils.add_test_run(
232 plan, planner_job, tko_test, hostname, status)
233 updated.append({'status': status,
234 'tko_test_idx': tko_test.test_idx,
235 'hostname': hostname})
236
237 return updated
jamesrenb852bce2010-04-07 20:36:13 +0000238
239
240def get_failures(plan_id):
241 """
242 Gets a list of the untriaged failures associated with this plan
243
244 @return a list of dictionaries:
245 id: the failure ID, for passing back to triage the failure
246 group: the group for the failure. Normally the same as the
247 reason, but can be different for custom queries
248 machine: the failed machine
249 blocked: True if the failure caused the machine to block
250 test_name: Concatenation of the Planner alias and the TKO test
251 name for the failed test
252 reason: test failure reason
253 seen: True if the failure is marked as "seen"
254 """
255 plan = models.Plan.smart_get(plan_id)
256 result = {}
257
258 failures = plan.testrun_set.filter(
259 finalized=True, triaged=False,
260 status=model_attributes.TestRunStatus.FAILED)
261 failures = failures.select_related('test_job__test', 'host__host',
262 'tko_test')
263 for failure in failures:
264 test_name = '%s:%s' % (
265 failure.test_job.test_config.alias, failure.tko_test.test)
266
267 group_failures = result.setdefault(failure.tko_test.reason, [])
268 failure_dict = {'id': failure.id,
269 'machine': failure.host.host.hostname,
270 'blocked': bool(failure.host.blocked),
271 'test_name': test_name,
272 'reason': failure.tko_test.reason,
273 'seen': bool(failure.seen)}
274 group_failures.append(failure_dict)
275
276 return result
277
278
279def get_static_data():
280 result = {'motd': afe_rpc_utils.get_motd()}
281 return result