github.com/GoogleCloudPlatform/testgrid@v0.0.174/util/gcs/sort.go (about) 1 /* 2 Copyright 2021 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package gcs 18 19 import ( 20 "context" 21 "errors" 22 "sort" 23 "sync" 24 "time" 25 26 "cloud.google.com/go/storage" 27 "github.com/fvbommel/sortorder" 28 "github.com/sirupsen/logrus" 29 ) 30 31 // StatResult contains the result of calling Stat, including any error 32 type StatResult struct { 33 Attrs *storage.ObjectAttrs 34 Err error 35 } 36 37 // Stat multiple paths using concurrent workers. Result indexes match paths. 38 func Stat(ctx context.Context, client Stater, workers int, paths ...Path) []StatResult { 39 var wg sync.WaitGroup 40 wg.Add(workers) 41 out := make([]StatResult, len(paths)) 42 ch := make(chan int) 43 for i := 0; i < workers; i++ { 44 go func() { 45 defer wg.Done() 46 for idx := range ch { 47 out[idx].Attrs, out[idx].Err = client.Stat(ctx, paths[idx]) 48 } 49 }() 50 } 51 for idx := range paths { 52 ch <- idx 53 } 54 close(ch) 55 wg.Wait() 56 return out 57 } 58 59 // StatExisting reduces Stat() to an array of ObjectAttrs. 60 // 61 // Non-existent objects will return a pointer to a zero storage.ObjectAttrs. 62 // Objects that fail to stat will be nil (and log). 63 func StatExisting(ctx context.Context, log logrus.FieldLogger, client Stater, paths ...Path) []*storage.ObjectAttrs { 64 out := make([]*storage.ObjectAttrs, len(paths)) 65 66 attrs := Stat(ctx, client, 20, paths...) 67 for i, attrs := range attrs { 68 err := attrs.Err 69 switch { 70 case attrs.Attrs != nil: 71 out[i] = attrs.Attrs 72 case errors.Is(err, storage.ErrObjectNotExist): 73 out[i] = &storage.ObjectAttrs{} 74 default: 75 log.WithError(err).WithField("path", paths[i]).Info("Failed to stat") 76 } 77 } 78 return out 79 } 80 81 // LeastRecentlyUpdated sorts paths by their update timestamp, noting generations and any errors. 82 func LeastRecentlyUpdated(ctx context.Context, log logrus.FieldLogger, client Stater, paths []Path) map[Path]int64 { 83 ctx, cancel := context.WithTimeout(ctx, 5*time.Minute) 84 defer cancel() 85 log.Debug("Sorting groups") 86 const workers = 20 87 attrs := Stat(ctx, client, workers, paths...) 88 updated := make(map[Path]time.Time, len(paths)) 89 generations := make(map[Path]int64, len(paths)) 90 91 for i, path := range paths { 92 attrs, err := attrs[i].Attrs, attrs[i].Err 93 switch { 94 case err == storage.ErrObjectNotExist: 95 generations[path] = 0 96 case err != nil: 97 log.WithError(err).WithField("path", path).Warning("Stat failed") 98 generations[path] = -1 99 default: 100 updated[path] = attrs.Updated 101 generations[path] = attrs.Generation 102 } 103 } 104 105 sort.SliceStable(paths, func(i, j int) bool { 106 return !updated[paths[i]].After(updated[paths[j]]) 107 }) 108 109 if n := len(paths) - 1; n > 0 { 110 p0 := paths[0] 111 pn := paths[n] 112 log.WithFields(logrus.Fields{ 113 "newest-path": pn, 114 "newest": updated[pn], 115 "oldest-path": p0, 116 "oldest": updated[p0], 117 }).Info("Sorted") 118 } 119 120 return generations 121 } 122 123 // Touch attempts to win an update of the object. 124 // 125 // Cloud copies the current object to itself when the object already exists. 126 // Otherwise uploads genZero bytes. 127 func Touch(ctx context.Context, client ConditionalClient, path Path, generation int64, genZero []byte) (*storage.ObjectAttrs, error) { 128 var cond storage.Conditions 129 if generation != 0 { 130 // Attempt to cloud-copy the object to its current location 131 // - only 1 will win in a concurrent situation 132 // - Increases the last update time. 133 cond.GenerationMatch = generation 134 return client.If(&cond, &cond).Copy(ctx, path, path) 135 } 136 137 // New group, upload the bytes for this situation. 138 cond.DoesNotExist = true 139 return client.If(&cond, &cond).Upload(ctx, path, genZero, DefaultACL, NoCache) 140 } 141 142 // Sort the builds by monotonically decreasing original prefix base name. 143 // 144 // In other words, 145 // gs://b/1 146 // gs://a/5 147 // gs://c/10 148 // becomes: 149 // gs://c/10 150 // gs://a/5 151 // gs://b/1 152 func Sort(builds []Build) { 153 sort.SliceStable(builds, func(i, j int) bool { // greater 154 return !sortorder.NaturalLess(builds[i].baseName, builds[j].baseName) && builds[i].baseName != builds[j].baseName 155 }) 156 }