1 /* 2 Copyright 2016 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 retry 18 19 import ( 20 "time" 21 22 "k8s.io/apimachinery/pkg/api/errors" 23 "k8s.io/apimachinery/pkg/util/wait" 24 ) 25 26 // DefaultRetry is the recommended retry for a conflict where multiple clients 27 // are making changes to the same resource. 28 var DefaultRetry = wait.Backoff{ 29 Steps: 5, 30 Duration: 10 * time.Millisecond, 31 Factor: 1.0, 32 Jitter: 0.1, 33 } 34 35 // DefaultBackoff is the recommended backoff for a conflict where a client 36 // may be attempting to make an unrelated modification to a resource under 37 // active management by one or more controllers. 38 var DefaultBackoff = wait.Backoff{ 39 Steps: 4, 40 Duration: 10 * time.Millisecond, 41 Factor: 5.0, 42 Jitter: 0.1, 43 } 44 45 // OnError allows the caller to retry fn in case the error returned by fn is retriable 46 // according to the provided function. backoff defines the maximum retries and the wait 47 // interval between two retries. 48 func OnError(backoff wait.Backoff, retriable func(error) bool, fn func() error) error { 49 var lastErr error 50 err := wait.ExponentialBackoff(backoff, func() (bool, error) { 51 err := fn() 52 switch { 53 case err == nil: 54 return true, nil 55 case retriable(err): 56 lastErr = err 57 return false, nil 58 default: 59 return false, err 60 } 61 }) 62 if err == wait.ErrWaitTimeout { 63 err = lastErr 64 } 65 return err 66 } 67 68 // RetryOnConflict is used to make an update to a resource when you have to worry about 69 // conflicts caused by other code making unrelated updates to the resource at the same 70 // time. fn should fetch the resource to be modified, make appropriate changes to it, try 71 // to update it, and return (unmodified) the error from the update function. On a 72 // successful update, RetryOnConflict will return nil. If the update function returns a 73 // "Conflict" error, RetryOnConflict will wait some amount of time as described by 74 // backoff, and then try again. On a non-"Conflict" error, or if it retries too many times 75 // and gives up, RetryOnConflict will return an error to the caller. 76 // 77 // err := retry.RetryOnConflict(retry.DefaultRetry, func() error { 78 // // Fetch the resource here; you need to refetch it on every try, since 79 // // if you got a conflict on the last update attempt then you need to get 80 // // the current version before making your own changes. 81 // pod, err := c.Pods("mynamespace").Get(name, metav1.GetOptions{}) 82 // if err != nil { 83 // return err 84 // } 85 // 86 // // Make whatever updates to the resource are needed 87 // pod.Status.Phase = v1.PodFailed 88 // 89 // // Try to update 90 // _, err = c.Pods("mynamespace").UpdateStatus(pod) 91 // // You have to return err itself here (not wrapped inside another error) 92 // // so that RetryOnConflict can identify it correctly. 93 // return err 94 // }) 95 // if err != nil { 96 // // May be conflict if max retries were hit, or may be something unrelated 97 // // like permissions or a network error 98 // return err 99 // } 100 // ... 101 // 102 // TODO: Make Backoff an interface? 103 func RetryOnConflict(backoff wait.Backoff, fn func() error) error { 104 return OnError(backoff, errors.IsConflict, fn) 105 } 106