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  }