go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/bisection/compilefailureanalysis/cancelanalysis/cancel_analysis.go (about)

     1  // Copyright 2022 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package cancelanalysis handles cancelation of existing analyses.
    16  package cancelanalysis
    17  
    18  import (
    19  	"context"
    20  
    21  	"google.golang.org/protobuf/proto"
    22  
    23  	bbpb "go.chromium.org/luci/buildbucket/proto"
    24  	"go.chromium.org/luci/common/clock"
    25  	"go.chromium.org/luci/common/errors"
    26  	"go.chromium.org/luci/common/logging"
    27  	"go.chromium.org/luci/common/retry/transient"
    28  	"go.chromium.org/luci/gae/service/datastore"
    29  	"go.chromium.org/luci/server/tq"
    30  
    31  	"go.chromium.org/luci/bisection/compilefailureanalysis/statusupdater"
    32  	"go.chromium.org/luci/bisection/internal/buildbucket"
    33  	"go.chromium.org/luci/bisection/model"
    34  	pb "go.chromium.org/luci/bisection/proto/v1"
    35  	tpb "go.chromium.org/luci/bisection/task/proto"
    36  	"go.chromium.org/luci/bisection/util/datastoreutil"
    37  	"go.chromium.org/luci/bisection/util/loggingutil"
    38  )
    39  
    40  const (
    41  	taskClass = "cancel-analysis"
    42  	queue     = "cancel-analysis"
    43  )
    44  
    45  // RegisterTaskClass registers the task class for tq dispatcher.
    46  func RegisterTaskClass() {
    47  	tq.RegisterTaskClass(tq.TaskClass{
    48  		ID:        taskClass,
    49  		Prototype: (*tpb.CancelAnalysisTask)(nil),
    50  		Queue:     queue,
    51  		Kind:      tq.NonTransactional,
    52  		Handler: func(c context.Context, payload proto.Message) error {
    53  			task := payload.(*tpb.CancelAnalysisTask)
    54  			logging.Infof(c, "Process CancelAnalysisTask with id = %d", task.GetAnalysisId())
    55  			err := CancelAnalysis(c, task.GetAnalysisId())
    56  			if err != nil {
    57  				err := errors.Annotate(err, "cancelAnalysis id=%d", task.GetAnalysisId()).Err()
    58  				logging.Errorf(c, err.Error())
    59  				// If the error is transient, return err to retry
    60  				if transient.Tag.In(err) {
    61  					return err
    62  				}
    63  				return nil
    64  			}
    65  			return nil
    66  		},
    67  	})
    68  }
    69  
    70  // CancelAnalysis cancels all pending and running reruns for an analysis.
    71  func CancelAnalysis(c context.Context, analysisID int64) error {
    72  	c, err := loggingutil.UpdateLoggingWithAnalysisID(c, analysisID)
    73  	if err != nil {
    74  		// not critical, just log
    75  		err := errors.Annotate(err, "failed UpdateLoggingWithAnalysisID %d", analysisID)
    76  		logging.Errorf(c, "%v", err)
    77  	}
    78  	logging.Infof(c, "Cancel analysis %d", analysisID)
    79  
    80  	cfa, err := datastoreutil.GetCompileFailureAnalysis(c, analysisID)
    81  	if err != nil {
    82  		return errors.Annotate(err, "couldn't get analysis %d", analysisID).Err()
    83  	}
    84  	reruns, err := datastoreutil.GetRerunsForAnalysis(c, cfa)
    85  	if err != nil {
    86  		return errors.Annotate(err, "couldn't get reruns for analysis %d", analysisID).Err()
    87  	}
    88  
    89  	var errs []error
    90  	for _, rerun := range reruns {
    91  		if rerun.Status == pb.RerunStatus_RERUN_STATUS_IN_PROGRESS {
    92  			bbid := rerun.RerunBuild.IntID()
    93  			_, err := buildbucket.CancelBuild(c, bbid, "analysis was canceled")
    94  			if err != nil {
    95  				errs = append(errs, errors.Annotate(err, "couldn't cancel build %d", bbid).Err())
    96  			} else {
    97  				err = updateCancelStatusForRerun(c, rerun)
    98  				if err != nil {
    99  					errs = append(errs, errors.Annotate(err, "couldn't update rerun status %d", rerun.RerunBuild.IntID()).Err())
   100  				}
   101  			}
   102  		}
   103  	}
   104  
   105  	if len(errs) > 0 {
   106  		return errors.NewMultiError(errs...)
   107  	}
   108  
   109  	// Update status of analysis and nthsection analysis
   110  	newStatus := cfa.Status
   111  	// Only updates status if is was running
   112  	if cfa.Status == pb.AnalysisStatus_RUNNING {
   113  		newStatus = pb.AnalysisStatus_NOTFOUND
   114  	}
   115  	err = statusupdater.UpdateStatus(c, cfa, newStatus, pb.AnalysisRunStatus_CANCELED)
   116  
   117  	if err != nil {
   118  		return errors.Annotate(err, "couldn't update status for analysis %d", cfa.Id).Err()
   119  	}
   120  
   121  	// Update status of nthsection analysis
   122  	nsa, err := datastoreutil.GetNthSectionAnalysis(c, cfa)
   123  	if err != nil {
   124  		return err
   125  	}
   126  	if nsa == nil {
   127  		return nil
   128  	}
   129  	newStatus = nsa.Status
   130  	if nsa.Status == pb.AnalysisStatus_RUNNING {
   131  		newStatus = pb.AnalysisStatus_NOTFOUND
   132  	}
   133  
   134  	err = statusupdater.UpdateNthSectionStatus(c, nsa, newStatus, pb.AnalysisRunStatus_CANCELED)
   135  	if err != nil {
   136  		return errors.Annotate(err, "couldn't update status for nthsection for analysis %d", cfa.Id).Err()
   137  	}
   138  
   139  	return nil
   140  }
   141  
   142  func updateCancelStatusForRerun(c context.Context, rerun *model.SingleRerun) error {
   143  	return datastore.RunInTransaction(c, func(c context.Context) error {
   144  		// Update rerun
   145  		err := datastore.Get(c, rerun)
   146  		if err != nil {
   147  			return err
   148  		}
   149  		rerun.EndTime = clock.Now(c)
   150  		rerun.Status = pb.RerunStatus_RERUN_STATUS_CANCELED
   151  
   152  		err = datastore.Put(c, rerun)
   153  		if err != nil {
   154  			return err
   155  		}
   156  
   157  		// Update rerun build model
   158  		rerunBuild := &model.CompileRerunBuild{
   159  			Id: rerun.RerunBuild.IntID(),
   160  		}
   161  		err = datastore.Get(c, rerunBuild)
   162  		if err != nil {
   163  			return err
   164  		}
   165  		rerunBuild.EndTime = clock.Now(c)
   166  		rerunBuild.Status = bbpb.Status_CANCELED
   167  		err = datastore.Put(c, rerunBuild)
   168  		if err != nil {
   169  			return err
   170  		}
   171  
   172  		// Also if the rerun is for culprit verification, set the status of the suspect
   173  		if rerun.Suspect != nil {
   174  			suspect := &model.Suspect{
   175  				Id:             rerun.Suspect.IntID(),
   176  				ParentAnalysis: rerun.Suspect.Parent(),
   177  			}
   178  			err = datastore.Get(c, suspect)
   179  			if err != nil {
   180  				return errors.Annotate(err, "couldn't get suspect for rerun").Err()
   181  			}
   182  
   183  			suspect.VerificationStatus = model.SuspectVerificationStatus_Canceled
   184  			err = datastore.Put(c, suspect)
   185  
   186  			if err != nil {
   187  				return errors.Annotate(err, "couldn't update suspect status").Err()
   188  			}
   189  		}
   190  		return nil
   191  	}, nil)
   192  }