Beispiel #1
0
  def testGetEntityFromUrlsafeKey(self):
    self.assertEqual(None, entity_util.GetEntityFromUrlsafeKey(None))
    self.assertEqual(None, entity_util.GetEntityFromUrlsafeKey('notvalid'))

    model = TestModel(name='name')
    model.put()
    k = model.key.urlsafe()
    self.assertEqual(model, entity_util.GetEntityFromUrlsafeKey(k))
Beispiel #2
0
def _CanCommitRevert(parameters, analysis_id, codereview_info):
    """Checks if an auto-created revert of a culprit can be committed.

  This function will call several different functions to check the culprit
  and/or revert from many different aspects and make the final decision.

  The criteria included so far are:
   + Revert is created by Findit;
   + Can the revert be committed in current analysis;
   + Was the change committed within time;
   + Was the change to be reverted authored by an auto-roller;
   + Are there other changes by the culprit's author depending on the culprit.
  """
    if not parameters.revert_status == constants.CREATED_BY_FINDIT:
        return False

    culprit = entity_util.GetEntityFromUrlsafeKey(parameters.cl_key)
    assert culprit

    action_settings = waterfall_config.GetActionSettings()
    culprit_commit_limit_hours = action_settings.get(
        'culprit_commit_limit_hours',
        constants.DEFAULT_CULPRIT_COMMIT_LIMIT_HOURS)

    return (_CanCommitRevertInAnalysis(parameters.cl_key, analysis_id)
            and git.ChangeCommittedWithinTime(culprit.revision,
                                              hours=culprit_commit_limit_hours)
            and not git.IsAuthoredByNoAutoRevertAccount(culprit.revision)
            and not gerrit.ExistCQedDependingChanges(codereview_info))
Beispiel #3
0
def SendMessageToIRC(parameters):
    """Sends a message to irc if Findit auto-reverts a culprit."""
    revert_status = parameters.revert_status
    commit_status = parameters.commit_status
    sent = False
    should_send = False

    if revert_status == constants.CREATED_BY_FINDIT:
        should_send = True
        culprit = entity_util.GetEntityFromUrlsafeKey(parameters.cl_key)

        if not culprit:
            logging.error('Failed to send notification to irc about culprit:'
                          ' entity not found in datastore.')
        else:
            repo_name = culprit.repo_name
            revision = culprit.revision
            revert_cl_url = culprit.revert_cl_url
            if not revert_cl_url:
                logging.error(
                    'Failed to send notification to irc about culprit %s, %s:'
                    ' revert CL url not found.' % (repo_name, revision))
            else:
                sent = irc.SendMessageToIrc(revert_cl_url,
                                            culprit.commit_position, revision,
                                            culprit.key.urlsafe(),
                                            commit_status)

    MonitoringCulpritNotification(parameters.failure_type, 'irc', sent,
                                  should_send)
    return sent
Beispiel #4
0
def SendNotificationForCulprit(parameters):
    culprit = entity_util.GetEntityFromUrlsafeKey(parameters.cl_key)
    assert culprit

    revision = culprit.revision
    repo_name = culprit.repo_name

    force_notify = parameters.force_notify
    revert_status = parameters.revert_status
    sent = False
    should_send = False

    action_settings = waterfall_config.GetActionSettings()
    # Set some impossible default values to prevent notification by default.
    build_num_threshold = action_settings.get(
        'cr_notification_build_threshold', 100000)
    if _ShouldSendNotification(repo_name, revision, force_notify,
                               revert_status, build_num_threshold):
        should_send = True
        codereview_info = GetCodeReviewDataForACulprit(parameters.cl_key)
        sent = gerrit.SendNotificationForCulprit(parameters, codereview_info)

    MonitoringCulpritNotification(parameters.failure_type, 'culprit', sent,
                                  should_send)
    return sent
