go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/internal/baselines/baselines.go (about) 1 // Copyright 2023 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 baselines captures baseline objects and basic interactions with them. 16 package baselines 17 18 import ( 19 "context" 20 "time" 21 22 "cloud.google.com/go/spanner" 23 24 "google.golang.org/grpc/codes" 25 26 "go.chromium.org/luci/common/errors" 27 "go.chromium.org/luci/resultdb/internal/spanutil" 28 "go.chromium.org/luci/resultdb/pbutil" 29 ) 30 31 // Baseline captures baseline identifiers, which map to a set of test variants. 32 // When determining new tests, baselines that have been created within 33 // 3 days are considered to be in a spin up state, and will not be able to 34 // calculate new tests. 35 type Baseline struct { 36 Project string 37 BaselineID string 38 LastUpdatedTime time.Time 39 CreationTime time.Time 40 } 41 42 const SpinUpDuration = 72 * time.Hour 43 44 // IsSpinningUp checks whether the creation time is within 72 hours. 45 func (b Baseline) IsSpinningUp(now time.Time) bool { 46 return now.Sub(b.CreationTime) <= SpinUpDuration 47 } 48 49 // NotFound is the error returned by Read if the row could not be found. 50 var NotFound = errors.New("baseline not found") 51 52 // Read reads the baseline with the given LUCI project and baseline ID. 53 // If the row does not exist, the error NotFound is returned. 54 func Read(ctx context.Context, project, baselineID string) (*Baseline, error) { 55 key := spanner.Key{project, baselineID} 56 57 var proj, bID string 58 var lastUpdatedTime, creationTime time.Time 59 err := spanutil.ReadRow(ctx, "Baselines", key, map[string]any{ 60 "Project": &proj, 61 "BaselineId": &bID, 62 "LastUpdatedTime": &lastUpdatedTime, 63 "CreationTime": &creationTime, 64 }) 65 if err != nil { 66 if spanner.ErrCode(err) == codes.NotFound { 67 err = NotFound 68 } 69 return &Baseline{}, err 70 } 71 res := &Baseline{ 72 Project: proj, 73 BaselineID: bID, 74 LastUpdatedTime: lastUpdatedTime, 75 CreationTime: creationTime, 76 } 77 return res, nil 78 } 79 80 // Create returns a Spanner mutation that creates the baseline, setting CreationTime and LastUpdatedTime to spanner.CommitTimestamp 81 func Create(project, baselineID string) *spanner.Mutation { 82 // Returns mutation that creates baseline, setting CreationTime and LastUpdatedTime to spanner.CommitTimestamp. 83 row := map[string]any{ 84 "Project": project, 85 "BaselineId": baselineID, 86 "LastUpdatedTime": spanner.CommitTimestamp, 87 "CreationTime": spanner.CommitTimestamp, 88 } 89 return spanutil.InsertMap("Baselines", row) 90 } 91 92 // UpdateLastUpdatedTime refreshes a Baseline's LastUpdatedTime. 93 func UpdateLastUpdatedTime(project, baselineID string) *spanner.Mutation { 94 row := map[string]any{ 95 "Project": project, 96 "BaselineId": baselineID, 97 "LastUpdatedTime": spanner.CommitTimestamp, 98 } 99 100 return spanutil.UpdateMap("Baselines", row) 101 } 102 103 // MustParseBaselineName parses a baseline name to project and baselineID. 104 // Panics if the name is invalid. Useful for situations when name was already 105 // validated. 106 func MustParseBaselineName(name string) (project, baselineID string) { 107 project, baselineID, err := pbutil.ParseBaselineName(name) 108 if err != nil { 109 panic(err) 110 } 111 return project, baselineID 112 }