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)
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': '' })
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