Beispiel #5
0
def _UpdateCulprit(culprit_urlsafe_key,
                   revert_status=None,
                   revert_cl=None,
                   skip_revert_reason=None,
                   revert_submission_status=None):
    """Updates culprit entity."""
    culprit = entity_util.GetEntityFromUrlsafeKey(culprit_urlsafe_key)
    assert culprit
    culprit.should_be_reverted = True

    culprit.revert_status = revert_status or culprit.revert_status
    culprit.revert_cl = revert_cl or culprit.revert_cl
    culprit.skip_revert_reason = skip_revert_reason or culprit.skip_revert_reason
    culprit.revert_submission_status = (revert_submission_status
                                        or culprit.revert_submission_status)

    if culprit.revert_status != analysis_status.RUNNING:  # pragma: no branch
        # Only stores revert_pipeline_id when the revert is ongoing.
        culprit.revert_pipeline_id = None

    if revert_cl:
        culprit.cr_notification_status = analysis_status.COMPLETED
        culprit.revert_created_time = time_util.GetUTCNow()
        culprit.cr_notification_time = time_util.GetUTCNow()

    if (culprit.revert_submission_status !=
            analysis_status.RUNNING):  # pragma: no branch
        culprit.submit_revert_pipeline_id = None

    if culprit.revert_submission_status == analysis_status.COMPLETED:
        culprit.revert_committed_time = time_util.GetUTCNow()

    culprit.put()
    def RunImpl(self, parameters):
        analysis_urlsafe_key = parameters.analysis_urlsafe_key
        analysis = entity_util.GetEntityFromUrlsafeKey(analysis_urlsafe_key)
        assert analysis, 'Cannot retrieve analysis entry from datastore {}'.format(
            analysis_urlsafe_key)

        # Determine if flakiness is still persistent.
        still_flaky = flake_analysis_util.FlakyAtMostRecentlyAnalyzedCommit(
            analysis)

        # TODO(crbug.com/905754): Call auto actions as an async taskqueue task.
        flake_analysis_actions.OnCulpritIdentified(analysis_urlsafe_key)

        if not still_flaky:  # pragma: no cover.
            analysis.LogInfo(
                'No further actions taken due to latest commit being stable.')
            return

        # Data needed for reverts.
        build_key = BaseBuildModel.CreateBuildKey(
            analysis.original_master_name, analysis.original_builder_name,
            analysis.original_build_number)
        with pipeline.InOrder():
            # Revert culprit if applicable.
            yield CreateAndSubmitRevertPipeline(
                self.CreateInputObjectInstance(
                    CreateAndSubmitRevertInput,
                    analysis_urlsafe_key=analysis.key.urlsafe(),
                    build_key=build_key))

            # Update culprit code review.
            yield NotifyCulpritPipeline(
                self.CreateInputObjectInstance(
                    NotifyCulpritInput,
                    analysis_urlsafe_key=analysis_urlsafe_key))
Beispiel #7
0
def CommitRevert(parameters, codereview_info):
  """Commits a revert.

  Args:
    parameters(SubmitRevertCLParameters): parameters needed to commit a revert.
    codereview_info(dict_: Code review information for the change that will be
      reverted.
  """
  # Note that we don't know which was the final action taken by the pipeline
  # before this point. That is why this is where we increment the appropriate
  # metrics.
  culprit = entity_util.GetEntityFromUrlsafeKey(parameters.cl_key)
  assert culprit

  revision = culprit.revision

  codereview = _GetCodeReview(codereview_info)
  if not codereview:
    logging.error('Failed to get codereview object for change %s/%s.',
                  culprit.repo_name, revision)
    return services_constants.ERROR

  if not codereview_util.IsCodeReviewGerrit(
      codereview_info['review_server_host']):
    return services_constants.SKIPPED

  revert_change_id = codereview.GetChangeIdFromReviewUrl(culprit.revert_cl_url)

  committed = codereview.SubmitRevert(revert_change_id)

  if committed:
    _AddReviewers(revision, culprit, codereview, revert_change_id, True)
  else:
    _AddReviewers(revision, culprit, codereview, revert_change_id, False)
  return services_constants.COMMITTED if committed else services_constants.ERROR
