blob: 7bff379c770692431b9026f0399e913611b49657 [file]
# Copyright 2016 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.
from google.appengine.ext import ndb
from gae_libs.model.versioned_model import VersionedModel
from model.flake.master_flake_analysis import MasterFlakeAnalysis
class BuildStep(ndb.Model):
"""Represents a build step on Waterfall or Commit Queue.
For a build step on Commit Queue, the matching Waterfall build step could be
added if found.
"""
# The build step in which a flake actually occurred.
master_name = ndb.StringProperty(indexed=False)
builder_name = ndb.StringProperty(indexed=False)
build_number = ndb.IntegerProperty(indexed=False)
step_name = ndb.StringProperty(indexed=False)
# When the flake was reported on this step.
reported_time = ndb.DateTimeProperty(indexed=False)
# The matching build step on the Waterfall of the matching test configuration.
wf_master_name = ndb.StringProperty(indexed=False)
wf_builder_name = ndb.StringProperty(indexed=False)
wf_build_number = ndb.IntegerProperty(indexed=False)
wf_step_name = ndb.StringProperty(indexed=False)
# Indicate whether the flake is run on Swarming.
swarmed = ndb.BooleanProperty(indexed=False, default=False)
# Indicate whether analysis on the step is supported.
supported = ndb.BooleanProperty(indexed=False, default=False)
# Indicate whether the analysis of flake on this configuration is scheduled.
scheduled = ndb.BooleanProperty(indexed=False, default=False)
# Step metadata of the step.
step_metadata = ndb.JsonProperty(compressed=True)
@staticmethod
def _StripMasterPrefix(name):
master_prefix = 'master.'
if name.startswith(master_prefix):
return name[len(master_prefix):]
return name
@staticmethod
def Create(master_name, builder_name, build_number, step_name, reported_time):
return BuildStep(
master_name=BuildStep._StripMasterPrefix(master_name),
builder_name=builder_name,
build_number=build_number,
step_name=step_name,
reported_time=reported_time)
@property
def has_matching_waterfall_step(self):
return None not in (self.wf_master_name, self.wf_builder_name,
self.wf_build_number, self.wf_step_name)
@property
def on_cq(self):
"""Returns True if the build step of the flake is on Commit Queue."""
return self.master_name.startswith('tryserver.')
class FlakeAnalysisRequest(VersionedModel):
"""Represents a request to analyze a flake.
The name of the flake will be the key, and the model is versioned.
"""
# Name of the flake. Could be a step name, or a test name.
# Assume there are no step and test with the same name.
name = ndb.StringProperty(indexed=True)
# Indicate whether the flake is a step or a test.
is_step = ndb.BooleanProperty(indexed=True, default=True)
# Indicate whether the flake is run on Swarming for some configuration.
swarmed = ndb.BooleanProperty(indexed=False, default=False)
# Indicate whether analysis on this flake is supported.
supported = ndb.BooleanProperty(indexed=False, default=False)
# The bug id for this flake on Monorail.
bug_id = ndb.IntegerProperty(indexed=False)
# The reporter of this bug, use triggering_source as an emun to define this
# value. The triggering source FINDIT_PIPELINES should be used for when
# Findit itself reports a bug.
bug_reported_by = ndb.IntegerProperty(indexed=False)
# The emails of users who request analysis of this flake.
user_emails = ndb.StringProperty(indexed=False, repeated=True)
# Whether the user emails have been obscured.
user_emails_obscured = ndb.BooleanProperty(indexed=True, default=False)
# When was the last edit of the email list.
user_emails_last_edit = ndb.DateTimeProperty(indexed=True)
# The build steps in which the flake occurred.
build_steps = ndb.LocalStructuredProperty(
BuildStep, compressed=True, repeated=True)
# Executed analyses on different test configurations.
analyses = ndb.KeyProperty(MasterFlakeAnalysis, repeated=True)
# Arguments number differs from overridden method - pylint: disable=W0221
@classmethod
def Create(cls, name, is_step, bug_id):
instance = super(cls, FlakeAnalysisRequest).Create(key=name)
instance.name = name
instance.is_step = is_step
instance.bug_id = bug_id
return instance
def AddBuildStep(self, master_name, builder_name, build_number, step_name,
reported_time):
"""Adds a build step in which the flake is found."""
for s in self.build_steps:
if s.master_name == master_name and s.builder_name == builder_name:
# For the same builder/tester, only analyze the earliest build.
# TODO: re-evaluate cases that flakes might be re-introduced in between.
if s.build_number <= build_number:
return False
s.build_number = build_number
s.reported_time = reported_time
return True
self.build_steps.append(
BuildStep.Create(master_name, builder_name, build_number, step_name,
reported_time))
return True
def CopyFrom(self, other):
"""Copies all states from the given request."""
assert isinstance(other, FlakeAnalysisRequest)
self.is_step = other.is_step
self.bug_id = other.bug_id
self.user_emails = other.user_emails
self.build_steps = other.build_steps
self.analyses = other.analyses
@property
def on_cq(self):
"""Returns True if the flake is on Commit Queue."""
return any(step.on_cq for step in self.build_steps)
def _GetNormalizedConfigurationNames(self, master_name, builder_name):
for build_step in self.build_steps:
if ((build_step.master_name == master_name and
build_step.builder_name == builder_name) or
(build_step.wf_master_name == master_name and
build_step.wf_builder_name == builder_name)):
return build_step.wf_master_name, build_step.wf_builder_name
return None, None
def FindMatchingAnalysisForConfiguration(self, master_name, builder_name):
# Returns the analysis that corresponds to the requested master and builder.
normalized_master_name, normalized_builder_name = (
self._GetNormalizedConfigurationNames(master_name, builder_name))
if not normalized_master_name or not normalized_builder_name:
return None
for analysis_key in self.analyses:
analysis_master_name, analysis_builder_name = (
MasterFlakeAnalysis.GetBuildConfigurationFromKey(analysis_key))
if (analysis_master_name == normalized_master_name and
analysis_builder_name == normalized_builder_name):
return analysis_key.get()
return None
def Update(self, **kwargs):
"""Updates fields according to what's specified in kwargs.
Fields specified in kwargs will be updated accordingly, while those not
present in kwargs will be untouched.
Args:
**kwargs (dict): The keys in kwargs should match the name of the field you
want to update, and the values should be the value you update it to.
"""
# TODO(crbug.com/772156): Refactor this into a base model.
any_changes = False
for arg, value in kwargs.iteritems():
current_value = getattr(self, arg, None)
if current_value != value:
setattr(self, arg, value)
any_changes = True
if any_changes:
self.put()