| # -*- coding: utf-8 -*- |
| # Copyright 2018 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. |
| |
| """Database interface for all calls from Chromite. |
| |
| BuildStore will be the interface which communicates between CIDB, |
| Buildbucket as the underlying databases and Chromite and other callers |
| as the clients of the data. |
| """ |
| |
| from __future__ import print_function |
| |
| import os |
| import sys |
| |
| from chromite.lib import buildbucket_v2 |
| from chromite.lib import cidb |
| from chromite.lib import constants |
| from chromite.lib import failure_message_lib |
| from chromite.lib import fake_cidb |
| |
| |
| assert sys.version_info >= (3, 6), 'This module requires Python 3.6+' |
| |
| |
| class BuildStoreException(Exception): |
| """General exception class for this module.""" |
| |
| |
| class BuildIdentifier(object): |
| """The class maintains all the IDs corresponding to a build.""" |
| |
| def __init__(self, cidb_id=None, buildbucket_id=None): |
| """Instantiate a container class for all IDs. |
| |
| Args: |
| cidb_id: ID of the build in CIDB. |
| buildbucket_id: ID of the build in Buildbucket. |
| """ |
| self.cidb_id = cidb_id |
| self.buildbucket_id = buildbucket_id |
| |
| |
| class BuildStore(object): |
| """BuildStore class to handle all DB calls.""" |
| |
| NUM_RESULTS_NO_LIMIT = 1000 |
| |
| def __init__(self, _read_from_bb=True, _write_to_bb=True, |
| _write_to_cidb=True, cidb_creds=None, for_service=None): |
| """Get an instance of the BuildStore. |
| |
| Args: |
| _read_from_bb: Specify the read source. |
| _write_to_bb: Determines whether information is written to Buildbucket. |
| _write_to_cidb: Determines whether information is written to CIDB. |
| cidb_creds: CIDB credentials for scripts running outside of cbuildbot. |
| for_service: Argument for CIDBConnection.__init__(). |
| """ |
| self._read_from_bb = _read_from_bb |
| self._write_to_bb = _write_to_bb |
| self._write_to_cidb = _write_to_cidb |
| self.cidb_creds = cidb_creds |
| self.for_service = for_service |
| self.cidb_conn = None |
| self.bb_client = None |
| self.process_id = os.getpid() |
| |
| def _IsCIDBClientMissing(self): |
| """Checks to see if CIDB client is needed and is missing. |
| |
| Returns: |
| Boolean indicating the state of CIDB client. |
| """ |
| need_for_cidb = self._write_to_cidb or not self._read_from_bb |
| cidb_is_running = self.cidb_conn is not None |
| |
| return need_for_cidb and not cidb_is_running |
| |
| def _IsBuildbucketClientMissing(self): |
| """Checks to see if Buildbucket v2 client is needed and is missing. |
| |
| Returns: |
| Boolean indicating the state of Buildbucket v2 client. |
| """ |
| need_for_bb = self._write_to_bb or self._read_from_bb |
| bb_is_running = self.bb_client is not None |
| |
| return need_for_bb and not bb_is_running |
| |
| def GetCIDBHandle(self): |
| """Retrieve cidb_conn. |
| |
| Returns: |
| self.cidb_conn if initialized. |
| """ |
| if not self.InitializeClients(): |
| raise BuildStoreException('BuildStore clients could not be initialized.') |
| |
| if self.cidb_conn: |
| return self.cidb_conn |
| else: |
| raise BuildStoreException('CIDBConnection not found.') |
| |
| def InitializeClients(self): |
| """Check if underlying clients are initialized. |
| |
| Returns: |
| A boolean indicating the client statuses. |
| """ |
| pid_mismatch = (self.process_id != os.getpid()) |
| if self._IsCIDBClientMissing() or pid_mismatch: |
| self.process_id = os.getpid() |
| if self.cidb_creds: |
| for_service = self.for_service if self.for_service else False |
| self.cidb_conn = cidb.CIDBConnection(self.cidb_creds, |
| for_service=for_service) |
| elif not cidb.CIDBConnectionFactory.IsCIDBSetup(): |
| self.cidb_conn = None |
| else: |
| self.cidb_conn = ( |
| cidb.CIDBConnectionFactory.GetCIDBConnectionForBuilder()) |
| |
| if self._IsBuildbucketClientMissing() or pid_mismatch: |
| self.bb_client = buildbucket_v2.BuildbucketV2() |
| |
| return not (self._IsCIDBClientMissing() or |
| self._IsBuildbucketClientMissing()) |
| |
| def AreClientsReady(self): |
| """A front-end function for InitializeClients().""" |
| return self.InitializeClients() |
| |
| def InsertBuild(self, |
| builder_name, |
| build_number, |
| build_config, |
| bot_hostname, |
| master_build_id=None, |
| timeout_seconds=None, |
| important=None, |
| buildbucket_id=None, |
| branch=None): |
| """Insert a build row. |
| |
| Args: |
| builder_name: buildbot builder name. |
| build_number: buildbot build number. |
| build_config: cbuildbot config of build |
| bot_hostname: hostname of bot running the build |
| master_build_id: (Optional) primary key of master build to this build. |
| timeout_seconds: (Optional) If provided, total time allocated for this |
| build. A deadline is recorded in CIDB for the current |
| build to end. |
| important: (Optional) If provided, the |important| value for this build. |
| buildbucket_id: (Optional) If provided, the |buildbucket_id| value for |
| this build. |
| branch: (Optional) Manifest branch name of this build. |
| |
| Returns: |
| build_id: incremental primary ID of the build in CIDB. |
| """ |
| if not self.InitializeClients(): |
| raise BuildStoreException('BuildStore clients could not be initialized.') |
| build_id = 0 |
| if self._write_to_cidb: |
| build_id = self.cidb_conn.InsertBuild( |
| builder_name, build_number, build_config, bot_hostname, |
| master_build_id, timeout_seconds, important, buildbucket_id, branch) |
| if self._write_to_bb: |
| buildbucket_v2.UpdateSelfCommonBuildProperties(critical=important, |
| cidb_id=build_id) |
| |
| return build_id |
| |
| def GetSlaveStatuses(self, master_build_identifier): |
| """Gets the statuses of slave builders to given build. |
| |
| Args: |
| master_build_identifier: BuildIdentifier of the master build to fetch the |
| slave statuses for. |
| |
| Returns: |
| A list containing a dictionary with keys BUILD_STATUS_KEYS. |
| The list contains all child builds of the given master. |
| """ |
| if not self.InitializeClients(): |
| raise BuildStoreException('BuildStore clients could not be initialized.') |
| if (self._read_from_bb and |
| master_build_identifier.buildbucket_id is not None): |
| return self.bb_client.GetChildStatuses( |
| int(master_build_identifier.buildbucket_id)) |
| elif not self._read_from_bb and master_build_identifier.cidb_id is not None: |
| return self.cidb_conn.GetSlaveStatuses(master_build_identifier.cidb_id, |
| None) |
| |
| def GetKilledChildBuilds(self, build_identifier): |
| """Get the child builds that were killed by the given master. |
| |
| Args: |
| build_identifier: The master build to get children for. |
| |
| Returns: |
| A list of child buildbucket_ids of the build that were killed. |
| """ |
| if not self.InitializeClients(): |
| raise BuildStoreException('BuildStore clients could not be initialized.') |
| if self._read_from_bb: |
| if build_identifier.buildbucket_id is not None: |
| return self.bb_client.GetKilledChildBuilds( |
| int(build_identifier.buildbucket_id)) |
| else: |
| if build_identifier.cidb_id is not None: |
| return [int(message['message_value']) for message in |
| self.cidb_conn.GetBuildMessages( |
| build_identifier.cidb_id, |
| message_type=constants.MESSAGE_TYPE_IGNORED_REASON, |
| message_subtype=constants.MESSAGE_SUBTYPE_SELF_DESTRUCTION)] |
| |
| def GetBuildHistory( |
| self, build_config, num_results, |
| ignore_build_id=None, start_date=None, end_date=None, branch=None, |
| platform_version=None, starting_build_id=None): |
| """Returns basic information about most recent builds for build config. |
| |
| By default this function returns the most recent builds. Some arguments can |
| restrict the result to older builds. |
| |
| Args: |
| build_config: config name of the build to get history. |
| num_results: Number of builds to search back. |
| ignore_build_id: (Optional) Ignore a specific build. This is most useful |
| to ignore the current build when querying recent past builds from a |
| build in flight. |
| start_date: (Optional, type: datetime.date) Get builds that occured on or |
| after this date. |
| end_date: (Optional, type:datetime.date) Get builds that occured on or |
| before this date. |
| branch: (Optional) Return only results for this branch. |
| platform_version: (Optional) Return only results for this |
| platform_version. |
| starting_build_id: (Optional) The oldest build_id till which builds should |
| be retrieved. |
| |
| Returns: |
| A sorted list of dicts containing up to |number| dictionaries for |
| build statuses in descending order (if |reverse| is True, ascending |
| order). |
| """ |
| if not self.InitializeClients(): |
| raise BuildStoreException('BuildStore clients could not be initialized.') |
| if platform_version or not self._read_from_bb: |
| return self.cidb_conn.GetBuildHistory( |
| build_config, num_results, ignore_build_id=ignore_build_id, |
| start_date=start_date, end_date=end_date, branch=branch, |
| platform_version=platform_version, |
| starting_build_id=starting_build_id) |
| else: |
| return self.bb_client.GetBuildHistory( |
| build_config, num_results, ignore_build_id=ignore_build_id, |
| start_date=start_date, end_date=end_date, branch=branch, |
| start_build_id=starting_build_id) |
| |
| def InsertBuildStage(self, |
| build_id, |
| name, |
| board=None, |
| status=constants.BUILDER_STATUS_PLANNED): |
| """Insert a build stage entry into database. |
| |
| Args: |
| build_id: primary key of the build in buildTable. |
| name: Full name of build stage. |
| board: (Optional) board name, if this is a board-specific stage. |
| status: (Optional) stage status, one of constants.BUILDER_ALL_STATUSES. |
| Default constants.BUILDER_STATUS_PLANNED. |
| |
| Returns: |
| Integer primary key of inserted stage, i.e. build_stage_id |
| """ |
| if not self.InitializeClients(): |
| raise BuildStoreException('BuildStore clients could not be initialized.') |
| if self._write_to_cidb: |
| return self.cidb_conn.InsertBuildStage(build_id, name, board, status) |
| |
| def InsertBuildMessage( |
| self, build_id, message_type=constants.MESSAGE_TYPE_IGNORED_REASON, |
| message_subtype=constants.MESSAGE_SUBTYPE_SELF_DESTRUCTION, |
| message_value=None, board=None): |
| """Insert a build message into database. |
| |
| Args: |
| build_id: primary key of build recording this message. |
| message_type: Optional str name of message type. |
| message_subtype: Optional str name of message subtype. |
| message_value: Optional value of message. |
| board: Optional str name of the board. |
| """ |
| assert isinstance(message_value, list) |
| if not self.InitializeClients(): |
| raise BuildStoreException('BuildStore clients could not be initialized.') |
| if self._write_to_cidb: |
| for buildbucket_id in message_value: |
| self.cidb_conn.InsertBuildMessage( |
| build_id, message_type=message_type, |
| message_subtype=message_subtype, message_value=str(buildbucket_id), |
| board=board) |
| if self._write_to_bb: |
| buildbucket_v2.UpdateSelfCommonBuildProperties( |
| killed_child_builds=message_value) |
| |
| def UpdateLuciNotifyProperties(self, email_notify=None): |
| """Update the buildbucket build with luci-notify specific properties. |
| |
| Args: |
| email_notify: List of luci-notify email_notify values representing the |
| recipients of failure alerts to for this builder. |
| """ |
| if not self.InitializeClients(): |
| raise BuildStoreException('BuildStore clients could not be initialized.') |
| if self._write_to_bb: |
| buildbucket_v2.UpdateSelfCommonBuildProperties(email_notify=email_notify) |
| |
| def FinishBuild(self, build_id, status=None, summary=None, metadata_url=None, |
| strict=True): |
| """Update the given build row, marking it as finished. |
| |
| This should be called once per build, as the last update to the build. |
| This will also mark the row's final=True. |
| |
| Args: |
| build_id: id of row to update. |
| status: Final build status, one of |
| constants.BUILDER_COMPLETED_STATUSES. |
| summary: A summary of the build (failures) collected from all slaves. |
| metadata_url: google storage url to metadata.json file for this build, |
| e.g. ('gs://chromeos-image-archive/master-paladin/' |
| 'R39-6225.0.0-rc1/metadata.json') |
| strict: If |strict| is True, can only update the build status when 'final' |
| is False. |strict| can only be False when the caller wants to change the |
| entry ignoring the 'final' value (For example, a build was marked as |
| status='aborted' and final='true', a cron job to adjust the finish_time |
| will call this method with strict=False). |
| """ |
| if not self.InitializeClients(): |
| raise BuildStoreException('BuildStore clients could not be initialized.') |
| if self._write_to_cidb: |
| self.cidb_conn.FinishBuild( |
| build_id, status=status, summary=summary, metadata_url=metadata_url, |
| strict=strict) |
| if self._write_to_bb: |
| buildbucket_v2.UpdateSelfCommonBuildProperties(metadata_url=metadata_url) |
| |
| def FinishChildConfig(self, build_id, child_config, status=None): |
| """Marks the given child config as finished with |status|. |
| |
| This should be called before FinishBuild, on all child configs that |
| were used in a build. |
| |
| Args: |
| build_id: primary key of the build in the buildTable |
| child_config: String child_config name. |
| status: Final child_config status, one of |
| constants.BUILDER_COMPLETED_STATUSES or None |
| for default "inflight". |
| """ |
| if not self.InitializeClients(): |
| raise BuildStoreException('BuildStore clients could not be initialized.') |
| if self._write_to_cidb: |
| self.cidb_conn.FinishChildConfig(build_id, child_config, status=status) |
| |
| def StartBuildStage(self, build_stage_id): |
| """Marks a build stage as inflight, in the database. |
| |
| Args: |
| build_stage_id: primary key of the build stage in buildStageTable. |
| """ |
| if not self.InitializeClients(): |
| raise BuildStoreException('BuildStore clients could not be initialized.') |
| if self._write_to_cidb: |
| return self.cidb_conn.StartBuildStage(build_stage_id) |
| |
| def WaitBuildStage(self, build_stage_id): |
| """Marks a build stage as waiting, in the database. |
| |
| Args: |
| build_stage_id: primary key of the build stage in buildStageTable. |
| """ |
| if not self.InitializeClients(): |
| raise BuildStoreException('BuildStore clients could not be initialized.') |
| if self._write_to_cidb: |
| return self.cidb_conn.WaitBuildStage(build_stage_id) |
| |
| def FinishBuildStage(self, build_stage_id, status): |
| """Marks a build stage as finished, in the database. |
| |
| Args: |
| build_stage_id: primary key of the build stage in buildStageTable. |
| status: one of constants.BUILDER_COMPLETED_STATUSES |
| """ |
| if not self.InitializeClients(): |
| raise BuildStoreException('BuildStore clients could not be initialized.') |
| if self._write_to_cidb: |
| return self.cidb_conn.FinishBuildStage(build_stage_id, status) |
| |
| def InsertBoardPerBuild(self, build_id, board, board_metadata=None): |
| """Inserts board-per-build into the database. |
| |
| This function redirects both InsertBoardPerBuild and |
| UpdateBoardPerBuildMetadata of CIDB. |
| |
| Args: |
| build_id: CIDB id of the build. |
| board: Board of the build. |
| board_metadata: A dict with keys - 'main-firmware-version' and |
| 'ec-firmware-version'. |
| """ |
| if not self.InitializeClients(): |
| raise BuildStoreException('BuildStore clients could not be initialized.') |
| if self._write_to_cidb: |
| if board_metadata is None: |
| self.cidb_conn.InsertBoardPerBuild(build_id, board) |
| else: |
| self.cidb_conn.UpdateBoardPerBuildMetadata(build_id, board, |
| board_metadata) |
| if self._write_to_bb: |
| if board_metadata is None: |
| buildbucket_v2.UpdateSelfCommonBuildProperties(board=board) |
| else: |
| buildbucket_v2.UpdateSelfCommonBuildProperties( |
| board=board, |
| main_firmware_version=board_metadata.get('main-firmware-version'), |
| ec_firmware_version=board_metadata.get('ec-firmware-version')) |
| |
| def UpdateMetadata(self, build_id, metadata): |
| """Update the given metadata row in database. |
| |
| Args: |
| build_id: CIDB id of the build to update. |
| metadata: CBuildbotMetadata instance to update with. |
| """ |
| if not self.InitializeClients(): |
| raise BuildStoreException('BuildStore clients could not be initialized.') |
| update_status = 0 |
| if self._write_to_cidb: |
| update_status = self.cidb_conn.UpdateMetadata(build_id, metadata) |
| if self._write_to_bb: |
| buildbucket_v2.UpdateBuildMetadata(metadata) |
| return update_status |
| |
| def GetBuildsFailures(self, buildbucket_ids=None): |
| """Gets the failure entries for all listed buildbucket_ids. |
| |
| Args: |
| buildbucket_ids: list of build ids of the builds to fetch failures for. |
| |
| Returns: |
| A list of failure_message_lib.StageFailure instances. This will change |
| with Buildbucket implementation. |
| """ |
| if not self.InitializeClients(): |
| raise BuildStoreException('BuildStore clients could not be initialized.') |
| if not buildbucket_ids: |
| return [] |
| elif self._read_from_bb: |
| failure_list = [] |
| for buildbucket_id in buildbucket_ids: |
| bb_list = self.bb_client.GetStageFailures(int(buildbucket_id)) |
| for stage in bb_list: |
| failure_list.append(failure_message_lib.StageFailure( |
| id=None, build_stage_id=None, outer_failure_id=None, |
| exception_type=None, exception_message=None, |
| exception_category=None, extra_info=None, timestamp=None, |
| stage_name=stage['stage_name'], board=None, |
| stage_status=stage['stage_status'], build_id=None, |
| master_build_id=None, builder_name=None, build_number=None, |
| build_config=stage['build_config'], |
| build_status=stage['build_status'], |
| important=stage['important'], buildbucket_id=buildbucket_id)) |
| return failure_list |
| else: |
| return self.cidb_conn.GetBuildsFailures(buildbucket_ids) |
| |
| def GetBuildsStages(self, buildbucket_ids): |
| """Gets all the stages for all listed build_ids. |
| |
| Args: |
| buildbucket_ids: list of Buildbucket IDs to query for. |
| |
| Returns: |
| A list containing, for each stage of the builds found, a dictionary with |
| keys (id, build_id, name, board, status, last_updated, start_time, |
| finish_time, final). |
| """ |
| if not self.InitializeClients(): |
| raise BuildStoreException('BuildStore clients could not be initialized.') |
| if not buildbucket_ids: |
| return [] |
| elif self._read_from_bb: |
| stage_list = [] |
| for buildbucket_id in buildbucket_ids: |
| stage_list += self.bb_client.GetBuildStages(int(buildbucket_id)) |
| return stage_list |
| else: |
| return self.cidb_conn.GetBuildsStagesWithBuildbucketIds(buildbucket_ids) |
| |
| def GetBuildStatuses(self, buildbucket_ids=None, build_ids=None): |
| """Retrieve the build statuses of list of builds. |
| |
| The two arguments are to be mutually exclusive. If both are provided, |
| an error will be raised. If both are absent, an empty list will be returned. |
| |
| Args: |
| buildbucket_ids: list of buildbucket_id's to query. |
| build_ids: list of CIDB id's to query. |
| |
| Returns: |
| A list of Dictionaries with keys (id, build_config, start_time, |
| finish_time, status, platform_version, full_version, |
| milestone_version, important). |
| """ |
| if not self.InitializeClients(): |
| raise BuildStoreException('BuildStore clients could not be initialized.') |
| if buildbucket_ids and build_ids: |
| raise BuildStoreException('GetBuildStatuses: Cannot process both ' |
| 'buildbucket_ids and build_ids.') |
| # build_ids have to serviced from CIDB. This codepath will be defunct after |
| # CQ is shut down. |
| if build_ids: |
| return self.cidb_conn.GetBuildStatuses(build_ids) |
| elif not buildbucket_ids: |
| return [] |
| elif self._read_from_bb: |
| return [self.bb_client.GetBuildStatus(int(buildbucket_id)) |
| for buildbucket_id in buildbucket_ids] |
| elif not self._read_from_bb: |
| return self.cidb_conn.GetBuildStatusesWithBuildbucketIds( |
| buildbucket_ids) |
| |
| |
| # pylint: disable=unused-argument |
| class FakeBuildStore(object): |
| """Fake BuildStore class to be used only in unittests.""" |
| |
| def __init__(self, fake_cidb_conn=None): |
| super(FakeBuildStore, self).__init__() |
| if fake_cidb_conn: |
| self.fake_cidb = fake_cidb_conn |
| else: |
| self.fake_cidb = fake_cidb.FakeCIDBConnection() |
| |
| def InitializeClients(self): |
| return True |
| |
| def AreClientsReady(self): |
| return True |
| |
| def GetCIDBHandle(self): |
| return self.fake_cidb |
| |
| def InsertBuild(self, |
| builder_name, |
| build_number, |
| build_config, |
| bot_hostname, |
| master_build_id=None, |
| timeout_seconds=None, |
| status=constants.BUILDER_STATUS_PASSED, |
| important=None, |
| buildbucket_id=None, |
| milestone_version=None, |
| platform_version=None, |
| start_time=None, |
| build_type=None, |
| branch=None): |
| build_id = self.fake_cidb.InsertBuild( |
| builder_name, build_number, build_config, bot_hostname, master_build_id, |
| timeout_seconds, status, important, buildbucket_id, milestone_version, |
| platform_version, start_time, build_type, branch) |
| return build_id |
| |
| def GetBuildHistory( |
| self, build_config, num_results, ignore_build_id=None, start_date=None, |
| end_date=None, branch=None, platform_version=None, starting_build_id=None, |
| ending_build_id=None): |
| return self.fake_cidb.GetBuildHistory( |
| build_config, num_results, ignore_build_id=ignore_build_id, |
| start_date=start_date, end_date=end_date, |
| platform_version=platform_version, starting_build_id=starting_build_id) |
| |
| def InsertBuildStage(self, |
| build_id, |
| name, |
| board=None, |
| status=constants.BUILDER_STATUS_PLANNED): |
| build_stage_id = self.fake_cidb.InsertBuildStage(build_id, name, board, |
| status) |
| return build_stage_id |
| |
| def GetSlaveStatuses(self, master_build_id, buildbucket_ids=None): |
| return self.fake_cidb.GetSlaveStatuses(master_build_id, buildbucket_ids) |
| |
| def GetKilledChildBuilds(self, build_identifier): |
| return [m['message_value'] for m in self.fake_cidb.GetBuildMessages( |
| build_identifier.cidb_id, |
| message_type=constants.MESSAGE_TYPE_IGNORED_REASON, |
| message_subtype=constants.MESSAGE_SUBTYPE_SELF_DESTRUCTION)] |
| |
| def InsertBuildMessage( |
| self, build_id, message_type=constants.MESSAGE_TYPE_IGNORED_REASON, |
| message_subtype=constants.MESSAGE_SUBTYPE_SELF_DESTRUCTION, |
| message_value=None, board=None): |
| for buildbucket_id in message_value: |
| self.fake_cidb.InsertBuildMessage( |
| build_id, message_type=message_type, |
| message_subtype=message_subtype, message_value=str(buildbucket_id), |
| board=board) |
| |
| def UpdateLuciNotifyProperties(self, email_notify=None): |
| return |
| |
| def FinishBuild(self, build_id, status=None, summary=None, metadata_url=None, |
| strict=True): |
| return |
| |
| def StartBuildStage(self, build_stage_id): |
| return build_stage_id |
| |
| def WaitBuildStage(self, build_stage_id): |
| return build_stage_id |
| |
| def FinishBuildStage(self, build_stage_id, status): |
| return build_stage_id |
| |
| def InsertBoardPerBuild(self, build_id, board, board_metadata=None): |
| pass |
| |
| def UpdateMetadata(self, build_id, metadata): |
| return |
| |
| def GetBuildsFailures(self, buildbucket_ids=None): |
| if buildbucket_ids: |
| return self.fake_cidb.GetBuildsFailures(buildbucket_ids) |
| else: |
| return [] |
| |
| def GetBuildsStages(self, buildbucket_ids=None, build_ids=None): |
| if buildbucket_ids: |
| return self.fake_cidb.GetBuildsStagesWithBuildbucketIds(buildbucket_ids) |
| elif build_ids: |
| return self.fake_cidb.GetBuildsStages(build_ids) |
| else: |
| return [] |
| |
| def GetBuildStatuses(self, buildbucket_ids=None, build_ids=None): |
| if buildbucket_ids: |
| return self.fake_cidb.GetBuildStatusesWithBuildbucketIds(buildbucket_ids) |
| elif build_ids: |
| return self.fake_cidb.GetBuildStatuses(build_ids) |
| else: |
| return [] |