Beispiel #8
0
def _GetAutoAssignOwner(analysis):
    """Determines the best owner for the culprit of an analysis.

    Rules for determining an owner:
    1. None if no culprit.
    2. Return the culprit CL author if @chromium.org or @google.com
    3. TODO(crbug.com913032): Fallback to the reviewer(s) and check for
       @chromium.org or @google.com.

  Args:
    analysis (MasterFlakeAnalysis): The analysis for whose results are to be
      used to update the bug with.

  Returns:
    owner (str): The best-guess owner or None if not determined.
  """
    if not analysis.culprit_urlsafe_key:
        # No culprit, so no owner.
        return None

    culprit = entity_util.GetEntityFromUrlsafeKey(analysis.culprit_urlsafe_key)
    assert culprit, (
        'Culprit missing unexpectedly when trying to get owner for bug!')

    author = git.GetAuthor(culprit.revision)

    if not author:
        return None

    email = author.email
    if email.endswith('@chromium.org') or email.endswith('@google.com'):
        return email

    return None
Beispiel #9
0
  def HandleGet(self):
    key = self.request.get('key')
    if not key:
      return self.CreateError(
          'Key is required to identify a flaky test.', return_code=404)

    flake = entity_util.GetEntityFromUrlsafeKey(key)
    if not flake:
      return self.CreateError(
          'Didn\'t find Flake for key %s.' % key, return_code=404)

    show_all_occurrences = self.request.get('show_all_occurrences')
    flake_dict = flake_detection_utils.GetFlakeInformation(
        flake,
        max_occurrence_count=(_DEFAULT_OCCURRENCE_COUNT
                              if not show_all_occurrences else None))

    data = {
        'flake_json':
            flake_dict,
        'key':
            key,
        'show_all_occurrences':
            show_all_occurrences,
        'weights': [[
            FLAKE_TYPE_DESCRIPTIONS[flake_type], FLAKE_TYPE_WEIGHT[flake_type]
        ] for flake_type in sorted(FLAKE_TYPE_DESCRIPTIONS)]
    }
    return {'template': 'flake/detection/show_flake.html', 'data': data}
Beispiel #10
0
def _CanCommitRevertInAnalysis(cl_key, analysis_id):
    """Checks if an auto-created revert of a culprit can be committed in the
    analysis.

  The revert of the culprit can be committed in the analysis if all below are
  True:
    0. Findit reverts the culprit;
    1. There is a revert for the culprit;
    2. The revert has completed;
    3. The revert should be auto commited;
    4. No other analysis is committing the revert;
    5. No other analysis is supposed to handle the auto commit.
  """

    culprit = entity_util.GetEntityFromUrlsafeKey(cl_key)
    assert culprit

    if (not culprit.revert_cl
            or culprit.revert_submission_status == analysis_status.COMPLETED
            or culprit.revert_status != analysis_status.COMPLETED
            or culprit.revert_submission_status == analysis_status.SKIPPED
            or (culprit.revert_submission_status == analysis_status.RUNNING
                and culprit.submit_revert_pipeline_id
                and culprit.submit_revert_pipeline_id != analysis_id)):
        return False

    # Update culprit to ensure only current analysis can commit the revert.
    culprit.revert_submission_status = analysis_status.RUNNING
    culprit.submit_revert_pipeline_id = analysis_id
    culprit.put()
    return True
Beispiel #11
0
def _CanCreateRevertForCulprit(parameters, analysis_id):
    """Checks if a culprit can be reverted by a specific analysis.

  The culprit can be reverted by the analysis if:
    No revert of this culprit is complete or skipped, and
    no other pipeline is doing the revert on the same culprit.
  """
    culprit = entity_util.GetEntityFromUrlsafeKey(parameters.cl_key)
    assert culprit

    if ((culprit.revert_cl
         and culprit.revert_status == analysis_status.COMPLETED)
            or culprit.revert_status == analysis_status.SKIPPED
            or (culprit.revert_status == analysis_status.RUNNING
                and culprit.revert_pipeline_id
                and culprit.revert_pipeline_id != analysis_id)):
        # Revert of the culprit has been created or is being created by another
        # analysis.
        return False

    # Update culprit to ensure only current analysis can revert the culprit.
    culprit.revert_status = analysis_status.RUNNING
    culprit.revert_pipeline_id = analysis_id
    culprit.put()
    return True
Beispiel #12
0
  def OnAbort(self, pipeline_input):
    culprit = entity_util.GetEntityFromUrlsafeKey(pipeline_input.cl_key)
    assert culprit

    if culprit.revert_pipeline_id == self.pipeline_id:
      if culprit.revert_status and culprit.revert_status != status.COMPLETED:
        culprit.revert_status = status.ERROR
      culprit.revert_pipeline_id = None
      culprit.put()
