Revert "Revert "[autotest] TKO parser mark original tests as invalid""
This reverts commit dbd9037df0cbe1232fcb016a2c261f5994ae03a2.
Commit this cl again. Once this cl lands, deploy the db changes together with
https://chrome-internal-review.googlesource.com/#/c/161494/
https://chrome-internal-review.googlesource.com/#/c/161426/
DEPLOY=apache, migrate, wmatrix db changes should be pushed together (see commit messeage)
Change-Id: I47916708e7b49bbc2064370262e3f9e231f08efe
Reviewed-on: https://chromium-review.googlesource.com/197306
Reviewed-by: Dan Shi <[email protected]>
Tested-by: Fang Deng <[email protected]>
Commit-Queue: Fang Deng <[email protected]>
diff --git a/frontend/migrations/086_add_invalidates_test_idx_to_tko_tests.py b/frontend/migrations/086_add_invalidates_test_idx_to_tko_tests.py
new file mode 100644
index 0000000..ffded8c
--- /dev/null
+++ b/frontend/migrations/086_add_invalidates_test_idx_to_tko_tests.py
@@ -0,0 +1,35 @@
+ADD_COLUMN = """
+ALTER TABLE tko_tests
+ADD COLUMN `invalidates_test_idx` int(10) unsigned DEFAULT NULL;
+"""
+ADD_INDEX = """ALTER TABLE tko_tests ADD INDEX(invalidates_test_idx);"""
+ADD_FOREIGN_KEY = """
+ALTER TABLE tko_tests
+ADD CONSTRAINT invalidates_test_idx_fk FOREIGN KEY
+(`invalidates_test_idx`) REFERENCES `tko_tests`(`test_idx`)
+ON DELETE NO ACTION;
+"""
+DROP_FOREIGN_KEY = """
+ALTER TABLE tko_tests DROP FOREIGN KEY `invalidates_test_idx_fk`;
+"""
+DROP_COLUMN = """ALTER TABLE tko_tests DROP `invalidates_test_idx`; """
+
+def migrate_up(manager):
+ """Pick up the changes.
+
+ @param manager: A MigrationManager object.
+
+ """
+ manager.execute(ADD_COLUMN)
+ manager.execute(ADD_INDEX)
+ manager.execute(ADD_FOREIGN_KEY)
+
+
+def migrate_down(manager):
+ """Drop the changes.
+
+ @param manager: A MigrationManager object.
+
+ """
+ manager.execute(DROP_FOREIGN_KEY)
+ manager.execute(DROP_COLUMN)
diff --git a/frontend/tko/models.py b/frontend/tko/models.py
index a9c6bf0..286279b 100644
--- a/frontend/tko/models.py
+++ b/frontend/tko/models.py
@@ -210,6 +210,10 @@
machine = dbmodels.ForeignKey(Machine, db_column='machine_idx')
finished_time = dbmodels.DateTimeField(null=True, blank=True)
started_time = dbmodels.DateTimeField(null=True, blank=True)
+ invalid = dbmodels.BooleanField(default=False)
+ invalidates_test = dbmodels.ForeignKey(
+ 'self', null=True, db_column='invalidates_test_idx',
+ related_name='invalidates_test_set')
objects = model_logic.ExtendedManager()
diff --git a/tko/parse.py b/tko/parse.py
index ef7971a..0105a28 100755
--- a/tko/parse.py
+++ b/tko/parse.py
@@ -3,11 +3,14 @@
import os, sys, optparse, fcntl, errno, traceback, socket
import common
+from autotest_lib.frontend import setup_django_environment
from autotest_lib.client.common_lib import mail, pidfile
+from autotest_lib.frontend.tko import models as tko_models
from autotest_lib.tko import db as tko_db, utils as tko_utils
from autotest_lib.tko import models, status_lib
from autotest_lib.tko.perf_upload import perf_uploader
from autotest_lib.client.common_lib import utils
+from autotest_lib.server.cros.dynamic_suite import constants
def parse_args():
@@ -71,6 +74,85 @@
mail.send("", job.user, "", subject, message_header + message)
+def _invalidate_original_tests(orig_job_idx, retry_job_idx):
+ """Retry tests invalidates original tests.
+
+ Whenever a retry job is complete, we want to invalidate the original
+ job's test results, such that the consumers of the tko database
+ (e.g. tko frontend, wmatrix) could figure out which results are the latest.
+
+ When a retry job is parsed, we retrieve the original job's afe_job_id
+ from the retry job's keyvals, which is then converted to tko job_idx and
+ passed into this method as |orig_job_idx|.
+
+ In this method, we are going to invalidate the rows in tko_tests that are
+ associated with the original job by flipping their 'invalid' bit to True.
+ In addition, in tko_tests, we also maintain a pointer from the retry results
+ to the original results, so that later we can always know which rows in
+ tko_tests are retries and which are the corresponding original results.
+ This is done by setting the field 'invalidates_test_idx' of the tests
+ associated with the retry job.
+
+ For example, assume Job(job_idx=105) are retried by Job(job_idx=108), after
+ this method is run, their tko_tests rows will look like:
+ __________________________________________________________________________
+ test_idx| job_idx | test | ... | invalid | invalidates_test_idx
+ 10 | 105 | dummy_Fail.Error| ... | 1 | NULL
+ 11 | 105 | dummy_Fail.Fail | ... | 1 | NULL
+ ...
+ 20 | 108 | dummy_Fail.Error| ... | 0 | 10
+ 21 | 108 | dummy_Fail.Fail | ... | 0 | 11
+ __________________________________________________________________________
+ Note the invalid bits of the rows for Job(job_idx=105) are set to '1'.
+ And the 'invalidates_test_idx' fields of the rows for Job(job_idx=108)
+ are set to 10 and 11 (the test_idx of the rows for the original job).
+
+ @param orig_job_idx: An integer representing the original job's
+ tko job_idx. Tests associated with this job will
+ be marked as 'invalid'.
+ @param retry_job_idx: An integer representing the retry job's
+ tko job_idx. The field 'invalidates_test_idx'
+ of the tests associated with this job will be updated.
+
+ """
+ msg = 'orig_job_idx: %s, retry_job_idx: %s' % (orig_job_idx, retry_job_idx)
+ if not orig_job_idx or not retry_job_idx:
+ tko_utils.dprint('ERROR: Could not invalidate tests: ' + msg)
+ # Using django models here makes things easier, but make sure that
+ # before this method is called, all other relevant transactions have been
+ # committed to avoid race condition. In the long run, we might consider
+ # to make the rest of parser use django models.
+ orig_tests = tko_models.Test.objects.filter(job__job_idx=orig_job_idx)
+ retry_tests = tko_models.Test.objects.filter(job__job_idx=retry_job_idx)
+
+ # Invalidate original tests.
+ orig_tests.update(invalid=True)
+
+ # Maintain a dictionary that maps (test, subdir) to original tests.
+ # Note that within the scope of a job, (test, subdir) uniquelly
+ # identifies a test run, but 'test' does not.
+ # In a control file, one could run the same test with different
+ # 'subdir_tag', for example,
+ # job.run_test('dummy_Fail', tag='Error', subdir_tag='subdir_1')
+ # job.run_test('dummy_Fail', tag='Error', subdir_tag='subdir_2')
+ # In tko, we will get
+ # (test='dummy_Fail.Error', subdir='dummy_Fail.Error.subdir_1')
+ # (test='dummy_Fail.Error', subdir='dummy_Fail.Error.subdir_2')
+ invalidated_tests = {(orig_test.test, orig_test.subdir): orig_test
+ for orig_test in orig_tests}
+ for retry in retry_tests:
+ # It is possible that (retry.test, retry.subdir) doesn't exist
+ # in invalidated_tests. This could happen when the original job
+ # didn't run some of its tests. For example, a dut goes offline
+ # since the beginning of the job, in which case invalidated_tests
+ # will only have one entry for 'SERVER_JOB'.
+ orig_test = invalidated_tests.get((retry.test, retry.subdir), None)
+ if orig_test:
+ retry.invalidates_test = orig_test
+ retry.save()
+ tko_utils.dprint('DEBUG: Invalidated tests associated to job: ' + msg)
+
+
def parse_one(db, jobname, path, reparse, mail_on_failure):
"""
Parse a single job. Optionally send email on failure.
@@ -158,13 +240,26 @@
% (jobname, job.user))
mailfailure(jobname, job, message)
- # write the job into the database
+ # write the job into the database.
db.insert_job(jobname, job)
# Upload perf values to the perf dashboard, if applicable.
for test in job.tests:
perf_uploader.upload_test(job, test)
+ # Although the cursor has autocommit, we still need to force it to commit
+ # existing changes before we can use django models, otherwise it
+ # will go into deadlock when django models try to start a new trasaction
+ # while the current one has not finished yet.
+ db.commit()
+
+ # Handle retry job.
+ orig_afe_job_id = job_keyval.get(constants.RETRY_ORIGINAL_JOB_ID, None)
+ if orig_afe_job_id:
+ orig_job_idx = tko_models.Job.objects.get(
+ afe_job_id=orig_afe_job_id).job_idx
+ _invalidate_original_tests(orig_job_idx, job.index)
+
# Serializing job into a binary file
try:
from autotest_lib.tko import tko_pb2
diff --git a/tko/site_parse_unittest.py b/tko/site_parse_unittest.py
index 4a2a485..f54b772 100755
--- a/tko/site_parse_unittest.py
+++ b/tko/site_parse_unittest.py
@@ -11,6 +11,8 @@
import common
from autotest_lib.client.common_lib import global_config
+from autotest_lib.frontend import setup_django_environment
+from autotest_lib.frontend import setup_test_environment
from autotest_lib.tko.site_parse import StackTrace
@@ -18,6 +20,7 @@
def setUp(self):
+ setup_test_environment.set_up()
self._fake_results = tempfile.mkdtemp()
self._cros_src_dir = global_config.global_config.get_config_value(
'CROS', 'source_tree', default=None)
@@ -39,6 +42,7 @@
def tearDown(self):
+ setup_test_environment.tear_down()
shutil.rmtree(self._fake_results)
if os.path.exists(self._cache_dir):
shutil.rmtree(self._cache_dir)