mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 18:43:34 +00:00
vendor files
This commit is contained in:
92
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/BUILD
generated
vendored
Normal file
92
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/BUILD
generated
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"errors.go",
|
||||
"garbagecollector.go",
|
||||
"graph.go",
|
||||
"graph_builder.go",
|
||||
"operations.go",
|
||||
"patch.go",
|
||||
"uid_cache.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/controller/garbagecollector",
|
||||
deps = [
|
||||
"//pkg/controller:go_default_library",
|
||||
"//pkg/controller/garbagecollector/metaonly:go_default_library",
|
||||
"//pkg/util/reflector/prometheus:go_default_library",
|
||||
"//pkg/util/workqueue/prometheus:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/github.com/golang/groupcache/lru:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||
"//vendor/k8s.io/client-go/discovery:go_default_library",
|
||||
"//vendor/k8s.io/client-go/dynamic:go_default_library",
|
||||
"//vendor/k8s.io/client-go/informers:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/retry:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/workqueue:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["garbagecollector_test.go"],
|
||||
importpath = "k8s.io/kubernetes/pkg/controller/garbagecollector",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/api/legacyscheme:go_default_library",
|
||||
"//pkg/apis/core/install:go_default_library",
|
||||
"//pkg/controller/garbagecollector/metaonly:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
|
||||
"//vendor/k8s.io/client-go/discovery:go_default_library",
|
||||
"//vendor/k8s.io/client-go/dynamic:go_default_library",
|
||||
"//vendor/k8s.io/client-go/informers:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/workqueue:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/controller/garbagecollector/metaonly:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
8
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/OWNERS
generated
vendored
Executable file
8
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/OWNERS
generated
vendored
Executable file
@ -0,0 +1,8 @@
|
||||
approvers:
|
||||
- caesarxuchao
|
||||
- lavalamp
|
||||
- deads2k
|
||||
reviewers:
|
||||
- caesarxuchao
|
||||
- lavalamp
|
||||
- deads2k
|
43
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/errors.go
generated
vendored
Normal file
43
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/errors.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package garbagecollector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type restMappingError struct {
|
||||
kind string
|
||||
version string
|
||||
}
|
||||
|
||||
func (r *restMappingError) Error() string {
|
||||
versionKind := fmt.Sprintf("%s/%s", r.version, r.kind)
|
||||
return fmt.Sprintf("unable to get REST mapping for %s.", versionKind)
|
||||
}
|
||||
|
||||
// Message prints more details
|
||||
func (r *restMappingError) Message() string {
|
||||
versionKind := fmt.Sprintf("%s/%s", r.version, r.kind)
|
||||
errMsg := fmt.Sprintf("unable to get REST mapping for %s. ", versionKind)
|
||||
errMsg += fmt.Sprintf(" If %s is an invalid resource, then you should manually remove ownerReferences that refer %s objects.", versionKind, versionKind)
|
||||
return errMsg
|
||||
}
|
||||
|
||||
func newRESTMappingError(kind, version string) *restMappingError {
|
||||
return &restMappingError{kind: kind, version: version}
|
||||
}
|
615
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/garbagecollector.go
generated
vendored
Normal file
615
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/garbagecollector.go
generated
vendored
Normal file
@ -0,0 +1,615 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package garbagecollector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
_ "k8s.io/kubernetes/pkg/util/reflector/prometheus" // for reflector metric registration
|
||||
// install the prometheus plugin
|
||||
_ "k8s.io/kubernetes/pkg/util/workqueue/prometheus"
|
||||
// import known versions
|
||||
_ "k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
const ResourceResyncTime time.Duration = 0
|
||||
|
||||
// GarbageCollector runs reflectors to watch for changes of managed API
|
||||
// objects, funnels the results to a single-threaded dependencyGraphBuilder,
|
||||
// which builds a graph caching the dependencies among objects. Triggered by the
|
||||
// graph changes, the dependencyGraphBuilder enqueues objects that can
|
||||
// potentially be garbage-collected to the `attemptToDelete` queue, and enqueues
|
||||
// objects whose dependents need to be orphaned to the `attemptToOrphan` queue.
|
||||
// The GarbageCollector has workers who consume these two queues, send requests
|
||||
// to the API server to delete/update the objects accordingly.
|
||||
// Note that having the dependencyGraphBuilder notify the garbage collector
|
||||
// ensures that the garbage collector operates with a graph that is at least as
|
||||
// up to date as the notification is sent.
|
||||
type GarbageCollector struct {
|
||||
restMapper resettableRESTMapper
|
||||
// clientPool uses the regular dynamicCodec. We need it to update
|
||||
// finalizers. It can be removed if we support patching finalizers.
|
||||
clientPool dynamic.ClientPool
|
||||
// garbage collector attempts to delete the items in attemptToDelete queue when the time is ripe.
|
||||
attemptToDelete workqueue.RateLimitingInterface
|
||||
// garbage collector attempts to orphan the dependents of the items in the attemptToOrphan queue, then deletes the items.
|
||||
attemptToOrphan workqueue.RateLimitingInterface
|
||||
dependencyGraphBuilder *GraphBuilder
|
||||
// GC caches the owners that do not exist according to the API server.
|
||||
absentOwnerCache *UIDCache
|
||||
sharedInformers informers.SharedInformerFactory
|
||||
|
||||
workerLock sync.RWMutex
|
||||
}
|
||||
|
||||
func NewGarbageCollector(
|
||||
metaOnlyClientPool dynamic.ClientPool,
|
||||
clientPool dynamic.ClientPool,
|
||||
mapper resettableRESTMapper,
|
||||
deletableResources map[schema.GroupVersionResource]struct{},
|
||||
ignoredResources map[schema.GroupResource]struct{},
|
||||
sharedInformers informers.SharedInformerFactory,
|
||||
informersStarted <-chan struct{},
|
||||
) (*GarbageCollector, error) {
|
||||
attemptToDelete := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "garbage_collector_attempt_to_delete")
|
||||
attemptToOrphan := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "garbage_collector_attempt_to_orphan")
|
||||
absentOwnerCache := NewUIDCache(500)
|
||||
gc := &GarbageCollector{
|
||||
clientPool: clientPool,
|
||||
restMapper: mapper,
|
||||
attemptToDelete: attemptToDelete,
|
||||
attemptToOrphan: attemptToOrphan,
|
||||
absentOwnerCache: absentOwnerCache,
|
||||
}
|
||||
gb := &GraphBuilder{
|
||||
metaOnlyClientPool: metaOnlyClientPool,
|
||||
informersStarted: informersStarted,
|
||||
restMapper: mapper,
|
||||
graphChanges: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "garbage_collector_graph_changes"),
|
||||
uidToNode: &concurrentUIDToNode{
|
||||
uidToNode: make(map[types.UID]*node),
|
||||
},
|
||||
attemptToDelete: attemptToDelete,
|
||||
attemptToOrphan: attemptToOrphan,
|
||||
absentOwnerCache: absentOwnerCache,
|
||||
sharedInformers: sharedInformers,
|
||||
ignoredResources: ignoredResources,
|
||||
}
|
||||
if err := gb.syncMonitors(deletableResources); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to sync all monitors: %v", err))
|
||||
}
|
||||
gc.dependencyGraphBuilder = gb
|
||||
|
||||
return gc, nil
|
||||
}
|
||||
|
||||
// resyncMonitors starts or stops resource monitors as needed to ensure that all
|
||||
// (and only) those resources present in the map are monitored.
|
||||
func (gc *GarbageCollector) resyncMonitors(deletableResources map[schema.GroupVersionResource]struct{}) error {
|
||||
if err := gc.dependencyGraphBuilder.syncMonitors(deletableResources); err != nil {
|
||||
return err
|
||||
}
|
||||
gc.dependencyGraphBuilder.startMonitors()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gc *GarbageCollector) Run(workers int, stopCh <-chan struct{}) {
|
||||
defer utilruntime.HandleCrash()
|
||||
defer gc.attemptToDelete.ShutDown()
|
||||
defer gc.attemptToOrphan.ShutDown()
|
||||
defer gc.dependencyGraphBuilder.graphChanges.ShutDown()
|
||||
|
||||
glog.Infof("Starting garbage collector controller")
|
||||
defer glog.Infof("Shutting down garbage collector controller")
|
||||
|
||||
go gc.dependencyGraphBuilder.Run(stopCh)
|
||||
|
||||
if !controller.WaitForCacheSync("garbage collector", stopCh, gc.dependencyGraphBuilder.IsSynced) {
|
||||
return
|
||||
}
|
||||
|
||||
glog.Infof("Garbage collector: all resource monitors have synced. Proceeding to collect garbage")
|
||||
|
||||
// gc workers
|
||||
for i := 0; i < workers; i++ {
|
||||
go wait.Until(gc.runAttemptToDeleteWorker, 1*time.Second, stopCh)
|
||||
go wait.Until(gc.runAttemptToOrphanWorker, 1*time.Second, stopCh)
|
||||
}
|
||||
|
||||
<-stopCh
|
||||
}
|
||||
|
||||
// resettableRESTMapper is a RESTMapper which is capable of resetting itself
|
||||
// from discovery.
|
||||
type resettableRESTMapper interface {
|
||||
meta.RESTMapper
|
||||
Reset()
|
||||
}
|
||||
|
||||
// Sync periodically resyncs the garbage collector when new resources are
|
||||
// observed from discovery. When new resources are detected, Sync will stop all
|
||||
// GC workers, reset gc.restMapper, and resync the monitors.
|
||||
//
|
||||
// Note that discoveryClient should NOT be shared with gc.restMapper, otherwise
|
||||
// the mapper's underlying discovery client will be unnecessarily reset during
|
||||
// the course of detecting new resources.
|
||||
func (gc *GarbageCollector) Sync(discoveryClient discovery.DiscoveryInterface, period time.Duration, stopCh <-chan struct{}) {
|
||||
oldResources := make(map[schema.GroupVersionResource]struct{})
|
||||
wait.Until(func() {
|
||||
// Get the current resource list from discovery.
|
||||
newResources := GetDeletableResources(discoveryClient)
|
||||
|
||||
// Decide whether discovery has reported a change.
|
||||
if reflect.DeepEqual(oldResources, newResources) {
|
||||
glog.V(5).Infof("no resource updates from discovery, skipping garbage collector sync")
|
||||
return
|
||||
}
|
||||
|
||||
// Something has changed, time to sync.
|
||||
glog.V(2).Infof("syncing garbage collector with updated resources from discovery: %v", newResources)
|
||||
|
||||
// Ensure workers are paused to avoid processing events before informers
|
||||
// have resynced.
|
||||
gc.workerLock.Lock()
|
||||
defer gc.workerLock.Unlock()
|
||||
|
||||
// Resetting the REST mapper will also invalidate the underlying discovery
|
||||
// client. This is a leaky abstraction and assumes behavior about the REST
|
||||
// mapper, but we'll deal with it for now.
|
||||
gc.restMapper.Reset()
|
||||
|
||||
// Perform the monitor resync and wait for controllers to report cache sync.
|
||||
//
|
||||
// NOTE: It's possible that newResources will diverge from the resources
|
||||
// discovered by restMapper during the call to Reset, since they are
|
||||
// distinct discovery clients invalidated at different times. For example,
|
||||
// newResources may contain resources not returned in the restMapper's
|
||||
// discovery call if the resources appeared inbetween the calls. In that
|
||||
// case, the restMapper will fail to map some of newResources until the next
|
||||
// sync period.
|
||||
if err := gc.resyncMonitors(newResources); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to sync resource monitors: %v", err))
|
||||
return
|
||||
}
|
||||
// TODO: WaitForCacheSync can block forever during normal operation. Could
|
||||
// pass a timeout channel, but we have to consider the implications of
|
||||
// un-pausing the GC with a partially synced graph builder.
|
||||
if !controller.WaitForCacheSync("garbage collector", stopCh, gc.dependencyGraphBuilder.IsSynced) {
|
||||
utilruntime.HandleError(fmt.Errorf("timed out waiting for dependency graph builder sync during GC sync"))
|
||||
return
|
||||
}
|
||||
|
||||
// Finally, keep track of our new state. Do this after all preceding steps
|
||||
// have succeeded to ensure we'll retry on subsequent syncs if an error
|
||||
// occured.
|
||||
oldResources = newResources
|
||||
glog.V(2).Infof("synced garbage collector")
|
||||
}, period, stopCh)
|
||||
}
|
||||
|
||||
func (gc *GarbageCollector) IsSynced() bool {
|
||||
return gc.dependencyGraphBuilder.IsSynced()
|
||||
}
|
||||
|
||||
func (gc *GarbageCollector) runAttemptToDeleteWorker() {
|
||||
for gc.attemptToDeleteWorker() {
|
||||
}
|
||||
}
|
||||
|
||||
func (gc *GarbageCollector) attemptToDeleteWorker() bool {
|
||||
item, quit := gc.attemptToDelete.Get()
|
||||
gc.workerLock.RLock()
|
||||
defer gc.workerLock.RUnlock()
|
||||
if quit {
|
||||
return false
|
||||
}
|
||||
defer gc.attemptToDelete.Done(item)
|
||||
n, ok := item.(*node)
|
||||
if !ok {
|
||||
utilruntime.HandleError(fmt.Errorf("expect *node, got %#v", item))
|
||||
return true
|
||||
}
|
||||
err := gc.attemptToDeleteItem(n)
|
||||
if err != nil {
|
||||
if _, ok := err.(*restMappingError); ok {
|
||||
// There are at least two ways this can happen:
|
||||
// 1. The reference is to an object of a custom type that has not yet been
|
||||
// recognized by gc.restMapper (this is a transient error).
|
||||
// 2. The reference is to an invalid group/version. We don't currently
|
||||
// have a way to distinguish this from a valid type we will recognize
|
||||
// after the next discovery sync.
|
||||
// For now, record the error and retry.
|
||||
glog.V(5).Infof("error syncing item %s: %v", n, err)
|
||||
} else {
|
||||
utilruntime.HandleError(fmt.Errorf("error syncing item %s: %v", n, err))
|
||||
}
|
||||
// retry if garbage collection of an object failed.
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
// returns its latest state.
|
||||
func (gc *GarbageCollector) isDangling(reference metav1.OwnerReference, item *node) (
|
||||
dangling bool, owner *unstructured.Unstructured, err error) {
|
||||
if gc.absentOwnerCache.Has(reference.UID) {
|
||||
glog.V(5).Infof("according to the absentOwnerCache, object %s's owner %s/%s, %s does not exist", item.identity.UID, reference.APIVersion, reference.Kind, reference.Name)
|
||||
return true, nil, nil
|
||||
}
|
||||
// TODO: we need to verify the reference resource is supported by the
|
||||
// system. If it's not a valid resource, the garbage collector should i)
|
||||
// ignore the reference when decide if the object should be deleted, and
|
||||
// ii) should update the object to remove such references. This is to
|
||||
// prevent objects having references to an old resource from being
|
||||
// deleted during a cluster upgrade.
|
||||
fqKind := schema.FromAPIVersionAndKind(reference.APIVersion, reference.Kind)
|
||||
client, err := gc.clientPool.ClientForGroupVersionKind(fqKind)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
resource, err := gc.apiResource(reference.APIVersion, reference.Kind)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
// TODO: It's only necessary to talk to the API server if the owner node
|
||||
// is a "virtual" node. The local graph could lag behind the real
|
||||
// status, but in practice, the difference is small.
|
||||
owner, err = client.Resource(resource, item.identity.Namespace).Get(reference.Name, metav1.GetOptions{})
|
||||
switch {
|
||||
case errors.IsNotFound(err):
|
||||
gc.absentOwnerCache.Add(reference.UID)
|
||||
glog.V(5).Infof("object %s's owner %s/%s, %s is not found", item.identity.UID, reference.APIVersion, reference.Kind, reference.Name)
|
||||
return true, nil, nil
|
||||
case err != nil:
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
if owner.GetUID() != reference.UID {
|
||||
glog.V(5).Infof("object %s's owner %s/%s, %s is not found, UID mismatch", item.identity.UID, reference.APIVersion, reference.Kind, reference.Name)
|
||||
gc.absentOwnerCache.Add(reference.UID)
|
||||
return true, nil, nil
|
||||
}
|
||||
return false, owner, nil
|
||||
}
|
||||
|
||||
// classify the latestReferences to three categories:
|
||||
// solid: the owner exists, and is not "waitingForDependentsDeletion"
|
||||
// dangling: the owner does not exist
|
||||
// waitingForDependentsDeletion: the owner exists, its deletionTimestamp is non-nil, and it has
|
||||
// FinalizerDeletingDependents
|
||||
// This function communicates with the server.
|
||||
func (gc *GarbageCollector) classifyReferences(item *node, latestReferences []metav1.OwnerReference) (
|
||||
solid, dangling, waitingForDependentsDeletion []metav1.OwnerReference, err error) {
|
||||
for _, reference := range latestReferences {
|
||||
isDangling, owner, err := gc.isDangling(reference, item)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
if isDangling {
|
||||
dangling = append(dangling, reference)
|
||||
continue
|
||||
}
|
||||
|
||||
ownerAccessor, err := meta.Accessor(owner)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
if ownerAccessor.GetDeletionTimestamp() != nil && hasDeleteDependentsFinalizer(ownerAccessor) {
|
||||
waitingForDependentsDeletion = append(waitingForDependentsDeletion, reference)
|
||||
} else {
|
||||
solid = append(solid, reference)
|
||||
}
|
||||
}
|
||||
return solid, dangling, waitingForDependentsDeletion, nil
|
||||
}
|
||||
|
||||
func ownerRefsToUIDs(refs []metav1.OwnerReference) []types.UID {
|
||||
var ret []types.UID
|
||||
for _, ref := range refs {
|
||||
ret = append(ret, ref.UID)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (gc *GarbageCollector) attemptToDeleteItem(item *node) error {
|
||||
glog.V(2).Infof("processing item %s", item.identity)
|
||||
// "being deleted" is an one-way trip to the final deletion. We'll just wait for the final deletion, and then process the object's dependents.
|
||||
if item.isBeingDeleted() && !item.isDeletingDependents() {
|
||||
glog.V(5).Infof("processing item %s returned at once, because its DeletionTimestamp is non-nil", item.identity)
|
||||
return nil
|
||||
}
|
||||
// TODO: It's only necessary to talk to the API server if this is a
|
||||
// "virtual" node. The local graph could lag behind the real status, but in
|
||||
// practice, the difference is small.
|
||||
latest, err := gc.getObject(item.identity)
|
||||
switch {
|
||||
case errors.IsNotFound(err):
|
||||
// the GraphBuilder can add "virtual" node for an owner that doesn't
|
||||
// exist yet, so we need to enqueue a virtual Delete event to remove
|
||||
// the virtual node from GraphBuilder.uidToNode.
|
||||
glog.V(5).Infof("item %v not found, generating a virtual delete event", 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
|
||||
case err != nil:
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
// TODO: attemptToOrphanWorker() routine is similar. Consider merging
|
||||
// attemptToOrphanWorker() into attemptToDeleteItem() as well.
|
||||
if item.isDeletingDependents() {
|
||||
return gc.processDeletingDependentsItem(item)
|
||||
}
|
||||
|
||||
// compute if we should delete the item
|
||||
ownerReferences := latest.GetOwnerReferences()
|
||||
if len(ownerReferences) == 0 {
|
||||
glog.V(2).Infof("object %s's doesn't have an owner, continue on next item", item.identity)
|
||||
return nil
|
||||
}
|
||||
|
||||
solid, dangling, waitingForDependentsDeletion, err := gc.classifyReferences(item, ownerReferences)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
glog.V(5).Infof("classify references of %s.\nsolid: %#v\ndangling: %#v\nwaitingForDependentsDeletion: %#v\n", item.identity, solid, dangling, waitingForDependentsDeletion)
|
||||
|
||||
switch {
|
||||
case len(solid) != 0:
|
||||
glog.V(2).Infof("object %s has at least one existing owner: %#v, will not garbage collect", solid, item.identity)
|
||||
if len(dangling) == 0 && len(waitingForDependentsDeletion) == 0 {
|
||||
return nil
|
||||
}
|
||||
glog.V(2).Infof("remove dangling references %#v and waiting references %#v for object %s", dangling, waitingForDependentsDeletion, item.identity)
|
||||
// waitingForDependentsDeletion needs to be deleted from the
|
||||
// ownerReferences, otherwise the referenced objects will be stuck with
|
||||
// the FinalizerDeletingDependents and never get deleted.
|
||||
patch := deleteOwnerRefPatch(item.identity.UID, append(ownerRefsToUIDs(dangling), ownerRefsToUIDs(waitingForDependentsDeletion)...)...)
|
||||
_, err = gc.patchObject(item.identity, patch)
|
||||
return err
|
||||
case len(waitingForDependentsDeletion) != 0 && item.dependentsLength() != 0:
|
||||
deps := item.getDependents()
|
||||
for _, dep := range deps {
|
||||
if dep.isDeletingDependents() {
|
||||
// this circle detection has false positives, we need to
|
||||
// apply a more rigorous detection if this turns out to be a
|
||||
// problem.
|
||||
// there are multiple workers run attemptToDeleteItem in
|
||||
// parallel, the circle detection can fail in a race condition.
|
||||
glog.V(2).Infof("processing object %s, some of its owners and its dependent [%s] have FinalizerDeletingDependents, to prevent potential cycle, its ownerReferences are going to be modified to be non-blocking, then the object is going to be deleted with Foreground", item.identity, dep.identity)
|
||||
patch, err := item.patchToUnblockOwnerReferences()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := gc.patchObject(item.identity, patch); err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
glog.V(2).Infof("at least one owner of object %s has FinalizerDeletingDependents, and the object itself has dependents, so it is going to be deleted in Foreground", item.identity)
|
||||
// the deletion event will be observed by the graphBuilder, so the item
|
||||
// will be processed again in processDeletingDependentsItem. If it
|
||||
// doesn't have dependents, the function will remove the
|
||||
// FinalizerDeletingDependents from the item, resulting in the final
|
||||
// deletion of the item.
|
||||
policy := metav1.DeletePropagationForeground
|
||||
return gc.deleteObject(item.identity, &policy)
|
||||
default:
|
||||
// item doesn't have any solid owner, so it needs to be garbage
|
||||
// collected. Also, none of item's owners is waiting for the deletion of
|
||||
// the dependents, so set propagationPolicy based on existing finalizers.
|
||||
var policy metav1.DeletionPropagation
|
||||
switch {
|
||||
case hasOrphanFinalizer(latest):
|
||||
// if an existing orphan finalizer is already on the object, honor it.
|
||||
policy = metav1.DeletePropagationOrphan
|
||||
case hasDeleteDependentsFinalizer(latest):
|
||||
// if an existing foreground finalizer is already on the object, honor it.
|
||||
policy = metav1.DeletePropagationForeground
|
||||
default:
|
||||
// otherwise, default to background.
|
||||
policy = metav1.DeletePropagationBackground
|
||||
}
|
||||
glog.V(2).Infof("delete object %s with propagation policy %s", item.identity, policy)
|
||||
return gc.deleteObject(item.identity, &policy)
|
||||
}
|
||||
}
|
||||
|
||||
// process item that's waiting for its dependents to be deleted
|
||||
func (gc *GarbageCollector) processDeletingDependentsItem(item *node) error {
|
||||
blockingDependents := item.blockingDependents()
|
||||
if len(blockingDependents) == 0 {
|
||||
glog.V(2).Infof("remove DeleteDependents finalizer for item %s", item.identity)
|
||||
return gc.removeFinalizer(item, metav1.FinalizerDeleteDependents)
|
||||
}
|
||||
for _, dep := range blockingDependents {
|
||||
if !dep.isDeletingDependents() {
|
||||
glog.V(2).Infof("adding %s to attemptToDelete, because its owner %s is deletingDependents", dep.identity, item.identity)
|
||||
gc.attemptToDelete.Add(dep)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// dependents are copies of pointers to the owner's dependents, they don't need to be locked.
|
||||
func (gc *GarbageCollector) orphanDependents(owner objectReference, dependents []*node) error {
|
||||
errCh := make(chan error, len(dependents))
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(dependents))
|
||||
for i := range dependents {
|
||||
go func(dependent *node) {
|
||||
defer wg.Done()
|
||||
// the dependent.identity.UID is used as precondition
|
||||
patch := deleteOwnerRefPatch(dependent.identity.UID, owner.UID)
|
||||
_, err := gc.patchObject(dependent.identity, patch)
|
||||
// note that if the target ownerReference doesn't exist in the
|
||||
// dependent, strategic merge patch will NOT return an error.
|
||||
if err != nil && !errors.IsNotFound(err) {
|
||||
errCh <- fmt.Errorf("orphaning %s failed, %v", dependent.identity, err)
|
||||
}
|
||||
}(dependents[i])
|
||||
}
|
||||
wg.Wait()
|
||||
close(errCh)
|
||||
|
||||
var errorsSlice []error
|
||||
for e := range errCh {
|
||||
errorsSlice = append(errorsSlice, e)
|
||||
}
|
||||
|
||||
if len(errorsSlice) != 0 {
|
||||
return fmt.Errorf("failed to orphan dependents of owner %s, got errors: %s", owner, utilerrors.NewAggregate(errorsSlice).Error())
|
||||
}
|
||||
glog.V(5).Infof("successfully updated all dependents of owner %s", owner)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gc *GarbageCollector) runAttemptToOrphanWorker() {
|
||||
for gc.attemptToOrphanWorker() {
|
||||
}
|
||||
}
|
||||
|
||||
// attemptToOrphanWorker dequeues a node from the attemptToOrphan, then finds its
|
||||
// dependents based on the graph maintained by the GC, then removes it from the
|
||||
// OwnerReferences of its dependents, and finally updates the owner to remove
|
||||
// the "Orphan" finalizer. The node is added back into the attemptToOrphan if any of
|
||||
// these steps fail.
|
||||
func (gc *GarbageCollector) attemptToOrphanWorker() bool {
|
||||
item, quit := gc.attemptToOrphan.Get()
|
||||
gc.workerLock.RLock()
|
||||
defer gc.workerLock.RUnlock()
|
||||
if quit {
|
||||
return false
|
||||
}
|
||||
defer gc.attemptToOrphan.Done(item)
|
||||
owner, ok := item.(*node)
|
||||
if !ok {
|
||||
utilruntime.HandleError(fmt.Errorf("expect *node, got %#v", item))
|
||||
return true
|
||||
}
|
||||
// we don't need to lock each element, because they never get updated
|
||||
owner.dependentsLock.RLock()
|
||||
dependents := make([]*node, 0, len(owner.dependents))
|
||||
for dependent := range owner.dependents {
|
||||
dependents = append(dependents, dependent)
|
||||
}
|
||||
owner.dependentsLock.RUnlock()
|
||||
|
||||
err := gc.orphanDependents(owner.identity, dependents)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("orphanDependents for %s failed with %v", owner.identity, err))
|
||||
gc.attemptToOrphan.AddRateLimited(item)
|
||||
return true
|
||||
}
|
||||
// update the owner, remove "orphaningFinalizer" from its finalizers list
|
||||
err = gc.removeFinalizer(owner, metav1.FinalizerOrphanDependents)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("removeOrphanFinalizer for %s failed with %v", owner.identity, err))
|
||||
gc.attemptToOrphan.AddRateLimited(item)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// *FOR TEST USE ONLY*
|
||||
// GraphHasUID returns if the GraphBuilder has a particular UID store in its
|
||||
// uidToNode graph. It's useful for debugging.
|
||||
// This method is used by integration tests.
|
||||
func (gc *GarbageCollector) GraphHasUID(UIDs []types.UID) bool {
|
||||
for _, u := range UIDs {
|
||||
if _, ok := gc.dependencyGraphBuilder.uidToNode.Read(u); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetDeletableResources returns all resources from discoveryClient that the
|
||||
// garbage collector should recognize and work with. More specifically, all
|
||||
// preferred resources which support the 'delete', 'list', and 'watch' verbs.
|
||||
//
|
||||
// All discovery errors are considered temporary. Upon encountering any error,
|
||||
// GetDeletableResources will log and return any discovered resources it was
|
||||
// able to process (which may be none).
|
||||
func GetDeletableResources(discoveryClient discovery.ServerResourcesInterface) map[schema.GroupVersionResource]struct{} {
|
||||
preferredResources, err := discoveryClient.ServerPreferredResources()
|
||||
if err != nil {
|
||||
if discovery.IsGroupDiscoveryFailedError(err) {
|
||||
glog.Warning("failed to discover some groups: %v", err.(*discovery.ErrGroupDiscoveryFailed).Groups)
|
||||
} else {
|
||||
glog.Warning("failed to discover preferred resources: %v", err)
|
||||
}
|
||||
}
|
||||
if preferredResources == nil {
|
||||
return map[schema.GroupVersionResource]struct{}{}
|
||||
}
|
||||
|
||||
// This is extracted from discovery.GroupVersionResources to allow tolerating
|
||||
// failures on a per-resource basis.
|
||||
deletableResources := discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"delete", "list", "watch"}}, preferredResources)
|
||||
deletableGroupVersionResources := map[schema.GroupVersionResource]struct{}{}
|
||||
for _, rl := range deletableResources {
|
||||
gv, err := schema.ParseGroupVersion(rl.GroupVersion)
|
||||
if err != nil {
|
||||
glog.Warning("ignoring invalid discovered resource %q: %v", rl.GroupVersion, err)
|
||||
continue
|
||||
}
|
||||
for i := range rl.APIResources {
|
||||
deletableGroupVersionResources[schema.GroupVersionResource{Group: gv.Group, Version: gv.Version, Resource: rl.APIResources[i].Name}] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return deletableGroupVersionResources
|
||||
}
|
788
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/garbagecollector_test.go
generated
vendored
Normal file
788
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/garbagecollector_test.go
generated
vendored
Normal file
@ -0,0 +1,788 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package garbagecollector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
_ "k8s.io/kubernetes/pkg/apis/core/install"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly"
|
||||
)
|
||||
|
||||
type testRESTMapper struct {
|
||||
meta.RESTMapper
|
||||
}
|
||||
|
||||
func (_ *testRESTMapper) Reset() {}
|
||||
|
||||
func TestGarbageCollectorConstruction(t *testing.T) {
|
||||
config := &restclient.Config{}
|
||||
config.ContentConfig.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: metaonly.NewMetadataCodecFactory()}
|
||||
tweakableRM := meta.NewDefaultRESTMapper(nil, nil)
|
||||
rm := &testRESTMapper{meta.MultiRESTMapper{tweakableRM, legacyscheme.Registry.RESTMapper()}}
|
||||
metaOnlyClientPool := dynamic.NewClientPool(config, rm, dynamic.LegacyAPIPathResolverFunc)
|
||||
config.ContentConfig.NegotiatedSerializer = nil
|
||||
clientPool := dynamic.NewClientPool(config, rm, dynamic.LegacyAPIPathResolverFunc)
|
||||
podResource := map[schema.GroupVersionResource]struct{}{
|
||||
{Version: "v1", Resource: "pods"}: {},
|
||||
}
|
||||
twoResources := map[schema.GroupVersionResource]struct{}{
|
||||
{Version: "v1", Resource: "pods"}: {},
|
||||
{Group: "tpr.io", Version: "v1", Resource: "unknown"}: {},
|
||||
}
|
||||
client := fake.NewSimpleClientset()
|
||||
sharedInformers := informers.NewSharedInformerFactory(client, 0)
|
||||
|
||||
// No monitor will be constructed for the non-core resource, but the GC
|
||||
// construction will not fail.
|
||||
alwaysStarted := make(chan struct{})
|
||||
close(alwaysStarted)
|
||||
gc, err := NewGarbageCollector(metaOnlyClientPool, clientPool, rm, twoResources, map[schema.GroupResource]struct{}{}, sharedInformers, alwaysStarted)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, 1, len(gc.dependencyGraphBuilder.monitors))
|
||||
|
||||
// Make sure resource monitor syncing creates and stops resource monitors.
|
||||
tweakableRM.Add(schema.GroupVersionKind{Group: "tpr.io", Version: "v1", Kind: "unknown"}, nil)
|
||||
err = gc.resyncMonitors(twoResources)
|
||||
if err != nil {
|
||||
t.Errorf("Failed adding a monitor: %v", err)
|
||||
}
|
||||
assert.Equal(t, 2, len(gc.dependencyGraphBuilder.monitors))
|
||||
|
||||
err = gc.resyncMonitors(podResource)
|
||||
if err != nil {
|
||||
t.Errorf("Failed removing a monitor: %v", err)
|
||||
}
|
||||
assert.Equal(t, 1, len(gc.dependencyGraphBuilder.monitors))
|
||||
|
||||
// Make sure the syncing mechanism also works after Run() has been called
|
||||
stopCh := make(chan struct{})
|
||||
defer close(stopCh)
|
||||
go gc.Run(1, stopCh)
|
||||
|
||||
err = gc.resyncMonitors(twoResources)
|
||||
if err != nil {
|
||||
t.Errorf("Failed adding a monitor: %v", err)
|
||||
}
|
||||
assert.Equal(t, 2, len(gc.dependencyGraphBuilder.monitors))
|
||||
|
||||
err = gc.resyncMonitors(podResource)
|
||||
if err != nil {
|
||||
t.Errorf("Failed removing a monitor: %v", err)
|
||||
}
|
||||
assert.Equal(t, 1, len(gc.dependencyGraphBuilder.monitors))
|
||||
}
|
||||
|
||||
// fakeAction records information about requests to aid in testing.
|
||||
type fakeAction struct {
|
||||
method string
|
||||
path string
|
||||
query string
|
||||
}
|
||||
|
||||
// String returns method=path to aid in testing
|
||||
func (f *fakeAction) String() string {
|
||||
return strings.Join([]string{f.method, f.path}, "=")
|
||||
}
|
||||
|
||||
type FakeResponse struct {
|
||||
statusCode int
|
||||
content []byte
|
||||
}
|
||||
|
||||
// fakeActionHandler holds a list of fakeActions received
|
||||
type fakeActionHandler struct {
|
||||
// statusCode and content returned by this handler for different method + path.
|
||||
response map[string]FakeResponse
|
||||
|
||||
lock sync.Mutex
|
||||
actions []fakeAction
|
||||
}
|
||||
|
||||
// ServeHTTP logs the action that occurred and always returns the associated status code
|
||||
func (f *fakeActionHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
f.actions = append(f.actions, fakeAction{method: request.Method, path: request.URL.Path, query: request.URL.RawQuery})
|
||||
fakeResponse, ok := f.response[request.Method+request.URL.Path]
|
||||
if !ok {
|
||||
fakeResponse.statusCode = 200
|
||||
fakeResponse.content = []byte("{\"kind\": \"List\"}")
|
||||
}
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
response.WriteHeader(fakeResponse.statusCode)
|
||||
response.Write(fakeResponse.content)
|
||||
}
|
||||
|
||||
// testServerAndClientConfig returns a server that listens and a config that can reference it
|
||||
func testServerAndClientConfig(handler func(http.ResponseWriter, *http.Request)) (*httptest.Server, *restclient.Config) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(handler))
|
||||
config := &restclient.Config{
|
||||
Host: srv.URL,
|
||||
}
|
||||
return srv, config
|
||||
}
|
||||
|
||||
type garbageCollector struct {
|
||||
*GarbageCollector
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
func setupGC(t *testing.T, config *restclient.Config) garbageCollector {
|
||||
config.ContentConfig.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: metaonly.NewMetadataCodecFactory()}
|
||||
metaOnlyClientPool := dynamic.NewClientPool(config, legacyscheme.Registry.RESTMapper(), dynamic.LegacyAPIPathResolverFunc)
|
||||
config.ContentConfig.NegotiatedSerializer = nil
|
||||
clientPool := dynamic.NewClientPool(config, legacyscheme.Registry.RESTMapper(), dynamic.LegacyAPIPathResolverFunc)
|
||||
podResource := map[schema.GroupVersionResource]struct{}{{Version: "v1", Resource: "pods"}: {}}
|
||||
client := fake.NewSimpleClientset()
|
||||
sharedInformers := informers.NewSharedInformerFactory(client, 0)
|
||||
alwaysStarted := make(chan struct{})
|
||||
close(alwaysStarted)
|
||||
gc, err := NewGarbageCollector(metaOnlyClientPool, clientPool, &testRESTMapper{legacyscheme.Registry.RESTMapper()}, podResource, ignoredResources, sharedInformers, alwaysStarted)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stop := make(chan struct{})
|
||||
go sharedInformers.Start(stop)
|
||||
return garbageCollector{gc, stop}
|
||||
}
|
||||
|
||||
func getPod(podName string, ownerReferences []metav1.OwnerReference) *v1.Pod {
|
||||
return &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: podName,
|
||||
Namespace: "ns1",
|
||||
OwnerReferences: ownerReferences,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func serilizeOrDie(t *testing.T, object interface{}) []byte {
|
||||
data, err := json.Marshal(object)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// test the attemptToDeleteItem function making the expected actions.
|
||||
func TestAttemptToDeleteItem(t *testing.T) {
|
||||
pod := getPod("ToBeDeletedPod", []metav1.OwnerReference{
|
||||
{
|
||||
Kind: "ReplicationController",
|
||||
Name: "owner1",
|
||||
UID: "123",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
})
|
||||
testHandler := &fakeActionHandler{
|
||||
response: map[string]FakeResponse{
|
||||
"GET" + "/api/v1/namespaces/ns1/replicationcontrollers/owner1": {
|
||||
404,
|
||||
[]byte{},
|
||||
},
|
||||
"GET" + "/api/v1/namespaces/ns1/pods/ToBeDeletedPod": {
|
||||
200,
|
||||
serilizeOrDie(t, pod),
|
||||
},
|
||||
},
|
||||
}
|
||||
srv, clientConfig := testServerAndClientConfig(testHandler.ServeHTTP)
|
||||
defer srv.Close()
|
||||
|
||||
gc := setupGC(t, clientConfig)
|
||||
defer close(gc.stop)
|
||||
|
||||
item := &node{
|
||||
identity: objectReference{
|
||||
OwnerReference: metav1.OwnerReference{
|
||||
Kind: pod.Kind,
|
||||
APIVersion: pod.APIVersion,
|
||||
Name: pod.Name,
|
||||
UID: pod.UID,
|
||||
},
|
||||
Namespace: pod.Namespace,
|
||||
},
|
||||
// owners are intentionally left empty. The attemptToDeleteItem routine should get the latest item from the server.
|
||||
owners: nil,
|
||||
}
|
||||
err := gc.attemptToDeleteItem(item)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected Error: %v", err)
|
||||
}
|
||||
expectedActionSet := sets.NewString()
|
||||
expectedActionSet.Insert("GET=/api/v1/namespaces/ns1/replicationcontrollers/owner1")
|
||||
expectedActionSet.Insert("DELETE=/api/v1/namespaces/ns1/pods/ToBeDeletedPod")
|
||||
expectedActionSet.Insert("GET=/api/v1/namespaces/ns1/pods/ToBeDeletedPod")
|
||||
|
||||
actualActionSet := sets.NewString()
|
||||
for _, action := range testHandler.actions {
|
||||
actualActionSet.Insert(action.String())
|
||||
}
|
||||
if !expectedActionSet.Equal(actualActionSet) {
|
||||
t.Errorf("expected actions:\n%v\n but got:\n%v\nDifference:\n%v", expectedActionSet,
|
||||
actualActionSet, expectedActionSet.Difference(actualActionSet))
|
||||
}
|
||||
}
|
||||
|
||||
// verifyGraphInvariants verifies that all of a node's owners list the node as a
|
||||
// dependent and vice versa. uidToNode has all the nodes in the graph.
|
||||
func verifyGraphInvariants(scenario string, uidToNode map[types.UID]*node, t *testing.T) {
|
||||
for myUID, node := range uidToNode {
|
||||
for dependentNode := range node.dependents {
|
||||
found := false
|
||||
for _, owner := range dependentNode.owners {
|
||||
if owner.UID == myUID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("scenario: %s: node %s has node %s as a dependent, but it's not present in the latter node's owners list", scenario, node.identity, dependentNode.identity)
|
||||
}
|
||||
}
|
||||
|
||||
for _, owner := range node.owners {
|
||||
ownerNode, ok := uidToNode[owner.UID]
|
||||
if !ok {
|
||||
// It's possible that the owner node doesn't exist
|
||||
continue
|
||||
}
|
||||
if _, ok := ownerNode.dependents[node]; !ok {
|
||||
t.Errorf("node %s has node %s as an owner, but it's not present in the latter node's dependents list", node.identity, ownerNode.identity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createEvent(eventType eventType, selfUID string, owners []string) event {
|
||||
var ownerReferences []metav1.OwnerReference
|
||||
for i := 0; i < len(owners); i++ {
|
||||
ownerReferences = append(ownerReferences, metav1.OwnerReference{UID: types.UID(owners[i])})
|
||||
}
|
||||
return event{
|
||||
eventType: eventType,
|
||||
obj: &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: types.UID(selfUID),
|
||||
OwnerReferences: ownerReferences,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessEvent(t *testing.T) {
|
||||
var testScenarios = []struct {
|
||||
name string
|
||||
// a series of events that will be supplied to the
|
||||
// GraphBuilder.graphChanges.
|
||||
events []event
|
||||
}{
|
||||
{
|
||||
name: "test1",
|
||||
events: []event{
|
||||
createEvent(addEvent, "1", []string{}),
|
||||
createEvent(addEvent, "2", []string{"1"}),
|
||||
createEvent(addEvent, "3", []string{"1", "2"}),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test2",
|
||||
events: []event{
|
||||
createEvent(addEvent, "1", []string{}),
|
||||
createEvent(addEvent, "2", []string{"1"}),
|
||||
createEvent(addEvent, "3", []string{"1", "2"}),
|
||||
createEvent(addEvent, "4", []string{"2"}),
|
||||
createEvent(deleteEvent, "2", []string{"doesn't matter"}),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test3",
|
||||
events: []event{
|
||||
createEvent(addEvent, "1", []string{}),
|
||||
createEvent(addEvent, "2", []string{"1"}),
|
||||
createEvent(addEvent, "3", []string{"1", "2"}),
|
||||
createEvent(addEvent, "4", []string{"3"}),
|
||||
createEvent(updateEvent, "2", []string{"4"}),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reverse test2",
|
||||
events: []event{
|
||||
createEvent(addEvent, "4", []string{"2"}),
|
||||
createEvent(addEvent, "3", []string{"1", "2"}),
|
||||
createEvent(addEvent, "2", []string{"1"}),
|
||||
createEvent(addEvent, "1", []string{}),
|
||||
createEvent(deleteEvent, "2", []string{"doesn't matter"}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
alwaysStarted := make(chan struct{})
|
||||
close(alwaysStarted)
|
||||
for _, scenario := range testScenarios {
|
||||
dependencyGraphBuilder := &GraphBuilder{
|
||||
informersStarted: alwaysStarted,
|
||||
graphChanges: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
|
||||
uidToNode: &concurrentUIDToNode{
|
||||
uidToNodeLock: sync.RWMutex{},
|
||||
uidToNode: make(map[types.UID]*node),
|
||||
},
|
||||
attemptToDelete: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
|
||||
absentOwnerCache: NewUIDCache(2),
|
||||
}
|
||||
for i := 0; i < len(scenario.events); i++ {
|
||||
dependencyGraphBuilder.graphChanges.Add(&scenario.events[i])
|
||||
dependencyGraphBuilder.processGraphChanges()
|
||||
verifyGraphInvariants(scenario.name, dependencyGraphBuilder.uidToNode.uidToNode, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDependentsRace relies on golang's data race detector to check if there is
|
||||
// data race among in the dependents field.
|
||||
func TestDependentsRace(t *testing.T) {
|
||||
gc := setupGC(t, &restclient.Config{})
|
||||
defer close(gc.stop)
|
||||
|
||||
const updates = 100
|
||||
owner := &node{dependents: make(map[*node]struct{})}
|
||||
ownerUID := types.UID("owner")
|
||||
gc.dependencyGraphBuilder.uidToNode.Write(owner)
|
||||
go func() {
|
||||
for i := 0; i < updates; i++ {
|
||||
dependent := &node{}
|
||||
gc.dependencyGraphBuilder.addDependentToOwners(dependent, []metav1.OwnerReference{{UID: ownerUID}})
|
||||
gc.dependencyGraphBuilder.removeDependentFromOwners(dependent, []metav1.OwnerReference{{UID: ownerUID}})
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
gc.attemptToOrphan.Add(owner)
|
||||
for i := 0; i < updates; i++ {
|
||||
gc.attemptToOrphanWorker()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// test the list and watch functions correctly converts the ListOptions
|
||||
func TestGCListWatcher(t *testing.T) {
|
||||
testHandler := &fakeActionHandler{}
|
||||
srv, clientConfig := testServerAndClientConfig(testHandler.ServeHTTP)
|
||||
defer srv.Close()
|
||||
clientPool := dynamic.NewClientPool(clientConfig, legacyscheme.Registry.RESTMapper(), dynamic.LegacyAPIPathResolverFunc)
|
||||
podResource := schema.GroupVersionResource{Version: "v1", Resource: "pods"}
|
||||
client, err := clientPool.ClientForGroupVersionResource(podResource)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
lw := listWatcher(client, podResource)
|
||||
lw.DisableChunking = true
|
||||
if _, err := lw.Watch(metav1.ListOptions{ResourceVersion: "1"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := lw.List(metav1.ListOptions{ResourceVersion: "1"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if e, a := 2, len(testHandler.actions); e != a {
|
||||
t.Errorf("expect %d requests, got %d", e, a)
|
||||
}
|
||||
if e, a := "resourceVersion=1&watch=true", testHandler.actions[0].query; e != a {
|
||||
t.Errorf("expect %s, got %s", e, a)
|
||||
}
|
||||
if e, a := "resourceVersion=1", testHandler.actions[1].query; e != a {
|
||||
t.Errorf("expect %s, got %s", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func podToGCNode(pod *v1.Pod) *node {
|
||||
return &node{
|
||||
identity: objectReference{
|
||||
OwnerReference: metav1.OwnerReference{
|
||||
Kind: pod.Kind,
|
||||
APIVersion: pod.APIVersion,
|
||||
Name: pod.Name,
|
||||
UID: pod.UID,
|
||||
},
|
||||
Namespace: pod.Namespace,
|
||||
},
|
||||
// owners are intentionally left empty. The attemptToDeleteItem routine should get the latest item from the server.
|
||||
owners: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func TestAbsentUIDCache(t *testing.T) {
|
||||
rc1Pod1 := getPod("rc1Pod1", []metav1.OwnerReference{
|
||||
{
|
||||
Kind: "ReplicationController",
|
||||
Name: "rc1",
|
||||
UID: "1",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
})
|
||||
rc1Pod2 := getPod("rc1Pod2", []metav1.OwnerReference{
|
||||
{
|
||||
Kind: "ReplicationController",
|
||||
Name: "rc1",
|
||||
UID: "1",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
})
|
||||
rc2Pod1 := getPod("rc2Pod1", []metav1.OwnerReference{
|
||||
{
|
||||
Kind: "ReplicationController",
|
||||
Name: "rc2",
|
||||
UID: "2",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
})
|
||||
rc3Pod1 := getPod("rc3Pod1", []metav1.OwnerReference{
|
||||
{
|
||||
Kind: "ReplicationController",
|
||||
Name: "rc3",
|
||||
UID: "3",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
})
|
||||
testHandler := &fakeActionHandler{
|
||||
response: map[string]FakeResponse{
|
||||
"GET" + "/api/v1/namespaces/ns1/pods/rc1Pod1": {
|
||||
200,
|
||||
serilizeOrDie(t, rc1Pod1),
|
||||
},
|
||||
"GET" + "/api/v1/namespaces/ns1/pods/rc1Pod2": {
|
||||
200,
|
||||
serilizeOrDie(t, rc1Pod2),
|
||||
},
|
||||
"GET" + "/api/v1/namespaces/ns1/pods/rc2Pod1": {
|
||||
200,
|
||||
serilizeOrDie(t, rc2Pod1),
|
||||
},
|
||||
"GET" + "/api/v1/namespaces/ns1/pods/rc3Pod1": {
|
||||
200,
|
||||
serilizeOrDie(t, rc3Pod1),
|
||||
},
|
||||
"GET" + "/api/v1/namespaces/ns1/replicationcontrollers/rc1": {
|
||||
404,
|
||||
[]byte{},
|
||||
},
|
||||
"GET" + "/api/v1/namespaces/ns1/replicationcontrollers/rc2": {
|
||||
404,
|
||||
[]byte{},
|
||||
},
|
||||
"GET" + "/api/v1/namespaces/ns1/replicationcontrollers/rc3": {
|
||||
404,
|
||||
[]byte{},
|
||||
},
|
||||
},
|
||||
}
|
||||
srv, clientConfig := testServerAndClientConfig(testHandler.ServeHTTP)
|
||||
defer srv.Close()
|
||||
gc := setupGC(t, clientConfig)
|
||||
defer close(gc.stop)
|
||||
gc.absentOwnerCache = NewUIDCache(2)
|
||||
gc.attemptToDeleteItem(podToGCNode(rc1Pod1))
|
||||
gc.attemptToDeleteItem(podToGCNode(rc2Pod1))
|
||||
// rc1 should already be in the cache, no request should be sent. rc1 should be promoted in the UIDCache
|
||||
gc.attemptToDeleteItem(podToGCNode(rc1Pod2))
|
||||
// after this call, rc2 should be evicted from the UIDCache
|
||||
gc.attemptToDeleteItem(podToGCNode(rc3Pod1))
|
||||
// check cache
|
||||
if !gc.absentOwnerCache.Has(types.UID("1")) {
|
||||
t.Errorf("expected rc1 to be in the cache")
|
||||
}
|
||||
if gc.absentOwnerCache.Has(types.UID("2")) {
|
||||
t.Errorf("expected rc2 to not exist in the cache")
|
||||
}
|
||||
if !gc.absentOwnerCache.Has(types.UID("3")) {
|
||||
t.Errorf("expected rc3 to be in the cache")
|
||||
}
|
||||
// check the request sent to the server
|
||||
count := 0
|
||||
for _, action := range testHandler.actions {
|
||||
if action.String() == "GET=/api/v1/namespaces/ns1/replicationcontrollers/rc1" {
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count != 1 {
|
||||
t.Errorf("expected only 1 GET rc1 request, got %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteOwnerRefPatch(t *testing.T) {
|
||||
original := v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "100",
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{UID: "1"},
|
||||
{UID: "2"},
|
||||
{UID: "3"},
|
||||
},
|
||||
},
|
||||
}
|
||||
originalData := serilizeOrDie(t, original)
|
||||
expected := v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "100",
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{UID: "1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
patch := deleteOwnerRefPatch("100", "2", "3")
|
||||
patched, err := strategicpatch.StrategicMergePatch(originalData, patch, v1.Pod{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var got v1.Pod
|
||||
if err := json.Unmarshal(patched, &got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(expected, got) {
|
||||
t.Errorf("expected: %#v,\ngot: %#v", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnblockOwnerReference(t *testing.T) {
|
||||
trueVar := true
|
||||
falseVar := false
|
||||
original := v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "100",
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{UID: "1", BlockOwnerDeletion: &trueVar},
|
||||
{UID: "2", BlockOwnerDeletion: &falseVar},
|
||||
{UID: "3"},
|
||||
},
|
||||
},
|
||||
}
|
||||
originalData := serilizeOrDie(t, original)
|
||||
expected := v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "100",
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{UID: "1", BlockOwnerDeletion: &falseVar},
|
||||
{UID: "2", BlockOwnerDeletion: &falseVar},
|
||||
{UID: "3"},
|
||||
},
|
||||
},
|
||||
}
|
||||
accessor, err := meta.Accessor(&original)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n := node{
|
||||
owners: accessor.GetOwnerReferences(),
|
||||
}
|
||||
patch, err := n.patchToUnblockOwnerReferences()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
patched, err := strategicpatch.StrategicMergePatch(originalData, patch, v1.Pod{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var got v1.Pod
|
||||
if err := json.Unmarshal(patched, &got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(expected, got) {
|
||||
t.Errorf("expected: %#v,\ngot: %#v", expected, got)
|
||||
t.Errorf("expected: %#v,\ngot: %#v", expected.OwnerReferences, got.OwnerReferences)
|
||||
for _, ref := range got.OwnerReferences {
|
||||
t.Errorf("ref.UID=%s, ref.BlockOwnerDeletion=%v", ref.UID, *ref.BlockOwnerDeletion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrphanDependentsFailure(t *testing.T) {
|
||||
testHandler := &fakeActionHandler{
|
||||
response: map[string]FakeResponse{
|
||||
"PATCH" + "/api/v1/namespaces/ns1/pods/pod": {
|
||||
409,
|
||||
[]byte{},
|
||||
},
|
||||
},
|
||||
}
|
||||
srv, clientConfig := testServerAndClientConfig(testHandler.ServeHTTP)
|
||||
defer srv.Close()
|
||||
|
||||
gc := setupGC(t, clientConfig)
|
||||
defer close(gc.stop)
|
||||
|
||||
dependents := []*node{
|
||||
{
|
||||
identity: objectReference{
|
||||
OwnerReference: metav1.OwnerReference{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
Name: "pod",
|
||||
},
|
||||
Namespace: "ns1",
|
||||
},
|
||||
},
|
||||
}
|
||||
err := gc.orphanDependents(objectReference{}, dependents)
|
||||
expected := `the server reported a conflict (patch pods pod)`
|
||||
if err == nil || !strings.Contains(err.Error(), expected) {
|
||||
t.Errorf("expected error contains text %s, got %v", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetDeletableResources ensures GetDeletableResources always returns
|
||||
// something usable regardless of discovery output.
|
||||
func TestGetDeletableResources(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
serverResources []*metav1.APIResourceList
|
||||
err error
|
||||
deletableResources map[schema.GroupVersionResource]struct{}
|
||||
}{
|
||||
"no error": {
|
||||
serverResources: []*metav1.APIResourceList{
|
||||
{
|
||||
// Valid GroupVersion
|
||||
GroupVersion: "apps/v1",
|
||||
APIResources: []metav1.APIResource{
|
||||
{Name: "pods", Namespaced: true, Kind: "Pod", Verbs: metav1.Verbs{"delete", "list", "watch"}},
|
||||
{Name: "services", Namespaced: true, Kind: "Service"},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Invalid GroupVersion, should be ignored
|
||||
GroupVersion: "foo//whatever",
|
||||
APIResources: []metav1.APIResource{
|
||||
{Name: "bars", Namespaced: true, Kind: "Bar", Verbs: metav1.Verbs{"delete", "list", "watch"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Valid GroupVersion, missing required verbs, should be ignored
|
||||
GroupVersion: "acme/v1",
|
||||
APIResources: []metav1.APIResource{
|
||||
{Name: "widgets", Namespaced: true, Kind: "Widget", Verbs: metav1.Verbs{"delete"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
deletableResources: map[schema.GroupVersionResource]struct{}{
|
||||
{Group: "apps", Version: "v1", Resource: "pods"}: {},
|
||||
},
|
||||
},
|
||||
"nonspecific failure, includes usable results": {
|
||||
serverResources: []*metav1.APIResourceList{
|
||||
{
|
||||
GroupVersion: "apps/v1",
|
||||
APIResources: []metav1.APIResource{
|
||||
{Name: "pods", Namespaced: true, Kind: "Pod", Verbs: metav1.Verbs{"delete", "list", "watch"}},
|
||||
{Name: "services", Namespaced: true, Kind: "Service"},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: fmt.Errorf("internal error"),
|
||||
deletableResources: map[schema.GroupVersionResource]struct{}{
|
||||
{Group: "apps", Version: "v1", Resource: "pods"}: {},
|
||||
},
|
||||
},
|
||||
"partial discovery failure, includes usable results": {
|
||||
serverResources: []*metav1.APIResourceList{
|
||||
{
|
||||
GroupVersion: "apps/v1",
|
||||
APIResources: []metav1.APIResource{
|
||||
{Name: "pods", Namespaced: true, Kind: "Pod", Verbs: metav1.Verbs{"delete", "list", "watch"}},
|
||||
{Name: "services", Namespaced: true, Kind: "Service"},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: &discovery.ErrGroupDiscoveryFailed{
|
||||
Groups: map[schema.GroupVersion]error{
|
||||
{Group: "foo", Version: "v1"}: fmt.Errorf("discovery failure"),
|
||||
},
|
||||
},
|
||||
deletableResources: map[schema.GroupVersionResource]struct{}{
|
||||
{Group: "apps", Version: "v1", Resource: "pods"}: {},
|
||||
},
|
||||
},
|
||||
"discovery failure, no results": {
|
||||
serverResources: nil,
|
||||
err: fmt.Errorf("internal error"),
|
||||
deletableResources: map[schema.GroupVersionResource]struct{}{},
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Logf("testing %q", name)
|
||||
client := &fakeServerResources{
|
||||
PreferredResources: test.serverResources,
|
||||
Error: test.err,
|
||||
}
|
||||
actual := GetDeletableResources(client)
|
||||
if !reflect.DeepEqual(test.deletableResources, actual) {
|
||||
t.Errorf("expected resources:\n%v\ngot:\n%v", test.deletableResources, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type fakeServerResources struct {
|
||||
PreferredResources []*metav1.APIResourceList
|
||||
Error error
|
||||
}
|
||||
|
||||
func (_ *fakeServerResources) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (_ *fakeServerResources) ServerResources() ([]*metav1.APIResourceList, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *fakeServerResources) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
|
||||
return f.PreferredResources, f.Error
|
||||
}
|
||||
|
||||
func (_ *fakeServerResources) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
|
||||
return nil, nil
|
||||
}
|
181
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/graph.go
generated
vendored
Normal file
181
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/graph.go
generated
vendored
Normal file
@ -0,0 +1,181 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package garbagecollector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
type objectReference struct {
|
||||
metav1.OwnerReference
|
||||
// This is needed by the dynamic client
|
||||
Namespace string
|
||||
}
|
||||
|
||||
func (s objectReference) String() string {
|
||||
return fmt.Sprintf("[%s/%s, namespace: %s, name: %s, uid: %s]", s.APIVersion, s.Kind, s.Namespace, s.Name, s.UID)
|
||||
}
|
||||
|
||||
// The single-threaded GraphBuilder.processGraphChanges() is the sole writer of the
|
||||
// nodes. The multi-threaded GarbageCollector.attemptToDeleteItem() reads the nodes.
|
||||
// WARNING: node has different locks on different fields. setters and getters
|
||||
// use the respective locks, so the return values of the getters can be
|
||||
// inconsistent.
|
||||
type node struct {
|
||||
identity objectReference
|
||||
// dependents will be read by the orphan() routine, we need to protect it with a lock.
|
||||
dependentsLock sync.RWMutex
|
||||
// dependents are the nodes that have node.identity as a
|
||||
// metadata.ownerReference.
|
||||
dependents map[*node]struct{}
|
||||
// this is set by processGraphChanges() if the object has non-nil DeletionTimestamp
|
||||
// and has the FinalizerDeleteDependents.
|
||||
deletingDependents bool
|
||||
deletingDependentsLock sync.RWMutex
|
||||
// this records if the object's deletionTimestamp is non-nil.
|
||||
beingDeleted bool
|
||||
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
|
||||
// ownerReferences with the owners recorded in the graph.
|
||||
owners []metav1.OwnerReference
|
||||
}
|
||||
|
||||
// An object is on a one way trip to its final deletion if it starts being
|
||||
// deleted, so we only provide a function to set beingDeleted to true.
|
||||
func (n *node) markBeingDeleted() {
|
||||
n.beingDeletedLock.Lock()
|
||||
defer n.beingDeletedLock.Unlock()
|
||||
n.beingDeleted = true
|
||||
}
|
||||
|
||||
func (n *node) isBeingDeleted() bool {
|
||||
n.beingDeletedLock.RLock()
|
||||
defer n.beingDeletedLock.RUnlock()
|
||||
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() {
|
||||
n.deletingDependentsLock.Lock()
|
||||
defer n.deletingDependentsLock.Unlock()
|
||||
n.deletingDependents = true
|
||||
}
|
||||
|
||||
func (n *node) isDeletingDependents() bool {
|
||||
n.deletingDependentsLock.RLock()
|
||||
defer n.deletingDependentsLock.RUnlock()
|
||||
return n.deletingDependents
|
||||
}
|
||||
|
||||
func (ownerNode *node) addDependent(dependent *node) {
|
||||
ownerNode.dependentsLock.Lock()
|
||||
defer ownerNode.dependentsLock.Unlock()
|
||||
ownerNode.dependents[dependent] = struct{}{}
|
||||
}
|
||||
|
||||
func (ownerNode *node) deleteDependent(dependent *node) {
|
||||
ownerNode.dependentsLock.Lock()
|
||||
defer ownerNode.dependentsLock.Unlock()
|
||||
delete(ownerNode.dependents, dependent)
|
||||
}
|
||||
|
||||
func (ownerNode *node) dependentsLength() int {
|
||||
ownerNode.dependentsLock.RLock()
|
||||
defer ownerNode.dependentsLock.RUnlock()
|
||||
return len(ownerNode.dependents)
|
||||
}
|
||||
|
||||
// Note that this function does not provide any synchronization guarantees;
|
||||
// items could be added to or removed from ownerNode.dependents the moment this
|
||||
// function returns.
|
||||
func (ownerNode *node) getDependents() []*node {
|
||||
ownerNode.dependentsLock.RLock()
|
||||
defer ownerNode.dependentsLock.RUnlock()
|
||||
var ret []*node
|
||||
for dep := range ownerNode.dependents {
|
||||
ret = append(ret, dep)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// blockingDependents returns the dependents that are blocking the deletion of
|
||||
// n, i.e., the dependent that has an ownerReference pointing to n, and
|
||||
// the BlockOwnerDeletion field of that ownerReference is true.
|
||||
// Note that this function does not provide any synchronization guarantees;
|
||||
// items could be added to or removed from ownerNode.dependents the moment this
|
||||
// function returns.
|
||||
func (n *node) blockingDependents() []*node {
|
||||
dependents := n.getDependents()
|
||||
var ret []*node
|
||||
for _, dep := range dependents {
|
||||
for _, owner := range dep.owners {
|
||||
if owner.UID == n.identity.UID && owner.BlockOwnerDeletion != nil && *owner.BlockOwnerDeletion {
|
||||
ret = append(ret, dep)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// String renders node as a string using fmt. Acquires a read lock to ensure the
|
||||
// reflective dump of dependents doesn't race with any concurrent writes.
|
||||
func (n *node) String() string {
|
||||
n.dependentsLock.RLock()
|
||||
defer n.dependentsLock.RUnlock()
|
||||
return fmt.Sprintf("%#v", n)
|
||||
}
|
||||
|
||||
type concurrentUIDToNode struct {
|
||||
uidToNodeLock sync.RWMutex
|
||||
uidToNode map[types.UID]*node
|
||||
}
|
||||
|
||||
func (m *concurrentUIDToNode) Write(node *node) {
|
||||
m.uidToNodeLock.Lock()
|
||||
defer m.uidToNodeLock.Unlock()
|
||||
m.uidToNode[node.identity.UID] = node
|
||||
}
|
||||
|
||||
func (m *concurrentUIDToNode) Read(uid types.UID) (*node, bool) {
|
||||
m.uidToNodeLock.RLock()
|
||||
defer m.uidToNodeLock.RUnlock()
|
||||
n, ok := m.uidToNode[uid]
|
||||
return n, ok
|
||||
}
|
||||
|
||||
func (m *concurrentUIDToNode) Delete(uid types.UID) {
|
||||
m.uidToNodeLock.Lock()
|
||||
defer m.uidToNodeLock.Unlock()
|
||||
delete(m.uidToNode, uid)
|
||||
}
|
677
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/graph_builder.go
generated
vendored
Normal file
677
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/graph_builder.go
generated
vendored
Normal file
@ -0,0 +1,677 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package garbagecollector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly"
|
||||
)
|
||||
|
||||
type eventType int
|
||||
|
||||
func (e eventType) String() string {
|
||||
switch e {
|
||||
case addEvent:
|
||||
return "add"
|
||||
case updateEvent:
|
||||
return "update"
|
||||
case deleteEvent:
|
||||
return "delete"
|
||||
default:
|
||||
return fmt.Sprintf("unknown(%d)", int(e))
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
addEvent eventType = iota
|
||||
updateEvent
|
||||
deleteEvent
|
||||
)
|
||||
|
||||
type event struct {
|
||||
eventType eventType
|
||||
obj interface{}
|
||||
// the update event comes with an old object, but it's not used by the garbage collector.
|
||||
oldObj interface{}
|
||||
gvk schema.GroupVersionKind
|
||||
}
|
||||
|
||||
// GraphBuilder: based on the events supplied by the informers, GraphBuilder updates
|
||||
// uidToNode, a graph that caches the dependencies as we know, and enqueues
|
||||
// items to the attemptToDelete and attemptToOrphan.
|
||||
type GraphBuilder struct {
|
||||
restMapper meta.RESTMapper
|
||||
|
||||
// each monitor list/watches a resource, the results are funneled to the
|
||||
// dependencyGraphBuilder
|
||||
monitors monitors
|
||||
monitorLock sync.Mutex
|
||||
// informersStarted is closed after after all of the controllers have been initialized and are running.
|
||||
// After that it is safe to start them here, before that it is not.
|
||||
informersStarted <-chan struct{}
|
||||
|
||||
// stopCh drives shutdown. If it is nil, it indicates that Run() has not been
|
||||
// called yet. If it is non-nil, then when closed it indicates everything
|
||||
// should shut down.
|
||||
//
|
||||
// This channel is also protected by monitorLock.
|
||||
stopCh <-chan struct{}
|
||||
|
||||
// metaOnlyClientPool uses a special codec, which removes fields except for
|
||||
// apiVersion, kind, and metadata during decoding.
|
||||
metaOnlyClientPool dynamic.ClientPool
|
||||
// monitors are the producer of the graphChanges queue, graphBuilder alters
|
||||
// the in-memory graph according to the changes.
|
||||
graphChanges workqueue.RateLimitingInterface
|
||||
// uidToNode doesn't require a lock to protect, because only the
|
||||
// single-threaded GraphBuilder.processGraphChanges() reads/writes it.
|
||||
uidToNode *concurrentUIDToNode
|
||||
// GraphBuilder is the producer of attemptToDelete and attemptToOrphan, GC is the consumer.
|
||||
attemptToDelete workqueue.RateLimitingInterface
|
||||
attemptToOrphan workqueue.RateLimitingInterface
|
||||
// GraphBuilder and GC share the absentOwnerCache. Objects that are known to
|
||||
// be non-existent are added to the cached.
|
||||
absentOwnerCache *UIDCache
|
||||
sharedInformers informers.SharedInformerFactory
|
||||
ignoredResources map[schema.GroupResource]struct{}
|
||||
}
|
||||
|
||||
// monitor runs a Controller with a local stop channel.
|
||||
type monitor struct {
|
||||
controller cache.Controller
|
||||
|
||||
// stopCh stops Controller. If stopCh is nil, the monitor is considered to be
|
||||
// not yet started.
|
||||
stopCh chan struct{}
|
||||
}
|
||||
|
||||
// Run is intended to be called in a goroutine. Multiple calls of this is an
|
||||
// error.
|
||||
func (m *monitor) Run() {
|
||||
m.controller.Run(m.stopCh)
|
||||
}
|
||||
|
||||
type monitors map[schema.GroupVersionResource]*monitor
|
||||
|
||||
func listWatcher(client dynamic.Interface, resource schema.GroupVersionResource) *cache.ListWatch {
|
||||
return &cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
// APIResource.Kind is not used by the dynamic client, so
|
||||
// leave it empty. We want to list this resource in all
|
||||
// namespaces if it's namespace scoped, so leave
|
||||
// APIResource.Namespaced as false is all right.
|
||||
apiResource := metav1.APIResource{Name: resource.Resource}
|
||||
return client.ParameterCodec(dynamic.VersionedParameterEncoderWithV1Fallback).
|
||||
Resource(&apiResource, metav1.NamespaceAll).
|
||||
List(options)
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
// APIResource.Kind is not used by the dynamic client, so
|
||||
// leave it empty. We want to list this resource in all
|
||||
// namespaces if it's namespace scoped, so leave
|
||||
// APIResource.Namespaced as false is all right.
|
||||
apiResource := metav1.APIResource{Name: resource.Resource}
|
||||
return client.ParameterCodec(dynamic.VersionedParameterEncoderWithV1Fallback).
|
||||
Resource(&apiResource, metav1.NamespaceAll).
|
||||
Watch(options)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (gb *GraphBuilder) controllerFor(resource schema.GroupVersionResource, kind schema.GroupVersionKind) (cache.Controller, error) {
|
||||
handlers := cache.ResourceEventHandlerFuncs{
|
||||
// add the event to the dependencyGraphBuilder's graphChanges.
|
||||
AddFunc: func(obj interface{}) {
|
||||
event := &event{
|
||||
eventType: addEvent,
|
||||
obj: obj,
|
||||
gvk: kind,
|
||||
}
|
||||
gb.graphChanges.Add(event)
|
||||
},
|
||||
UpdateFunc: func(oldObj, newObj interface{}) {
|
||||
// TODO: check if there are differences in the ownerRefs,
|
||||
// finalizers, and DeletionTimestamp; if not, ignore the update.
|
||||
event := &event{
|
||||
eventType: updateEvent,
|
||||
obj: newObj,
|
||||
oldObj: oldObj,
|
||||
gvk: kind,
|
||||
}
|
||||
gb.graphChanges.Add(event)
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
// delta fifo may wrap the object in a cache.DeletedFinalStateUnknown, unwrap it
|
||||
if deletedFinalStateUnknown, ok := obj.(cache.DeletedFinalStateUnknown); ok {
|
||||
obj = deletedFinalStateUnknown.Obj
|
||||
}
|
||||
event := &event{
|
||||
eventType: deleteEvent,
|
||||
obj: obj,
|
||||
gvk: kind,
|
||||
}
|
||||
gb.graphChanges.Add(event)
|
||||
},
|
||||
}
|
||||
shared, err := gb.sharedInformers.ForResource(resource)
|
||||
if err == nil {
|
||||
glog.V(4).Infof("using a shared informer for resource %q, kind %q", resource.String(), kind.String())
|
||||
// need to clone because it's from a shared cache
|
||||
shared.Informer().AddEventHandlerWithResyncPeriod(handlers, ResourceResyncTime)
|
||||
return shared.Informer().GetController(), nil
|
||||
} else {
|
||||
glog.V(4).Infof("unable to use a shared informer for resource %q, kind %q: %v", resource.String(), kind.String(), err)
|
||||
}
|
||||
|
||||
// TODO: consider store in one storage.
|
||||
glog.V(5).Infof("create storage for resource %s", resource)
|
||||
client, err := gb.metaOnlyClientPool.ClientForGroupVersionKind(kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, monitor := cache.NewInformer(
|
||||
listWatcher(client, resource),
|
||||
nil,
|
||||
ResourceResyncTime,
|
||||
// don't need to clone because it's not from shared cache
|
||||
handlers,
|
||||
)
|
||||
return monitor, nil
|
||||
}
|
||||
|
||||
// syncMonitors rebuilds the monitor set according to the supplied resources,
|
||||
// creating or deleting monitors as necessary. It will return any error
|
||||
// encountered, but will make an attempt to create a monitor for each resource
|
||||
// instead of immediately exiting on an error. It may be called before or after
|
||||
// Run. Monitors are NOT started as part of the sync. To ensure all existing
|
||||
// monitors are started, call startMonitors.
|
||||
func (gb *GraphBuilder) syncMonitors(resources map[schema.GroupVersionResource]struct{}) error {
|
||||
gb.monitorLock.Lock()
|
||||
defer gb.monitorLock.Unlock()
|
||||
|
||||
toRemove := gb.monitors
|
||||
if toRemove == nil {
|
||||
toRemove = monitors{}
|
||||
}
|
||||
current := monitors{}
|
||||
errs := []error{}
|
||||
kept := 0
|
||||
added := 0
|
||||
for resource := range resources {
|
||||
if _, ok := gb.ignoredResources[resource.GroupResource()]; ok {
|
||||
continue
|
||||
}
|
||||
if m, ok := toRemove[resource]; ok {
|
||||
current[resource] = m
|
||||
delete(toRemove, resource)
|
||||
kept++
|
||||
continue
|
||||
}
|
||||
kind, err := gb.restMapper.KindFor(resource)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("couldn't look up resource %q: %v", resource, err))
|
||||
continue
|
||||
}
|
||||
c, err := gb.controllerFor(resource, kind)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("couldn't start monitor for resource %q: %v", resource, err))
|
||||
continue
|
||||
}
|
||||
current[resource] = &monitor{controller: c}
|
||||
added++
|
||||
}
|
||||
gb.monitors = current
|
||||
|
||||
for _, monitor := range toRemove {
|
||||
if monitor.stopCh != nil {
|
||||
close(monitor.stopCh)
|
||||
}
|
||||
}
|
||||
|
||||
glog.V(4).Infof("synced monitors; added %d, kept %d, removed %d", added, kept, len(toRemove))
|
||||
// NewAggregate returns nil if errs is 0-length
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// startMonitors ensures the current set of monitors are running. Any newly
|
||||
// started monitors will also cause shared informers to be started.
|
||||
//
|
||||
// If called before Run, startMonitors does nothing (as there is no stop channel
|
||||
// to support monitor/informer execution).
|
||||
func (gb *GraphBuilder) startMonitors() {
|
||||
gb.monitorLock.Lock()
|
||||
defer gb.monitorLock.Unlock()
|
||||
|
||||
if gb.stopCh == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// we're waiting until after the informer start that happens once all the controllers are initialized. This ensures
|
||||
// that they don't get unexpected events on their work queues.
|
||||
<-gb.informersStarted
|
||||
|
||||
monitors := gb.monitors
|
||||
started := 0
|
||||
for _, monitor := range monitors {
|
||||
if monitor.stopCh == nil {
|
||||
monitor.stopCh = make(chan struct{})
|
||||
gb.sharedInformers.Start(gb.stopCh)
|
||||
go monitor.Run()
|
||||
started++
|
||||
}
|
||||
}
|
||||
glog.V(4).Infof("started %d new monitors, %d currently running", started, len(monitors))
|
||||
}
|
||||
|
||||
// IsSynced returns true if any monitors exist AND all those monitors'
|
||||
// controllers HasSynced functions return true. This means IsSynced could return
|
||||
// true at one time, and then later return false if all monitors were
|
||||
// reconstructed.
|
||||
func (gb *GraphBuilder) IsSynced() bool {
|
||||
gb.monitorLock.Lock()
|
||||
defer gb.monitorLock.Unlock()
|
||||
|
||||
if len(gb.monitors) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, monitor := range gb.monitors {
|
||||
if !monitor.controller.HasSynced() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Run sets the stop channel and starts monitor execution until stopCh is
|
||||
// closed. Any running monitors will be stopped before Run returns.
|
||||
func (gb *GraphBuilder) Run(stopCh <-chan struct{}) {
|
||||
glog.Infof("GraphBuilder running")
|
||||
defer glog.Infof("GraphBuilder stopping")
|
||||
|
||||
// Set up the stop channel.
|
||||
gb.monitorLock.Lock()
|
||||
gb.stopCh = stopCh
|
||||
gb.monitorLock.Unlock()
|
||||
|
||||
// Start monitors and begin change processing until the stop channel is
|
||||
// closed.
|
||||
gb.startMonitors()
|
||||
wait.Until(gb.runProcessGraphChanges, 1*time.Second, stopCh)
|
||||
|
||||
// Stop any running monitors.
|
||||
gb.monitorLock.Lock()
|
||||
defer gb.monitorLock.Unlock()
|
||||
monitors := gb.monitors
|
||||
stopped := 0
|
||||
for _, monitor := range monitors {
|
||||
if monitor.stopCh != nil {
|
||||
stopped++
|
||||
close(monitor.stopCh)
|
||||
}
|
||||
}
|
||||
|
||||
// reset monitors so that the graph builder can be safely re-run/synced.
|
||||
gb.monitors = nil
|
||||
glog.Infof("stopped %d of %d monitors", stopped, len(monitors))
|
||||
}
|
||||
|
||||
var ignoredResources = map[schema.GroupResource]struct{}{
|
||||
{Group: "extensions", Resource: "replicationcontrollers"}: {},
|
||||
{Group: "", Resource: "bindings"}: {},
|
||||
{Group: "", Resource: "componentstatuses"}: {},
|
||||
{Group: "", Resource: "events"}: {},
|
||||
{Group: "authentication.k8s.io", Resource: "tokenreviews"}: {},
|
||||
{Group: "authorization.k8s.io", Resource: "subjectaccessreviews"}: {},
|
||||
{Group: "authorization.k8s.io", Resource: "selfsubjectaccessreviews"}: {},
|
||||
{Group: "authorization.k8s.io", Resource: "localsubjectaccessreviews"}: {},
|
||||
{Group: "authorization.k8s.io", Resource: "selfsubjectrulesreviews"}: {},
|
||||
{Group: "apiregistration.k8s.io", Resource: "apiservices"}: {},
|
||||
{Group: "apiextensions.k8s.io", Resource: "customresourcedefinitions"}: {},
|
||||
}
|
||||
|
||||
// DefaultIgnoredResources returns the default set of resources that the garbage collector controller
|
||||
// should ignore. This is exposed so downstream integrators can have access to the defaults, and add
|
||||
// to them as necessary when constructing the controller.
|
||||
func DefaultIgnoredResources() map[schema.GroupResource]struct{} {
|
||||
return ignoredResources
|
||||
}
|
||||
|
||||
// enqueueVirtualDeleteEvent is used to add a virtual delete event to be processed for virtual nodes
|
||||
// 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
|
||||
// exist in the gb.uidToNode yet, a "virtual" node will be created to represent
|
||||
// the owner. The "virtual" node will be enqueued to the attemptToDelete, so that
|
||||
// attemptToDeleteItem() will verify if the owner exists according to the API server.
|
||||
func (gb *GraphBuilder) addDependentToOwners(n *node, owners []metav1.OwnerReference) {
|
||||
for _, owner := range owners {
|
||||
ownerNode, ok := gb.uidToNode.Read(owner.UID)
|
||||
if !ok {
|
||||
// Create a "virtual" node in the graph for the owner if it doesn't
|
||||
// exist in the graph yet.
|
||||
ownerNode = &node{
|
||||
identity: objectReference{
|
||||
OwnerReference: owner,
|
||||
Namespace: n.identity.Namespace,
|
||||
},
|
||||
dependents: make(map[*node]struct{}),
|
||||
virtual: true,
|
||||
}
|
||||
glog.V(5).Infof("add virtual node.identity: %s\n\n", ownerNode.identity)
|
||||
gb.uidToNode.Write(ownerNode)
|
||||
}
|
||||
ownerNode.addDependent(n)
|
||||
if !ok {
|
||||
// Enqueue the virtual node into attemptToDelete.
|
||||
// The garbage processor will enqueue a virtual delete
|
||||
// event to delete it from the graph if API server confirms this
|
||||
// owner doesn't exist.
|
||||
gb.attemptToDelete.Add(ownerNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// insertNode insert the node to gb.uidToNode; then it finds all owners as listed
|
||||
// in n.owners, and adds the node to their dependents list.
|
||||
func (gb *GraphBuilder) insertNode(n *node) {
|
||||
gb.uidToNode.Write(n)
|
||||
gb.addDependentToOwners(n, n.owners)
|
||||
}
|
||||
|
||||
// removeDependentFromOwners remove n from owners' dependents list.
|
||||
func (gb *GraphBuilder) removeDependentFromOwners(n *node, owners []metav1.OwnerReference) {
|
||||
for _, owner := range owners {
|
||||
ownerNode, ok := gb.uidToNode.Read(owner.UID)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
ownerNode.deleteDependent(n)
|
||||
}
|
||||
}
|
||||
|
||||
// removeNode removes the node from gb.uidToNode, then finds all
|
||||
// owners as listed in n.owners, and removes n from their dependents list.
|
||||
func (gb *GraphBuilder) removeNode(n *node) {
|
||||
gb.uidToNode.Delete(n.identity.UID)
|
||||
gb.removeDependentFromOwners(n, n.owners)
|
||||
}
|
||||
|
||||
type ownerRefPair struct {
|
||||
oldRef metav1.OwnerReference
|
||||
newRef metav1.OwnerReference
|
||||
}
|
||||
|
||||
// TODO: profile this function to see if a naive N^2 algorithm performs better
|
||||
// when the number of references is small.
|
||||
func referencesDiffs(old []metav1.OwnerReference, new []metav1.OwnerReference) (added []metav1.OwnerReference, removed []metav1.OwnerReference, changed []ownerRefPair) {
|
||||
oldUIDToRef := make(map[string]metav1.OwnerReference)
|
||||
for _, value := range old {
|
||||
oldUIDToRef[string(value.UID)] = value
|
||||
}
|
||||
oldUIDSet := sets.StringKeySet(oldUIDToRef)
|
||||
newUIDToRef := make(map[string]metav1.OwnerReference)
|
||||
for _, value := range new {
|
||||
newUIDToRef[string(value.UID)] = value
|
||||
}
|
||||
newUIDSet := sets.StringKeySet(newUIDToRef)
|
||||
|
||||
addedUID := newUIDSet.Difference(oldUIDSet)
|
||||
removedUID := oldUIDSet.Difference(newUIDSet)
|
||||
intersection := oldUIDSet.Intersection(newUIDSet)
|
||||
|
||||
for uid := range addedUID {
|
||||
added = append(added, newUIDToRef[uid])
|
||||
}
|
||||
for uid := range removedUID {
|
||||
removed = append(removed, oldUIDToRef[uid])
|
||||
}
|
||||
for uid := range intersection {
|
||||
if !reflect.DeepEqual(oldUIDToRef[uid], newUIDToRef[uid]) {
|
||||
changed = append(changed, ownerRefPair{oldRef: oldUIDToRef[uid], newRef: newUIDToRef[uid]})
|
||||
}
|
||||
}
|
||||
return added, removed, changed
|
||||
}
|
||||
|
||||
// returns if the object in the event just transitions to "being deleted".
|
||||
func deletionStarts(oldObj interface{}, newAccessor metav1.Object) bool {
|
||||
// The delta_fifo may combine the creation and update of the object into one
|
||||
// event, so if there is no oldObj, we just return if the newObj (via
|
||||
// newAccessor) is being deleted.
|
||||
if oldObj == nil {
|
||||
if newAccessor.GetDeletionTimestamp() == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
oldAccessor, err := meta.Accessor(oldObj)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("cannot access oldObj: %v", err))
|
||||
return false
|
||||
}
|
||||
return beingDeleted(newAccessor) && !beingDeleted(oldAccessor)
|
||||
}
|
||||
|
||||
func beingDeleted(accessor metav1.Object) bool {
|
||||
return accessor.GetDeletionTimestamp() != nil
|
||||
}
|
||||
|
||||
func hasDeleteDependentsFinalizer(accessor metav1.Object) bool {
|
||||
finalizers := accessor.GetFinalizers()
|
||||
for _, finalizer := range finalizers {
|
||||
if finalizer == metav1.FinalizerDeleteDependents {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasOrphanFinalizer(accessor metav1.Object) bool {
|
||||
finalizers := accessor.GetFinalizers()
|
||||
for _, finalizer := range finalizers {
|
||||
if finalizer == metav1.FinalizerOrphanDependents {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// this function takes newAccessor directly because the caller already
|
||||
// instantiates an accessor for the newObj.
|
||||
func startsWaitingForDependentsDeleted(oldObj interface{}, newAccessor metav1.Object) bool {
|
||||
return deletionStarts(oldObj, newAccessor) && hasDeleteDependentsFinalizer(newAccessor)
|
||||
}
|
||||
|
||||
// this function takes newAccessor directly because the caller already
|
||||
// instantiates an accessor for the newObj.
|
||||
func startsWaitingForDependentsOrphaned(oldObj interface{}, newAccessor metav1.Object) bool {
|
||||
return deletionStarts(oldObj, newAccessor) && hasOrphanFinalizer(newAccessor)
|
||||
}
|
||||
|
||||
// if an blocking ownerReference points to an object gets removed, or gets set to
|
||||
// "BlockOwnerDeletion=false", add the object to the attemptToDelete queue.
|
||||
func (gb *GraphBuilder) addUnblockedOwnersToDeleteQueue(removed []metav1.OwnerReference, changed []ownerRefPair) {
|
||||
for _, ref := range removed {
|
||||
if ref.BlockOwnerDeletion != nil && *ref.BlockOwnerDeletion {
|
||||
node, found := gb.uidToNode.Read(ref.UID)
|
||||
if !found {
|
||||
glog.V(5).Infof("cannot find %s in uidToNode", ref.UID)
|
||||
continue
|
||||
}
|
||||
gb.attemptToDelete.Add(node)
|
||||
}
|
||||
}
|
||||
for _, c := range changed {
|
||||
wasBlocked := c.oldRef.BlockOwnerDeletion != nil && *c.oldRef.BlockOwnerDeletion
|
||||
isUnblocked := c.newRef.BlockOwnerDeletion == nil || (c.newRef.BlockOwnerDeletion != nil && !*c.newRef.BlockOwnerDeletion)
|
||||
if wasBlocked && isUnblocked {
|
||||
node, found := gb.uidToNode.Read(c.newRef.UID)
|
||||
if !found {
|
||||
glog.V(5).Infof("cannot find %s in uidToNode", c.newRef.UID)
|
||||
continue
|
||||
}
|
||||
gb.attemptToDelete.Add(node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gb *GraphBuilder) processTransitions(oldObj interface{}, newAccessor metav1.Object, n *node) {
|
||||
if startsWaitingForDependentsOrphaned(oldObj, newAccessor) {
|
||||
glog.V(5).Infof("add %s to the attemptToOrphan", n.identity)
|
||||
gb.attemptToOrphan.Add(n)
|
||||
return
|
||||
}
|
||||
if startsWaitingForDependentsDeleted(oldObj, newAccessor) {
|
||||
glog.V(2).Infof("add %s to the attemptToDelete, because it's waiting for its dependents to be deleted", n.identity)
|
||||
// if the n is added as a "virtual" node, its deletingDependents field is not properly set, so always set it here.
|
||||
n.markDeletingDependents()
|
||||
for dep := range n.dependents {
|
||||
gb.attemptToDelete.Add(dep)
|
||||
}
|
||||
gb.attemptToDelete.Add(n)
|
||||
}
|
||||
}
|
||||
|
||||
func (gb *GraphBuilder) runProcessGraphChanges() {
|
||||
for gb.processGraphChanges() {
|
||||
}
|
||||
}
|
||||
|
||||
// Dequeueing an event from graphChanges, updating graph, populating dirty_queue.
|
||||
func (gb *GraphBuilder) processGraphChanges() bool {
|
||||
item, quit := gb.graphChanges.Get()
|
||||
if quit {
|
||||
return false
|
||||
}
|
||||
defer gb.graphChanges.Done(item)
|
||||
event, ok := item.(*event)
|
||||
if !ok {
|
||||
utilruntime.HandleError(fmt.Errorf("expect a *event, got %v", item))
|
||||
return true
|
||||
}
|
||||
obj := event.obj
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("cannot access obj: %v", err))
|
||||
return true
|
||||
}
|
||||
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
|
||||
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 {
|
||||
case (event.eventType == addEvent || event.eventType == updateEvent) && !found:
|
||||
newNode := &node{
|
||||
identity: objectReference{
|
||||
OwnerReference: metav1.OwnerReference{
|
||||
APIVersion: event.gvk.GroupVersion().String(),
|
||||
Kind: event.gvk.Kind,
|
||||
UID: accessor.GetUID(),
|
||||
Name: accessor.GetName(),
|
||||
},
|
||||
Namespace: accessor.GetNamespace(),
|
||||
},
|
||||
dependents: make(map[*node]struct{}),
|
||||
owners: accessor.GetOwnerReferences(),
|
||||
deletingDependents: beingDeleted(accessor) && hasDeleteDependentsFinalizer(accessor),
|
||||
beingDeleted: beingDeleted(accessor),
|
||||
}
|
||||
gb.insertNode(newNode)
|
||||
// the underlying delta_fifo may combine a creation and a deletion into
|
||||
// one event, so we need to further process the event.
|
||||
gb.processTransitions(event.oldObj, accessor, newNode)
|
||||
case (event.eventType == addEvent || event.eventType == updateEvent) && found:
|
||||
// handle changes in ownerReferences
|
||||
added, removed, changed := referencesDiffs(existingNode.owners, accessor.GetOwnerReferences())
|
||||
if len(added) != 0 || len(removed) != 0 || len(changed) != 0 {
|
||||
// check if the changed dependency graph unblock owners that are
|
||||
// waiting for the deletion of their dependents.
|
||||
gb.addUnblockedOwnersToDeleteQueue(removed, changed)
|
||||
// update the node itself
|
||||
existingNode.owners = accessor.GetOwnerReferences()
|
||||
// Add the node to its new owners' dependent lists.
|
||||
gb.addDependentToOwners(existingNode, added)
|
||||
// remove the node from the dependent list of node that are no longer in
|
||||
// the node's owners list.
|
||||
gb.removeDependentFromOwners(existingNode, removed)
|
||||
}
|
||||
|
||||
if beingDeleted(accessor) {
|
||||
existingNode.markBeingDeleted()
|
||||
}
|
||||
gb.processTransitions(event.oldObj, accessor, existingNode)
|
||||
case event.eventType == deleteEvent:
|
||||
if !found {
|
||||
glog.V(5).Infof("%v doesn't exist in the graph, this shouldn't happen", accessor.GetUID())
|
||||
return true
|
||||
}
|
||||
// removeNode updates the graph
|
||||
gb.removeNode(existingNode)
|
||||
existingNode.dependentsLock.RLock()
|
||||
defer existingNode.dependentsLock.RUnlock()
|
||||
if len(existingNode.dependents) > 0 {
|
||||
gb.absentOwnerCache.Add(accessor.GetUID())
|
||||
}
|
||||
for dep := range existingNode.dependents {
|
||||
gb.attemptToDelete.Add(dep)
|
||||
}
|
||||
for _, owner := range existingNode.owners {
|
||||
ownerNode, found := gb.uidToNode.Read(owner.UID)
|
||||
if !found || !ownerNode.isDeletingDependents() {
|
||||
continue
|
||||
}
|
||||
// this is to let attempToDeleteItem check if all the owner's
|
||||
// dependents are deleted, if so, the owner will be deleted.
|
||||
gb.attemptToDelete.Add(ownerNode)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
53
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly/BUILD
generated
vendored
Normal file
53
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly/BUILD
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"metaonly.go",
|
||||
"types.go",
|
||||
"zz_generated.deepcopy.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly",
|
||||
deps = [
|
||||
"//pkg/api/legacyscheme:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["metaonly_test.go"],
|
||||
importpath = "k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/apis/core/install:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
66
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly/metaonly.go
generated
vendored
Normal file
66
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly/metaonly.go
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package metaonly
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
)
|
||||
|
||||
type metaOnlyJSONScheme struct{}
|
||||
|
||||
// This function can be extended to mapping different gvk to different MetadataOnlyObject,
|
||||
// which embedded with different version of ObjectMeta. Currently the system
|
||||
// only supports metav1.ObjectMeta.
|
||||
func gvkToMetadataOnlyObject(gvk schema.GroupVersionKind) runtime.Object {
|
||||
if strings.HasSuffix(gvk.Kind, "List") {
|
||||
return &MetadataOnlyObjectList{}
|
||||
} else {
|
||||
return &MetadataOnlyObject{}
|
||||
}
|
||||
}
|
||||
|
||||
func NewMetadataCodecFactory() serializer.CodecFactory {
|
||||
// populating another scheme from legacyscheme.Scheme, registering every kind with
|
||||
// MetadataOnlyObject (or MetadataOnlyObjectList).
|
||||
scheme := runtime.NewScheme()
|
||||
allTypes := legacyscheme.Scheme.AllKnownTypes()
|
||||
for kind := range allTypes {
|
||||
if kind.Version == runtime.APIVersionInternal {
|
||||
continue
|
||||
}
|
||||
if kind == metav1.Unversioned.WithKind("Status") {
|
||||
// this is added below as unversioned
|
||||
continue
|
||||
}
|
||||
metaOnlyObject := gvkToMetadataOnlyObject(kind)
|
||||
scheme.AddKnownTypeWithName(kind, metaOnlyObject)
|
||||
}
|
||||
scheme.AddUnversionedTypes(metav1.Unversioned, &metav1.Status{})
|
||||
return serializer.NewCodecFactory(scheme)
|
||||
}
|
||||
|
||||
// String converts a MetadataOnlyObject to a human-readable string.
|
||||
func (metaOnly MetadataOnlyObject) String() string {
|
||||
return fmt.Sprintf("%s/%s, name: %s, DeletionTimestamp:%v", metaOnly.TypeMeta.APIVersion, metaOnly.TypeMeta.Kind, metaOnly.ObjectMeta.Name, metaOnly.ObjectMeta.DeletionTimestamp)
|
||||
}
|
164
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly/metaonly_test.go
generated
vendored
Normal file
164
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly/metaonly_test.go
generated
vendored
Normal file
@ -0,0 +1,164 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package metaonly
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
_ "k8s.io/kubernetes/pkg/apis/core/install"
|
||||
)
|
||||
|
||||
func getPod() *v1.Pod {
|
||||
return &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pod",
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{UID: "1234"},
|
||||
},
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "fake-name",
|
||||
Image: "fakeimage",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getPodJson(t *testing.T) []byte {
|
||||
data, err := json.Marshal(getPod())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func getPodListJson(t *testing.T) []byte {
|
||||
data, err := json.Marshal(&v1.PodList{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "PodList",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
Items: []v1.Pod{
|
||||
*getPod(),
|
||||
*getPod(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func verfiyMetadata(description string, t *testing.T, in *MetadataOnlyObject) {
|
||||
pod := getPod()
|
||||
if e, a := pod.ObjectMeta, in.ObjectMeta; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("%s: expected %#v, got %#v", description, e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeToMetadataOnlyObject(t *testing.T) {
|
||||
data := getPodJson(t)
|
||||
cf := serializer.DirectCodecFactory{CodecFactory: NewMetadataCodecFactory()}
|
||||
info, ok := runtime.SerializerInfoForMediaType(cf.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
||||
if !ok {
|
||||
t.Fatalf("expected to get a JSON serializer")
|
||||
}
|
||||
codec := cf.DecoderToVersion(info.Serializer, schema.GroupVersion{Group: "SOMEGROUP", Version: "SOMEVERSION"})
|
||||
// decode with into
|
||||
into := &MetadataOnlyObject{}
|
||||
ret, _, err := codec.Decode(data, nil, into)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
metaOnly, ok := ret.(*MetadataOnlyObject)
|
||||
if !ok {
|
||||
t.Fatalf("expected ret to be *runtime.MetadataOnlyObject")
|
||||
}
|
||||
verfiyMetadata("check returned metaonly with into", t, metaOnly)
|
||||
verfiyMetadata("check into", t, into)
|
||||
// decode without into
|
||||
ret, _, err = codec.Decode(data, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
metaOnly, ok = ret.(*MetadataOnlyObject)
|
||||
if !ok {
|
||||
t.Fatalf("expected ret to be *runtime.MetadataOnlyObject")
|
||||
}
|
||||
verfiyMetadata("check returned metaonly without into", t, metaOnly)
|
||||
}
|
||||
|
||||
func verifyListMetadata(t *testing.T, metaOnlyList *MetadataOnlyObjectList) {
|
||||
items, err := meta.ExtractList(metaOnlyList)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, item := range items {
|
||||
metaOnly, ok := item.(*MetadataOnlyObject)
|
||||
if !ok {
|
||||
t.Fatalf("expected item to be *MetadataOnlyObject")
|
||||
}
|
||||
verfiyMetadata("check list", t, metaOnly)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeToMetadataOnlyObjectList(t *testing.T) {
|
||||
data := getPodListJson(t)
|
||||
cf := serializer.DirectCodecFactory{CodecFactory: NewMetadataCodecFactory()}
|
||||
info, ok := runtime.SerializerInfoForMediaType(cf.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
||||
if !ok {
|
||||
t.Fatalf("expected to get a JSON serializer")
|
||||
}
|
||||
codec := cf.DecoderToVersion(info.Serializer, schema.GroupVersion{Group: "SOMEGROUP", Version: "SOMEVERSION"})
|
||||
// decode with into
|
||||
into := &MetadataOnlyObjectList{}
|
||||
ret, _, err := codec.Decode(data, nil, into)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
metaOnlyList, ok := ret.(*MetadataOnlyObjectList)
|
||||
if !ok {
|
||||
t.Fatalf("expected ret to be *runtime.UnstructuredList")
|
||||
}
|
||||
verifyListMetadata(t, metaOnlyList)
|
||||
verifyListMetadata(t, into)
|
||||
// decode without into
|
||||
ret, _, err = codec.Decode(data, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
metaOnlyList, ok = ret.(*MetadataOnlyObjectList)
|
||||
if !ok {
|
||||
t.Fatalf("expected ret to be *runtime.UnstructuredList")
|
||||
}
|
||||
verifyListMetadata(t, metaOnlyList)
|
||||
}
|
47
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly/types.go
generated
vendored
Normal file
47
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly/types.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package metaonly
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// MetadataOnlyObject allows decoding only the apiVersion, kind, and metadata fields of
|
||||
// JSON data.
|
||||
// TODO: enable meta-only decoding for protobuf.
|
||||
//
|
||||
// +k8s:deepcopy-gen=true
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type MetadataOnlyObject struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
// +optional
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// MetadataOnlyObjectList allows decoding from JSON data only the typemeta and metadata of
|
||||
// a list, and those of the enclosing objects.
|
||||
// TODO: enable meta-only decoding for protobuf.
|
||||
//
|
||||
// +k8s:deepcopy-gen=true
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type MetadataOnlyObjectList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
// +optional
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
|
||||
Items []MetadataOnlyObject `json:"items"`
|
||||
}
|
86
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly/zz_generated.deepcopy.go
generated
vendored
Normal file
86
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly/zz_generated.deepcopy.go
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// This file was autogenerated by deepcopy-gen. Do not edit it manually!
|
||||
|
||||
package metaonly
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MetadataOnlyObject) DeepCopyInto(out *MetadataOnlyObject) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetadataOnlyObject.
|
||||
func (in *MetadataOnlyObject) DeepCopy() *MetadataOnlyObject {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MetadataOnlyObject)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *MetadataOnlyObject) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MetadataOnlyObjectList) DeepCopyInto(out *MetadataOnlyObjectList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
out.ListMeta = in.ListMeta
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]MetadataOnlyObject, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetadataOnlyObjectList.
|
||||
func (in *MetadataOnlyObjectList) DeepCopy() *MetadataOnlyObjectList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MetadataOnlyObjectList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *MetadataOnlyObjectList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
143
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/operations.go
generated
vendored
Normal file
143
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/operations.go
generated
vendored
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package garbagecollector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/util/retry"
|
||||
)
|
||||
|
||||
// apiResource consults the REST mapper to translate an <apiVersion, kind,
|
||||
// namespace> tuple to a unversioned.APIResource struct.
|
||||
func (gc *GarbageCollector) apiResource(apiVersion, kind string) (*metav1.APIResource, error) {
|
||||
fqKind := schema.FromAPIVersionAndKind(apiVersion, kind)
|
||||
mapping, err := gc.restMapper.RESTMapping(fqKind.GroupKind(), apiVersion)
|
||||
if err != nil {
|
||||
return nil, newRESTMappingError(kind, apiVersion)
|
||||
}
|
||||
glog.V(5).Infof("map kind %s, version %s to resource %s", kind, apiVersion, mapping.Resource)
|
||||
resource := metav1.APIResource{
|
||||
Name: mapping.Resource,
|
||||
Namespaced: mapping.Scope == meta.RESTScopeNamespace,
|
||||
Kind: kind,
|
||||
}
|
||||
return &resource, nil
|
||||
}
|
||||
|
||||
func (gc *GarbageCollector) deleteObject(item objectReference, policy *metav1.DeletionPropagation) error {
|
||||
fqKind := schema.FromAPIVersionAndKind(item.APIVersion, item.Kind)
|
||||
client, err := gc.clientPool.ClientForGroupVersionKind(fqKind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resource, err := gc.apiResource(item.APIVersion, item.Kind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uid := item.UID
|
||||
preconditions := metav1.Preconditions{UID: &uid}
|
||||
deleteOptions := metav1.DeleteOptions{Preconditions: &preconditions, PropagationPolicy: policy}
|
||||
return client.Resource(resource, item.Namespace).Delete(item.Name, &deleteOptions)
|
||||
}
|
||||
|
||||
func (gc *GarbageCollector) getObject(item objectReference) (*unstructured.Unstructured, error) {
|
||||
fqKind := schema.FromAPIVersionAndKind(item.APIVersion, item.Kind)
|
||||
client, err := gc.clientPool.ClientForGroupVersionKind(fqKind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resource, err := gc.apiResource(item.APIVersion, item.Kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.Resource(resource, item.Namespace).Get(item.Name, metav1.GetOptions{})
|
||||
}
|
||||
|
||||
func (gc *GarbageCollector) updateObject(item objectReference, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||||
fqKind := schema.FromAPIVersionAndKind(item.APIVersion, item.Kind)
|
||||
client, err := gc.clientPool.ClientForGroupVersionKind(fqKind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resource, err := gc.apiResource(item.APIVersion, item.Kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.Resource(resource, item.Namespace).Update(obj)
|
||||
}
|
||||
|
||||
func (gc *GarbageCollector) patchObject(item objectReference, patch []byte) (*unstructured.Unstructured, error) {
|
||||
fqKind := schema.FromAPIVersionAndKind(item.APIVersion, item.Kind)
|
||||
client, err := gc.clientPool.ClientForGroupVersionKind(fqKind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resource, err := gc.apiResource(item.APIVersion, item.Kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.Resource(resource, item.Namespace).Patch(item.Name, types.StrategicMergePatchType, patch)
|
||||
}
|
||||
|
||||
// TODO: Using Patch when strategicmerge supports deleting an entry from a
|
||||
// slice of a base type.
|
||||
func (gc *GarbageCollector) removeFinalizer(owner *node, targetFinalizer string) error {
|
||||
err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
ownerObject, err := gc.getObject(owner.identity)
|
||||
if errors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot finalize owner %s, because cannot get it: %v. The garbage collector will retry later.", owner.identity, err)
|
||||
}
|
||||
accessor, err := meta.Accessor(ownerObject)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot access the owner object %v: %v. The garbage collector will retry later.", ownerObject, err)
|
||||
}
|
||||
finalizers := accessor.GetFinalizers()
|
||||
var newFinalizers []string
|
||||
found := false
|
||||
for _, f := range finalizers {
|
||||
if f == targetFinalizer {
|
||||
found = true
|
||||
continue
|
||||
}
|
||||
newFinalizers = append(newFinalizers, f)
|
||||
}
|
||||
if !found {
|
||||
glog.V(5).Infof("the orphan finalizer is already removed from object %s", owner.identity)
|
||||
return nil
|
||||
}
|
||||
// remove the owner from dependent's OwnerReferences
|
||||
ownerObject.SetFinalizers(newFinalizers)
|
||||
_, err = gc.updateObject(owner.identity, ownerObject)
|
||||
return err
|
||||
})
|
||||
if errors.IsConflict(err) {
|
||||
return fmt.Errorf("updateMaxRetries(%d) has reached. The garbage collector will retry later for owner %v.", retry.DefaultBackoff.Steps, owner.identity)
|
||||
}
|
||||
return err
|
||||
}
|
54
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/patch.go
generated
vendored
Normal file
54
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/patch.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package garbagecollector
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/controller/garbagecollector/metaonly"
|
||||
)
|
||||
|
||||
func deleteOwnerRefPatch(dependentUID types.UID, ownerUIDs ...types.UID) []byte {
|
||||
var pieces []string
|
||||
for _, ownerUID := range ownerUIDs {
|
||||
pieces = append(pieces, fmt.Sprintf(`{"$patch":"delete","uid":"%s"}`, ownerUID))
|
||||
}
|
||||
patch := fmt.Sprintf(`{"metadata":{"ownerReferences":[%s],"uid":"%s"}}`, strings.Join(pieces, ","), dependentUID)
|
||||
return []byte(patch)
|
||||
}
|
||||
|
||||
// generate a patch that unsets the BlockOwnerDeletion field of all
|
||||
// ownerReferences of node.
|
||||
func (n *node) patchToUnblockOwnerReferences() ([]byte, error) {
|
||||
var dummy metaonly.MetadataOnlyObject
|
||||
var blockingRefs []metav1.OwnerReference
|
||||
falseVar := false
|
||||
for _, owner := range n.owners {
|
||||
if owner.BlockOwnerDeletion != nil && *owner.BlockOwnerDeletion {
|
||||
ref := owner
|
||||
ref.BlockOwnerDeletion = &falseVar
|
||||
blockingRefs = append(blockingRefs, ref)
|
||||
}
|
||||
}
|
||||
dummy.ObjectMeta.SetOwnerReferences(blockingRefs)
|
||||
dummy.ObjectMeta.UID = n.identity.UID
|
||||
return json.Marshal(dummy)
|
||||
}
|
52
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/uid_cache.go
generated
vendored
Normal file
52
vendor/k8s.io/kubernetes/pkg/controller/garbagecollector/uid_cache.go
generated
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package garbagecollector
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/golang/groupcache/lru"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
// UIDCache is an LRU cache for uid.
|
||||
type UIDCache struct {
|
||||
mutex sync.Mutex
|
||||
cache *lru.Cache
|
||||
}
|
||||
|
||||
// NewUIDCache returns a UIDCache.
|
||||
func NewUIDCache(maxCacheEntries int) *UIDCache {
|
||||
return &UIDCache{
|
||||
cache: lru.New(maxCacheEntries),
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a uid to the cache.
|
||||
func (c *UIDCache) Add(uid types.UID) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
c.cache.Add(uid, nil)
|
||||
}
|
||||
|
||||
// Has returns if a uid is in the cache.
|
||||
func (c *UIDCache) Has(uid types.UID) bool {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
_, found := c.cache.Get(uid)
|
||||
return found
|
||||
}
|
Reference in New Issue
Block a user