Skip to content

Commit 2bfe453

Browse files
Shawn Hurleyfabianvf
authored andcommitted
pkg/ansible/proxy: refactor proxy to remove public exposed functions (#1413)
1 parent 83d06d0 commit 2bfe453

File tree

3 files changed

+436
-326
lines changed

3 files changed

+436
-326
lines changed
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
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+
}

0 commit comments

Comments
 (0)