[re-land] sync_stages: fix _VerifyMasterId to respect milestone_version
This is a re-land of https://chromium-review.googlesource.com/344762
The original relied on cidb to return a build status is that it was not
returning. The issue was masked in unit tests, because fake_cidb *was*
returning this key. In this patch, the underlying issue is fixed, and
fake_cidb is brought into compliance with cidb in terms of exactly what
keys it returns (so that in the future, unit tests will not be fooled).
BUG=chromium:611929
TEST=cidb_integration_test; unit tests
Change-Id: Ib002348e957f75cc12fc626c2729e0c27e0bb2a7
Previous-Reviewed-on: https://chromium-review.googlesource.com/345430
(cherry picked from commit 3468867ee9e0a8469afb8fba5da0ca82ad1715e3)
Reviewed-on: https://chromium-review.googlesource.com/345699
Tested-by: Aviv Keshet <akeshet@chromium.org>
Reviewed-by: Aviv Keshet <akeshet@chromium.org>
Commit-Queue: Aviv Keshet <akeshet@chromium.org>
diff --git a/cbuildbot/stages/sync_stages.py b/cbuildbot/stages/sync_stages.py
index 254177f..20202e0 100644
--- a/cbuildbot/stages/sync_stages.py
+++ b/cbuildbot/stages/sync_stages.py
@@ -699,7 +699,9 @@
if db and master_id:
assert not self._run.options.force_version
master_build_status = db.GetBuildStatus(master_id)
- latest = db.GetBuildHistory(master_build_status['build_config'], 1)
+ latest = db.GetBuildHistory(
+ master_build_status['build_config'], 1,
+ milestone_version=master_build_status['milestone_version'])
if latest and latest[0]['id'] != master_id:
raise failures_lib.MasterSlaveVersionMismatchFailure(
'This slave\'s master (id=%s) has been supplanted by a newer '
diff --git a/cbuildbot/stages/sync_stages_unittest.py b/cbuildbot/stages/sync_stages_unittest.py
index dd38654..9c4d7ba 100644
--- a/cbuildbot/stages/sync_stages_unittest.py
+++ b/cbuildbot/stages/sync_stages_unittest.py
@@ -16,6 +16,7 @@
from chromite.cbuildbot import chromeos_config
from chromite.cbuildbot import constants
+from chromite.cbuildbot import failures_lib
from chromite.cbuildbot import lkgm_manager
from chromite.cbuildbot import manifest_version
from chromite.cbuildbot import manifest_version_unittest
@@ -185,8 +186,6 @@
self.PatchObject(sync_stages.ManifestVersionedSyncStage,
'_GetMasterVersion', return_value='foo',
autospec=True)
- self.PatchObject(sync_stages.ManifestVersionedSyncStage,
- '_VerifyMasterId', autospec=True)
self.PatchObject(manifest_version.BuildSpecsManager, 'BootstrapFromVersion',
autospec=True)
self.PatchObject(repository.RepoRepository, 'Sync', autospec=True)
@@ -450,14 +449,17 @@
class SlaveCQSyncTest(BaseCQTestCase):
"""Tests the CommitQueueSync stage for the paladin slaves."""
BOT_ID = 'x86-alex-paladin'
+ MILESTONE_VERSION = '10'
def setUp(self):
- self._run.options.master_build_id = 1234
+ self._run.options.master_build_id = self.fake_db.InsertBuild(
+ 'master builder name', 'waterfall', 1, 'master-paladin', 'bot hostname')
+ self.fake_db.UpdateMetadata(
+ self._run.options.master_build_id,
+ {'version': {'milestone': self.MILESTONE_VERSION}})
self.PatchObject(sync_stages.ManifestVersionedSyncStage,
'_GetMasterVersion', return_value='foo',
autospec=True)
- self.PatchObject(sync_stages.MasterSlaveLKGMSyncStage,
- '_VerifyMasterId', autospec=True)
self.PatchObject(lkgm_manager.LKGMManager, 'BootstrapFromVersion',
return_value=self.manifest_path, autospec=True)
self.PatchObject(repository.RepoRepository, 'Sync', autospec=True)
@@ -467,6 +469,29 @@
self.sync_stage.PerformStage()
self.ReloadPool()
+ def testSupplantedMaster(self):
+ """Test that stage fails if master has been supplanted."""
+ new_master_build_id = self.fake_db.InsertBuild(
+ 'master builder name', 'waterfall', 2, 'master-paladin', 'bot hostname')
+ self.fake_db.UpdateMetadata(
+ new_master_build_id,
+ {'version': {'milestone': self.MILESTONE_VERSION}})
+ with self.assertRaises(failures_lib.MasterSlaveVersionMismatchFailure):
+ self.sync_stage.PerformStage()
+
+ def testSupplantedMasterDifferentMilestone(self):
+ """Test that master supplanting logic respects milestone.
+
+ The master-was-supplanted logic should ignore masters for different
+ milestone version.
+ """
+ new_master_build_id = self.fake_db.InsertBuild(
+ 'master builder name', 'waterfall', 2, 'master-paladin', 'bot hostname')
+ self.fake_db.UpdateMetadata(
+ new_master_build_id,
+ {'version': {'milestone': 'foo'}})
+ self.sync_stage.PerformStage()
+
class MasterCQSyncTestCase(BaseCQTestCase):
"""Helper class for testing the CommitQueueSync stage masters."""
@@ -1169,8 +1194,8 @@
{'version': {'full': 'R43-7141.0.0-rc1'}})
self._run.attrs.metadata.UpdateWithDict(
{'version': {'full': 'R44-7142.0.0-rc1'}})
- self.fake_db.UpdateMetadata(id1 + 1, metadata_1)
- self.fake_db.UpdateMetadata(id2 + 1, metadata_2)
+ self.fake_db.UpdateMetadata(id1, metadata_1)
+ self.fake_db.UpdateMetadata(id2, metadata_2)
v = self.sync_stage.GetLastChromeOSVersion()
self.assertEqual(v.milestone, '43')
self.assertEqual(v.platform, '7141.0.0-rc1')
diff --git a/lib/cidb.py b/lib/cidb.py
index e581d2f..fec50c3 100644
--- a/lib/cidb.py
+++ b/lib/cidb.py
@@ -588,6 +588,11 @@
NUM_RESULTS_NO_LIMIT = -1
+ BUILD_STATUS_KEYS = (
+ 'id', 'build_config', 'start_time', 'finish_time', 'status', 'waterfall',
+ 'build_number', 'builder_name', 'platform_version', 'full_version',
+ 'milestone_version', 'important')
+
def __init__(self, db_credentials_dir, *args, **kwargs):
super(CIDBConnection, self).__init__('cidb', CIDB_MIGRATIONS_DIR,
db_credentials_dir, *args, **kwargs)
@@ -947,15 +952,13 @@
Returns:
A list of dictionary with keys (id, build_config, start_time,
finish_time, status, waterfall, build_number, builder_name,
- platform_version, full_version, important), or None if no build
- with this id was found.
+ platform_version, full_version, milestone_version, important)
+ (see BUILD_STATUS_KEYS) or None if no build with this id was found.
"""
return self._SelectWhere(
'buildTable',
'id IN (%s)' % ','.join(str(int(x)) for x in build_ids),
- ['id', 'build_config', 'start_time', 'finish_time', 'status',
- 'waterfall', 'build_number', 'builder_name', 'platform_version',
- 'full_version', 'important'])
+ self.BUILD_STATUS_KEYS)
@minimum_schema(30)
def GetBuildStages(self, build_id):
@@ -1001,13 +1004,11 @@
Returns:
A list containing, for each slave build (row) found, a dictionary
- with keys (id, build_config, start_time, finish_time, status, important).
+ with keys BUILD_STATUS_KEYS.
"""
return self._SelectWhere('buildTable',
'master_build_id = %d' % master_build_id,
- ['id', 'build_config', 'start_time',
- 'finish_time', 'status', 'important',
- 'waterfall'])
+ self.BUILD_STATUS_KEYS)
@minimum_schema(30)
def GetSlaveStages(self, master_build_id):
@@ -1093,7 +1094,7 @@
@minimum_schema(43)
def GetBuildHistory(self, build_config, num_results,
ignore_build_id=None, start_date=None, end_date=None,
- starting_build_number=None):
+ starting_build_number=None, milestone_version=None):
"""Returns basic information about most recent builds.
By default this function returns the most recent builds. Some arguments can
@@ -1113,6 +1114,8 @@
before this date.
starting_build_number: (Optional) The minimum build_number on the CQ
master for which data should be retrieved.
+ milestone_version: (Optional) Return only results for this
+ milestone_version.
Returns:
A sorted list of dicts containing up to |number| dictionaries for
@@ -1121,6 +1124,7 @@
start_time, finish_time, platform_version, full_version, status,
important].
"""
+ # TODO(akeshet): Unify this with BUILD_STATUS_KEYS
columns = ['id', 'build_config', 'buildbot_generation', 'waterfall',
'build_number', 'start_time', 'finish_time', 'platform_version',
'full_version', 'status', 'important']
@@ -1136,6 +1140,8 @@
where_clauses.append('build_number >= %d' % starting_build_number)
if ignore_build_id is not None:
where_clauses.append('id != %d' % ignore_build_id)
+ if milestone_version is not None:
+ where_clauses.append('milestone_version = "%s"' % milestone_version)
query = (
'SELECT %s'
' FROM buildTable'
diff --git a/lib/cidb_integration_test.py b/lib/cidb_integration_test.py
index 3a57316..682e70a 100644
--- a/lib/cidb_integration_test.py
+++ b/lib/cidb_integration_test.py
@@ -200,6 +200,14 @@
current_db_time = db.GetTime()
self.assertEqual(type(current_db_time), datetime.datetime)
+ def testGetBuildStatusKeys(self):
+ db = self._PrepareFreshDatabase()
+ build_id = db.InsertBuild('builder_name', 'waterfall', 1, 'build_config',
+ 'bot_hostname')
+ build_status = db.GetBuildStatus(build_id)
+ for k in db.BUILD_STATUS_KEYS:
+ self.assertIn(k, build_status)
+
def testBuildMessages(self):
db = self._PrepareFreshDatabase(45)
self.assertEqual([], db.GetBuildMessages(1))
@@ -356,6 +364,9 @@
self.assertEqual(len(last_status), 1)
last_status = readonly_db.GetBuildHistory('master-paladin', 5)
self.assertEqual(len(last_status), 5)
+ last_status = readonly_db.GetBuildHistory('master-paladin', 5,
+ milestone_version=52)
+ self.assertEqual(len(last_status), 0)
# Make sure keys are sorted correctly.
build_ids = []
for index, status in enumerate(last_status):
diff --git a/lib/fake_cidb.py b/lib/fake_cidb.py
index 5cabeb7..2908c49 100644
--- a/lib/fake_cidb.py
+++ b/lib/fake_cidb.py
@@ -10,6 +10,7 @@
import itertools
from chromite.cbuildbot import constants
+from chromite.lib import cidb
from chromite.lib import clactions
@@ -30,6 +31,11 @@
self.fake_time = None
self.fake_keyvals = fake_keyvals or {}
+ def _TrimStatus(self, status):
+ """Trims a build row to keys that should be returned by GetBuildStatus"""
+ return {k: v for (k, v) in status.items()
+ if k in cidb.CIDBConnection.BUILD_STATUS_KEYS}
+
def SetTime(self, fake_time):
"""Sets a fake time to be retrieved by GetTime.
@@ -74,10 +80,18 @@
return build_id
def UpdateMetadata(self, build_id, metadata):
- """See cidb.UpdateMetadata."""
- d = metadata.GetDict()
+ """See cidb.UpdateMetadata.
+
+ Args:
+ build_id: The build to update.
+ metadata: A cbuildbot metadata object. Or, a dictionary (note: using
+ a dictionary is not supported by the base cidb API, but
+ is provided for this fake class for ease of use in test
+ set-up code).
+ """
+ d = metadata if isinstance(metadata, dict) else metadata.GetDict()
versions = d.get('version') or {}
- self.buildTable[build_id - 1].update(
+ self.buildTable[build_id].update(
{'chrome_version': versions.get('chrome'),
'milestone_version': versions.get('milestone'),
'platform_version': versions.get('platform'),
@@ -192,48 +206,48 @@
def GetBuildStatus(self, build_id):
"""Gets the status of the build."""
- return self.buildTable[build_id - 1]
+ try:
+ return self._TrimStatus(self.buildTable[build_id])
+ except IndexError:
+ return None
def GetBuildStatuses(self, build_ids):
"""Gets the status of the builds."""
- return [self.buildTable[x -1] for x in build_ids]
+ return [self._TrimStatus(self.buildTable[x]) for x in build_ids]
def GetSlaveStatuses(self, master_build_id):
"""Gets the slaves of given build."""
- return [b for b in self.buildTable
+ return [self._TrimStatus(b) for b in self.buildTable
if b['master_build_id'] == master_build_id]
def GetBuildHistory(self, build_config, num_results,
ignore_build_id=None, start_date=None, end_date=None,
- starting_build_number=None):
+ starting_build_number=None, milestone_version=None):
"""Returns the build history for the given |build_config|."""
- def ReduceToBuildConfig(new_list, current_build):
- """Filters a build list to only those of a given config."""
- if current_build['build_config'] == build_config:
- new_list.append(current_build)
-
- return new_list
-
- build_configs = reduce(ReduceToBuildConfig, self.buildTable, [])
+ builds = [b for b in self.buildTable
+ if b['build_config'] == build_config]
# Reverse sort as that's what's expected.
- build_configs = sorted(build_configs[-num_results:], reverse=True)
+ builds = sorted(builds[-num_results:], reverse=True)
# Filter results.
if ignore_build_id is not None:
- build_configs = [b for b in build_configs if b['id'] != ignore_build_id]
+ builds = [b for b in builds if b['id'] != ignore_build_id]
if start_date is not None:
- build_configs = [b for b in build_configs
- if b['start_time'].date() >= start_date]
+ builds = [b for b in builds
+ if b['start_time'].date() >= start_date]
if end_date is not None:
- build_configs = [b for b in build_configs
- if 'finish_time' in b and
- b['finish_time'] and
- b['finish_time'].date() <= end_date]
+ builds = [b for b in builds
+ if 'finish_time' in b and
+ b['finish_time'] and
+ b['finish_time'].date() <= end_date]
if starting_build_number is not None:
- build_configs = [b for b in build_configs
- if b['build_number'] >= starting_build_number]
+ builds = [b for b in builds
+ if b['build_number'] >= starting_build_number]
+ if milestone_version is not None:
+ builds = [b for b in builds
+ if b['milestone_version'] == milestone_version]
- return build_configs
+ return builds
def GetTimeToDeadline(self, build_id):
"""Gets the time remaining until deadline."""