Skip to content

Commit f9d902f

Browse files
author
Shawn Hurley
authored
pkg/ansible: Bugfix/update owner ref behavior (#1148)
* Handles watching dependent resources accross namespace boundaries * handles namespaced resource watching cluster resource * handles namespaced resource watching namespaced resource in another namespace * Removing owner references crossing namespace boundaries * Fixing controller map to watch gvk's across namespaces * need to handle same GVK with different enqueue behavior
1 parent e89a1eb commit f9d902f

File tree

5 files changed

+245
-111
lines changed

5 files changed

+245
-111
lines changed

Gopkg.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/ansible/operator/operator.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,11 @@ func Run(done chan error, mgr manager.Manager, f *flags.AnsibleOperatorFlags, cM
7373
done <- errors.New("failed to add controller")
7474
return
7575
}
76-
cMap.Store(o.GVK, &controllermap.ControllerMapContents{Controller: *ctr,
76+
cMap.Store(o.GVK, &controllermap.Contents{Controller: *ctr,
7777
WatchDependentResources: runner.GetWatchDependentResources(),
7878
WatchClusterScopedResources: runner.GetWatchClusterScopedResources(),
79-
WatchMap: controllermap.NewWatchMap(),
80-
UIDMap: controllermap.NewUIDMap(),
79+
OwnerWatchMap: controllermap.NewWatchMap(),
80+
AnnotationWatchMap: controllermap.NewWatchMap(),
8181
})
8282
}
8383
done <- mgr.Start(c)

pkg/ansible/proxy/controllermap/controllermap.go

Lines changed: 8 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,13 @@ import (
1818
"sync"
1919

2020
"k8s.io/apimachinery/pkg/runtime/schema"
21-
"k8s.io/apimachinery/pkg/types"
2221
"sigs.k8s.io/controller-runtime/pkg/controller"
2322
)
2423

2524
// ControllerMap - map of GVK to ControllerMapContents
2625
type ControllerMap struct {
2726
mutex sync.RWMutex
28-
internal map[schema.GroupVersionKind]*ControllerMapContents
29-
}
30-
31-
// UIDMap - map of UID to namespaced name of owner
32-
type UIDMap struct {
33-
mutex sync.RWMutex
34-
internal map[types.UID]types.NamespacedName
27+
internal map[schema.GroupVersionKind]*Contents
3528
}
3629

3730
// WatchMap - map of GVK to interface. Determines if resource is being watched already
@@ -40,20 +33,20 @@ type WatchMap struct {
4033
internal map[schema.GroupVersionKind]interface{}
4134
}
4235

43-
// ControllerMapContents- Contains internal data associated with each controller
44-
type ControllerMapContents struct {
36+
// Contents - Contains internal data associated with each controller
37+
type Contents struct {
4538
Controller controller.Controller
4639
WatchDependentResources bool
4740
WatchClusterScopedResources bool
48-
WatchMap *WatchMap
49-
UIDMap *UIDMap
41+
OwnerWatchMap *WatchMap
42+
AnnotationWatchMap *WatchMap
5043
}
5144

5245
// NewControllerMap returns a new object that contains a mapping between GVK
5346
// and ControllerMapContents object
5447
func NewControllerMap() *ControllerMap {
5548
return &ControllerMap{
56-
internal: make(map[schema.GroupVersionKind]*ControllerMapContents),
49+
internal: make(map[schema.GroupVersionKind]*Contents),
5750
}
5851
}
5952

@@ -65,16 +58,9 @@ func NewWatchMap() *WatchMap {
6558
}
6659
}
6760

68-
// NewUIDMap - returns a new object that maps UID to namespaced name of owner
69-
func NewUIDMap() *UIDMap {
70-
return &UIDMap{
71-
internal: make(map[types.UID]types.NamespacedName),
72-
}
73-
}
74-
7561
// Get - Returns a ControllerMapContents given a GVK as the key. `ok`
7662
// determines if the key exists
77-
func (cm *ControllerMap) Get(key schema.GroupVersionKind) (value *ControllerMapContents, ok bool) {
63+
func (cm *ControllerMap) Get(key schema.GroupVersionKind) (value *Contents, ok bool) {
7864
cm.mutex.RLock()
7965
defer cm.mutex.RUnlock()
8066
value, ok = cm.internal[key]
@@ -89,7 +75,7 @@ func (cm *ControllerMap) Delete(key schema.GroupVersionKind) {
8975
}
9076

9177
// Store - Adds a new GVK to controller mapping
92-
func (cm *ControllerMap) Store(key schema.GroupVersionKind, value *ControllerMapContents) {
78+
func (cm *ControllerMap) Store(key schema.GroupVersionKind, value *Contents) {
9379
cm.mutex.Lock()
9480
defer cm.mutex.Unlock()
9581
cm.internal[key] = value
@@ -116,25 +102,3 @@ func (wm *WatchMap) Store(key schema.GroupVersionKind) {
116102
defer wm.mutex.Unlock()
117103
wm.internal[key] = nil
118104
}
119-
120-
// Get - Returns a NamespacedName of the owner given a UID
121-
func (um *UIDMap) Get(key types.UID) (value types.NamespacedName, ok bool) {
122-
um.mutex.RLock()
123-
defer um.mutex.RUnlock()
124-
value, ok = um.internal[key]
125-
return value, ok
126-
}
127-
128-
// Delete - Deletes associated UID to NamespacedName mapping
129-
func (um *UIDMap) Delete(key types.UID) {
130-
um.mutex.Lock()
131-
defer um.mutex.Unlock()
132-
delete(um.internal, key)
133-
}
134-
135-
// Store - Adds a new UID to NamespacedName mapping
136-
func (um *UIDMap) Store(key types.UID, value types.NamespacedName) {
137-
um.mutex.Lock()
138-
defer um.mutex.Unlock()
139-
um.internal[key] = value
140-
}

pkg/ansible/proxy/proxy.go

Lines changed: 118 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,17 @@ import (
3232
"github.com/operator-framework/operator-sdk/pkg/ansible/proxy/controllermap"
3333
"github.com/operator-framework/operator-sdk/pkg/ansible/proxy/kubeconfig"
3434
k8sRequest "github.com/operator-framework/operator-sdk/pkg/ansible/proxy/requestfactory"
35+
osdkHandler "github.com/operator-framework/operator-sdk/pkg/handler"
3536
"k8s.io/apimachinery/pkg/api/meta"
3637
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
3738
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3839
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3940
"k8s.io/apimachinery/pkg/runtime/schema"
40-
"k8s.io/apimachinery/pkg/types"
4141
"k8s.io/apimachinery/pkg/util/sets"
4242
"k8s.io/client-go/rest"
4343
"sigs.k8s.io/controller-runtime/pkg/cache"
4444
"sigs.k8s.io/controller-runtime/pkg/client"
4545
"sigs.k8s.io/controller-runtime/pkg/handler"
46-
"sigs.k8s.io/controller-runtime/pkg/reconcile"
4746
"sigs.k8s.io/controller-runtime/pkg/source"
4847
)
4948

@@ -152,25 +151,8 @@ func CacheResponseHandler(h http.Handler, informerCache cache.Cache, restMapper
152151
// Once we get the resource, we are going to attempt to recover the dependent watches here,
153152
// This will happen in the background, and log errors.
154153
if injectOwnerRef {
155-
go func() {
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, cMap, un, restMapper)
165-
if err != nil {
166-
log.Error(err, "Could not recover dependent resource watch", "owner", ownerRef)
167-
return
168-
}
169-
}
170-
}
171-
}()
154+
go recoverDependentWatches(req, un, cMap, restMapper)
172155
}
173-
174156
}
175157

176158
i := bytes.Buffer{}
@@ -203,6 +185,38 @@ func CacheResponseHandler(h http.Handler, informerCache cache.Cache, restMapper
203185
})
204186
}
205187

188+
func recoverDependentWatches(req *http.Request, un *unstructured.Unstructured, cMap *controllermap.ControllerMap, restMapper meta.RESTMapper) {
189+
ownerRef, err := getRequestOwnerRef(req)
190+
if err != nil {
191+
log.Error(err, "Could not get ownerRef from proxy")
192+
return
193+
}
194+
195+
for _, oRef := range un.GetOwnerReferences() {
196+
if oRef.APIVersion == ownerRef.APIVersion && oRef.Kind == ownerRef.Kind {
197+
err := addWatchToController(ownerRef, cMap, un, restMapper, true)
198+
if err != nil {
199+
log.Error(err, "Could not recover dependent resource watch", "owner", ownerRef)
200+
return
201+
}
202+
}
203+
}
204+
if typeString, ok := un.GetAnnotations()[osdkHandler.TypeAnnotation]; ok {
205+
ownerGV, err := schema.ParseGroupVersion(ownerRef.APIVersion)
206+
if err != nil {
207+
log.Error(err, "Could not get ownerRef from proxy")
208+
return
209+
}
210+
if typeString == fmt.Sprintf("%v.%v", ownerRef.Kind, ownerGV.Group) {
211+
err := addWatchToController(ownerRef, cMap, un, restMapper, false)
212+
if err != nil {
213+
log.Error(err, "Could not recover dependent resource watch", "owner", ownerRef)
214+
return
215+
}
216+
}
217+
}
218+
}
219+
206220
// InjectOwnerReferenceHandler will handle proxied requests and inject the
207221
// owner reference found in the authorization header. The Authorization is
208222
// then deleted so that the proxy can re-set with the correct authorization.
@@ -248,7 +262,33 @@ func InjectOwnerReferenceHandler(h http.Handler, cMap *controllermap.ControllerM
248262
http.Error(w, m, http.StatusBadRequest)
249263
return
250264
}
251-
data.SetOwnerReferences(append(data.GetOwnerReferences(), owner.OwnerReference))
265+
266+
addOwnerRef, err := shouldAddOwnerRef(data, owner, restMapper)
267+
if err != nil {
268+
m := "Could not determine if we should add owner ref"
269+
log.Error(err, m)
270+
http.Error(w, m, http.StatusBadRequest)
271+
return
272+
}
273+
if addOwnerRef {
274+
data.SetOwnerReferences(append(data.GetOwnerReferences(), owner.OwnerReference))
275+
} else {
276+
ownerGV, err := schema.ParseGroupVersion(owner.APIVersion)
277+
if err != nil {
278+
m := fmt.Sprintf("could not get broup version for: %v", owner)
279+
log.Error(err, m)
280+
http.Error(w, m, http.StatusBadRequest)
281+
return
282+
}
283+
a := data.GetAnnotations()
284+
if a == nil {
285+
a = map[string]string{}
286+
}
287+
a[osdkHandler.NamespacedNameAnnotation] = strings.Join([]string{owner.Namespace, owner.Name}, "/")
288+
a[osdkHandler.TypeAnnotation] = fmt.Sprintf("%v.%v", owner.Kind, ownerGV.Group)
289+
290+
data.SetAnnotations(a)
291+
}
252292
newBody, err := json.Marshal(data.Object)
253293
if err != nil {
254294
m := "Could not serialize body"
@@ -269,7 +309,7 @@ func InjectOwnerReferenceHandler(h http.Handler, cMap *controllermap.ControllerM
269309
_, allNsPresent := watchedNamespaces[metav1.NamespaceAll]
270310
_, reqNsPresent := watchedNamespaces[r.Namespace]
271311
if allNsPresent || reqNsPresent {
272-
err = addWatchToController(owner, cMap, data, restMapper)
312+
err = addWatchToController(owner, cMap, data, restMapper, addOwnerRef)
273313
if err != nil {
274314
m := "could not add watch to controller"
275315
log.Error(err, m)
@@ -289,6 +329,39 @@ func removeAuthorizationHeader(h http.Handler) http.Handler {
289329
})
290330
}
291331

332+
func shouldAddOwnerRef(data *unstructured.Unstructured, owner kubeconfig.NamespacedOwnerReference, restMapper meta.RESTMapper) (bool, error) {
333+
dataMapping, err := restMapper.RESTMapping(data.GroupVersionKind().GroupKind(), data.GroupVersionKind().Version)
334+
if err != nil {
335+
m := fmt.Sprintf("Could not get rest mapping for: %v", data.GroupVersionKind())
336+
log.Error(err, m)
337+
return false, err
338+
339+
}
340+
// We need to determine whether or not the owner is a cluster-scoped
341+
// resource because enqueue based on an owner reference does not work if
342+
// a namespaced resource owns a cluster-scoped resource
343+
ownerGV, err := schema.ParseGroupVersion(owner.APIVersion)
344+
if err != nil {
345+
m := fmt.Sprintf("could not get group version for: %v", owner)
346+
log.Error(err, m)
347+
return false, err
348+
}
349+
ownerMapping, err := restMapper.RESTMapping(schema.GroupKind{Kind: owner.Kind, Group: ownerGV.Group}, ownerGV.Version)
350+
if err != nil {
351+
m := fmt.Sprintf("could not get rest mapping for: %v", owner)
352+
log.Error(err, m)
353+
return false, err
354+
}
355+
356+
dataNamespaceScoped := dataMapping.Scope.Name() != meta.RESTScopeNameRoot
357+
ownerNamespaceScoped := ownerMapping.Scope.Name() != meta.RESTScopeNameRoot
358+
359+
if dataNamespaceScoped && ownerNamespaceScoped && data.GetNamespace() == owner.Namespace {
360+
return true, nil
361+
}
362+
return false, nil
363+
}
364+
292365
// RequestLogHandler - log the requests that come through the proxy.
293366
func RequestLogHandler(h http.Handler) http.Handler {
294367
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
@@ -399,17 +472,14 @@ func Run(done chan error, o Options) error {
399472
return nil
400473
}
401474

402-
func addWatchToController(owner kubeconfig.NamespacedOwnerReference, cMap *controllermap.ControllerMap, resource *unstructured.Unstructured, restMapper meta.RESTMapper) error {
475+
func addWatchToController(owner kubeconfig.NamespacedOwnerReference, cMap *controllermap.ControllerMap, resource *unstructured.Unstructured, restMapper meta.RESTMapper, useOwnerRef bool) error {
403476
dataMapping, err := restMapper.RESTMapping(resource.GroupVersionKind().GroupKind(), resource.GroupVersionKind().Version)
404477
if err != nil {
405478
m := fmt.Sprintf("Could not get rest mapping for: %v", resource.GroupVersionKind())
406479
log.Error(err, m)
407480
return err
408481

409482
}
410-
// We need to determine whether or not the owner is a cluster-scoped
411-
// resource because enqueue based on an owner reference does not work if
412-
// a namespaced resource owns a cluster-scoped resource
413483
ownerGV, err := schema.ParseGroupVersion(owner.APIVersion)
414484
if err != nil {
415485
m := fmt.Sprintf("could not get broup version for: %v", owner)
@@ -424,59 +494,43 @@ func addWatchToController(owner kubeconfig.NamespacedOwnerReference, cMap *contr
424494
}
425495

426496
dataNamespaceScoped := dataMapping.Scope.Name() != meta.RESTScopeNameRoot
427-
ownerNamespaceScoped := ownerMapping.Scope.Name() != meta.RESTScopeNameRoot
428-
useOwnerReference := !ownerNamespaceScoped || dataNamespaceScoped
429497
contents, ok := cMap.Get(ownerMapping.GroupVersionKind)
430498
if !ok {
431499
return errors.New("failed to find controller in map")
432500
}
433-
wMap := contents.WatchMap
434-
uMap := contents.UIDMap
501+
owMap := contents.OwnerWatchMap
502+
awMap := contents.AnnotationWatchMap
435503
u := &unstructured.Unstructured{}
436504
u.SetGroupVersionKind(ownerMapping.GroupVersionKind)
437505
// Add a watch to controller
438506
if contents.WatchDependentResources {
439-
// Store UID
440-
uMap.Store(owner.UID, types.NamespacedName{
441-
Name: owner.Name,
442-
Namespace: owner.Namespace,
443-
})
444-
_, exists := wMap.Get(resource.GroupVersionKind())
445-
// If already watching resource no need to add a new watch
446-
if exists {
447-
return nil
448-
}
449507
// Store watch in map
450-
wMap.Store(resource.GroupVersionKind())
451508
// Use EnqueueRequestForOwner unless user has configured watching cluster scoped resources and we have to
452-
if useOwnerReference {
509+
switch {
510+
case useOwnerRef:
511+
_, exists := owMap.Get(resource.GroupVersionKind())
512+
// If already watching resource no need to add a new watch
513+
if exists {
514+
return nil
515+
}
516+
517+
owMap.Store(resource.GroupVersionKind())
453518
log.Info("Watching child resource", "kind", resource.GroupVersionKind(), "enqueue_kind", u.GroupVersionKind())
454519
// Store watch in map
455-
err = contents.Controller.Watch(&source.Kind{Type: resource}, &handler.EnqueueRequestForOwner{OwnerType: u})
520+
err := contents.Controller.Watch(&source.Kind{Type: resource}, &handler.EnqueueRequestForOwner{OwnerType: u})
456521
if err != nil {
457522
return err
458523
}
459-
} else if contents.WatchClusterScopedResources {
460-
log.Info("Watching child resource which can be cluster-scoped", "kind", resource.GroupVersionKind(), "enqueue_kind", u.GroupVersionKind())
461-
// Add watch
462-
err = contents.Controller.Watch(
463-
&source.Kind{Type: resource},
464-
// Use Map func since EnqueuRequestForOwner won't work
465-
&handler.EnqueueRequestsFromMapFunc{ToRequests: handler.ToRequestsFunc(func(a handler.MapObject) []reconcile.Request {
466-
log.V(2).Info("Creating reconcile request from object", "gvk", ownerMapping.GroupVersionKind, "name", a.Meta.GetName())
467-
ownRefs := a.Meta.GetOwnerReferences()
468-
for _, ref := range ownRefs {
469-
nn, exists := uMap.Get(ref.UID)
470-
if !exists {
471-
continue
472-
}
473-
return []reconcile.Request{
474-
{NamespacedName: nn},
475-
}
476-
}
477-
return nil
478-
})},
479-
)
524+
case (!useOwnerRef && dataNamespaceScoped) || contents.WatchClusterScopedResources:
525+
_, exists := awMap.Get(resource.GroupVersionKind())
526+
// If already watching resource no need to add a new watch
527+
if exists {
528+
return nil
529+
}
530+
awMap.Store(resource.GroupVersionKind())
531+
typeString := fmt.Sprintf("%v.%v", owner.Kind, ownerGV.Group)
532+
log.Info("Watching child resource", "kind", resource.GroupVersionKind(), "enqueue_annotation_type", typeString)
533+
err = contents.Controller.Watch(&source.Kind{Type: resource}, &osdkHandler.EnqueueRequestForAnnotation{Type: typeString})
480534
if err != nil {
481535
return err
482536
}

0 commit comments

Comments
 (0)