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 }