| # -*- coding: utf-8 -*- |
| # Copyright 2017 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 to manage builder statuses.""" |
| |
| from __future__ import print_function |
| |
| import collections |
| import pickle |
| import sys |
| |
| from chromite.lib import buildbucket_lib |
| from chromite.lib import build_failure_message |
| from chromite.lib import config_lib |
| from chromite.lib import constants |
| from chromite.lib import cros_collections |
| from chromite.lib import cros_logging as logging |
| from chromite.lib import failure_message_lib |
| from chromite.lib import metrics |
| |
| |
| assert sys.version_info >= (3, 6), 'This module requires Python 3.6+' |
| |
| |
| BUILD_STATUS_URL = ( |
| '%s/builder-status' % config_lib.GetSiteParams().MANIFEST_VERSIONS_GS_URL) |
| NUM_RETRIES = 20 |
| |
| # Namedtupe to store CIDB status info. |
| CIDBStatusInfo = collections.namedtuple( |
| 'CIDBStatusInfo', |
| ['buildbucket_id', 'status']) |
| |
| |
| def CancelBuilds(buildbucket_ids, buildbucket_client, |
| debug=True, config=None): |
| """Cancel Buildbucket builds in a set. |
| |
| Args: |
| buildbucket_ids: A list of build_ids (strings). |
| buildbucket_client: Instance of buildbucket_lib.buildbucket_client. |
| debug: Boolean indicating whether it's a dry run. Default to True. |
| config: Instance of config_lib.BuildConfig. Config dict for the master |
| build initiating the cancel. Optional. |
| """ |
| if buildbucket_ids: |
| logging.info('Canceling buildbucket_ids: %s', buildbucket_ids) |
| if (not debug) and config: |
| fields = {'build_type': config.build_type, |
| 'build_name': config.name} |
| metrics.Counter(constants.MON_BB_CANCEL_BATCH_BUILDS_COUNT).increment( |
| fields=fields) |
| cancel_results = buildbucket_client.CancelBatchBuildsRequest( |
| buildbucket_ids, |
| dryrun=debug) |
| result_map = buildbucket_lib.GetResultMap(cancel_results) |
| for buildbucket_id, result in result_map.items(): |
| # Check for error messages |
| if buildbucket_lib.GetNestedAttr(result, ['error']): |
| # TODO(nxia): Get build url and log url in the warnings. |
| logging.warning('Error cancelling build %s with reason: %s. ' |
| 'Please check the status of the build.', |
| buildbucket_id, |
| buildbucket_lib.GetErrorReason(result)) |
| |
| |
| def GetFailedMessages(statuses, failing): |
| """Gathers the BuildFailureMessages from the |failing| builders. |
| |
| Args: |
| statuses: A dict mapping build config names to their BuilderStatus. |
| failing: Names of the builders that failed. |
| |
| Returns: |
| A list of build_failure_message.BuildFailureMessage or NoneType objects. |
| """ |
| return [statuses[x].message for x in failing] |
| |
| |
| def GetBuildersWithNoneMessages(statuses, failing): |
| """Returns a list of failed builders with NoneType failure message. |
| |
| Args: |
| statuses: A dict mapping build config names to their BuilderStatus. |
| failing: Names of the builders that failed. |
| |
| Returns: |
| A list of builder names. |
| """ |
| return [x for x in failing if statuses[x].message is None] |
| |
| |
| def GetSlavesAbortedBySelfDestructedMaster(master_build_identifier, buildstore): |
| """Get the build configs of the slaves aborted by self-destruction. |
| |
| Args: |
| master_build_identifier: The BuildIdentifier instance of the master build. |
| buildstore: A BuildStore instance to make DB calls. |
| |
| Returns: |
| A set of build configs of the slaves recorded in CIDB. An empty set if no |
| db connection created. |
| """ |
| if not buildstore.AreClientsReady(): |
| return set() |
| |
| slave_buildbucket_ids = buildstore.GetKilledChildBuilds( |
| master_build_identifier) |
| # tentative fix for crbug.com/890651 |
| if not slave_buildbucket_ids: |
| logging.warning('No build message retrieved for master_build_id=%s', |
| master_build_identifier.cidb_id) |
| return set() |
| build_statuses = buildstore.GetBuildStatuses( |
| buildbucket_ids=slave_buildbucket_ids) |
| return set(b['build_config'] for b in build_statuses) |
| |
| |
| class BuilderStatus(object): |
| """Object representing the status of a build.""" |
| |
| def __init__(self, status, message, dashboard_url=None): |
| """Constructor for BuilderStatus. |
| |
| Args: |
| status: Status string (should be one of BUILDER_STATUS_FAILED, |
| BUILDER_STATUS_PASSED, BUILDER_STATUS_INFLIGHT, or |
| BUILDER_STATUS_MISSING). |
| message: A build_failure_message.BuildFailureMessage object with details |
| of builder failure. Or, None. |
| dashboard_url: Optional url linking to builder dashboard for this build. |
| """ |
| self.status = status |
| self.message = message |
| self.dashboard_url = dashboard_url |
| |
| # Helper methods to make checking the status object easy. |
| |
| def Failed(self): |
| """Returns True if the Builder failed.""" |
| return self.status == constants.BUILDER_STATUS_FAILED |
| |
| def Passed(self): |
| """Returns True if the Builder passed.""" |
| return self.status == constants.BUILDER_STATUS_PASSED |
| |
| def Inflight(self): |
| """Returns True if the Builder is still inflight.""" |
| return self.status == constants.BUILDER_STATUS_INFLIGHT |
| |
| def Missing(self): |
| """Returns True if the Builder is missing any status.""" |
| return self.status == constants.BUILDER_STATUS_MISSING |
| |
| def Completed(self): |
| """Returns True if the Builder has completed.""" |
| return self.status in constants.BUILDER_COMPLETED_STATUSES |
| |
| @classmethod |
| def GetCompletedStatus(cls, success): |
| """Return the appropriate status constant for a completed build. |
| |
| Args: |
| success: Whether the build was successful or not. |
| """ |
| if success: |
| return constants.BUILDER_STATUS_PASSED |
| else: |
| return constants.BUILDER_STATUS_FAILED |
| |
| def AsFlatDict(self): |
| """Returns a flat json-able representation of this builder status. |
| |
| Returns: |
| A dictionary of the form {'status' : status, 'message' : message, |
| 'dashboard_url' : dashboard_url} where all values are guaranteed |
| to be strings. If dashboard_url is None, the key will be excluded. |
| """ |
| flat_dict = {'status' : str(self.status), |
| 'message' : str(self.message), |
| 'reason' : str(None if self.message is None |
| else self.message.reason)} |
| if self.dashboard_url is not None: |
| flat_dict['dashboard_url'] = str(self.dashboard_url) |
| return flat_dict |
| |
| def AsPickledDict(self): |
| """Returns a pickled dictionary representation of this builder status.""" |
| return pickle.dumps(dict(status=self.status, message=self.message, |
| dashboard_url=self.dashboard_url)) |
| |
| |
| class BuilderStatusManager(object): |
| """Operations to manage BuilderStatus.""" |
| |
| @classmethod |
| def CreateBuildFailureMessage(cls, build_config, overlays, |
| dashboard_url, failure_messages, |
| aborted_by_self_destruction=False): |
| """Creates a message summarizing the failures. |
| |
| Args: |
| build_config: Build config name (string) of a slave build. |
| overlays: The overlays used for the build. |
| dashboard_url: The URL of the build. |
| failure_messages: A list of stage failure messages (instances of |
| StageFailureMessage or its sub-classes) of the given build. |
| aborted_by_self_destruction: Whether the build was canceled by master. |
| |
| Returns: |
| A build_failure_message.BuildFailureMessage object. |
| """ |
| internal = overlays in [constants.PRIVATE_OVERLAYS, |
| constants.BOTH_OVERLAYS] |
| details = [] |
| |
| if failure_messages: |
| details.append('the builder failed') |
| |
| if not details: |
| details = ['cbuildbot failed'] |
| if aborted_by_self_destruction: |
| details = ['aborted by self-destruction'] |
| |
| # reason does not include builder name or URL. This is mainly for |
| # populating the "failure message" column in the stats sheet. |
| reason = ' '.join(details) |
| details.append('in %s' % dashboard_url) |
| msg_summary = '%s: %s' % (build_config, ' '.join(details)) |
| |
| return build_failure_message.BuildFailureMessage( |
| msg_summary, failure_messages, internal, reason, build_config) |
| |
| @classmethod |
| def AbortedBySelfDestruction(cls, buildstore, buildbucket_id, |
| master_build_identifier): |
| """Check BuildStore for whether a specified build was aborted by master. |
| |
| Args: |
| buildstore: A BuildStore instance to make DB calls. |
| buildbucket_id: The buildbucket ID (int) of the build to get status of |
| master_build_identifier: The build ID (int) of the master build which may |
| have aborted it. |
| |
| Retuns: |
| A boolean for whether the build was canceled by master during |
| self-destruction. |
| """ |
| if (master_build_identifier is None |
| or master_build_identifier.cidb_id is None |
| or master_build_identifier.buildbucket_id is None): |
| # Builds without master_build_id can't be aborted by self-destruction. |
| return False |
| |
| buildbucket_ids = buildstore.GetKilledChildBuilds(master_build_identifier) |
| # Both child_id and buildbucket_id can be str or int. Convert them both |
| # into int before comparison. |
| if buildbucket_ids is None: |
| return False |
| return any(child_id for child_id in buildbucket_ids |
| if int(child_id) == int(buildbucket_id)) |
| |
| |
| class SlaveBuilderStatus(object): |
| """Operations to manage slave BuilderStatus. |
| |
| This class fetches slave statuses and slave failures from Buildbucket and |
| CIDB, generates BuilderStatus instances for slave builds. This class only |
| fetches BuilderStatus information for important slaves. |
| """ |
| |
| def __init__(self, master_build_identifier, buildstore, config, metadata, |
| buildbucket_client, builders_array, dry_run, |
| exclude_experimental=True): |
| """Create an instance of SlaveBuilderStatus for a given master build. |
| |
| Args: |
| master_build_identifier: The build_identifier of the master build. |
| buildstore: A BuildStore instance to make DB calls with. |
| config: Instance of config_lib.BuildConfig. Config dict of this build. |
| metadata: Instance of metadata_lib.CBuildbotMetadata. Metadata of this |
| build. |
| buildbucket_client: Instance of buildbucket_lib.buildbucket_client. |
| builders_array: List of the expected and important slave builds. |
| dry_run: Boolean indicating whether it's a dry run. Default to True. |
| exclude_experimental: Whether to exclude the builds which are important in |
| the config but are marked as experimental in the tree status. Default to |
| True. |
| """ |
| self.master_build_identifier = master_build_identifier |
| self.master_build_id = master_build_identifier.cidb_id |
| self.buildstore = buildstore |
| self.db = buildstore.GetCIDBHandle() |
| self.config = config |
| self.metadata = metadata |
| self.buildbucket_client = buildbucket_client |
| self.builders_array = builders_array |
| self.dry_run = dry_run |
| self.exclude_experimental = exclude_experimental |
| |
| self.buildbucket_info_dict = None |
| self.cidb_info_dict = None |
| self.slave_failures_dict = None |
| self.aborted_slaves = None |
| self._InitSlaveInfo() |
| |
| def _GetSlaveFailures(self, buildbucket_info_dict): |
| """Get a dict mapping slave builds to their build failures. |
| |
| Args: |
| buildbucket_info_dict: A dict mapping slave build config names |
| (strings) to their BuildbucketInfos. |
| |
| Returns: |
| A dict mapping the slave build config names (strings) to stage failure |
| messages (See return type of |
| FailureMessageManager.ConstructStageFailureMessages) |
| """ |
| slave_failures_dict = {} |
| |
| child_buildbucket_ids = ( |
| None if buildbucket_info_dict is None else |
| [bb_info.buildbucket_id for bb_info in buildbucket_info_dict.values()]) |
| |
| stage_failures = self.buildstore.GetBuildsFailures(child_buildbucket_ids) |
| stage_failures_by_build = cros_collections.GroupNamedtuplesByKey( |
| stage_failures, 'build_config') |
| |
| failure_msg_manager = failure_message_lib.FailureMessageManager() |
| for build_config, stage_failures in stage_failures_by_build.items(): |
| slave_failures_dict[build_config] = ( |
| failure_msg_manager.ConstructStageFailureMessages(stage_failures)) |
| |
| return slave_failures_dict |
| |
| def _GetSlavesAbortedBySelfDestruction(self, cidb_info_dict): |
| """Get slaves aborted by self-destruction of the master. |
| |
| Args: |
| cidb_info_dict: A dict mapping slave build config names (strings) to their |
| cidb infos (in the format of CIDBStatusInfo). |
| |
| Returns: |
| A set of build config names (strings) of slaves aborted by |
| self-destruction. |
| """ |
| return set(build_config |
| for build_config, cidb_info in cidb_info_dict.items() |
| if BuilderStatusManager.AbortedBySelfDestruction( |
| self.buildstore, cidb_info.buildbucket_id, |
| self.master_build_identifier)) |
| |
| def _InitSlaveInfo(self): |
| """Init slave info including buildbucket info, cidb info and failures.""" |
| scheduled_buildbucket_info_dict = buildbucket_lib.GetBuildInfoDict( |
| self.metadata, exclude_experimental=self.exclude_experimental) |
| self.buildbucket_info_dict = self.GetAllSlaveBuildbucketInfo( |
| self.buildbucket_client, scheduled_buildbucket_info_dict, |
| dry_run=self.dry_run) |
| self.builders_array = list(self.buildbucket_info_dict) |
| |
| self.cidb_info_dict = self.GetAllSlaveCIDBStatusInfo( |
| self.buildstore, self.master_build_identifier, |
| self.buildbucket_info_dict) |
| |
| self.slave_failures_dict = self._GetSlaveFailures( |
| self.buildbucket_info_dict) |
| |
| self.aborted_slaves = self._GetSlavesAbortedBySelfDestruction( |
| self.cidb_info_dict) |
| |
| def _GetStatus(self, build_config, cidb_info_dict, buildbucket_info_dict): |
| """Get status of a given build. |
| |
| Args: |
| build_config: Build config name (string) of a slave build. |
| cidb_info_dict: A dict mapping slave build config names (strings) to their |
| cidb infos (in the format of CIDBStatusInfo). |
| buildbucket_info_dict: A dict mapping slave build config names (strings) |
| to their Buildbucket infos (in the format of BuildbucketInfo). |
| |
| Returns: |
| Builder status of the given build. |
| """ |
| cidb_info = cidb_info_dict.get(build_config) |
| if cidb_info is None: |
| return constants.BUILDER_STATUS_MISSING |
| elif (cidb_info.status in (constants.BUILDER_STATUS_PASSED, |
| constants.BUILDER_STATUS_FAILED)): |
| return cidb_info.status |
| else: |
| if buildbucket_info_dict is not None: |
| if (buildbucket_info_dict[build_config].status == |
| constants.BUILDBUCKET_BUILDER_STATUS_STARTED): |
| return constants.BUILDER_STATUS_INFLIGHT |
| else: |
| return constants.BUILDER_STATUS_FAILED |
| else: |
| return constants.BUILDER_STATUS_INFLIGHT |
| |
| # TODO(nxia): Buildbucket response returns luci-milo instead buildbot urls. |
| def _GetDashboardUrl(self, build_config, _cidb_info_dict, |
| buildbucket_info_dict): |
| """Get dashboard url of a given build. |
| |
| Args: |
| build_config: Build config name (string) of a slave build. |
| cidb_info_dict: A dict mapping slave build config names (strings) to their |
| cidb infos (in the format of CIDBStatusInfo). |
| buildbucket_info_dict: A dict mapping slave build config names (strings) |
| to their Buildbucket infos (in the format of BuildbucketInfo). |
| |
| Returns: |
| Dashboard url of the given build. None if no entry found for this given |
| build in CIDB and buildbucket_info_dict is None. |
| """ |
| if buildbucket_info_dict is not None: |
| # If no entry found in CIDB, get the buildbot url from Buildbucket. |
| return buildbucket_info_dict[build_config].url |
| |
| def _GetMessage(self, build_config, status, dashboard_url, |
| slave_failures_dict, aborted_slaves): |
| """Get build_failure_message.BuildFailureMessage of a given build. |
| |
| Args: |
| build_config: Build config name (string) of a slave build. |
| status: The status of the build (See return type of self._GetStatus()) |
| dashboard_url: The URL of the build. |
| slave_failures_dict: A dict mapping the slave build config names (strings) |
| to stage failure messages (See return type of _GetSlaveFailures) |
| aborted_slaves: A set of build config names (strings) of slaves aborted by |
| self-destruction. |
| |
| Returns: |
| A build_failure_message.BuildFailureMessage object if the status is |
| constants.BUILDER_STATUS_FAILED; else, None. |
| """ |
| site_config = config_lib.GetConfig() |
| if status == constants.BUILDER_STATUS_FAILED: |
| failure_messages = slave_failures_dict.get(build_config) |
| overlays = site_config[build_config].overlays |
| aborted = build_config in aborted_slaves |
| return BuilderStatusManager.CreateBuildFailureMessage( |
| build_config, overlays, dashboard_url, failure_messages, |
| aborted_by_self_destruction=aborted) |
| |
| def GetBuilderStatusForBuild(self, build_config): |
| """Get BuilderStatus for a given build. |
| |
| Args: |
| build_config: Build config name (string) of a slave build. |
| |
| Returns: |
| An instance of BuilderStatus of the given build. |
| """ |
| status = self._GetStatus( |
| build_config, self.cidb_info_dict, self.buildbucket_info_dict) |
| dashboard_url = self._GetDashboardUrl( |
| build_config, self.cidb_info_dict, self.buildbucket_info_dict) |
| message = self._GetMessage( |
| build_config, status, dashboard_url, self.slave_failures_dict, |
| self.aborted_slaves) |
| |
| return BuilderStatus(status, message, dashboard_url=dashboard_url) |
| |
| @staticmethod |
| def GetAllSlaveBuildbucketInfo(buildbucket_client, |
| scheduled_buildbucket_info_dict, |
| dry_run=True): |
| """Get buildbucket info from Buildbucket for all scheduled slave builds. |
| |
| For each build in the scheduled builds dict, get build status and build |
| result from Buildbucket and return a updated buildbucket_info_dict. |
| |
| Args: |
| buildbucket_client: Instance of buildbucket_lib.buildbucket_client. |
| scheduled_buildbucket_info_dict: A dict mapping scheduled slave build |
| config name to its buildbucket information in the format of |
| BuildbucketInfo (see buildbucket.GetBuildInfoDict for details). |
| dry_run: Boolean indicating whether it's a dry run. Default to True. |
| |
| Returns: |
| A dict mapping all scheduled slave build config names to their |
| BuildbucketInfos (The BuildbucketInfo of the most recently retried one of |
| there're multiple retries for a slave build config). |
| """ |
| # TODO(nxia): Consider replacing this with a more elaborate fix. |
| if buildbucket_client is None: |
| return {} |
| |
| all_buildbucket_info_dict = {} |
| for build_config, build_info in scheduled_buildbucket_info_dict.items(): |
| buildbucket_id = build_info.buildbucket_id |
| retry = build_info.retry |
| created_ts = build_info.created_ts |
| status = None |
| result = None |
| url = None |
| |
| try: |
| content = buildbucket_client.GetBuildRequest(buildbucket_id, dry_run) |
| status = buildbucket_lib.GetBuildStatus(content) |
| result = buildbucket_lib.GetBuildResult(content) |
| url = buildbucket_lib.GetBuildURL(content) |
| except buildbucket_lib.BuildbucketResponseException as e: |
| # If we have a temporary issue accessing the build status from the |
| # Buildbucket, log the error and continue with other builds. |
| # SlaveStatus will handle the missing builds in ShouldWait(). |
| logging.error('Failed to get status for build %s id %s: %s', |
| build_config, buildbucket_id, e) |
| |
| all_buildbucket_info_dict[build_config] = buildbucket_lib.BuildbucketInfo( |
| buildbucket_id, retry, created_ts, status, result, url) |
| |
| return all_buildbucket_info_dict |
| |
| @staticmethod |
| def GetAllSlaveCIDBStatusInfo(buildstore, master_build_identifier, |
| all_buildbucket_info_dict): |
| """Get build status information from CIDB for all slaves. |
| |
| Args: |
| buildstore: An instance of buildstore.BuildStore. |
| master_build_identifier: The BuildIdentifier of the master build. |
| all_buildbucket_info_dict: A dict mapping all build config names to their |
| information fetched from Buildbucket server (in the format of |
| BuildbucketInfo). |
| |
| Returns: |
| A dict mapping build config names to their cidb infos (in the format of |
| CIDBStatusInfo). If all_buildbucket_info_dict is not None, the returned |
| map only contains slave builds which are associated with buildbucket_ids |
| recorded in all_buildbucket_info_dict. |
| """ |
| # TODO(buildstore): Make sure buildstore is BuildStore, not CIDBConnection. |
| all_cidb_status_dict = {} |
| if buildstore is not None: |
| buildbucket_ids = None if all_buildbucket_info_dict is None else [ |
| info.buildbucket_id for info in all_buildbucket_info_dict.values()] |
| |
| if buildbucket_ids: |
| slave_statuses = buildstore.GetBuildStatuses( |
| buildbucket_ids=buildbucket_ids) |
| else: |
| slave_statuses = buildstore.GetSlaveStatuses(master_build_identifier) |
| |
| all_cidb_status_dict = {s['build_config']: CIDBStatusInfo( |
| s['buildbucket_id'], s['status']) for s in slave_statuses} |
| |
| return all_cidb_status_dict |
| |
| |
| class BuilderStatusesFetcher(object): |
| """Class to fetch BuilderStatus of a build and its slave builds(if any).""" |
| |
| def __init__(self, build_identifier, buildstore, success, message, config, |
| metadata, buildbucket_client, builders_array=None, |
| exclude_experimental=True, dry_run=True): |
| """Initialize BuilderStatusesFetcher. |
| |
| Args: |
| build_identifier: Build id of the build. |
| buildstore: A BuildStore instance to make DB calls. |
| success: Whether the build succeeded so far. |
| message: The failure message (see return type of |
| generic_stages.GetBuildFailureMessage) of the build. |
| config: Instance of config_lib.BuildConfig. Config dict of this build. |
| metadata: Instance of metadata_lib.CBuildbotMetadata. Metadata of this |
| build. |
| buildbucket_client: Instance of buildbucket_lib.buildbucket_client. |
| builders_array: List of the expected and slave builds, it also contains |
| the builds marked as experimental in the tree status. Default to None. |
| exclude_experimental: Whether to exclude the builds which are important in |
| the config but are marked as experimental in the tree status. Default to |
| True. |
| dry_run: Boolean indicating whether it's a dry run. Default to True. |
| """ |
| self.build_identifier = build_identifier |
| self.build_id = build_identifier.cidb_id |
| self.buildstore = buildstore |
| self.db = buildstore.GetCIDBHandle() |
| self.success = success |
| self.message = message |
| self.config = config |
| self.metadata = metadata |
| self.buildbucket_client = buildbucket_client |
| self.dry_run = dry_run |
| self.exclude_experimental = exclude_experimental |
| |
| self.builders_array = buildbucket_lib.FetchCurrentSlaveBuilders( |
| self.config, self.metadata, builders_array, |
| exclude_experimental=self.exclude_experimental) |
| |
| def _FetchLocalBuilderStatus(self): |
| """Fetch the BuilderStatus of the local build. |
| |
| Returns: |
| A dict mapping the bot_id of the build to its builder_status. |
| """ |
| status = BuilderStatus.GetCompletedStatus(self.success) |
| status_obj = BuilderStatus(status, self.message) |
| return {self.config.name: status_obj} |
| |
| def _FetchSlaveBuilderStatuses(self): |
| """Fetch the BuilderStatus of the slaves if the local build. |
| |
| Returns: |
| A dict mapping build configs (strings) to their BuilderStatus instances. |
| It contains the statuses for builders marked as experimental in the tree |
| status. |
| """ |
| if not self.builders_array: |
| return {} |
| |
| slave_builder_statuses = SlaveBuilderStatus( |
| self.build_identifier, self.buildstore, self.config, self.metadata, |
| self.buildbucket_client, self.builders_array, self.dry_run, |
| exclude_experimental=self.exclude_experimental) |
| |
| slave_builder_status_dict = {} |
| for builder in self.builders_array: |
| logging.info('Creating BuilderStatus for builder %s', builder) |
| builder_status = slave_builder_statuses.GetBuilderStatusForBuild(builder) |
| slave_builder_status_dict[builder] = builder_status |
| message = (builder_status.message.BuildFailureMessageToStr() |
| if builder_status.message is not None else None) |
| logging.info( |
| 'Builder %s BuilderStatus.status %s BuilderStatus.message %s' |
| ' BuilderStatus.dashboard_url %s ', |
| builder, builder_status.status, message, |
| builder_status.dashboard_url) |
| return slave_builder_status_dict |
| |
| def GetBuilderStatuses(self): |
| """Get BuilderStatus of a given build and its slave builds (if any). |
| |
| Returns: |
| A pair of dict mapping build names (strings) to their BuilderStatus |
| instances. important_statuses only contains builds which are important and |
| not marked experimental in the tree status. experimental_statuses contains |
| builds marked as experimental in the tree status. |
| """ |
| statuses = self._FetchLocalBuilderStatus() |
| |
| if not self.config.master: |
| # The build returns its own status. |
| logging.info('The build is not a master.') |
| |
| return statuses, {} |
| |
| logging.info('Fetching BuilderStatus of slaves.') |
| |
| statuses.update(self._FetchSlaveBuilderStatuses()) |
| |
| # Get builders marked as experimental in the tree status from metadata. |
| experimental_builders = self.metadata.GetValueWithDefault( |
| constants.METADATA_EXPERIMENTAL_BUILDERS, []) |
| |
| if not experimental_builders: |
| return statuses, {} |
| |
| important_statuses = {} |
| experimental_statuses = {} |
| for k, v in statuses.items(): |
| if k in experimental_builders: |
| experimental_statuses[k] = v |
| else: |
| important_statuses[k] = v |
| |
| return important_statuses, experimental_statuses |
| |
| @staticmethod |
| def GetFailingBuilds(statuses): |
| """Get the names of builds which are failed. |
| |
| Args: |
| statuses: A dict mapping build config names to their BuilderStatus. |
| |
| Returns: |
| A set of failed build config names. |
| """ |
| return set(builder for builder, status in statuses.items() |
| if status.Failed()) |
| |
| @staticmethod |
| def GetInflightBuilds(statuses): |
| """Get the names of builds which are inflight. |
| |
| Args: |
| statuses: A dict mapping build config names to their BuilderStatus. |
| |
| Returns: |
| A set of inflight build config names. |
| """ |
| return set(builder for builder, status in statuses.items() |
| if status.Inflight()) |
| |
| @staticmethod |
| def GetNostatBuilds(statuses): |
| """Get the names of builds which are missing. |
| |
| Args: |
| statuses: A dict mapping build config names to their BuilderStatus. |
| |
| Returns: |
| A set of missing build config names. |
| """ |
| return set(builder for builder, status in statuses.items() |
| if status.Missing()) |