Beispiel #13
0
def GetCodeReviewDataForACulprit(culprit_urlsafe_key):
    """Gets code review related data and object of a culprit.

  Returns:
    (dict, CodeReview): code review related data and object.
  """
    culprit = entity_util.GetEntityFromUrlsafeKey(culprit_urlsafe_key)
    assert culprit, 'Could\'t get culprit object for key %s' % culprit_urlsafe_key
    revision = culprit.revision
    return git.GetCodeReviewInfoForACommit(revision)
def OnCulpritIdentified(analysis_urlsafe_key):
    """All operations to perform when a culprit is identified.

    Should only be called for results with high confidence.

  Args:
    analysis_urlafe_key (str): The urlsafe-key to the MasterFlakeAnalysis to
        update culprit information for.
    revision (str): The culprit's chromium revision.
    commit_position (int): The culprit's commit position.
    project (str): The name of the project/repo the culprit is in.
  """
    analysis = entity_util.GetEntityFromUrlsafeKey(analysis_urlsafe_key)
    assert analysis, 'Analysis missing unexpectedly!'

    if not flake_bug_util.HasSufficientConfidenceInCulprit(
            analysis, flake_bug_util.GetMinimumConfidenceToUpdateEndpoints()):
        analysis.LogInfo(
            'Skipping auto actions due to insufficient confidence {}'.format(
                analysis.confidence_in_culprit))
        return

    analysis = ndb.Key(urlsafe=analysis_urlsafe_key).get()
    assert analysis, 'Analysis {} missing unexpectedly!'.format(
        analysis_urlsafe_key)

    # TODO(crbug.com/944670): make sure every analysis links to a flake - even
    # for manually triggered ones.
    flake = analysis.flake_key.get() if analysis.flake_key else None
    flake_issue_key = flake.flake_issue_key if flake else None
    flake_issue = flake_issue_key.get() if flake_issue_key else None
    flake_culprit_key = ndb.Key(urlsafe=analysis.culprit_urlsafe_key)

    if not flake_issue:
        # Attaches culprit's flake issue to the flake.
        _AttachCulpritFlakeIssueToFlake(flake, flake_culprit_key)
    else:
        # Deduplicate bugs or split them based on culprit.
        (duplicate_flake_issue_key,
         destination_flake_issue_key) = MergeOrSplitFlakeIssueByCulprit(
             flake_issue_key, flake_culprit_key)

        if (duplicate_flake_issue_key
                and destination_flake_issue_key):  # pragma: no branch
            # A merge occurred. Update potentially impacted FlakeIssue merge
            # destination keys.
            flake_issue_util.UpdateIssueLeaves(duplicate_flake_issue_key,
                                               destination_flake_issue_key)

    # TODO(crbug.com/942224): Log bugs if no FlakeIssue and still flaky.

    # TODO(crbug.com/893787): Other auto actions based on outcome.
    UpdateMonorailBugWithCulprit(analysis_urlsafe_key)
Beispiel #15
0
def SendNotificationForCulprit(parameters, codereview_info):
    """Sends notification for a change on Gerrit.

  Args:
    parameters(SendNotificationForCulpritParameters): parameters needed to send
      notificaiton to a change.
    codereview_info(dict_: Code review information for the culprit change.
  """
    culprit = entity_util.GetEntityFromUrlsafeKey(parameters.cl_key)
    assert culprit

    culprit_link = culprit.GetCulpritLink()
    revision = culprit.revision
    repo_name = culprit.repo_name

    codereview = _GetCodeReview(codereview_info)
    commit_position = codereview_info['commit_position']
    review_change_id = codereview_info['review_change_id']

    false_positive_bug_link = CreateFinditWrongBugLink(
        FINDIT_BUILD_FAILURE_COMPONENT, culprit_link, revision)

    sent = False
    if codereview and review_change_id:
        # Occasionally, a commit was not uploaded for code-review.
        action = 'identified'
        should_email = True
        if parameters.revert_status == services_constants.CREATED_BY_SHERIFF:
            action = 'confirmed'
            should_email = False

        message = textwrap.dedent("""
    Findit (https://goo.gl/kROfz5) %s this CL at revision %s as the culprit for
    failures in the build cycles as shown on:
    %s
    If it is a false positive, please report it at %s.""") % (
            action, commit_position
            or revision, culprit_link, false_positive_bug_link)
        sent = codereview.PostMessage(review_change_id, message, should_email)
    else:
        logging.error('No code-review url for %s/%s', repo_name, revision)

    suspected_cl_util.UpdateCulpritNotificationStatus(
        culprit.key.urlsafe(), status.COMPLETED if sent else status.ERROR)
    return sent
