Remove BPA from Makefile
[icn.git] / cmd / bpa-operator / vendor / k8s.io / client-go / discovery / cached_discovery.go
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 discovery
18
19 import (
20         "errors"
21         "io/ioutil"
22         "net/http"
23         "os"
24         "path/filepath"
25         "sync"
26         "time"
27
28         "github.com/googleapis/gnostic/OpenAPIv2"
29         "k8s.io/klog"
30
31         metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32         "k8s.io/apimachinery/pkg/runtime"
33         "k8s.io/apimachinery/pkg/version"
34         "k8s.io/client-go/kubernetes/scheme"
35         restclient "k8s.io/client-go/rest"
36 )
37
38 // CachedDiscoveryClient implements the functions that discovery server-supported API groups,
39 // versions and resources.
40 type CachedDiscoveryClient struct {
41         delegate DiscoveryInterface
42
43         // cacheDirectory is the directory where discovery docs are held.  It must be unique per host:port combination to work well.
44         cacheDirectory string
45
46         // ttl is how long the cache should be considered valid
47         ttl time.Duration
48
49         // mutex protects the variables below
50         mutex sync.Mutex
51
52         // ourFiles are all filenames of cache files created by this process
53         ourFiles map[string]struct{}
54         // invalidated is true if all cache files should be ignored that are not ours (e.g. after Invalidate() was called)
55         invalidated bool
56         // fresh is true if all used cache files were ours
57         fresh bool
58 }
59
60 var _ CachedDiscoveryInterface = &CachedDiscoveryClient{}
61
62 // ServerResourcesForGroupVersion returns the supported resources for a group and version.
63 func (d *CachedDiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
64         filename := filepath.Join(d.cacheDirectory, groupVersion, "serverresources.json")
65         cachedBytes, err := d.getCachedFile(filename)
66         // don't fail on errors, we either don't have a file or won't be able to run the cached check. Either way we can fallback.
67         if err == nil {
68                 cachedResources := &metav1.APIResourceList{}
69                 if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), cachedBytes, cachedResources); err == nil {
70                         klog.V(10).Infof("returning cached discovery info from %v", filename)
71                         return cachedResources, nil
72                 }
73         }
74
75         liveResources, err := d.delegate.ServerResourcesForGroupVersion(groupVersion)
76         if err != nil {
77                 klog.V(3).Infof("skipped caching discovery info due to %v", err)
78                 return liveResources, err
79         }
80         if liveResources == nil || len(liveResources.APIResources) == 0 {
81                 klog.V(3).Infof("skipped caching discovery info, no resources found")
82                 return liveResources, err
83         }
84
85         if err := d.writeCachedFile(filename, liveResources); err != nil {
86                 klog.V(1).Infof("failed to write cache to %v due to %v", filename, err)
87         }
88
89         return liveResources, nil
90 }
91
92 // ServerResources returns the supported resources for all groups and versions.
93 func (d *CachedDiscoveryClient) ServerResources() ([]*metav1.APIResourceList, error) {
94         return ServerResources(d)
95 }
96
97 // ServerGroups returns the supported groups, with information like supported versions and the
98 // preferred version.
99 func (d *CachedDiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
100         filename := filepath.Join(d.cacheDirectory, "servergroups.json")
101         cachedBytes, err := d.getCachedFile(filename)
102         // don't fail on errors, we either don't have a file or won't be able to run the cached check. Either way we can fallback.
103         if err == nil {
104                 cachedGroups := &metav1.APIGroupList{}
105                 if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), cachedBytes, cachedGroups); err == nil {
106                         klog.V(10).Infof("returning cached discovery info from %v", filename)
107                         return cachedGroups, nil
108                 }
109         }
110
111         liveGroups, err := d.delegate.ServerGroups()
112         if err != nil {
113                 klog.V(3).Infof("skipped caching discovery info due to %v", err)
114                 return liveGroups, err
115         }
116         if liveGroups == nil || len(liveGroups.Groups) == 0 {
117                 klog.V(3).Infof("skipped caching discovery info, no groups found")
118                 return liveGroups, err
119         }
120
121         if err := d.writeCachedFile(filename, liveGroups); err != nil {
122                 klog.V(1).Infof("failed to write cache to %v due to %v", filename, err)
123         }
124
125         return liveGroups, nil
126 }
127
128 func (d *CachedDiscoveryClient) getCachedFile(filename string) ([]byte, error) {
129         // after invalidation ignore cache files not created by this process
130         d.mutex.Lock()
131         _, ourFile := d.ourFiles[filename]
132         if d.invalidated && !ourFile {
133                 d.mutex.Unlock()
134                 return nil, errors.New("cache invalidated")
135         }
136         d.mutex.Unlock()
137
138         file, err := os.Open(filename)
139         if err != nil {
140                 return nil, err
141         }
142         defer file.Close()
143
144         fileInfo, err := file.Stat()
145         if err != nil {
146                 return nil, err
147         }
148
149         if time.Now().After(fileInfo.ModTime().Add(d.ttl)) {
150                 return nil, errors.New("cache expired")
151         }
152
153         // the cache is present and its valid.  Try to read and use it.
154         cachedBytes, err := ioutil.ReadAll(file)
155         if err != nil {
156                 return nil, err
157         }
158
159         d.mutex.Lock()
160         defer d.mutex.Unlock()
161         d.fresh = d.fresh && ourFile
162
163         return cachedBytes, nil
164 }
165
166 func (d *CachedDiscoveryClient) writeCachedFile(filename string, obj runtime.Object) error {
167         if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil {
168                 return err
169         }
170
171         bytes, err := runtime.Encode(scheme.Codecs.LegacyCodec(), obj)
172         if err != nil {
173                 return err
174         }
175
176         f, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename)+".")
177         if err != nil {
178                 return err
179         }
180         defer os.Remove(f.Name())
181         _, err = f.Write(bytes)
182         if err != nil {
183                 return err
184         }
185
186         err = os.Chmod(f.Name(), 0755)
187         if err != nil {
188                 return err
189         }
190
191         name := f.Name()
192         err = f.Close()
193         if err != nil {
194                 return err
195         }
196
197         // atomic rename
198         d.mutex.Lock()
199         defer d.mutex.Unlock()
200         err = os.Rename(name, filename)
201         if err == nil {
202                 d.ourFiles[filename] = struct{}{}
203         }
204         return err
205 }
206
207 // RESTClient returns a RESTClient that is used to communicate with API server
208 // by this client implementation.
209 func (d *CachedDiscoveryClient) RESTClient() restclient.Interface {
210         return d.delegate.RESTClient()
211 }
212
213 // ServerPreferredResources returns the supported resources with the version preferred by the
214 // server.
215 func (d *CachedDiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
216         return ServerPreferredResources(d)
217 }
218
219 // ServerPreferredNamespacedResources returns the supported namespaced resources with the
220 // version preferred by the server.
221 func (d *CachedDiscoveryClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
222         return ServerPreferredNamespacedResources(d)
223 }
224
225 // ServerVersion retrieves and parses the server's version (git version).
226 func (d *CachedDiscoveryClient) ServerVersion() (*version.Info, error) {
227         return d.delegate.ServerVersion()
228 }
229
230 // OpenAPISchema retrieves and parses the swagger API schema the server supports.
231 func (d *CachedDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
232         return d.delegate.OpenAPISchema()
233 }
234
235 // Fresh is supposed to tell the caller whether or not to retry if the cache
236 // fails to find something (false = retry, true = no need to retry).
237 func (d *CachedDiscoveryClient) Fresh() bool {
238         d.mutex.Lock()
239         defer d.mutex.Unlock()
240
241         return d.fresh
242 }
243
244 // Invalidate enforces that no cached data is used in the future that is older than the current time.
245 func (d *CachedDiscoveryClient) Invalidate() {
246         d.mutex.Lock()
247         defer d.mutex.Unlock()
248
249         d.ourFiles = map[string]struct{}{}
250         d.fresh = true
251         d.invalidated = true
252 }
253
254 // NewCachedDiscoveryClientForConfig creates a new DiscoveryClient for the given config, and wraps
255 // the created client in a CachedDiscoveryClient. The provided configuration is updated with a
256 // custom transport that understands cache responses.
257 // We receive two distinct cache directories for now, in order to preserve old behavior
258 // which makes use of the --cache-dir flag value for storing cache data from the CacheRoundTripper,
259 // and makes use of the hardcoded destination (~/.kube/cache/discovery/...) for storing
260 // CachedDiscoveryClient cache data. If httpCacheDir is empty, the restconfig's transport will not
261 // be updated with a roundtripper that understands cache responses.
262 // If discoveryCacheDir is empty, cached server resource data will be looked up in the current directory.
263 // TODO(juanvallejo): the value of "--cache-dir" should be honored. Consolidate discoveryCacheDir with httpCacheDir
264 // so that server resources and http-cache data are stored in the same location, provided via config flags.
265 func NewCachedDiscoveryClientForConfig(config *restclient.Config, discoveryCacheDir, httpCacheDir string, ttl time.Duration) (*CachedDiscoveryClient, error) {
266         if len(httpCacheDir) > 0 {
267                 // update the given restconfig with a custom roundtripper that
268                 // understands how to handle cache responses.
269                 wt := config.WrapTransport
270                 config.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {
271                         if wt != nil {
272                                 rt = wt(rt)
273                         }
274                         return newCacheRoundTripper(httpCacheDir, rt)
275                 }
276         }
277
278         discoveryClient, err := NewDiscoveryClientForConfig(config)
279         if err != nil {
280                 return nil, err
281         }
282
283         return newCachedDiscoveryClient(discoveryClient, discoveryCacheDir, ttl), nil
284 }
285
286 // NewCachedDiscoveryClient creates a new DiscoveryClient.  cacheDirectory is the directory where discovery docs are held.  It must be unique per host:port combination to work well.
287 func newCachedDiscoveryClient(delegate DiscoveryInterface, cacheDirectory string, ttl time.Duration) *CachedDiscoveryClient {
288         return &CachedDiscoveryClient{
289                 delegate:       delegate,
290                 cacheDirectory: cacheDirectory,
291                 ttl:            ttl,
292                 ourFiles:       map[string]struct{}{},
293                 fresh:          true,
294         }
295 }