blob: b350d9c63c0b0e82f5654b913098cee4291e1e09 [file] [log] [blame]
// Copyright 2020 The LUCI Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package run
import (
const (
// RunCLKind is the Datastore entity kind for RunCL.
RunCLKind = "RunCL"
// RunLogKind is the Datastore entity kind for RunLog.
RunLogKind = "RunLog"
// MaxTryjobs limits maximum number of tryjobs that a single Run can track.
// Because all Run's tryjobs' results are kept in a Run.Tryjobs,
// a single Datastore entity size limit of 1 MiB applies.
// 1024 is chosen because:
// * it is a magnitude above what was observed in practice before;
// * although stored tryjob results are user-influenced,
// they should be highly compressible, so even at 1KiB each,
// 1024 tryjob results should amount to much less than 1 MiB.
MaxTryjobs = 1024
// Run is an entity that contains high-level information about a CV Run.
// In production, Run entities are created only by the "runcreator" package.
// Detailed information about CLs and Tryjobs are stored in its child entities.
type Run struct {
// $kind must match common.RunKind.
_kind string `gae:"$kind,Run"`
_extra datastore.PropertyMap `gae:"-,extra"`
// ID is the RunID generated at triggering time.
// See doc for type `common.RunID` about the format.
ID common.RunID `gae:"$id"`
// CreationOperationID is a string used to de-dup Run creation attempts.
CreationOperationID string `gae:",noindex"`
// Mode dictates the behavior of this Run.
Mode Mode `gae:",noindex"`
// Status describes the status of this Run.
Status Status
// EVersion is the entity version.
// It increments by one upon every successful modification.
EVersion int64 `gae:",noindex"`
// CreateTime is the timestamp when this Run was created.
// This is the timestamp of the last vote, on a Gerrit CL, that triggers this Run.
CreateTime time.Time `gae:",noindex"`
// StartTime is the timestamp when this Run was started.
StartTime time.Time `gae:",noindex"`
// UpdateTime is the timestamp when this entity was last updated.
UpdateTime time.Time `gae:",noindex"`
// EndTime is the timestamp when this Run has completed.
EndTime time.Time `gae:",noindex"`
// Owner is the identity of the owner of this Run.
// Currently, it is the same as owner of the CL. If `combine_cls` is
// enabled for the ConfigGroup used by this Run, the owner is the CL which
// has the latest triggering timestamp.
Owner identity.Identity `gae:",noindex"`
// ConfigGroupID is ID of the ConfigGroup that is used by this Run.
// RunManager may update the ConfigGroup in the middle of the Run if it is
// notified that a new version of Config has been imported into CV.
ConfigGroupID prjcfg.ConfigGroupID `gae:",noindex"`
// CLs are IDs of all CLs involved in this Run.
// The index of Runs by CL is provided via RunCL's `IndexedID` field.
CLs common.CLIDs `gae:",noindex"`
// Options are Run-specific additions on top of LUCI project config.
Options *Options
// CancellationReasons are the reasons for cancelling a Run.
// Typically, only one reason will be available. But it's possible that Run
// Manager received multiple cancellation requested at the approximately
// the same time.
// Reasons should not contain duplication and empty reason.
CancellationReasons []string `gae:",noindex"`
// OngoingLongOps tracks long operations currently happening.
OngoingLongOps *OngoingLongOps
// Submission is the state of Run Submission.
// If set, Submission is in progress or has completed.
Submission *Submission
// Tryjobs is the state of the Run tryjobs.
Tryjobs *Tryjobs
// LatestCLsRefresh is the latest time when Run Manager scheduled async
// refresh of CLs.
LatestCLsRefresh time.Time `gae:",noindex"`
// Mutate mutates the Run by executing `mut`.
// It ensures EVersion and UpdateTime are correctly updated if `mut` has
// changed the Run.
func (r *Run) Mutate(ctx context.Context, mut func(*Run) (updated bool)) (updated bool) {
prevEV := r.EVersion
updated = mut(r)
if !updated {
return false
r.EVersion = prevEV + 1
r.UpdateTime = datastore.RoundTime(clock.Now(ctx).UTC())
return true
// RunOwner keeps tracks of all open (active or pending) Runs for a user.
type RunOwner struct {
_kind string `gae:"$kind,RunOwner"`
// ID is the user identity.
ID identity.Identity `gae:"$id"`
// ActiveRuns are all Runs triggered by this user that are active.
ActiveRuns common.RunIDs `gae:",noindex"`
// PendingRuns are all Runs triggered by this user that are
// yet-to-be-launched (i.e. quota doesn't permit).
PendingRuns common.RunIDs `gae:",noindex"`
// RunCL is an immutable snapshot of a CL at the time of the Run start.
type RunCL struct {
_kind string `gae:"$kind,RunCL"`
ID common.CLID `gae:"$id"`
Run *datastore.Key `gae:"$parent"`
ExternalID changelist.ExternalID `gae:",noindex"`
Detail *changelist.Snapshot
Trigger *Trigger
// IndexedID is a copy of ID to get an index on just the CLID,
// as the primary automatic index is on (Run(parent), ID).
IndexedID common.CLID
// TODO(tandrii): add field of (ExternalID + "/" + patchset) to support for
// direct searches for gerrit/host/change/patchset.
// RunLog is an immutable entry for meaningful changes to a Run's state.
// RunLog entity is written
type RunLog struct {
_kind string `gae:"$kind,RunLog"`
// ID is the value Run.EVersion which was saved transactionally with the
// creation of the RunLog entity.
// Thus, ordering by ID (default Datastore ordering) will automatically
// provide semantically chronological order.
ID int64 `gae:"$id"`
Run *datastore.Key `gae:"$parent"`
// Entries record what happened to the Run.
// There is always at least one.
// Ordered from logically oldest to newest.
// Entries are stored as a protobuf in order to expose them using Admin API
// with ease.
// This is however opaque to Datastore, and so it has a downside of inability
// to index or filter by kinds of LogEntry within the Datastore itself.
// However, total number of LogEntries per Run should be <<1000 and the
// intended consumption are humans, therefore indexing isn't a deal breaker.
Entries *LogEntries