blob: b98620bb5e3443d638a78aee29b083eda549c09f [file] [log] [blame]
# Copyright 2019 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""This module is for logic to process a buildbucket build."""
import logging
from go.chromium.org.luci.buildbucket.proto import common_pb2
from google.appengine.ext import ndb
from google.protobuf.field_mask_pb2 import FieldMask
from common.waterfall import buildbucket_client
from findit_v2.services import projects
from findit_v2.services.constants import ANALYZED_BUILD_ID_TAG_KEY
from findit_v2.services.context import Context
def GetFailedStepsInBuild(context, build):
"""Gets failed steps and their types for a LUCI build.
Args:
context (findit_v2.services.context.Context): Scope of the analysis.
build (buildbucket build.proto): ALL info about the build.
Returns:
A list of tuples, each tuple contains the information of a failed step and
its type.
"""
return _GetClassifiedStepsInBuildByStatus(context, build, common_pb2.FAILURE)
def GetPassingStepsInBuild(context, build):
"""Gets passing steps and their types for a LUCI build.
Args:
context (findit_v2.services.context.Context): Scope of the analysis.
build (buildbucket build.proto): ALL info about the build.
Returns:
A list of tuples, each tuple contains the information of a passing step and
its type.
"""
return _GetClassifiedStepsInBuildByStatus(context, build, common_pb2.SUCCESS)
def _GetClassifiedStepsInBuildByStatus(context, build, wanted_status):
"""Gets steps in the given status and their types for a LUCI build.
Args:
context (findit_v2.services.context.Context): Scope of the analysis.
build (buildbucket build.proto): ALL info about the build.
Returns:
A list of tuples, each tuple contains the information of a step and its
type.
"""
project_api = projects.GetProjectAPI(context.luci_project_name)
filtered_steps = []
for step in build.steps:
if step.status != wanted_status:
continue
step_type = project_api.ClassifyStepType(build, step)
filtered_steps.append((step, step_type))
return filtered_steps
def GetAnalyzedBuildIdFromRerunBuild(build):
"""Gets analyzed build id from rerun_build's tag, otherwise None.
Args:
rerun_build (buildbucket build.proto): ALL info about the build.
Returns:
int: build_id of the analyzed build.
"""
for tag in build.tags:
if tag.key == ANALYZED_BUILD_ID_TAG_KEY:
return int(tag.value)
return None
def GetBuildAndContextForAnalysis(project, build_id):
"""Gets all information about a build and generates context from it.
Args:
project (str): Luci project of the build.
build_id (int): Id of the build.
Returns:
(buildbucket build.proto): ALL info about the build.
(Context): Context of an analysis.
"""
build = buildbucket_client.GetV2Build(build_id, fields=FieldMask(paths=['*']))
if not build:
logging.error('Failed to get build info for build %d.', build_id)
return None, None
if (build.input.gitiles_commit.host !=
projects.GERRIT_PROJECTS[project]['gitiles-host'] or
build.input.gitiles_commit.project !=
projects.GERRIT_PROJECTS[project]['name']):
logging.warning('Unexpected gitiles project for build: %r', build_id)
return None, None
context = Context(
luci_project_name=project,
gitiles_host=build.input.gitiles_commit.host,
gitiles_project=build.input.gitiles_commit.project,
gitiles_ref=build.input.gitiles_commit.ref,
gitiles_id=build.input.gitiles_commit.id)
return build, context
def AllLaterBuildsHaveOverlappingFailure(context, build_id, culprit):
"""Gets later builds on the same builder with overlapping failed steps.
Queries buildbucket for later builds on the same builder and checks if all of
them fail with some overlap with the failures the culprit is responsible for,
based on failed step names.
Args:
context (findit_v2.services.context.Context): Scope of the analysis.
build_id: Id of the build to check
culprit (findit_v2.model.gitiles_commit.Culprit): The culprit that
introduces the failures we are checking.
Returns:
True if all completed builds on the builder after the original failure are
also failed _and_ the failed steps of each overlap with the failed steps in
the original failure. False if any of the builds completed successfully or
with only warnings, or if any build fails, but succeeds at all the steps
that the original failure failed at.
"""
def _StepNamesOnly(step_type_tuples):
return set(s.name for s, st in step_type_tuples)
build = buildbucket_client.GetV2Build(build_id, fields=FieldMask(paths=['*']))
builder_id = build.builder
latest_builds = GetRecentCompletedBuilds(
builder_id, at_or_after_build=build_id)
failures = [ndb.Key(urlsafe=k).get() for k in culprit.failure_urlsafe_keys]
original_failed_steps = set(f.step_ui_name for f in failures)
for newer_build in latest_builds:
if newer_build.number <= build.number:
break
if newer_build.status == common_pb2.SUCCESS:
logging.info('Found later build that succeeded BuildId:%d',
newer_build.id)
return False
new_failed_steps = _StepNamesOnly(
GetFailedStepsInBuild(context, newer_build))
new_passing_steps = _StepNamesOnly(
GetPassingStepsInBuild(context, newer_build))
if (not original_failed_steps & new_failed_steps and
original_failed_steps.issubset(new_passing_steps)):
logging.info(
'All steps faild due to cuprit %s succeeded in later build %d',
culprit.key.id(), newer_build.number)
return False
return True
def GetRecentCompletedBuilds(builder_id, page_size=20, at_or_after_build=0):
"""Gets the most recent <page_size> completed builds in the builder.
If given, filter out builds with build id earlier than <at_or_after_build>.
Args:
builder_id (buildbucket_proto.build.BuilderID): project/bucket/builder.
page_size (int): How many builds to retrieve (ending at the most recent).
at_or_after_build (int): If greater than zero, exclude all builds with a
build id earlier than this. N.B. Build id is monotonically decreasing.
"""
search_builds_response = buildbucket_client.SearchV2BuildsOnBuilder(
builder_id, status=common_pb2.ENDED_MASK, page_size=page_size)
if search_builds_response:
return sorted([
build for build in search_builds_response.builds
if (not at_or_after_build or build.id <= at_or_after_build)
],
key=lambda x: x.id)
return None