blob: 5468e68534b076b855b6370b1d4ba63bfa10104b [file] [edit]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package models
import (
"fmt"
"os"
"strings"
"time"
"go.chromium.org/luci/gae/service/datastore"
)
const (
// RepositoryStaleIndexingDuration defines maximum duration between
// full_scan_lock refreshes after which indexing will be considered
// incomplete. At that point, any backend instance of cr-rev can take
// over indexing.
RepositoryStaleIndexingDuration = 1 * time.Hour
)
// RepoID uniquely identifies a repository. It's used as a primary key for
// the Repository kind.
type RepoID struct {
Host string
Repository string
}
// ToProperty serializes RepoID into string representation, separated by slash.
func (rID *RepoID) ToProperty() (p datastore.Property, err error) {
if rID.Host == "" || rID.Repository == "" {
return p, fmt.Errorf("Host or Repository not defined")
}
p.SetValue(
fmt.Sprintf("%s/%s", rID.Host, rID.Repository),
true)
return
}
// FromProperty deserializes RepoID from its string representation.
func (rID *RepoID) FromProperty(p datastore.Property) error {
id, ok := p.Value().(string)
if !ok {
return fmt.Errorf("Failed to cast property value to string")
}
idx := strings.Index(id, "/")
// idx is set to -1 if not found. Valid property value can't be start
// with slash (index 0), nor have first slash as last element.
if idx <= 0 || idx+1 == len(id) {
return fmt.Errorf("Unexpected ID format: %s", id)
}
rID.Host, rID.Repository = id[:idx], id[idx+1:]
return nil
}
// Repository represents a document in datastore. It stores information about
// indexed repository. Before new repository is indexed, cr-rev creates entry
// in datastore with FullScanLock set to current time and FullScanLastRun to
// zero value. On successful execution, FullScanLastRun is set to current time.
type Repository struct {
ID RepoID `gae:"$id"`
// FullScanLastRun holds information when was last successful full
// import. Zero value means the repository was never fully indexed.
FullScanLastRun time.Time `gae:",noindex"`
// FullScanLeaseStartTime, if non-zero value, indicates on-going
// indexing. The indexing process should periodically update this
// value. If the last update was longer than
// RepositoryStaleIndexingDuration, the import process is considered
// stalled (e.g. crashed, networking partition) and a new import job
// can be triggered.
FullScanLeaseStartTime time.Time
// FullScanLeaseHostname tracks which hostname has the lease for the
// repository.
FullScanLeaseHostname string `gae:",noindex"`
}
// IsScanRequired returns true if full repository scan is required. This
// happens if repository was never indexed or if previous lease expired and
// left this repository partially indexed.
func (r *Repository) IsScanRequired(currentTime time.Time) bool {
if !r.FullScanLastRun.IsZero() {
return false
}
if r.FullScanLeaseStartTime.IsZero() {
return true
}
deadline := r.FullScanLeaseStartTime.Add(RepositoryStaleIndexingDuration)
return currentTime.After(deadline)
}
// SetIndexingCompleted marks Repository as successfully indexed and removes
// lease.
func (r *Repository) SetIndexingCompleted(currentTime time.Time) {
r.FullScanLastRun = currentTime
r.FullScanLeaseStartTime = time.Time{}
r.FullScanLeaseHostname = ""
}
// SetStartIndexing marks Repository for initial import and sets lease
// information. It is on client to ensure lease is renewed periodically, by
// calling ExtendLease
func (r *Repository) SetStartIndexing(currentTime time.Time, hostname string) {
r.FullScanLastRun, r.FullScanLeaseStartTime = time.Time{}, currentTime
r.FullScanLeaseHostname = os.Getenv("GAE_INSTANCE")
}
// ExtendLease extends currently active lease. This function doesn't check
// lease ownership.
func (r *Repository) ExtendLease(currentTime time.Time) {
r.FullScanLeaseStartTime = currentTime
}