Requeue unobserved nodes in attemptToDelete

pull/6/head
Jordan Liggitt 2017-12-21 02:56:09 -05:00
parent a7c7da76d5
commit df60789a7e
No known key found for this signature in database
GPG Key ID: 39928704103C7229
3 changed files with 46 additions and 28 deletions

View File

@ -38,7 +38,6 @@ import (
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
"k8s.io/client-go/util/workqueue" "k8s.io/client-go/util/workqueue"
"k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly"
_ "k8s.io/kubernetes/pkg/util/reflector/prometheus" // for reflector metric registration _ "k8s.io/kubernetes/pkg/util/reflector/prometheus" // for reflector metric registration
// install the prometheus plugin // install the prometheus plugin
_ "k8s.io/kubernetes/pkg/util/workqueue/prometheus" _ "k8s.io/kubernetes/pkg/util/workqueue/prometheus"
@ -259,24 +258,16 @@ func (gc *GarbageCollector) attemptToDeleteWorker() bool {
} }
// retry if garbage collection of an object failed. // retry if garbage collection of an object failed.
gc.attemptToDelete.AddRateLimited(item) gc.attemptToDelete.AddRateLimited(item)
} else if !n.isObserved() {
// requeue if item hasn't been observed via an informer event yet.
// otherwise a virtual node for an item added AND removed during watch reestablishment can get stuck in the graph and never removed.
// see https://issue.k8s.io/56121
glog.V(5).Infof("item %s hasn't been observed via informer yet", n.identity)
gc.attemptToDelete.AddRateLimited(item)
} }
return true return true
} }
func objectReferenceToMetadataOnlyObject(ref objectReference) *metaonly.MetadataOnlyObject {
return &metaonly.MetadataOnlyObject{
TypeMeta: metav1.TypeMeta{
APIVersion: ref.APIVersion,
Kind: ref.Kind,
},
ObjectMeta: metav1.ObjectMeta{
Namespace: ref.Namespace,
UID: ref.UID,
Name: ref.Name,
},
}
}
// isDangling check if a reference is pointing to an object that doesn't exist. // isDangling check if a reference is pointing to an object that doesn't exist.
// If isDangling looks up the referenced object at the API server, it also // If isDangling looks up the referenced object at the API server, it also
// returns its latest state. // returns its latest state.
@ -353,15 +344,6 @@ func (gc *GarbageCollector) classifyReferences(item *node, latestReferences []me
return solid, dangling, waitingForDependentsDeletion, nil return solid, dangling, waitingForDependentsDeletion, nil
} }
func (gc *GarbageCollector) generateVirtualDeleteEvent(identity objectReference) {
event := &event{
eventType: deleteEvent,
obj: objectReferenceToMetadataOnlyObject(identity),
}
glog.V(5).Infof("generating virtual delete event for %s\n\n", event.obj)
gc.dependencyGraphBuilder.enqueueChanges(event)
}
func ownerRefsToUIDs(refs []metav1.OwnerReference) []types.UID { func ownerRefsToUIDs(refs []metav1.OwnerReference) []types.UID {
var ret []types.UID var ret []types.UID
for _, ref := range refs { for _, ref := range refs {
@ -387,7 +369,10 @@ func (gc *GarbageCollector) attemptToDeleteItem(item *node) error {
// exist yet, so we need to enqueue a virtual Delete event to remove // exist yet, so we need to enqueue a virtual Delete event to remove
// the virtual node from GraphBuilder.uidToNode. // the virtual node from GraphBuilder.uidToNode.
glog.V(5).Infof("item %v not found, generating a virtual delete event", item.identity) glog.V(5).Infof("item %v not found, generating a virtual delete event", item.identity)
gc.generateVirtualDeleteEvent(item.identity) gc.dependencyGraphBuilder.enqueueVirtualDeleteEvent(item.identity)
// since we're manually inserting a delete event to remove this node,
// we don't need to keep tracking it as a virtual node and requeueing in attemptToDelete
item.markObserved()
return nil return nil
case err != nil: case err != nil:
return err return err
@ -395,7 +380,10 @@ func (gc *GarbageCollector) attemptToDeleteItem(item *node) error {
if latest.GetUID() != item.identity.UID { if latest.GetUID() != item.identity.UID {
glog.V(5).Infof("UID doesn't match, item %v not found, generating a virtual delete event", item.identity) glog.V(5).Infof("UID doesn't match, item %v not found, generating a virtual delete event", item.identity)
gc.generateVirtualDeleteEvent(item.identity) gc.dependencyGraphBuilder.enqueueVirtualDeleteEvent(item.identity)
// since we're manually inserting a delete event to remove this node,
// we don't need to keep tracking it as a virtual node and requeueing in attemptToDelete
item.markObserved()
return nil return nil
} }

View File

@ -53,6 +53,9 @@ type node struct {
// this records if the object's deletionTimestamp is non-nil. // this records if the object's deletionTimestamp is non-nil.
beingDeleted bool beingDeleted bool
beingDeletedLock sync.RWMutex beingDeletedLock sync.RWMutex
// this records if the object was constructed virtually and never observed via informer event
virtual bool
virtualLock sync.RWMutex
// when processing an Update event, we need to compare the updated // when processing an Update event, we need to compare the updated
// ownerReferences with the owners recorded in the graph. // ownerReferences with the owners recorded in the graph.
owners []metav1.OwnerReference owners []metav1.OwnerReference
@ -72,6 +75,17 @@ func (n *node) isBeingDeleted() bool {
return n.beingDeleted return n.beingDeleted
} }
func (n *node) markObserved() {
n.virtualLock.Lock()
defer n.virtualLock.Unlock()
n.virtual = false
}
func (n *node) isObserved() bool {
n.virtualLock.RLock()
defer n.virtualLock.RUnlock()
return n.virtual == false
}
func (n *node) markDeletingDependents() { func (n *node) markDeletingDependents() {
n.deletingDependentsLock.Lock() n.deletingDependentsLock.Lock()
defer n.deletingDependentsLock.Unlock() defer n.deletingDependentsLock.Unlock()

View File

@ -37,6 +37,7 @@ import (
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue" "k8s.io/client-go/util/workqueue"
"k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly"
) )
type eventType int type eventType int
@ -369,8 +370,16 @@ func DefaultIgnoredResources() map[schema.GroupResource]struct{} {
return ignoredResources return ignoredResources
} }
func (gb *GraphBuilder) enqueueChanges(e *event) { // enqueueVirtualDeleteEvent is used to add a virtual delete event to be processed for virtual nodes
gb.graphChanges.Add(e) // once it is determined they do not have backing objects in storage
func (gb *GraphBuilder) enqueueVirtualDeleteEvent(ref objectReference) {
gb.graphChanges.Add(&event{
eventType: deleteEvent,
obj: &metaonly.MetadataOnlyObject{
TypeMeta: metav1.TypeMeta{APIVersion: ref.APIVersion, Kind: ref.Kind},
ObjectMeta: metav1.ObjectMeta{Namespace: ref.Namespace, UID: ref.UID, Name: ref.Name},
},
})
} }
// addDependentToOwners adds n to owners' dependents list. If the owner does not // addDependentToOwners adds n to owners' dependents list. If the owner does not
@ -389,6 +398,7 @@ func (gb *GraphBuilder) addDependentToOwners(n *node, owners []metav1.OwnerRefer
Namespace: n.identity.Namespace, Namespace: n.identity.Namespace,
}, },
dependents: make(map[*node]struct{}), dependents: make(map[*node]struct{}),
virtual: true,
} }
glog.V(5).Infof("add virtual node.identity: %s\n\n", ownerNode.identity) glog.V(5).Infof("add virtual node.identity: %s\n\n", ownerNode.identity)
gb.uidToNode.Write(ownerNode) gb.uidToNode.Write(ownerNode)
@ -591,6 +601,12 @@ func (gb *GraphBuilder) processGraphChanges() bool {
glog.V(5).Infof("GraphBuilder process object: %s/%s, namespace %s, name %s, uid %s, event type %v", event.gvk.GroupVersion().String(), event.gvk.Kind, accessor.GetNamespace(), accessor.GetName(), string(accessor.GetUID()), event.eventType) glog.V(5).Infof("GraphBuilder process object: %s/%s, namespace %s, name %s, uid %s, event type %v", event.gvk.GroupVersion().String(), event.gvk.Kind, accessor.GetNamespace(), accessor.GetName(), string(accessor.GetUID()), event.eventType)
// Check if the node already exsits // Check if the node already exsits
existingNode, found := gb.uidToNode.Read(accessor.GetUID()) existingNode, found := gb.uidToNode.Read(accessor.GetUID())
if found {
// this marks the node as having been observed via an informer event
// 1. this depends on graphChanges only containing add/update events from the actual informer
// 2. this allows things tracking virtual nodes' existence to stop polling and rely on informer events
existingNode.markObserved()
}
switch { switch {
case (event.eventType == addEvent || event.eventType == updateEvent) && !found: case (event.eventType == addEvent || event.eventType == updateEvent) && !found:
newNode := &node{ newNode := &node{