go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/deploy/service/model/utils.go (about) 1 // Copyright 2022 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 model 16 17 import ( 18 "context" 19 "strings" 20 "time" 21 22 "google.golang.org/protobuf/types/known/timestamppb" 23 24 "go.chromium.org/luci/common/errors" 25 "go.chromium.org/luci/common/logging" 26 "go.chromium.org/luci/common/retry/transient" 27 "go.chromium.org/luci/gae/service/datastore" 28 29 "go.chromium.org/luci/deploy/api/modelpb" 30 ) 31 32 // Txn runs the callback in a datastore transaction. 33 // 34 // Transient-tagged errors trigger a transaction retry. If all retries are 35 // exhausted, returns a transient-tagged error itself. 36 func Txn(ctx context.Context, cb func(context.Context) error) error { 37 var attempt int 38 var innerErr error 39 40 err := datastore.RunInTransaction(ctx, func(ctx context.Context) error { 41 attempt++ 42 if attempt != 1 { 43 if innerErr != nil { 44 logging.Warningf(ctx, "Retrying the transaction after the error: %s", innerErr) 45 } else { 46 logging.Warningf(ctx, "Retrying the transaction: failed to commit") 47 } 48 } 49 innerErr = cb(ctx) 50 if transient.Tag.In(innerErr) { 51 return datastore.ErrConcurrentTransaction // causes a retry 52 } 53 return innerErr 54 }, nil) 55 56 if err != nil { 57 // If the transaction callback failed, prefer its error. 58 if innerErr != nil { 59 return innerErr 60 } 61 // Here it can only be a commit error (i.e. produced by RunInTransaction 62 // itself, not by its callback). We treat them as transient. 63 return transient.Tag.Apply(err) 64 } 65 66 return nil 67 } 68 69 // EqualStrSlice compares two string slices. 70 func EqualStrSlice(a, b []string) bool { 71 if len(a) != len(b) { 72 return false 73 } 74 for i, s := range a { 75 if b[i] != s { 76 return false 77 } 78 } 79 return true 80 } 81 82 // asTime converts a timestamp proto to optional time.Time for Datastore. 83 func asTime(ts *timestamppb.Timestamp) time.Time { 84 if ts == nil { 85 return time.Time{} 86 } 87 return ts.AsTime().UTC() 88 } 89 90 // fetchAssets fetches a bunch of asset entities given their IDs. 91 // 92 // If shouldExist is true, fails if some of them do not exist. Otherwise 93 // constructs new empty Asset structs in place of missing ones. 94 func fetchAssets(ctx context.Context, assets []string, shouldExist bool) (map[string]*Asset, error) { 95 ents := make([]*Asset, len(assets)) 96 for idx, id := range assets { 97 ents[idx] = &Asset{ID: id} 98 } 99 100 if err := datastore.Get(ctx, ents); err != nil { 101 merr, ok := err.(errors.MultiError) 102 if !ok { 103 return nil, transient.Tag.Apply(err) 104 } 105 106 var missing []string 107 for idx, err := range merr { 108 switch { 109 case err == datastore.ErrNoSuchEntity: 110 if shouldExist { 111 missing = append(missing, assets[idx]) 112 } else { 113 ents[idx] = &Asset{ 114 ID: assets[idx], 115 Asset: &modelpb.Asset{Id: assets[idx]}, 116 } 117 } 118 case err != nil: 119 return nil, transient.Tag.Apply(err) 120 } 121 } 122 123 if len(missing) != 0 { 124 return nil, errors.Reason("assets entities unexpectedly missing: %s", strings.Join(missing, ", ")).Err() 125 } 126 } 127 128 assetMap := make(map[string]*Asset, len(assets)) 129 for _, ent := range ents { 130 assetMap[ent.ID] = ent 131 } 132 133 return assetMap, nil 134 } 135 136 // IsActuateDecision returns true for ACTUATE_* decisions. 137 func IsActuateDecision(d modelpb.ActuationDecision_Decision) bool { 138 return d == modelpb.ActuationDecision_ACTUATE_STALE || 139 d == modelpb.ActuationDecision_ACTUATE_FORCE 140 }