Beispiel #16
0
  def HandlePost(self):
    report_key = self.request.get('report_key')
    path = self.request.get('path')
    revision = self.request.get('revision')

    assert report_key, 'report_key is required'
    assert path, 'path is required'
    assert revision, 'revision is required'

    report = entity_util.GetEntityFromUrlsafeKey(report_key)
    assert report, (
        'Postsubmit report does not exist for urlsafe key' % report_key)

    file_content = _GetFileContentFromGitiles(report, path, revision)
    if not file_content:
      logging.error(
          'Failed to get file from gitiles for %s@%s' % (path, revision))
      return

    gs_path = _ComposeSourceFileGsPath(report, path, revision)
    _WriteFileContentToGs(gs_path, file_content)
Beispiel #17
0
def RevertCulprit(parameters, analysis_id):
    culprit = entity_util.GetEntityFromUrlsafeKey(parameters.cl_key)
    assert culprit

    repo_name = culprit.repo_name
    revision = culprit.revision
    build_key = parameters.build_key

    if _CanCreateRevertForCulprit(parameters, analysis_id):
        codereview_info = GetCodeReviewDataForACulprit(parameters.cl_key)
        revert_status, revert_cl, skip_reason = gerrit.RevertCulprit(
            parameters.cl_key, build_key, parameters.failure_type,
            GetSampleFailedStepName(repo_name, revision, build_key),
            codereview_info)
        _UpdateCulprit(parameters.cl_key,
                       revert_status=constants.
                       AUTO_REVERT_STATUS_TO_ANALYSIS_STATUS[revert_status],
                       revert_cl=revert_cl,
                       skip_revert_reason=skip_reason)
        return revert_status
    return constants.SKIPPED
