go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/prjmanager/notify.go (about) 1 // Copyright 2020 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 prjmanager 16 17 import ( 18 "context" 19 "time" 20 21 "google.golang.org/protobuf/proto" 22 23 "go.chromium.org/luci/common/errors" 24 "go.chromium.org/luci/gae/service/datastore" 25 "go.chromium.org/luci/server/tq" 26 27 "go.chromium.org/luci/cv/internal/changelist" 28 "go.chromium.org/luci/cv/internal/common" 29 "go.chromium.org/luci/cv/internal/common/eventbox" 30 "go.chromium.org/luci/cv/internal/prjmanager/prjpb" 31 "go.chromium.org/luci/cv/internal/run" 32 ) 33 34 // EventboxRecipient returns eventbox.Recipient for a given LUCI project. 35 func EventboxRecipient(ctx context.Context, luciProject string) eventbox.Recipient { 36 return eventbox.Recipient{ 37 Key: datastore.MakeKey(ctx, ProjectKind, luciProject), 38 MonitoringString: "Project/" + luciProject, 39 } 40 } 41 42 // Notifier notifies Project Manager. 43 type Notifier struct { 44 // TasksBinding are used to register handlers of Project Manager Implementation & CL Purger to 45 // avoid circular dependency. 46 TasksBinding prjpb.TasksBinding 47 } 48 49 // NewNotifier creates a new Project Manager notifier and registers it in the 50 // provided tq.Dispatcher. 51 func NewNotifier(tqd *tq.Dispatcher) *Notifier { 52 return &Notifier{TasksBinding: prjpb.Register(tqd)} 53 } 54 55 // UpdateConfig tells Project Manager to read and update to newest ProjectConfig 56 // by fetching it from Datatstore. 57 // 58 // Results in stopping Project Manager if ProjectConfig got disabled or deleted. 59 func (n *Notifier) UpdateConfig(ctx context.Context, luciProject string) error { 60 return n.SendNow(ctx, luciProject, &prjpb.Event{ 61 Event: &prjpb.Event_NewConfig{ 62 NewConfig: &prjpb.NewConfig{}, 63 }, 64 }) 65 } 66 67 // Poke tells Project Manager to poke all downstream actors and check its own 68 // state. 69 func (n *Notifier) Poke(ctx context.Context, luciProject string) error { 70 return n.SendNow(ctx, luciProject, &prjpb.Event{ 71 Event: &prjpb.Event_Poke{ 72 Poke: &prjpb.Poke{}, 73 }, 74 }) 75 } 76 77 // NotifyCLsUpdated tells Project Manager to check latest versions of the given 78 // CLs. 79 func (n *Notifier) NotifyCLsUpdated(ctx context.Context, luciProject string, cls *changelist.CLUpdatedEvents) error { 80 return n.SendNow(ctx, luciProject, &prjpb.Event{ 81 Event: &prjpb.Event_ClsUpdated{ 82 ClsUpdated: cls, 83 }, 84 }) 85 } 86 87 // NotifyPurgeCompleted tells Project Manager that a CL purge has completed. 88 // 89 // The ultimate result of CL purge is the updated state of a CL itself, thus no 90 // information is provided here. 91 func (n *Notifier) NotifyPurgeCompleted(ctx context.Context, luciProject string, purgingCL *prjpb.PurgingCL) error { 92 return n.SendNow(ctx, luciProject, &prjpb.Event{ 93 Event: &prjpb.Event_PurgeCompleted{ 94 PurgeCompleted: &prjpb.PurgeCompleted{ 95 OperationId: purgingCL.GetOperationId(), 96 Clid: purgingCL.GetClid(), 97 }, 98 }, 99 }) 100 } 101 102 // NotifyRunCreated is sent by Project Manager to itself within a Run creation 103 // transaction. 104 // 105 // Unlike other event-sending of Notifier, this one only creates an event and 106 // doesn't create a task. This is fine because: 107 // - if Run creation transaction fails, then this event isn't actually 108 // created anyways. 109 // - if Project Manager observes the Run creation success, then it'll act as if 110 // this event was received in the upcoming state transition. Yes, it won't 111 // process this event immediately, but at this point the event is a noop, 112 // so it'll be cleared out from the eventbox upon next invocation of 113 // Project Manager. So there is no need to create a TQ task. 114 // - else, namely Run creation succeeds but Project Manager sees it as a 115 // failure OR Project Manager fails at any point before it can act on 116 // RunCreation, then the existing TQ task running Project Manager will be 117 // retried. So once again there is no need to create a TQ task. 118 func (n *Notifier) NotifyRunCreated(ctx context.Context, runID common.RunID) error { 119 return n.sendWithoutDispatch(ctx, runID.LUCIProject(), &prjpb.Event{ 120 Event: &prjpb.Event_RunCreated{ 121 RunCreated: &prjpb.RunCreated{ 122 RunId: string(runID), 123 }, 124 }, 125 }) 126 } 127 128 // NotifyRunFinished tells Project Manager that a run has finalized its state. 129 func (n *Notifier) NotifyRunFinished(ctx context.Context, runID common.RunID, status run.Status) error { 130 return n.SendNow(ctx, runID.LUCIProject(), &prjpb.Event{ 131 Event: &prjpb.Event_RunFinished{ 132 RunFinished: &prjpb.RunFinished{ 133 RunId: string(runID), 134 Status: status, 135 }, 136 }, 137 }) 138 } 139 140 // SendNow sends the event to Project's eventbox and invokes Project Manager 141 // immediately. 142 func (n *Notifier) SendNow(ctx context.Context, luciProject string, e *prjpb.Event) error { 143 if err := n.sendWithoutDispatch(ctx, luciProject, e); err != nil { 144 return err 145 } 146 return n.TasksBinding.Dispatch(ctx, luciProject, time.Time{} /*asap*/) 147 } 148 149 // sendWithoutDispatch sends the event to Project's eventbox without invoking a 150 // PM. 151 func (n *Notifier) sendWithoutDispatch(ctx context.Context, luciProject string, e *prjpb.Event) error { 152 value, err := proto.Marshal(e) 153 if err != nil { 154 return errors.Annotate(err, "failed to marshal").Err() 155 } 156 return eventbox.Emit(ctx, value, EventboxRecipient(ctx, luciProject)) 157 } 158 159 // NotifyTriggeringCLDepsCompleted tells Project Manager CL deps trigger completion. 160 func (n *Notifier) NotifyTriggeringCLDepsCompleted(ctx context.Context, luciProject string, event *prjpb.TriggeringCLDepsCompleted) error { 161 return n.SendNow(ctx, luciProject, &prjpb.Event{ 162 Event: &prjpb.Event_TriggeringClDepsCompleted{ 163 TriggeringClDepsCompleted: event, 164 }, 165 }) 166 }