def SyncOpenFlakeIssuesWithMonorail():
    """Updates open FlakeIssues to reflect the latest state in Monorail."""
    flake_issues_needing_updating = _GetFlakeIssuesNeedingUpdating()

    for flake_issue in flake_issues_needing_updating:
        issue_id = flake_issue.issue_id
        monorail_project = flake_issue.monorail_project

        # TODO(crbug.com/914160): Monorail has a maximum of 300 requests per minute
        # within any 5 minute window. Should the limit be exceeded, requests will
        # result in 4xx errors and exponential backoff should be used.
        monorail_issue = monorail_util.GetMonorailIssueForIssueId(
            issue_id, monorail_project)
        if (not monorail_issue or monorail_issue.id is None
                or int(monorail_issue.id) != issue_id):  # pragma: no cover
            # No cover due to being unexpected, but log a warning regardless and skip.
            link = FlakeIssue.GetLinkForIssue(monorail_project, issue_id)
            logging.warning('Failed to get issue %s', link)
            continue

        _UpdateFlakeIssueWithMonorailIssue(flake_issue, monorail_issue)

        if monorail_issue.status in issue_constants.CLOSED_STATUSES_NO_DUPLICATE:
            # Issue is closed, detaches it from flakes.
            _ArchiveFlakesForClosedIssue(flake_issue)
Example #2
0
 def testGetMonorailIssueForIssueId(self, mocked_issue_tracker_api):
     issue = Issue({
         'status': 'Available',
         'summary': 'summary',
         'description': 'description',
         'projectId': 'chromium',
         'labels': [],
         'fieldValues': [],
         'state': 'open',
         'id': '12345'
     })
     mocked_issue_tracker_api.return_value.getIssue.return_value = issue
     self.assertEqual(
         issue, monorail_util.GetMonorailIssueForIssueId(12345, 'chromium'))
def _GetOrCreateFlakeIssue(issue_id, monorail_project):
    """Gets or creates a FlakeIssue entity for the monorail issue.

  Args:
    issue_id (int): Id of the issue.
    monorail_project (str): Monorail project of the issue.
  Returns:
    (FlakeIssue): a FlakeIssue entity of the issue.
  """
    flake_issue = FlakeIssue.Get(monorail_project, issue_id)
    if flake_issue:
        return flake_issue

    flake_issue = FlakeIssue.Create(monorail_project, issue_id)
    flake_issue.put()
    monorail_issue = monorail_util.GetMonorailIssueForIssueId(
        issue_id, monorail_project)

    _UpdateFlakeIssueWithMonorailIssue(flake_issue, monorail_issue)
    return flake_issue
def _MergeFlakeIssuesAndUpdateMonorail(culprit, culprit_flake_issue,
                                       flake_issue):
    """Merges flake issues and updates the corresponding issue ids on Monorail.

    This function should never be used outside the context of a cross-group
    transaction between flake issues and flake culprits.

  Args:
    culprit (FlakeCulprit): The culprit whose commit position will be used for
      generating the update comment in Monorail.
    culprit_flake_issue (FlakeIssue): The flake issue to be merged or merged
      into already associated with culprit.
    flake_issue (FlakeIssue): The flake issue to be merged or merged into
      not yet associated with a culprit.

  Returns:
    (flake_issue_key, flake_issue_key): The key of the flake issue that was
      merged and the key of the flake issue it was merged into.
  """
    assert ndb.in_transaction(), (
        '_MergeFlakeIssues should only be called from within a transaction')
    culprit_monorail_issue = monorail_util.GetMonorailIssueForIssueId(
        culprit_flake_issue.issue_id)
    flake_monorail_issue = monorail_util.GetMonorailIssueForIssueId(
        flake_issue.issue_id)
    if not culprit_monorail_issue or not flake_monorail_issue:
        logging.info(
            'Not merge flake issues due to no sufficient info on %d or %d',
            culprit_flake_issue.issue_id, flake_issue.issue_id)
        return None, None

    if (monorail_util.WasCreatedByFindit(culprit_monorail_issue)
            and not monorail_util.WasCreatedByFindit(flake_monorail_issue)):
        # If the flake's Monorail bug was created by a human while the bug already
        # associated with the culprit is that of Findit, merge into the human-
        # created bug.
        duplicate_monorail_issue = culprit_monorail_issue
        destination_monorail_issue = flake_monorail_issue
        duplicate_flake_issue = culprit_flake_issue
        destination_flake_issue = flake_issue
    else:
        # In all other cases (both created by humans or both created by Findit,
        # etc.), merge into the culprit's bug (first-come-first-serve).
        duplicate_monorail_issue = flake_monorail_issue
        destination_monorail_issue = culprit_monorail_issue
        duplicate_flake_issue = flake_issue
        destination_flake_issue = culprit_flake_issue

    assert duplicate_flake_issue.key != destination_flake_issue.key, (
        'Merging FlakeIssue into itself! {}'.format(duplicate_flake_issue.key))

    # Include a comment for why the merge is taking place.
    comment = issue_generator.GenerateDuplicateComment(culprit.commit_position)

    # Merge in Monorail.
    try:
        monorail_util.MergeDuplicateIssues(duplicate_monorail_issue,
                                           destination_monorail_issue, comment)
    except HttpError as error:  # pragma: no cover. This is unexpected.
        # Raise an exception to abort any merging of data on Findit side, as this
        # can lead to some inconsistent states between FlakeIssue and Monorail.
        logging.error('Could not merge %s into %s Monorail',
                      duplicate_monorail_issue.id,
                      destination_monorail_issue.id)
        raise error

    # Update the merged flake issue to point to the culprit if not already.
    if (destination_flake_issue.flake_culprit_key !=
            culprit.key):  # pragma: no branch
        destination_flake_issue.flake_culprit_key = culprit.key
        destination_flake_issue.put()

    # Update the duplicate's merge_destination_key to the merged flake issue.
    duplicate_flake_issue.merge_destination_key = destination_flake_issue.key
    duplicate_flake_issue.put()

    return duplicate_flake_issue.key, destination_flake_issue.key
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': ''
    })
