github.com/lablabs/operator-sdk@v0.8.2/pkg/ansible/proxy/cache_response.go (about) 1 // Copyright 2019 The Operator-SDK 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 proxy 16 17 import ( 18 "bytes" 19 "context" 20 "encoding/json" 21 "fmt" 22 "net/http" 23 "strings" 24 25 "github.com/operator-framework/operator-sdk/pkg/ansible/proxy/controllermap" 26 "github.com/operator-framework/operator-sdk/pkg/ansible/proxy/requestfactory" 27 k8sRequest "github.com/operator-framework/operator-sdk/pkg/ansible/proxy/requestfactory" 28 osdkHandler "github.com/operator-framework/operator-sdk/pkg/handler" 29 "k8s.io/apimachinery/pkg/api/meta" 30 metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 33 "k8s.io/apimachinery/pkg/runtime/schema" 34 "k8s.io/apimachinery/pkg/util/sets" 35 "sigs.k8s.io/controller-runtime/pkg/cache" 36 "sigs.k8s.io/controller-runtime/pkg/client" 37 ) 38 39 type marshaler interface { 40 MarshalJSON() ([]byte, error) 41 } 42 43 type cacheResponseHandler struct { 44 next http.Handler 45 informerCache cache.Cache 46 restMapper meta.RESTMapper 47 watchedNamespaces map[string]interface{} 48 cMap *controllermap.ControllerMap 49 injectOwnerRef bool 50 } 51 52 func (c *cacheResponseHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { 53 switch req.Method { 54 case http.MethodGet: 55 // GET request means we need to check the cache 56 rf := k8sRequest.RequestInfoFactory{APIPrefixes: sets.NewString("api", "apis"), GrouplessAPIPrefixes: sets.NewString("api")} 57 r, err := rf.NewRequestInfo(req) 58 if err != nil { 59 log.Error(err, "Failed to convert request") 60 break 61 } 62 63 if c.skipCacheLookup(r) { 64 break 65 } 66 67 gvr := schema.GroupVersionResource{ 68 Group: r.APIGroup, 69 Version: r.APIVersion, 70 Resource: r.Resource, 71 } 72 if c.restMapper == nil { 73 c.restMapper = meta.NewDefaultRESTMapper([]schema.GroupVersion{schema.GroupVersion{ 74 Group: r.APIGroup, 75 Version: r.APIVersion, 76 }}) 77 } 78 79 k, err := c.restMapper.KindFor(gvr) 80 if err != nil { 81 // break here in case resource doesn't exist in cache 82 log.Info("Cache miss, can not find in rest mapper", "GVR", gvr) 83 break 84 } 85 86 var m marshaler 87 88 log.V(2).Info("Get resource in our cache", "r", r) 89 if r.Verb == "list" { 90 m, err = c.getListFromCache(r, req, k) 91 if err != nil { 92 break 93 } 94 } else { 95 m, err = c.getObjectFromCache(r, req, k) 96 if err != nil { 97 break 98 } 99 } 100 101 i := bytes.Buffer{} 102 resp, err := m.MarshalJSON() 103 if err != nil { 104 // return will give a 500 105 log.Error(err, "Failed to marshal data") 106 http.Error(w, "", http.StatusInternalServerError) 107 return 108 } 109 110 // Set Content-Type header 111 w.Header().Set("Content-Type", "application/json") 112 // Set X-Cache header to signal that response is served from Cache 113 w.Header().Set("X-Cache", "HIT") 114 if err := json.Indent(&i, resp, "", " "); err != nil { 115 log.Error(err, "Failed to indent json") 116 } 117 _, err = w.Write(i.Bytes()) 118 if err != nil { 119 log.Error(err, "Failed to write response") 120 http.Error(w, "", http.StatusInternalServerError) 121 return 122 } 123 124 // Return so that request isn't passed along to APIserver 125 return 126 } 127 c.next.ServeHTTP(w, req) 128 } 129 130 // skipCacheLookup - determine if we should skip the cache lookup 131 func (c *cacheResponseHandler) skipCacheLookup(r *requestfactory.RequestInfo) bool { 132 // check if resource is present on request 133 if !r.IsResourceRequest { 134 return true 135 } 136 137 // check if resource doesn't exist in watched namespaces 138 // if watchedNamespaces[""] exists then we are watching all namespaces 139 // and want to continue 140 _, allNsPresent := c.watchedNamespaces[metav1.NamespaceAll] 141 _, reqNsPresent := c.watchedNamespaces[r.Namespace] 142 if !allNsPresent && !reqNsPresent { 143 return true 144 } 145 146 if strings.HasPrefix(r.Path, "/version") { 147 // Temporarily pass along to API server 148 // Ideally we cache this response as well 149 return true 150 } 151 152 return false 153 } 154 155 func (c *cacheResponseHandler) recoverDependentWatches(req *http.Request, un *unstructured.Unstructured) { 156 ownerRef, err := getRequestOwnerRef(req) 157 if err != nil { 158 log.Error(err, "Could not get ownerRef from proxy") 159 return 160 } 161 162 for _, oRef := range un.GetOwnerReferences() { 163 if oRef.APIVersion == ownerRef.APIVersion && oRef.Kind == ownerRef.Kind { 164 err := addWatchToController(ownerRef, c.cMap, un, c.restMapper, true) 165 if err != nil { 166 log.Error(err, "Could not recover dependent resource watch", "owner", ownerRef) 167 return 168 } 169 } 170 } 171 if typeString, ok := un.GetAnnotations()[osdkHandler.TypeAnnotation]; ok { 172 ownerGV, err := schema.ParseGroupVersion(ownerRef.APIVersion) 173 if err != nil { 174 log.Error(err, "Could not get ownerRef from proxy") 175 return 176 } 177 if typeString == fmt.Sprintf("%v.%v", ownerRef.Kind, ownerGV.Group) { 178 err := addWatchToController(ownerRef, c.cMap, un, c.restMapper, false) 179 if err != nil { 180 log.Error(err, "Could not recover dependent resource watch", "owner", ownerRef) 181 return 182 } 183 } 184 } 185 } 186 187 func (c *cacheResponseHandler) getListFromCache(r *requestfactory.RequestInfo, req *http.Request, k schema.GroupVersionKind) (marshaler, error) { 188 listOptions := &metav1.ListOptions{} 189 if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), metav1.SchemeGroupVersion, listOptions); err != nil { 190 log.Error(err, "Unable to decode list options from request") 191 return nil, err 192 } 193 lo := client.InNamespace(r.Namespace) 194 if err := lo.SetLabelSelector(listOptions.LabelSelector); err != nil { 195 log.Error(err, "Unable to set label selectors for the client") 196 return nil, err 197 } 198 if listOptions.FieldSelector != "" { 199 if err := lo.SetFieldSelector(listOptions.FieldSelector); err != nil { 200 log.Error(err, "Unable to set field selectors for the client") 201 return nil, err 202 } 203 } 204 k.Kind = k.Kind + "List" 205 un := unstructured.UnstructuredList{} 206 un.SetGroupVersionKind(k) 207 err := c.informerCache.List(context.Background(), lo, &un) 208 if err != nil { 209 // break here in case resource doesn't exist in cache but exists on APIserver 210 // This is very unlikely but provides user with expected 404 211 log.Info(fmt.Sprintf("cache miss: %v err-%v", k, err)) 212 return nil, err 213 } 214 return &un, nil 215 } 216 217 func (c *cacheResponseHandler) getObjectFromCache(r *requestfactory.RequestInfo, req *http.Request, k schema.GroupVersionKind) (marshaler, error) { 218 un := &unstructured.Unstructured{} 219 un.SetGroupVersionKind(k) 220 obj := client.ObjectKey{Namespace: r.Namespace, Name: r.Name} 221 err := c.informerCache.Get(context.Background(), obj, un) 222 if err != nil { 223 // break here in case resource doesn't exist in cache but exists on APIserver 224 // This is very unlikely but provides user with expected 404 225 log.Info(fmt.Sprintf("Cache miss: %v, %v", k, obj)) 226 return nil, err 227 } 228 // Once we get the resource, we are going to attempt to recover the dependent watches here, 229 // This will happen in the background, and log errors. 230 if c.injectOwnerRef { 231 go c.recoverDependentWatches(req, un) 232 } 233 return un, nil 234 }