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