OLD | NEW |
1 """\ | 1 """\ |
2 Functions to expose over the RPC interface. | 2 Functions to expose over the RPC interface. |
3 """ | 3 """ |
4 | 4 |
5 __author__ = 'jamesren@google.com (James Ren)' | 5 __author__ = 'jamesren@google.com (James Ren)' |
6 | 6 |
7 | 7 |
8 import os | 8 import os |
9 import common | 9 import common |
10 from django.db import models as django_models | 10 from django.db import models as django_models |
11 from autotest_lib.frontend import thread_local | 11 from autotest_lib.frontend import thread_local |
12 from autotest_lib.frontend.afe import model_logic, models as afe_models | 12 from autotest_lib.frontend.afe import model_logic, models as afe_models |
13 from autotest_lib.frontend.afe import rpc_utils as afe_rpc_utils | 13 from autotest_lib.frontend.afe import rpc_utils as afe_rpc_utils |
14 from autotest_lib.frontend.tko import models as tko_models | 14 from autotest_lib.frontend.tko import models as tko_models |
15 from autotest_lib.frontend.planner import models, rpc_utils | 15 from autotest_lib.frontend.planner import models, rpc_utils, model_attributes |
| 16 from autotest_lib.frontend.planner import failure_actions |
16 from autotest_lib.client.common_lib import utils | 17 from autotest_lib.client.common_lib import utils |
17 | 18 |
18 # basic getter/setter calls | 19 # basic getter/setter calls |
19 # TODO: deprecate the basic calls and reimplement them in the REST framework | 20 # TODO: deprecate the basic calls and reimplement them in the REST framework |
20 | 21 |
21 def get_plan(id): | 22 def get_plan(id): |
22 return afe_rpc_utils.prepare_for_serialization( | 23 return afe_rpc_utils.prepare_for_serialization( |
23 models.Plan.smart_get(id).get_object_dict()) | 24 models.Plan.smart_get(id).get_object_dict()) |
24 | 25 |
25 | 26 |
26 def modify_plan(id, **data): | 27 def modify_plan(id, **data): |
27 models.Plan.smart_get(id).update_object(data) | 28 models.Plan.smart_get(id).update_object(data) |
28 | 29 |
29 | 30 |
30 def 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 | |
36 def modify_test_run(id, **data): | 31 def modify_test_run(id, **data): |
37 models.TestRun.objects.get(id=id).update_object(data) | 32 models.TestRun.objects.get(id=id).update_object(data) |
38 | 33 |
39 | 34 |
40 def modify_host(id, **data): | 35 def modify_host(id, **data): |
41 models.Host.objects.get(id=id).update_object(data) | 36 models.Host.objects.get(id=id).update_object(data) |
42 | 37 |
43 | 38 |
44 def get_test_config(id): | 39 def get_test_config(id): |
45 return afe_rpc_utils.prepare_rows_as_nested_dicts( | 40 return afe_rpc_utils.prepare_rows_as_nested_dicts( |
(...skipping 17 matching lines...) Expand all Loading... |
63 @param name: the name of the plan | 58 @param name: the name of the plan |
64 @param hosts: a list of hostnames | 59 @param hosts: a list of hostnames |
65 @param host_labels: a list of host labels. The hosts under test will update | 60 @param host_labels: a list of host labels. The hosts under test will update |
66 to reflect changes in the label | 61 to reflect changes in the label |
67 @param tests: an ordered list of dictionaries: | 62 @param tests: an ordered list of dictionaries: |
68 alias: an alias for the test | 63 alias: an alias for the test |
69 control_file: the test control file | 64 control_file: the test control file |
70 is_server: True if is a server-side control file | 65 is_server: True if is a server-side control file |
71 estimated_runtime: estimated number of hours this test | 66 estimated_runtime: estimated number of hours this test |
72 will run | 67 will run |
73 @param support: the global support object | 68 @param support: the global support script |
74 @param label_override: label to prepend to all AFE jobs for this test plan. | 69 @param label_override: label to prepend to all AFE jobs for this test plan. |
75 Defaults to the plan name. | 70 Defaults to the plan name. |
76 """ | 71 """ |
77 host_objects = [] | 72 host_objects = [] |
78 label_objects = [] | 73 label_objects = [] |
79 | 74 |
80 for host in hosts or []: | 75 for host in hosts or []: |
81 try: | 76 try: |
82 host_objects.append( | 77 host_objects.append( |
83 afe_models.Host.valid_objects.get(hostname=host)) | 78 afe_models.Host.valid_objects.get(hostname=host)) |
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
178 next_configs: a list of dictionaries: | 173 next_configs: a list of dictionaries: |
179 host: ID of the host | 174 host: ID of the host |
180 next_test_config_id: ID of the next Planner test to run | 175 next_test_config_id: ID of the next Planner test to run |
181 """ | 176 """ |
182 plan = models.Plan.smart_get(plan_id) | 177 plan = models.Plan.smart_get(plan_id) |
183 | 178 |
184 result = {'next_configs': []} | 179 result = {'next_configs': []} |
185 | 180 |
186 rpc_utils.update_hosts_table(plan) | 181 rpc_utils.update_hosts_table(plan) |
187 for host in models.Host.objects.filter(plan=plan): | 182 for host in models.Host.objects.filter(plan=plan): |
188 next_test_config_id = rpc_utils.compute_next_test_config(plan, host) | 183 next_test_config = rpc_utils.compute_next_test_config(plan, host) |
189 if next_test_config_id: | 184 if next_test_config: |
190 config = {'next_test_config_id': next_test_config_id, | 185 config = {'next_test_config_id': next_test_config.id, |
| 186 'next_test_config_alias': next_test_config.alias, |
191 'host': host.host.hostname} | 187 'host': host.host.hostname} |
192 result['next_configs'].append(config) | 188 result['next_configs'].append(config) |
193 | 189 |
194 rpc_utils.check_for_completion(plan) | 190 rpc_utils.check_for_completion(plan) |
195 result['complete'] = plan.complete | 191 result['complete'] = plan.complete |
196 | 192 |
197 return result | 193 return result |
198 | 194 |
199 | 195 |
200 def update_test_runs(plan_id): | 196 def update_test_runs(plan_id): |
201 """ | 197 """ |
202 Add all applicable TKO jobs to the Planner DB tables | 198 Add all applicable TKO jobs to the Planner DB tables |
203 | 199 |
204 Looks for tests in the TKO tables that were started as a part of the test | 200 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. | 201 plan, and add them to the Planner tables. |
206 | 202 |
207 Also updates the status of the test run if the underlying TKO test move from | 203 Also updates the status of the test run if the underlying TKO test move from |
208 an active status to a completed status. | 204 an active status to a completed status. |
209 | 205 |
210 @return a list of dictionaries: | 206 @return a list of dictionaries: |
211 status: the status of the new (or updated) test run | 207 status: the status of the new (or updated) test run |
212 tko_test_idx: the ID of the TKO test added | 208 tko_test_idx: the ID of the TKO test added |
213 hostname: the host added | 209 hostname: the host added |
214 """ | 210 """ |
215 plan = models.Plan.objects.get(id=plan_id) | 211 plan = models.Plan.smart_get(plan_id) |
216 updated = [] | 212 updated = [] |
217 | 213 |
218 for planner_job in plan.job_set.all(): | 214 for planner_job in plan.job_set.all(): |
219 known_statuses = dict((test_run.tko_test.test_idx, test_run.status) | 215 known_statuses = dict((test_run.tko_test.test_idx, test_run.status) |
220 for test_run in planner_job.testrun_set.all()) | 216 for test_run in planner_job.testrun_set.all()) |
221 tko_tests_for_job = tko_models.Test.objects.filter( | 217 tko_tests_for_job = tko_models.Test.objects.filter( |
222 job__afe_job_id=planner_job.afe_job.id) | 218 job__afe_job_id=planner_job.afe_job.id) |
223 | 219 |
224 for tko_test in tko_tests_for_job: | 220 for tko_test in tko_tests_for_job: |
225 status = rpc_utils.compute_test_run_status(tko_test.status.word) | 221 status = rpc_utils.compute_test_run_status(tko_test.status.word) |
226 needs_update = (tko_test.test_idx not in known_statuses or | 222 needs_update = (tko_test.test_idx not in known_statuses or |
227 status != known_statuses[tko_test.test_idx]) | 223 status != known_statuses[tko_test.test_idx]) |
228 if needs_update: | 224 if needs_update: |
229 hostnames = tko_test.machine.hostname.split(',') | 225 hostnames = tko_test.machine.hostname.split(',') |
230 for hostname in hostnames: | 226 for hostname in hostnames: |
231 rpc_utils.add_test_run( | 227 rpc_utils.add_test_run( |
232 plan, planner_job, tko_test, hostname, status) | 228 plan, planner_job, tko_test, hostname, status) |
233 updated.append({'status': status, | 229 updated.append({'status': status, |
234 'tko_test_idx': tko_test.test_idx, | 230 'tko_test_idx': tko_test.test_idx, |
235 'hostname': hostname}) | 231 'hostname': hostname}) |
236 | 232 |
237 return updated | 233 return updated |
| 234 |
| 235 |
| 236 def get_failures(plan_id): |
| 237 """ |
| 238 Gets a list of the untriaged failures associated with this plan |
| 239 |
| 240 @return a list of dictionaries: |
| 241 id: the failure ID, for passing back to triage the failure |
| 242 group: the group for the failure. Normally the same as the |
| 243 reason, but can be different for custom queries |
| 244 machine: the failed machine |
| 245 blocked: True if the failure caused the machine to block |
| 246 test_name: Concatenation of the Planner alias and the TKO test |
| 247 name for the failed test |
| 248 reason: test failure reason |
| 249 seen: True if the failure is marked as "seen" |
| 250 """ |
| 251 plan = models.Plan.smart_get(plan_id) |
| 252 result = {} |
| 253 |
| 254 failures = plan.testrun_set.filter( |
| 255 finalized=True, triaged=False, |
| 256 status=model_attributes.TestRunStatus.FAILED) |
| 257 failures = failures.select_related('test_job__test', 'host__host', |
| 258 'tko_test') |
| 259 for failure in failures: |
| 260 test_name = '%s:%s' % ( |
| 261 failure.test_job.test_config.alias, failure.tko_test.test) |
| 262 |
| 263 group_failures = result.setdefault(failure.tko_test.reason, []) |
| 264 failure_dict = {'id': failure.id, |
| 265 'machine': failure.host.host.hostname, |
| 266 'blocked': bool(failure.host.blocked), |
| 267 'test_name': test_name, |
| 268 'reason': failure.tko_test.reason, |
| 269 'seen': bool(failure.seen)} |
| 270 group_failures.append(failure_dict) |
| 271 |
| 272 return result |
| 273 |
| 274 |
| 275 def get_test_runs(**filter_data): |
| 276 """ |
| 277 Gets a list of test runs that match the filter data. |
| 278 |
| 279 Returns a list of expanded TestRun object dictionaries. Specifically, the |
| 280 "host" and "test_job" fields are expanded. Additionally, the "test_config" |
| 281 field of the "test_job" expansion is also expanded. |
| 282 """ |
| 283 result = [] |
| 284 for test_run in models.TestRun.objects.filter(**filter_data): |
| 285 test_run_dict = test_run.get_object_dict() |
| 286 test_run_dict['host'] = test_run.host.get_object_dict() |
| 287 test_run_dict['test_job'] = test_run.test_job.get_object_dict() |
| 288 test_run_dict['test_job']['test_config'] = ( |
| 289 test_run.test_job.test_config.get_object_dict()) |
| 290 result.append(test_run_dict) |
| 291 return result |
| 292 |
| 293 |
| 294 def skip_test(test_config_id, hostname): |
| 295 """ |
| 296 Marks a test config as "skipped" for a given host |
| 297 """ |
| 298 config = models.TestConfig.objects.get(id=test_config_id) |
| 299 config.skipped_hosts.add(afe_models.Host.objects.get(hostname=hostname)) |
| 300 |
| 301 |
| 302 def mark_failures_as_seen(failure_ids): |
| 303 """ |
| 304 Marks a set of failures as 'seen' |
| 305 |
| 306 @param failure_ids: A list of failure IDs, as returned by get_failures(), to |
| 307 mark as seen |
| 308 """ |
| 309 models.TestRun.objects.filter(id__in=failure_ids).update(seen=True) |
| 310 |
| 311 |
| 312 def process_failure(failure_id, host_action, test_action, labels=(), |
| 313 keyvals=None, bugs=(), reason=None, invalidate=False): |
| 314 """ |
| 315 Triage a failure |
| 316 |
| 317 @param failure_id: The failure ID, as returned by get_failures() |
| 318 @param host_action: One of 'Block', 'Unblock', 'Reinstall' |
| 319 @param test_action: One of 'Skip', 'Rerun' |
| 320 |
| 321 @param labels: Test labels to apply, by name |
| 322 @param keyvals: Dictionary of job keyvals to add (or replace) |
| 323 @param bugs: List of bug IDs to associate with this failure |
| 324 @param reason: An override for the test failure reason |
| 325 @param invalidate: True if failure should be invalidated for the purposes of |
| 326 reporting. Defaults to False. |
| 327 """ |
| 328 if keyvals is None: |
| 329 keyvals = {} |
| 330 |
| 331 host_choices = failure_actions.HostAction.values |
| 332 test_choices = failure_actions.TestAction.values |
| 333 if host_action not in host_choices: |
| 334 raise model_logic.ValidationError( |
| 335 {'host_action': ('host action %s not valid; must be one of %s' |
| 336 % (host_action, ', '.join(host_choices)))}) |
| 337 if test_action not in test_choices: |
| 338 raise model_logic.ValidationError( |
| 339 {'test_action': ('test action %s not valid; must be one of %s' |
| 340 % (test_action, ', '.join(test_choices)))}) |
| 341 |
| 342 failure = models.TestRun.objects.get(id=failure_id) |
| 343 |
| 344 rpc_utils.process_host_action(failure.host, host_action) |
| 345 rpc_utils.process_test_action(failure.test_job, test_action) |
| 346 |
| 347 # Add the test labels |
| 348 for label in labels: |
| 349 tko_test_label, _ = ( |
| 350 tko_models.TestLabel.objects.get_or_create(name=label)) |
| 351 failure.tko_test.testlabel_set.add(tko_test_label) |
| 352 |
| 353 # Set the job keyvals |
| 354 for key, value in keyvals.iteritems(): |
| 355 keyval, created = tko_models.JobKeyval.objects.get_or_create( |
| 356 job=failure.tko_test.job, key=key) |
| 357 if not created: |
| 358 tko_models.JobKeyval.objects.create(job=failure.tko_test.job, |
| 359 key='original_' + key, |
| 360 value=keyval.value) |
| 361 keyval.value = value |
| 362 keyval.save() |
| 363 |
| 364 # Add the bugs |
| 365 for bug_id in bugs: |
| 366 bug, _ = models.Bug.objects.get_or_create(external_uid=bug_id) |
| 367 failure.bugs.add(bug) |
| 368 |
| 369 # Set the failure reason |
| 370 if reason is not None: |
| 371 tko_models.TestAttribute.objects.create(test=failure.tko_test, |
| 372 attribute='original_reason', |
| 373 value=failure.tko_test.reason) |
| 374 failure.tko_test.reason = reason |
| 375 failure.tko_test.save() |
| 376 |
| 377 # Set 'invalidated', 'seen', and 'triaged' |
| 378 failure.invalidated = invalidate |
| 379 failure.seen = True |
| 380 failure.triaged = True |
| 381 failure.save() |
| 382 |
| 383 |
| 384 def get_static_data(): |
| 385 result = {'motd': afe_rpc_utils.get_motd(), |
| 386 'host_actions': sorted(failure_actions.HostAction.values), |
| 387 'test_actions': sorted(failure_actions.TestAction.values)} |
| 388 return result |
OLD | NEW |