go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/gerrit/mirror.go (about) 1 // Copyright 2021 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 gerrit 16 17 import ( 18 "context" 19 "math/rand" 20 21 "google.golang.org/grpc" 22 23 "go.chromium.org/luci/common/api/gerrit" 24 "go.chromium.org/luci/common/data/rand/mathrand" 25 "go.chromium.org/luci/common/errors" 26 ) 27 28 // MirrorIterator starts with the Gerrit host as is and then iterates over 29 // mirrors. 30 // 31 // Fail-safe: if all mirrors have been used, uses the host as is. However, 32 // users should use Empty() to detect this and if necessary should construct a 33 // new MirrorIterator. 34 // 35 // Not goroutine-safe. 36 type MirrorIterator []string 37 38 func newMirrorIterator(ctx context.Context, mirrorHostPrefixes ...string) *MirrorIterator { 39 // First prefix "" means use host as is. 40 it := &MirrorIterator{""} 41 *it = append(*it, mirrorHostPrefixes...) // copy 42 // Shuffle the provided prefixes. 43 provided := (*it)[1:] 44 _ = mathrand.WithGoRand(ctx, func(r *rand.Rand) error { 45 rand.Shuffle(len(provided), func(i, j int) { provided[i], provided[j] = provided[j], provided[i] }) 46 return nil 47 }) 48 return it 49 } 50 51 // RetryIfStale retries the given function so long as it returns ErrStaleData 52 // and there is not yet tried mirror. 53 // 54 // Executes the given function at least once. If there are no mirrors, defaults 55 // to virtual gerrit host. 56 // 57 // Provides the given function with the grpc.CallOption which should be passed 58 // to the respective Client RPC call. 59 // 60 // Returns whatever error the given function returned last. 61 func (it *MirrorIterator) RetryIfStale(f func(opt grpc.CallOption) error) error { 62 for { 63 if err := f(it.Next()); errors.Unwrap(err) != ErrStaleData || it.Empty() { 64 return err 65 } 66 } 67 } 68 69 // Next returns a grpc.CallOption. 70 func (it *MirrorIterator) Next() grpc.CallOption { 71 return gerrit.UseGerritMirror(it.next()) 72 } 73 74 // next is an easily testable version of Next(). 75 func (it *MirrorIterator) next() func(host string) string { 76 prefix := "" // fail-safe to host as is when empty. 77 if len(*it) > 0 { 78 prefix = (*it)[0] 79 *it = (*it)[1:] 80 } 81 return func(host string) string { return prefix + host } 82 } 83 84 // Empty returns true if MirrorIterator has already iterated over all the mirrors. 85 func (it *MirrorIterator) Empty() bool { 86 return len(*it) == 0 87 }