go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/internal/bugs/cron/handler.go (about) 1 // Copyright 2023 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 cron defines the update-analysis-and-bugs cron job handler. 16 // The cron job exists to periodically update cluster analysis 17 // and to update rules and bugs in response to this analysis. 18 package cron 19 20 import ( 21 "context" 22 "time" 23 24 "google.golang.org/protobuf/types/known/timestamppb" 25 26 "go.chromium.org/luci/common/clock" 27 "go.chromium.org/luci/common/errors" 28 29 "go.chromium.org/luci/analysis/internal/analysis" 30 "go.chromium.org/luci/analysis/internal/config" 31 "go.chromium.org/luci/analysis/internal/services/bugupdater" 32 "go.chromium.org/luci/analysis/internal/tasks/taskspb" 33 ) 34 35 // NewHandler initialises a new Handler instance. 36 func NewHandler(cloudProject string, prod bool) *Handler { 37 return &Handler{cloudProject: cloudProject, prod: prod} 38 } 39 40 // Handler handles the update-analysis-and-bugs cron job. 41 type Handler struct { 42 cloudProject string 43 // prod is set when running in production (not a dev workstation). 44 prod bool 45 } 46 47 // CronHandler handles the update-analysis-and-bugs cron job. 48 func (h *Handler) CronHandler(ctx context.Context) error { 49 cfg, err := config.Get(ctx) 50 if err != nil { 51 return errors.Annotate(err, "get config").Err() 52 } 53 simulate := !h.prod 54 err = updateAnalysisAndBugs(ctx, h.cloudProject, simulate, cfg.BugUpdatesEnabled) 55 if err != nil { 56 return errors.Annotate(err, "update bugs").Err() 57 } 58 return nil 59 } 60 61 // updateAnalysisAndBugs updates BigQuery analysis, and then updates bugs 62 // to reflect this analysis. 63 // Simulate, if true, avoids any changes being applied to bugs in the 64 // issue tracker(s) themselves and logs the changes which would be made 65 // instead. This must be set when running on developer computers as 66 // LUCI Analysis-initiated monorail changes will appear on monorail 67 // as the developer themselves rather than the LUCI Analysis service. 68 // This leads to bugs errounously being detected as having manual priority 69 // changes. 70 func updateAnalysisAndBugs(ctx context.Context, gcpProject string, simulate, bugUpdatesEnabled bool) (retErr error) { 71 runMinute := clock.Now(ctx).Truncate(time.Minute) 72 73 projectCfg, err := config.Projects(ctx) 74 if err != nil { 75 return err 76 } 77 78 analysisClient, err := analysis.NewClient(ctx, gcpProject) 79 if err != nil { 80 return err 81 } 82 defer func() { 83 if err := analysisClient.Close(); err != nil && retErr == nil { 84 retErr = errors.Annotate(err, "closing analysis client").Err() 85 } 86 }() 87 88 if err := analysisClient.RebuildAnalysis(ctx); err != nil { 89 return errors.Annotate(err, "update cluster summary analysis").Err() 90 } 91 92 if bugUpdatesEnabled { 93 var errs []error 94 for _, project := range projectCfg.Keys() { 95 task := &taskspb.UpdateBugs{ 96 Project: project, 97 ReclusteringAttemptMinute: timestamppb.New(runMinute), 98 // This cron job runs every 15 minutes. Ensure the bug-filing task 99 // finishes by the time the next cron job runs. 100 Deadline: timestamppb.New(runMinute.Add(15 * time.Minute)), 101 } 102 if simulate { 103 // In local development only, kick off the work to update bugs 104 // inline. 105 // 106 // If you are encountering timeouts in local dev in this step, 107 // consider increasing the request timeout by passing to main.go: 108 // -default-request-timeout 15m0s 109 h := bugupdater.Handler{ 110 GCPProject: gcpProject, 111 Simulate: true, 112 } 113 if err := h.UpdateBugs(ctx, task); err != nil { 114 errs = append(errs, errors.Annotate(err, "update bugs for project %s", project).Err()) 115 } 116 } else { 117 // In production, create a task queue task to apply the 118 // bug updates. This allows us to use the full 15 minutes 119 // allotted to updating analysis + bugs intead of being 120 // limited by the 10 minute GAE request timeout. 121 if err := bugupdater.Schedule(ctx, task); err != nil { 122 errs = append(errs, errors.Annotate(err, "schedule bug update task").Err()) 123 } 124 } 125 } 126 if err := errors.Append(errs...); err != nil { 127 return err 128 } 129 } 130 // Do last, as this failing should not block bug updates. 131 return analysisClient.PurgeStaleRows(ctx) 132 }