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  }