Beispiel #18
0
def RevertCulprit(urlsafe_key, build_id, build_failure_type, sample_step_name,
                  codereview_info):
    """Creates a revert of a culprit and adds reviewers.

  Args:
    urlsafe_key (str): Key to the ndb model.
    build_id (str): Id of the sample failed build.
    build_failure_type (int): Failure type: compile, test or flake.
    sample_step_name (str): Sample failed step in the failed build.
    codereview_info (dict): Code review information about the culprit.

  Returns:
    (int, string, string):
      - Status of the reverting;
      - change_id of the revert;
      - reason why revert is skipped if it is skipped.
  """

    culprit = entity_util.GetEntityFromUrlsafeKey(urlsafe_key)
    repo_name = culprit.repo_name
    revision = culprit.revision
    # 0. Gets information about this culprit.
    culprit_commit_position = codereview_info['commit_position']
    culprit_change_id = codereview_info['review_change_id']

    codereview = _GetCodeReview(codereview_info)
    print codereview

    if not codereview or not culprit_change_id:  # pragma: no cover
        logging.error('Failed to get change id for %s/%s', repo_name, revision)
        return services_constants.ERROR, None, None

    culprit_cl_info = codereview.GetClDetails(culprit_change_id)
    if not culprit_cl_info:  # pragma: no cover
        logging.error('Failed to get cl_info for %s/%s', repo_name, revision)
        return services_constants.ERROR, None, None

    # Checks if the culprit is a revert. If yes, bail out.
    if _IsCulpritARevert(culprit_cl_info):
        return (services_constants.SKIPPED, None,
                services_constants.CULPRIT_IS_A_REVERT)

    if culprit_cl_info.auto_revert_off:
        return services_constants.SKIPPED, None, services_constants.AUTO_REVERT_OFF

    # 1. Checks if a revert CL by sheriff has been created.
    reverts = culprit_cl_info.GetRevertCLsByRevision(revision)

    if reverts is None:  # pragma: no cover
        # if no reverts, reverts should be [], only when some error happens it will
        # be None.
        logging.error('Failed to find patchset_id for %s/%s' %
                      (repo_name, revision))
        return services_constants.ERROR, None, None

    findit_revert = None
    for revert in reverts:
        if _IsOwnerFindit(revert.reverting_user_email):
            findit_revert = revert
            break

    if reverts and not findit_revert:
        # Sheriff(s) created the revert CL(s).
        return (services_constants.CREATED_BY_SHERIFF, None,
                services_constants.REVERTED_BY_SHERIFF)

    revert_change_id = None
    if findit_revert:
        revert_change_id = findit_revert.reverting_cl.change_id

    # 2. Crreate revert CL.
    # TODO (chanli): Better handle cases where 2 analyses are trying to revert
    # at the same time.
    if not revert_change_id:
        revert_reason = culprit.GenerateRevertReason(build_id,
                                                     culprit_commit_position,
                                                     revision,
                                                     sample_step_name)
        if not revert_reason:  # pragma: no cover.
            logging.error('Failed to get the reason for revert culprit %s',
                          culprit.key.urlsafe() if culprit else '')
            return services_constants.ERROR, None, None
        bug_id = _GetBugIdForCulprit(culprit)
        revert_change_id = codereview.CreateRevert(
            revert_reason,
            culprit_change_id,
            culprit_cl_info.GetPatchsetIdByRevision(revision),
            bug_id=bug_id)

        if not revert_change_id:  # pragma: no cover
            return services_constants.ERROR, None, None

    # Save revert CL info and notification info to culprit.
    revert_cl = None
    if not culprit.revert_cl:
        revert_cl = RevertCL()
        revert_cl.revert_cl_url = codereview.GetCodeReviewUrl(revert_change_id)
        revert_cl.created_time = time_util.GetUTCNow()

    # 3. Add reviewers.
    # If Findit cannot commit the revert, add sheriffs as reviewers and ask them
    # to 'LGTM' and commit the revert.
    action_settings = waterfall_config.GetActionSettings()
    can_commit_revert = (action_settings.get('auto_commit_revert_compile')
                         if build_failure_type == failure_type.COMPILE else
                         action_settings.get('auto_commit_revert_test'))
    if not can_commit_revert:
        success = _AddReviewers(revision, culprit, codereview,
                                revert_change_id, False)
        if not success:  # pragma: no cover
            logging.error('Failed to add reviewers for revert of'
                          ' culprit %s/%s' % (repo_name, revision))
            return services_constants.ERROR, revert_cl, None
    return services_constants.CREATED_BY_FINDIT, revert_cl, None
def _GetFlakeAnalysesResults(bug_id):
    """Gets flake analyses results for a flaky test.

  Uses bug_id for a flake to query all analyses for this flake, then gets
  culprits if found.

  Args:
    bug_id (int): Bug id of the flake. It should be the same ID to trigger the
      flake analyses.

  Returns:
    culprits, sample_analysis (list, dict): A list of culprits information or
    a dict of information for a sample analysis if there is no culprit at all.
  """
    culprits = {}

    # TODO(crbug/894215): Query for culprits directly after we change to file
    # a bug per culprit instead of flake.
    analyses = MasterFlakeAnalysis.query(
        MasterFlakeAnalysis.bug_id == bug_id).fetch()
    if not analyses:
        return [], None

    # Only shows culprits if they have high enough confidence.
    culprit_urlsafe_keys = set([
        analysis.culprit_urlsafe_key for analysis in analyses
        if analysis.culprit_urlsafe_key and analysis.confidence_in_culprit
        and analysis.confidence_in_culprit >=
        GetMinimumConfidenceToUpdateEndpoints()
    ])

    if culprit_urlsafe_keys:
        # Found culprits.
        for key in culprit_urlsafe_keys:
            culprit = entity_util.GetEntityFromUrlsafeKey(key)
            if not culprit:
                logging.error('Failed to get FlakeCulprit entity from key %s',
                              key)
                continue
            culprits[key] = {
                'revision': culprit.revision,
                'commit_position': culprit.commit_position,
                'culprit_key': key
            }

    if culprits:
        return culprits.values(), None

    # No culprits have been found for this flake.
    # Prior to use a completed analysis as a sample; otherwise a running one;
    # otherwise a pending analysis; failed analysis will not be used.
    sample_analysis = {}
    for analysis in analyses:
        if analysis.status == analysis_status.COMPLETED:
            # A completed analysis found, returns immediately.
            return [], {
                'status':
                ('%s, no culprit found' %
                 analysis_status.STATUS_TO_DESCRIPTION[analysis.status]),
                'analysis_key':
                analysis.key.urlsafe()
            }

        if analysis.status == analysis_status.RUNNING:
            sample_analysis = {
                'status': analysis_status.RUNNING,
                'analysis_key': analysis.key.urlsafe()
            }
        elif not sample_analysis and analysis.status == analysis_status.PENDING:
            sample_analysis = {
                'status': analysis_status.PENDING,
                'analysis_key': analysis.key.urlsafe()
            }

    if sample_analysis:
        sample_analysis['status'] = analysis_status.STATUS_TO_DESCRIPTION[
            sample_analysis['status']]
    return [], sample_analysis
