go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/prjmanager/model.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  	"go.chromium.org/luci/common/errors"
    22  	"go.chromium.org/luci/common/retry/transient"
    23  	"go.chromium.org/luci/gae/service/datastore"
    24  
    25  	"go.chromium.org/luci/cv/internal/common"
    26  	"go.chromium.org/luci/cv/internal/prjmanager/prjpb"
    27  )
    28  
    29  const (
    30  	// ProjectKind is the Datastore entity kind for Project.
    31  	ProjectKind = "Project"
    32  	// ProjectLogKind is the Datastore entity kind for ProjectLog.
    33  	ProjectLogKind = "ProjectLog"
    34  )
    35  
    36  // Project is an entity per LUCI Project in Datastore.
    37  type Project struct {
    38  	// $kind must match ProjectKind.
    39  	_kind  string                `gae:"$kind,Project"`
    40  	_extra datastore.PropertyMap `gae:"-,extra"`
    41  
    42  	// ID is LUCI project name.
    43  	ID string `gae:"$id"`
    44  
    45  	// EVersion is entity version. Every update should increment it by 1.
    46  	EVersion int64 `gae:",noindex"`
    47  	// UpdateTime is exact time of when this entity was last updated.
    48  	//
    49  	// It's not indexed to avoid hot areas in the index.
    50  	UpdateTime time.Time `gae:",noindex"`
    51  
    52  	// State serializes internal Project Manager state.
    53  	//
    54  	// The `LuciProject` field isn't set as it duplicates Project.ID.
    55  	State *prjpb.PState
    56  }
    57  
    58  // ProjectStateOffload stores rarely-changed project state duplicated from the
    59  // main Project entity for use in transactions creating Runs.
    60  //
    61  // Although this is already stored in the main Project entity, doing so would
    62  // result in retries of Run creation transactions, since Project entity is
    63  // frequently modified in busy projects.
    64  //
    65  // On the other hand, ProjectStateOffload is highly likely to remain unchanged
    66  // by the time Run creation transaction commits, thus avoiding needless
    67  // retries.
    68  type ProjectStateOffload struct {
    69  	_kind string `gae:"$kind,ProjectRarelyChanged"`
    70  
    71  	Project *datastore.Key `gae:"$parent"`
    72  	// ID is alaways the same, set/read only by the datastore ORM.
    73  	ID string `gae:"$id,const"`
    74  
    75  	// Status of the project {STARTED, STOPPING, STOPPED (disabled)}.
    76  	Status prjpb.Status `gae:",noindex"`
    77  	// ConfigHash is the latest processed Project Config hash.
    78  	ConfigHash string `gae:",noindex"`
    79  
    80  	// UpdateTime is the last time the entity was modifed.
    81  	UpdateTime time.Time `gae:",noindex"`
    82  }
    83  
    84  // ProjectLog stores historic state of a project.
    85  type ProjectLog struct {
    86  	_kind string `gae:"$kind,ProjectLog"`
    87  
    88  	Project *datastore.Key `gae:"$parent"`
    89  	// EVersion and other fields are the same as the Project & ProjectStateOffload
    90  	// entities written at the same time.
    91  	EVersion   int64        `gae:"$id"`
    92  	Status     prjpb.Status `gae:",noindex"`
    93  	ConfigHash string       `gae:",noindex"`
    94  	UpdateTime time.Time    `gae:",noindex"`
    95  	State      *prjpb.PState
    96  
    97  	// Reasons records why this Log entity was written.
    98  	//
    99  	// There can be more than one, all of which will be indexed.
   100  	Reasons []prjpb.LogReason `gae:"reason"`
   101  }
   102  
   103  // IncompleteRuns are IDs of Runs which aren't yet completed.
   104  func (p *Project) IncompleteRuns() (ids common.RunIDs) {
   105  	return p.State.IncompleteRuns()
   106  }
   107  
   108  // Status returns Project Manager status.
   109  func (p *Project) Status() prjpb.Status {
   110  	return p.State.GetStatus()
   111  }
   112  
   113  // ConfigHash returns Project's Config hash.
   114  func (p *Project) ConfigHash() string {
   115  	return p.State.GetConfigHash()
   116  }
   117  
   118  // Load loads LUCI project state from Datastore.
   119  //
   120  // If project doesn't exist in Datastore, returns nil, nil.
   121  func Load(ctx context.Context, luciProject string) (*Project, error) {
   122  	p := &Project{ID: luciProject}
   123  	switch err := datastore.Get(ctx, p); {
   124  	case err == datastore.ErrNoSuchEntity:
   125  		return nil, nil
   126  	case err != nil:
   127  		return nil, errors.Annotate(err, "failed to load Project state").Tag(transient.Tag).Err()
   128  	default:
   129  		return p, nil
   130  	}
   131  }