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  }