def UpdateMonorailBugWithCulprit(analysis_urlsafe_key):
    """Updates a bug in monorail with the culprit of a MasterFlakeAnalsyis"""
    analysis = entity_util.GetEntityFromUrlsafeKey(analysis_urlsafe_key)
    assert analysis, 'Analysis {} missing unexpectedly!'.format(
        analysis_urlsafe_key)

    if not analysis.flake_key:  # pragma: no cover.
        logging.warning(
            'Analysis %s has no flake key. Bug updates should only be '
            'routed through Flake and FlakeIssue', analysis_urlsafe_key)
        return

    flake = analysis.flake_key.get()
    assert flake, 'Analysis\' associated Flake {} missing unexpectedly!'.format(
        analysis.flake_key)

    flake_urlsafe_key = flake.key.urlsafe()
    if flake.archived:
        logging.info(
            'Flake %s has been archived when flake analysis %s completes.',
            flake_urlsafe_key, analysis_urlsafe_key)
        return

    if not flake.flake_issue_key:  # pragma: no cover.
        logging.warning(
            'Flake %s has no flake_issue_key. Bug updates should only'
            ' be routed through Flake and FlakeIssue', flake_urlsafe_key)
        return

    flake_issue = flake.flake_issue_key.get()
    assert flake_issue, 'Flake issue {} missing unexpectedly!'.format(
        flake.flake_issue_key)

    # Only comment on the latest flake issue.
    flake_issue_to_update = flake_issue.GetMostUpdatedIssue()
    issue_link = FlakeIssue.GetLinkForIssue(
        flake_issue_to_update.monorail_project, flake_issue_to_update.issue_id)

    # Don't comment if the issue is closed.
    latest_merged_monorail_issue = monorail_util.GetMonorailIssueForIssueId(
        flake_issue_to_update.issue_id)
    if not latest_merged_monorail_issue or not latest_merged_monorail_issue.open:
        logging.info(
            'Skipping updating issue %s which is not accessible or closed',
            issue_link)
        return

    # Don't comment if there are existing updates by Findit to prevent spamming.
    if flake_issue_to_update.last_updated_time_with_analysis_results:
        logging.info(
            'Skipping updating issue %s as it has already been updated',
            issue_link)
        return

    # Don't comment if Findit has filled the daily quota of monorail updates.
    if flake_issue_util.GetRemainingPostAnalysisDailyBugUpdatesCount() <= 0:
        logging.info(
            'Skipping updating issue %s due to maximum daily bug limit being '
            'reached', issue_link)
        return

    # Comment with link to FlakeCulprit.
    monorail_util.UpdateIssueWithIssueGenerator(
        flake_issue_to_update.issue_id,
        issue_generator.FlakeAnalysisIssueGenerator(analysis))
    flake_issue_to_update.last_updated_time_with_analysis_results = (
        time_util.GetUTCNow())
    flake_issue_to_update.last_updated_time_in_monorail = time_util.GetUTCNow()
    flake_issue_to_update.put()

    monitoring.flake_analyses.increment({
        'result': 'culprit-identified',
        'action_taken': 'bug-updated',
        'reason': ''
    })