blob: b33beea42eead1e785bc5cbd39d2981a676500e9 [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
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package run
import (
"context"
"time"
"go.chromium.org/luci/auth/identity"
"go.chromium.org/luci/common/clock"
"go.chromium.org/luci/gae/service/datastore"
"go.chromium.org/luci/cv/internal/changelist"
"go.chromium.org/luci/cv/internal/common"
"go.chromium.org/luci/cv/internal/configs/prjcfg"
)
const (
// RunKind is the Datastore entity kind for Run.
RunKind = "Run"
// RunKind 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 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 int `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
// 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"`
// CQAttemptKey is what CQDaemon exports to BigQuery as Attempt's key.
//
// In CQDaemon's source, it's equivalent to GerritAttempt.attempt_key_hash.
//
// TODO(crbug/1227523): delete this field.
CQDAttemptKey string
// FinalizedByCQD is true iff the Run was finalized by CQDaemon, which
// includes submitting CLs and/or removing CQ votes and sending BQ row.
//
// TODO(crbug/1227523): delete this field.
FinalizedByCQD bool `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
}