| # -*- coding: utf-8 -*- |
| # Copyright 2016 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Module for tracking relevant changes (i.e. CLs) to validate.""" |
| |
| from __future__ import print_function |
| |
| import datetime |
| |
| from chromite.cbuildbot.stages import artifact_stages |
| from chromite.lib import builder_status_lib |
| from chromite.lib import clactions |
| from chromite.lib import constants |
| from chromite.lib import config_lib |
| from chromite.lib import cros_collections |
| from chromite.lib import cros_logging as logging |
| from chromite.lib import patch as cros_patch |
| from chromite.lib import triage_lib |
| |
| |
| # Limit (hours) for looking back cl actions in the history for history-aware |
| # submission. |
| CQ_HISTORY_LOOKBACK_LIMIT_HOUR = 24*7 |
| |
| |
| class RelevantChanges(object): |
| """Class that quries and tracks relevant changes.""" |
| |
| @classmethod |
| def _GetSlaveMappingAndCLActions(cls, master_build_identifier, buildstore, |
| config, changes, slave_buildbucket_ids, |
| include_master=False): |
| """Query BuildStore to for slaves and CL actions. |
| |
| Args: |
| master_build_identifier: BuildIdentifier of this master build. |
| buildstore: Instance of buildstore.BuildStore. |
| config: Instance of config_lib.BuildConfig of this build. |
| changes: A list of GerritPatch instances to examine. |
| slave_buildbucket_ids: A list of buildbucket_ids (strings) of slave builds |
| scheduled by Buildbucket. |
| include_master: Boolean indicating whether to include the master build in |
| the config_map. Default to False. |
| |
| Returns: |
| A tuple of (config_map, action_history). The config_map is a dictionary |
| mapping build_id to config name for all slaves in this run. If |
| include_master is True, the config_map also includes master build. The |
| action_history is a list of all CL actions associated with |changes|. |
| """ |
| assert buildstore, 'No buildstore to use.' |
| assert config.master, 'This is not a master build.' |
| |
| # TODO(buildstore): make sure buildstore is BuildStore, not CIDBConnection. |
| if slave_buildbucket_ids: |
| slave_list = buildstore.GetBuildStatuses( |
| buildbucket_ids=slave_buildbucket_ids) |
| else: |
| slave_list = buildstore.GetSlaveStatuses( |
| master_build_identifier) |
| |
| # TODO(akeshet): We are getting the full action history for all changes that |
| # were in this CQ run. It would make more sense to only get the actions from |
| # build_ids of this master and its slaves. |
| action_history = buildstore.GetCIDBHandle().GetActionsForChanges(changes) |
| |
| config_map = dict() |
| |
| for d in slave_list: |
| config_map[d['id']] = d['build_config'] |
| |
| if include_master: |
| config_map[master_build_identifier.cidb_id] = config.name |
| |
| return config_map, action_history |
| |
| @classmethod |
| def GetRelevantChangesForSlaves(cls, master_build_identifier, buildstore, |
| config, changes, builds_not_passed_sync_stage, |
| slave_buildbucket_ids, |
| include_master=False): |
| """Compile a set of relevant changes for each slave. |
| |
| Args: |
| master_build_identifier: BuildIdentifier of this master build. |
| buildstore: Instance of buildstore.BuildStore. |
| config: Instance of config_lib.BuildConfig of this build. |
| changes: A list of GerritPatch instances to examine. |
| builds_not_passed_sync_stage: Set of build config names of slaves that |
| didn't pass the sync stages. |
| slave_buildbucket_ids: A list of buildbucket_ids (strings) of slave builds |
| scheduled by Buildbucket. |
| include_master: Boolean indicating whether to include the master build in |
| the config_map. Default to False. |
| |
| Returns: |
| A dictionary mapping a slave config name to a set of relevant changes |
| (as GerritPatch instances). If include_master is True, the dictionary |
| includes the master build config and its relevant changes. |
| """ |
| # TODO(buildstore): make sure buildstore is BuildStore, not CIDBConnection. |
| # Retrieve the slaves and clactions from CIDB. |
| config_map, action_history = cls._GetSlaveMappingAndCLActions( |
| master_build_identifier, buildstore, config, changes, |
| slave_buildbucket_ids, include_master=include_master) |
| changes_by_build_id = clactions.GetRelevantChangesForBuilds( |
| changes, action_history, config_map.keys()) |
| |
| # Convert index from build_ids to config names. |
| changes_by_config = dict() |
| for k, v in changes_by_build_id.items(): |
| changes_by_config[config_map[k]] = v |
| |
| for config in builds_not_passed_sync_stage: |
| # If a slave did not passed the sync stage, it means that the slave never |
| # finished applying the changes in the sync stage. Hence the CL pickup |
| # actions for this slave may be inaccurate. Conservatively assume all |
| # changes are relevant. |
| changes_by_config[config] = set(changes) |
| |
| return changes_by_config |
| |
| @classmethod |
| def GetPreviouslyPassedSlavesForChanges( |
| cls, master_build_identifier, buildstore, changes, |
| change_relevant_slaves_dict, |
| history_lookback_limit=CQ_HISTORY_LOOKBACK_LIMIT_HOUR): |
| """Get slaves passed in history (not from current run) for changes. |
| |
| If a previous slave build: |
| 1) inserted constants.CL_ACTION_RELEVANT_TO_SLAVE cl action for a change; |
| 2) is a passed build; |
| 3) is a relevant slave of the change |
| this slave is considered as a previously passed slave. |
| |
| Args: |
| master_build_identifier: The BuildIdentifier of current master. |
| buildstore: An instance of buildstore.BuildStore. |
| changes: A list of cros_patch.GerritPatch instance to check. |
| change_relevant_slaves_dict: A dict mapping changes to their relevant |
| slaves in current run. |
| history_lookback_limit: Limit (hours) for looking back cl actions in the |
| histor. If it's None, do not force the limit. |
| Default to CQ_HISTORY_LOOKBACK_LIMIT_HOUR. |
| |
| Returns: |
| A dict mapping changes (cros_patch.GerritPatch instances) to sets of |
| of build config name (strings) of their relevant slaves which passed in |
| history. |
| """ |
| assert buildstore, 'No buildstore to use.' |
| # TODO(buildstore): make sure buildstore is BuildStore, not CIDBConnection. |
| current_slaves = buildstore.GetSlaveStatuses(master_build_identifier) |
| current_slave_build_ids = [x['id'] for x in current_slaves] |
| |
| valid_configs = set() |
| for relevant_slaves in change_relevant_slaves_dict.values(): |
| valid_configs.update(relevant_slaves) |
| |
| changes_dict = {clactions.GerritPatchTuple(int(change.gerrit_number), |
| int(change.patch_number), |
| change.internal): |
| change for change in changes} |
| |
| start_time = None |
| if history_lookback_limit is not None: |
| start_time = (datetime.datetime.now() - |
| datetime.timedelta(hours=history_lookback_limit)) |
| |
| actions = buildstore.GetCIDBHandle().GetActionsForChanges( |
| changes, |
| ignore_patch_number=False, |
| status=constants.BUILDER_STATUS_PASSED, |
| action=constants.CL_ACTION_RELEVANT_TO_SLAVE, |
| start_time=start_time) |
| |
| change_passed_slaves_dict = {} |
| for action in actions: |
| if (action.build_config in valid_configs and |
| action.build_id not in current_slave_build_ids): |
| change = changes_dict.get(action.patch) |
| if change: |
| change_passed_slaves_dict.setdefault(change, set()).add( |
| action.build_config) |
| |
| return change_passed_slaves_dict |
| |
| |
| class TriageRelevantChanges(object): |
| """Class to triage relevant changes within a CQ run.. |
| |
| This class keeps track of relevant_changes of a list slave builds of given a |
| master build. With the build information fetched from Buildbucket and CIDB, |
| it performs relevant change triages, and returns a ShouldWait flag indicating |
| whether it's still meaningful for the master build to wait for the slave |
| builds. The triages include analyzing whether the failed slave builds have |
| passed the critial sync stage, whether the failures in failed slave builds |
| are ignorable for changes, classifying changes into will_submit, might_submit |
| and will_not_submit sets, and so on. |
| More context: go/self-destructed-commit-queue |
| """ |
| |
| # Accepted statues of the critical stages |
| ACCEPTED_STATUSES = { |
| constants.BUILDER_STATUS_PASSED, |
| constants.BUILDER_STATUS_SKIPPED |
| } |
| |
| # TODO(nxia): crbug.com/694749 |
| # Get stage names from stage classes instead of duplicating them here. |
| COMMIT_QUEUE_SYNC = 'CommitQueueSync' |
| MASTER_SLAVE_LKGM_SYNC = 'MasterSlaveLKGMSync' |
| STAGE_SYNC = {COMMIT_QUEUE_SYNC, MASTER_SLAVE_LKGM_SYNC} |
| |
| STAGE_UPLOAD_PREBUILTS = ( |
| artifact_stages.UploadPrebuiltsStage.StageNamePrefix()) |
| |
| def __init__(self, master_build_identifier, buildstore, builders_array, |
| config, metadata, version, build_root, changes, |
| buildbucket_info_dict, cidb_status_dict, completed_builds, |
| dependency_map, buildbucket_client, dry_run=True): |
| """Initialize an instance of TriageRelevantChanges. |
| |
| Args: |
| master_build_identifier: The BuildIdentifier instance of the master build. |
| buildstore: A BuildStore instance to make DB calls. |
| builders_array: A list of expected slave build config names (strings). |
| config: An instance of config_lib.BuildConfig. Config dict of this build. |
| metadata: Instance of metadata_lib.CBuildbotMetadata. Metadata of this |
| build. |
| version: Current manifest version string. See the return type of |
| VersionInfo.VersionString(). |
| build_root: Path to the build root directory. |
| changes: A list of changes (GerritPatch instances) which have been applied |
| to this build. |
| buildbucket_info_dict: A dict mapping all slave build config names to |
| their BuildbucketInfos (See SlaveBuilerStatus.GetAllSlaveBuildbucketInfo |
| for details). |
| cidb_status_dict: A dict mapping all slave build config names to their |
| CIDBStatusInfos (See SlaveBuilerStatus.GetAllSlaveCIDBStatusInfo for |
| details) |
| completed_builds: A set of slave build config names (strings) which |
| have completed and will not be retried. |
| dependency_map: A dict mapping a change (patch.GerritPatch instance) to a |
| set of changes (patch.GerritPatch instances) depending on this change. |
| (See ValidationPool.GetDependMapForChanges for details.) |
| buildbucket_client: Instance of buildbucket_lib.buildbucket_client. |
| dry_run: Boolean indicating whether it's a dry run. Default to True. |
| """ |
| self.master_build_identifier = master_build_identifier |
| self.buildstore = buildstore |
| self.db = buildstore.GetCIDBHandle() |
| self.builders_array = builders_array |
| self.config = config |
| self.metadata = metadata |
| self.version = version |
| self.buildbucket_info_dict = buildbucket_info_dict |
| self.cidb_status_dict = cidb_status_dict |
| self.completed_builds = completed_builds |
| self.build_root = build_root |
| self.changes = changes |
| self.dependency_map = dependency_map |
| self.buildbucket_client = buildbucket_client |
| self.dry_run = dry_run |
| |
| # Dict mapping slave config names to a list of stages |
| self.slave_stages_dict = None |
| # Dict mapping slave config names to relevant change sets. |
| self.slave_changes_dict = None |
| |
| # A set of changes which will be submitted by the master. |
| self.will_submit = set() |
| # A set of changes which are being tested by the slaves. |
| self.might_submit = set(self.changes) |
| # A set of chagnes which won't be submitted by the master. |
| self.will_not_submit = set() |
| |
| # A dict mapping build config name to a set of changes which can ignore the |
| # failures in the build. |
| self.build_ignorable_changes_dict = {} |
| |
| # A dict mapping changes to their relevant slaves (build_configs). |
| self.change_relevant_slaves_dict = None |
| |
| # A dict mapping changes to their passed slaves (build_configs) in history. |
| self.change_passed_slaves_dict = None |
| |
| self._UpdateSlaveInfo() |
| |
| def _UpdateSlaveInfo(self): |
| """Update slave infomation with stages, relevant_changes, and subsys.""" |
| self.slave_stages_dict = self.GetChildStages() |
| self.slave_changes_dict = self._GetRelevantChanges( |
| self.slave_stages_dict) |
| self.change_relevant_slaves_dict = cros_collections.InvertDictionary( |
| self.slave_changes_dict) |
| self.change_passed_slaves_dict = ( |
| RelevantChanges.GetPreviouslyPassedSlavesForChanges( |
| self.master_build_identifier, self.buildstore, self.changes, |
| self.change_relevant_slaves_dict)) |
| |
| @staticmethod |
| def GetDependChanges(changes, dependency_map): |
| """Get a set of changes depending on the given changes. |
| |
| Args: |
| changes: A set of changes to get the dependent change set. |
| dependency_map: A dict mapping a change (patch.GerritPatch instance) to a |
| set of changes (patch.GerritPatch instances) directly or indirectly |
| depending on this change. (See ValidationPool.GetDependMapForChanges for |
| details.) |
| |
| Returns: |
| A set of all changes directly or indirectly depending on the given |
| changes. |
| """ |
| return set().union(*[dependency_map.get(c, set()) for c in changes]) |
| |
| # TODO(nxia): Consolidate with completion_stages._ShouldSubmitPartialPool |
| def GetChildStages(self): |
| """Get child stages from buildstore. |
| |
| Returns: |
| A dict mapping all child config names (strings) to their stages (a list |
| of dicts, see BuildStore.GetBuildsStages for details.) |
| """ |
| slave_stages_dict = {} |
| slave_buildbucket_ids = [] |
| |
| if self.buildbucket_info_dict is not None: |
| for slave_config, bb_info in self.buildbucket_info_dict.items(): |
| # Set default value for all slaves, some may not have stages in CIDB. |
| slave_stages_dict.setdefault(slave_config, []) |
| slave_buildbucket_ids.append(bb_info.buildbucket_id) |
| |
| stages = self.buildstore.GetBuildsStages( |
| buildbucket_ids=slave_buildbucket_ids) |
| |
| for stage in stages: |
| slave_stages_dict[stage['build_config']].append(stage) |
| |
| return slave_stages_dict |
| |
| @classmethod |
| def PassedAnyOfStages(cls, stages, desired_stages): |
| """Check if the stages have passed any stage from desired_stages. |
| |
| Args: |
| stages: A list of stages (see the type of slave_stages_dict value part) |
| to check. |
| desired_stages: A set of desired stages (strings). |
| |
| Returns: |
| True if the accepted stages in the given stages cover any stage in |
| the desired_stages set; else, False. |
| """ |
| accepted_stages = {stage['name'] for stage in stages |
| if stage['status'] in cls.ACCEPTED_STATUSES} |
| |
| return accepted_stages.intersection(desired_stages) |
| |
| @classmethod |
| def GetBuildsPassedAnyOfStages(cls, build_stages_dict, desired_stages): |
| """Get builds which have passed any stage from desired_stages. |
| |
| Args: |
| build_stages_dict: A dict mapping build config names to their stage lists. |
| desired_stages: A set of desired stages (strings). |
| |
| Returns: |
| A set of build config names (strings) which have passed any stage in |
| desired_stages. |
| """ |
| return set(build_config |
| for build_config, stages in build_stages_dict.items() |
| if cls.PassedAnyOfStages(stages, desired_stages)) |
| |
| def _GetRelevantChanges(self, slave_stages_dict): |
| """Get relevant changes for slave builds. |
| |
| Args: |
| slave_stages_dict: A dict mapping slaves config names (strings) to their |
| stage lists. (see GetSlaveStages for details). |
| |
| Returns: |
| A dict mapping all slave config names (strings) to sets of changes which |
| are relevant to the slave builds. If a build has passed the STAGE_SYNC |
| stage, it has recorded the CLs it picked up in the CIDB, it's mapped to |
| its relevant change set. If a build failed to pass the STAGE_SYNC stage, |
| we assume it's relevant to all changes, so it's mapped to the change set |
| containing all the applied changes. |
| """ |
| # If a build passed the sync stage, the picked up change stats are recorded. |
| builds_passed_sync_stage = self.GetBuildsPassedAnyOfStages( |
| slave_stages_dict, self.STAGE_SYNC) |
| builds_not_passed_sync_stage = (set(self.buildbucket_info_dict.keys()) - |
| builds_passed_sync_stage) |
| slave_buildbucket_ids = [bb_info.buildbucket_id |
| for bb_info in self.buildbucket_info_dict.values()] |
| slave_changes_dict = RelevantChanges.GetRelevantChangesForSlaves( |
| self.master_build_identifier, self.buildstore, self.config, |
| self.changes, builds_not_passed_sync_stage, slave_buildbucket_ids) |
| |
| # Some slaves may not pick up any changes, update the value to set() |
| for slave_config in self.buildbucket_info_dict: |
| slave_changes_dict.setdefault(slave_config, set()) |
| |
| return slave_changes_dict |
| |
| def _GetIgnorableChanges(self, build_config, builder_status, |
| relevant_changes): |
| """Get changes that can ignore failures in BuilderStatus. |
| |
| Some projects are configured with ignored-stages in COMMIT_QUEUE.ini. The CQ |
| can still submit changes from these projects if all failed statges are |
| listed in ignored-stages. Please refer to |
| cq_config.CQConfigParser.GetStagesToIgnore for more details. |
| |
| 1) if the builder_status is in 'pass' status, it means the build uploaded a |
| 'pass' builder_status but failed other steps in or after the completion |
| stage. This is rare but still possible, and it should not blame any changes |
| as the build has finishes its testing. Returns all changes in |
| relevant_changes in this case. |
| 2) else if the builder_status is in 'fail' with failure messages, it |
| calculates and returns all ignorable changes given the failure messages. |
| 3) else, the builder_status is either in 'fail' status without failure |
| messages or in one of the 'inflight' and 'missing' statuses. It cannot |
| calculate ignorable changes without any failure message so should just |
| return an empty set. |
| |
| Args: |
| build_config: The config name (string) of the build. |
| builder_status: An instance of build_status.BuilderStatus. |
| relevant_changes: A set of relevant changes for triage to get the |
| ignorable changes. |
| |
| Returns: |
| A set of ignorable changes (GerritPatch instances). |
| """ |
| if builder_status.Passed(): |
| return relevant_changes |
| elif builder_status.Failed() and builder_status.message: |
| ignoreable_changes = set() |
| for change in relevant_changes: |
| ignore_result = triage_lib.CalculateSuspects.CanIgnoreFailures( |
| [builder_status.message], change, self.build_root) |
| |
| if ignore_result[0]: |
| logging.debug('change %s is ignoreable for failures of %s.', |
| cros_patch.GetChangesAsString([change]), build_config) |
| ignoreable_changes.add(change) |
| return ignoreable_changes |
| else: |
| return set() |
| |
| def _UpdateWillNotSubmitChanges(self, will_not_submit): |
| """Update will_not_submit change set. |
| |
| Args: |
| will_not_submit: A set of changes (GerritPatch instances) to add to |
| will_not_submit. |
| """ |
| self.will_not_submit.update(will_not_submit) |
| self.might_submit.difference_update(will_not_submit) |
| |
| def _GetWillNotSubmitChanges(self, build_config, changes): |
| """Get changes which will not be submitted because of the failed build. |
| |
| Get a list of changes which will not be submitted because the build_config |
| is relevant to the changes, and the build of this build_config didn't pass |
| in current run and the failures are not ignorable, and there's no passed |
| build of this build_config in history. |
| |
| Args: |
| build_config: The build config name (string) of the failed build. |
| changes: A list of cros_patch.GerritPatch instances to check. |
| |
| Returns: |
| A list of changes (cros_patch.GerritPatch) which will not be submitted. |
| """ |
| will_not_submit_changes = set() |
| for change in changes: |
| if (build_config in self.change_relevant_slaves_dict.get(change, set()) |
| and build_config not in self.change_passed_slaves_dict.get( |
| change, set())): |
| will_not_submit_changes.add(change) |
| |
| return will_not_submit_changes |
| |
| def _ProcessCompletedBuilds(self): |
| """Process completed and not retriable builds. |
| |
| This method goes through all the builds which completed without SUCCESS |
| result and will not be retried. |
| 1) if the failed build didn't pass the sync stage, iterate all changes, |
| move the changes which didn't pass this build config in history to |
| will_not_submit (as well as their dependencies). |
| 2) else, get BuilderStatus for the build (if there's no BuilderStatus |
| pickle file in GS, a BuilderStatus with 'missing' status will be returned). |
| Find not ignorable changes given the BuilderStatus, iterate the changes in |
| not ignorable changes, move the changes which didn't pass this build config |
| in history to will_not_submit (as well as their dependencies). |
| """ |
| # TODO(nxia): Improve SlaveBuilderStatus to take buildbucket_info_dict |
| # and cidb_status_dict as arguments to avoid extra queries. |
| slave_builder_statuses = builder_status_lib.SlaveBuilderStatus( |
| self.master_build_identifier, self.buildstore, self.config, |
| self.metadata, self.buildbucket_client, self.builders_array, |
| self.dry_run) |
| |
| for build_config, bb_info in self.buildbucket_info_dict.items(): |
| if (build_config in self.completed_builds and |
| bb_info.status == constants.BUILDBUCKET_BUILDER_STATUS_COMPLETED and |
| bb_info.result != constants.BUILDBUCKET_BUILDER_RESULT_SUCCESS): |
| # This build didn't succeed and cannot be retried. |
| logging.info('Processing relevant changes of build %s status %s ' |
| 'result %s', build_config, bb_info.status, bb_info.result) |
| |
| stages = self.slave_stages_dict[build_config] |
| if not self.PassedAnyOfStages(stages, self.STAGE_SYNC): |
| # The build_config didn't pass any of the sync stages. Find changes |
| # which don't have valid passed builds of this build_config in |
| # history. Move the changes and their dependencies to will_not_submit |
| # set. |
| will_not_submit_changes = self._GetWillNotSubmitChanges( |
| build_config, self.changes) |
| depend_changes = self.GetDependChanges( |
| will_not_submit_changes, self.dependency_map) |
| will_not_submit_changes |= depend_changes |
| |
| if will_not_submit_changes: |
| self._UpdateWillNotSubmitChanges(will_not_submit_changes) |
| logging.info('Build %s didn\'t pass any stage in %s. Will not' |
| ' submit changes: %s', build_config, self.STAGE_SYNC, |
| cros_patch.GetChangesAsString(will_not_submit_changes)) |
| else: |
| # The build passed the required sync stage. Get builder_status and |
| # get not ignorable changes based on the builder_status. Find changes |
| # in the not ignorable changes don't have valid passed builds of this |
| # build_config hi history. Move the changes and their dependencies to |
| # will_not_submit set. |
| relevant_changes = self.slave_changes_dict[build_config] |
| builder_status = slave_builder_statuses.GetBuilderStatusForBuild( |
| build_config) |
| ignorable_changes = self._GetIgnorableChanges( |
| build_config, builder_status, relevant_changes) |
| self.build_ignorable_changes_dict[build_config] = ignorable_changes |
| not_ignorable_changes = relevant_changes - ignorable_changes |
| |
| will_not_submit_changes = self._GetWillNotSubmitChanges( |
| build_config, not_ignorable_changes) |
| depend_changes = self.GetDependChanges( |
| will_not_submit_changes, self.dependency_map) |
| will_not_submit_changes = will_not_submit_changes | depend_changes |
| |
| if will_not_submit_changes: |
| self._UpdateWillNotSubmitChanges(will_not_submit_changes) |
| logging.info('Build %s failed with not ignorable failures, will not' |
| ' submit changes: %s', build_config, |
| cros_patch.GetChangesAsString(will_not_submit_changes)) |
| |
| if not self.might_submit: |
| # No need to process other completed builds, might_submit is empty. |
| return |
| |
| def _ChangeVerifiedByCurrentBuild(self, change, build_config, |
| buildbucket_info_dict, |
| build_ignorable_changes_dict): |
| """Whether the change can by verified by the current build of build_config. |
| |
| A change can be verified by the build if it satisfies the conditions: |
| 1) the build successfully completed; OR |
| 2) the build failed but its failures can be ignored by the change. |
| |
| Args: |
| change: An instance of cros_patch.GerritPatch to check. |
| build_config: The build config name (string) (relevant to the change) to |
| verify. |
| buildbucket_info_dict: A dict mapping all slave build config names to |
| their BuildbucketInfos (See SlaveBuilerStatus.GetAllSlaveBuildbucketInfo |
| for details). |
| build_ignorable_changes_dict: A dict mapping build config name (string) to |
| a set of changes (GerritPatch instances) which can ignore the failures |
| in the build. |
| |
| Returns: |
| True if the change can be verified by the current build of build_config; |
| else, False. |
| """ |
| bb_info = buildbucket_info_dict.get(build_config) |
| ignorable_changes = build_ignorable_changes_dict.get(build_config, set()) |
| if bb_info.status != constants.BUILDBUCKET_BUILDER_STATUS_COMPLETED: |
| return False |
| if bb_info.result != constants.BUILDBUCKET_BUILDER_RESULT_SUCCESS: |
| #If the build uploaded 'passed' BuilderStatus pickle or the build |
| # only contains failures which can be ignored by this change, change is |
| # in the value set for build_config in build_ignorable_changes_dict. |
| if change not in ignorable_changes: |
| return False |
| |
| return True |
| |
| def _ChangeCanBeSubmitted(self, change, relevant_slave_configs, |
| buildbucket_info_dict, build_ignorable_changes_dict, |
| change_passed_slaves_dict): |
| """Whether the change can be submitted by current cq or cq history. |
| |
| A change can NOT be submitted if at least one of its relevant slaves |
| satisfies the conditions: |
| 1) the relevant build_config failed in current build with not ignorable |
| failures; AND |
| 2) no passed build for the relevant build_config in cq history. |
| |
| Args: |
| change: A change (GerritPatch instance) to check. |
| relevant_slave_configs: A list of relevant slave config names (string) of |
| this change. |
| buildbucket_info_dict: A dict mapping all slave build config names to |
| their BuildbucketInfos (See SlaveBuilerStatus.GetAllSlaveBuildbucketInfo |
| for details). |
| build_ignorable_changes_dict: A dict mapping build config name (string) to |
| a set of changes (GerritPatch instances) which can ignore the failures |
| in the build. |
| change_passed_slaves_dict: A dict mapping changes (cros_patch.GerritPatch) |
| to their passed slaves (build_config name strings) in history. |
| |
| Returns: |
| True if the change can be submitted given the statues of its relevant |
| slaves; else, False. |
| """ |
| for build_config in relevant_slave_configs: |
| if (not self._ChangeVerifiedByCurrentBuild(change, build_config, |
| buildbucket_info_dict, |
| build_ignorable_changes_dict) |
| and build_config not in change_passed_slaves_dict.get(change, set())): |
| return False |
| |
| return True |
| |
| def _ProcessMightSubmitChanges(self): |
| """Process changes in might_submit set. |
| |
| This method goes through all the changes in current might_submit set. For |
| each change, get a set of its relevant slaves. If all the relevant slaves |
| can either be verified by current CQ or passed in CQ history, move the |
| change to will_submit set. |
| """ |
| if not self.might_submit: |
| return |
| |
| logging.info('Processing changes in might submit set.') |
| will_submit_changes = set() |
| for change in self.might_submit: |
| relevant_slaves = self.change_relevant_slaves_dict.get(change, set()) |
| if self._ChangeCanBeSubmitted( |
| change, relevant_slaves, self.buildbucket_info_dict, |
| self.build_ignorable_changes_dict, self.change_passed_slaves_dict): |
| will_submit_changes.add(change) |
| |
| if will_submit_changes: |
| self.will_submit.update(will_submit_changes) |
| self.might_submit.difference_update(will_submit_changes) |
| logging.info('Moving %s to will_submit set, because their relevant builds' |
| ' completed successfully or all failures are ignorable or ' |
| 'passed in CQ history.', |
| cros_patch.GetChangesAsString(will_submit_changes)) |
| |
| def _AllCompletedSlavesPassedUploadPrebuiltsStage(self): |
| """Check if all completed slaves have passed the UploadPrebuiltsStage.""" |
| site_config = config_lib.GetConfig() |
| for build_config in self.completed_builds: |
| # compilecheck builds don't run UploadPrebuiltsStage |
| if (not site_config[build_config].compilecheck and |
| not self.PassedAnyOfStages(self.slave_stages_dict[build_config], |
| {self.STAGE_UPLOAD_PREBUILTS})): |
| logging.info("Completed build %s didn't pass stage %s. " |
| "The master can't publish uprevs now.", |
| build_config, self.STAGE_UPLOAD_PREBUILTS) |
| return False |
| |
| return True |
| |
| def _AllUncompletedSlavesPassedUploadPrebuiltsStage(self): |
| """Check if all uncompleted slaves have passed the UploadPrebuiltsStage.""" |
| site_config = config_lib.GetConfig() |
| for build_config in set(self.builders_array) - self.completed_builds: |
| # compilecheck builds don't run UploadPrebuiltsStage |
| if (not site_config[build_config].compilecheck and |
| not self.PassedAnyOfStages(self.slave_stages_dict[build_config], |
| {self.STAGE_UPLOAD_PREBUILTS})): |
| logging.info("Uncompleted build %s haven't passed stage %s. " |
| "The master can't publish uprevs now.", |
| build_config, self.STAGE_UPLOAD_PREBUILTS) |
| return False |
| |
| return True |
| |
| def ShouldSelfDestruct(self): |
| """Process builds and relevant changes, decide whether to self-destruct. |
| |
| Returns: |
| A tuple of (boolean indicating if the master should self-destruct, |
| boolean indicating if the master should self-destruct with |
| with success) |
| """ |
| self._ProcessCompletedBuilds() |
| self._ProcessMightSubmitChanges() |
| |
| logging.info('will_submit set contains %d changes: [%s]\n' |
| 'might_submit set contains %d changes: [%s]\n' |
| 'will_not_submit set contains %d changes: [%s]\n', |
| len(self.will_submit), |
| cros_patch.GetChangesAsString(self.will_submit), |
| len(self.might_submit), |
| cros_patch.GetChangesAsString(self.might_submit), |
| len(self.will_not_submit), |
| cros_patch.GetChangesAsString(self.will_not_submit)) |
| |
| # The master should wait for all the necessary slaves to pass the |
| # UploadPrebuiltsStage so the master can publish prebuilts after |
| # self-destruction with success. More context: crbug.com/703819 |
| all_completed_slaves_passed = ( |
| self._AllCompletedSlavesPassedUploadPrebuiltsStage()) |
| all_uncompleted_slaves_passed = ( |
| self._AllUncompletedSlavesPassedUploadPrebuiltsStage()) |
| should_self_destruct = (bool(not self.might_submit) and |
| (not all_completed_slaves_passed or |
| all_uncompleted_slaves_passed)) |
| should_self_destruct_with_success = (bool(not self.might_submit) and |
| bool(not self.will_not_submit) and |
| all_completed_slaves_passed and |
| all_uncompleted_slaves_passed) |
| |
| return should_self_destruct, should_self_destruct_with_success |