Example #6
0
 def testGetMonorailIssueForIssueIdHttpError(self,
                                             mocked_issue_tracker_api):
     mocked_issue_tracker_api.return_value.getIssue.side_effect = HttpError(
         mock.Mock(), 'error')
     self.assertIsNone(
         monorail_util.GetMonorailIssueForIssueId(12345, 'chromium'))
def _MergeFlakeIssuesAndUpdateMonorail(culprit, culprit_flake_issue,
                                       flake_issue):
    """Merges flake issues and updates the corresponding issue ids on Monorail.

    This function should never be used outside the context of a cross-group
    transaction between flake issues and flake culprits.

  Args:
    culprit (FlakeCulprit): The culprit whose commit position will be used for
      generating the update comment in Monorail.
    culprit_flake_issue (FlakeIssue): The flake issue to be merged or merged
      into already associated with culprit.
    flake_issue (FlakeIssue): The flake issue to be merged or merged into
      not yet associated with a culprit.

  Returns:
    (flake_issue_key, flake_issue_key): The key of the flake issue that was
      merged and the key of the flake issue it was merged into.
  """
    assert ndb.in_transaction(), (
        '_MergeFlakeIssues should only be called from within a transaction')
    assert flake_issue.key != culprit_flake_issue.key, (
        'Merging FlakeIssue into itself! {}'.format(flake_issue.key))
    culprit_monorail_issue = monorail_util.GetMonorailIssueForIssueId(
        culprit_flake_issue.issue_id)
    flake_monorail_issue = monorail_util.GetMonorailIssueForIssueId(
        flake_issue.issue_id)
    if not culprit_monorail_issue or not flake_monorail_issue:
        logging.info(
            'Not merge flake issues due to no sufficient info on %d or %d',
            culprit_flake_issue.issue_id, flake_issue.issue_id)
        return None, None

    # Include a comment for why the merge is taking place.
    comment = issue_generator.GenerateDuplicateComment(culprit)

    # Always uses the culprit's bug as the merge destination. Because
    # 1. A comment can easily be added to the merged bug explaining the
    #   reason when merge it, but cannot do it for the merge destination.
    # 2. The culprit bug already has comments about the culprit and has also
    #   been assigned to the culprit owner, while nowhere in the flake bug has
    #   mentioned the culprit.
    try:
        monorail_util.MergeDuplicateIssues(flake_monorail_issue,
                                           culprit_monorail_issue, comment)
    except HttpError as error:  # pragma: no cover. This is unexpected.
        # Raise an exception to abort any merging of data on Findit side, as this
        # can lead to some inconsistent states between FlakeIssue and Monorail.
        logging.error('Could not merge %s into %s Monorail',
                      flake_monorail_issue.id, culprit_monorail_issue.id)
        raise error

    # Update the merged flake issue to point to the culprit if not already.
    if (culprit_flake_issue.flake_culprit_key !=
            culprit.key):  # pragma: no branch
        culprit_flake_issue.flake_culprit_key = culprit.key
        culprit_flake_issue.put()

    # Update the duplicate's merge_destination_key to the merged flake issue.
    flake_issue.merge_destination_key = culprit_flake_issue.key
    flake_issue.put()

    return flake_issue.key, culprit_flake_issue.key