mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-31 02:08:13 +00:00 
			
		
		
		
	Merge pull request #40362 from deads2k/client-14-move-pkgs
Automatic merge from submit-queue move client/cache and client/discovery to client-go mechanical changes to move those packages. Had to create a `k8s.io/kubernetes/pkg/client/tests` package for tests that were blacklisted from client-go. We can rewrite these tests later and move them, but for now they'll still run at least. @caesarxuchao @sttts
This commit is contained in:
		| @@ -21,7 +21,6 @@ go_test( | ||||
|         "//cmd/kube-aggregator/pkg/client/listers/apiregistration/internalversion:go_default_library", | ||||
|         "//pkg/api:go_default_library", | ||||
|         "//pkg/api/v1:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//pkg/client/listers/core/v1:go_default_library", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
| @@ -29,6 +28,7 @@ go_test( | ||||
|         "//vendor:k8s.io/apimachinery/pkg/util/sets", | ||||
|         "//vendor:k8s.io/apiserver/pkg/authentication/user", | ||||
|         "//vendor:k8s.io/apiserver/pkg/endpoints/request", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
| @@ -51,7 +51,6 @@ go_library( | ||||
|         "//cmd/kube-aggregator/pkg/client/listers/apiregistration/internalversion:go_default_library", | ||||
|         "//cmd/kube-aggregator/pkg/registry/apiservice/etcd:go_default_library", | ||||
|         "//pkg/api:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//pkg/client/clientset_generated/clientset:go_default_library", | ||||
|         "//pkg/client/informers/informers_generated:go_default_library", | ||||
|         "//pkg/client/listers/core/v1:go_default_library", | ||||
| @@ -75,6 +74,7 @@ go_library( | ||||
|         "//vendor:k8s.io/apimachinery/pkg/util/wait", | ||||
|         "//vendor:k8s.io/apiserver/pkg/endpoints/request", | ||||
|         "//vendor:k8s.io/client-go/rest", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|         "//vendor:k8s.io/client-go/transport", | ||||
|     ], | ||||
| ) | ||||
|   | ||||
| @@ -25,7 +25,7 @@ import ( | ||||
| 	apierrors "k8s.io/apimachinery/pkg/api/errors" | ||||
| 	utilruntime "k8s.io/apimachinery/pkg/util/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/util/wait" | ||||
| 	"k8s.io/kubernetes/pkg/client/cache" | ||||
| 	"k8s.io/client-go/tools/cache" | ||||
| 	"k8s.io/kubernetes/pkg/controller" | ||||
| 	"k8s.io/kubernetes/pkg/util/workqueue" | ||||
|  | ||||
|   | ||||
| @@ -26,9 +26,9 @@ import ( | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/util/diff" | ||||
| 	"k8s.io/client-go/tools/cache" | ||||
| 	"k8s.io/kubernetes/pkg/api" | ||||
| 	corev1 "k8s.io/kubernetes/pkg/api/v1" | ||||
| 	"k8s.io/kubernetes/pkg/client/cache" | ||||
| 	v1listers "k8s.io/kubernetes/pkg/client/listers/core/v1" | ||||
|  | ||||
| 	"k8s.io/kubernetes/cmd/kube-aggregator/pkg/apis/apiregistration" | ||||
|   | ||||
| @@ -21,9 +21,9 @@ go_library( | ||||
|         "//cmd/kube-aggregator/pkg/client/clientset_generated/internalclientset:go_default_library", | ||||
|         "//cmd/kube-aggregator/pkg/client/informers/apiregistration:go_default_library", | ||||
|         "//cmd/kube-aggregator/pkg/client/informers/internalinterfaces:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime/schema", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -19,10 +19,10 @@ go_library( | ||||
|         "//cmd/kube-aggregator/pkg/client/clientset_generated/internalclientset:go_default_library", | ||||
|         "//cmd/kube-aggregator/pkg/client/informers/internalinterfaces:go_default_library", | ||||
|         "//cmd/kube-aggregator/pkg/client/listers/apiregistration/internalversion:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -22,11 +22,11 @@ import ( | ||||
| 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	runtime "k8s.io/apimachinery/pkg/runtime" | ||||
| 	watch "k8s.io/apimachinery/pkg/watch" | ||||
| 	cache "k8s.io/client-go/tools/cache" | ||||
| 	apiregistration "k8s.io/kubernetes/cmd/kube-aggregator/pkg/apis/apiregistration" | ||||
| 	internalclientset "k8s.io/kubernetes/cmd/kube-aggregator/pkg/client/clientset_generated/internalclientset" | ||||
| 	internalinterfaces "k8s.io/kubernetes/cmd/kube-aggregator/pkg/client/informers/internalinterfaces" | ||||
| 	internalversion "k8s.io/kubernetes/cmd/kube-aggregator/pkg/client/listers/apiregistration/internalversion" | ||||
| 	cache "k8s.io/kubernetes/pkg/client/cache" | ||||
| 	time "time" | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -19,10 +19,10 @@ go_library( | ||||
|         "//cmd/kube-aggregator/pkg/client/clientset_generated/clientset:go_default_library", | ||||
|         "//cmd/kube-aggregator/pkg/client/informers/internalinterfaces:go_default_library", | ||||
|         "//cmd/kube-aggregator/pkg/client/listers/apiregistration/v1alpha1:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -22,11 +22,11 @@ import ( | ||||
| 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	runtime "k8s.io/apimachinery/pkg/runtime" | ||||
| 	watch "k8s.io/apimachinery/pkg/watch" | ||||
| 	cache "k8s.io/client-go/tools/cache" | ||||
| 	apiregistration_v1alpha1 "k8s.io/kubernetes/cmd/kube-aggregator/pkg/apis/apiregistration/v1alpha1" | ||||
| 	clientset "k8s.io/kubernetes/cmd/kube-aggregator/pkg/client/clientset_generated/clientset" | ||||
| 	internalinterfaces "k8s.io/kubernetes/cmd/kube-aggregator/pkg/client/informers/internalinterfaces" | ||||
| 	v1alpha1 "k8s.io/kubernetes/cmd/kube-aggregator/pkg/client/listers/apiregistration/v1alpha1" | ||||
| 	cache "k8s.io/kubernetes/pkg/client/cache" | ||||
| 	time "time" | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -21,11 +21,11 @@ package informers | ||||
| import ( | ||||
| 	runtime "k8s.io/apimachinery/pkg/runtime" | ||||
| 	schema "k8s.io/apimachinery/pkg/runtime/schema" | ||||
| 	cache "k8s.io/client-go/tools/cache" | ||||
| 	clientset "k8s.io/kubernetes/cmd/kube-aggregator/pkg/client/clientset_generated/clientset" | ||||
| 	internalclientset "k8s.io/kubernetes/cmd/kube-aggregator/pkg/client/clientset_generated/internalclientset" | ||||
| 	apiregistration "k8s.io/kubernetes/cmd/kube-aggregator/pkg/client/informers/apiregistration" | ||||
| 	internalinterfaces "k8s.io/kubernetes/cmd/kube-aggregator/pkg/client/informers/internalinterfaces" | ||||
| 	cache "k8s.io/kubernetes/pkg/client/cache" | ||||
| 	reflect "reflect" | ||||
| 	sync "sync" | ||||
| 	time "time" | ||||
|   | ||||
| @@ -21,9 +21,9 @@ package informers | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	schema "k8s.io/apimachinery/pkg/runtime/schema" | ||||
| 	cache "k8s.io/client-go/tools/cache" | ||||
| 	apiregistration "k8s.io/kubernetes/cmd/kube-aggregator/pkg/apis/apiregistration" | ||||
| 	v1alpha1 "k8s.io/kubernetes/cmd/kube-aggregator/pkg/apis/apiregistration/v1alpha1" | ||||
| 	cache "k8s.io/kubernetes/pkg/client/cache" | ||||
| ) | ||||
|  | ||||
| // GenericInformer is type of SharedIndexInformer which will locate and delegate to other | ||||
|   | ||||
| @@ -14,8 +14,8 @@ go_library( | ||||
|     deps = [ | ||||
|         "//cmd/kube-aggregator/pkg/client/clientset_generated/clientset:go_default_library", | ||||
|         "//cmd/kube-aggregator/pkg/client/clientset_generated/internalclientset:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -20,9 +20,9 @@ package internalinterfaces | ||||
|  | ||||
| import ( | ||||
| 	runtime "k8s.io/apimachinery/pkg/runtime" | ||||
| 	cache "k8s.io/client-go/tools/cache" | ||||
| 	clientset "k8s.io/kubernetes/cmd/kube-aggregator/pkg/client/clientset_generated/clientset" | ||||
| 	internalclientset "k8s.io/kubernetes/cmd/kube-aggregator/pkg/client/clientset_generated/internalclientset" | ||||
| 	cache "k8s.io/kubernetes/pkg/client/cache" | ||||
| 	time "time" | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -16,10 +16,10 @@ go_library( | ||||
|     tags = ["automanaged"], | ||||
|     deps = [ | ||||
|         "//cmd/kube-aggregator/pkg/apis/apiregistration:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/api/errors", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/labels", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -22,8 +22,8 @@ import ( | ||||
| 	"k8s.io/apimachinery/pkg/api/errors" | ||||
| 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/labels" | ||||
| 	"k8s.io/client-go/tools/cache" | ||||
| 	apiregistration "k8s.io/kubernetes/cmd/kube-aggregator/pkg/apis/apiregistration" | ||||
| 	"k8s.io/kubernetes/pkg/client/cache" | ||||
| ) | ||||
|  | ||||
| // APIServiceLister helps list APIServices. | ||||
|   | ||||
| @@ -17,10 +17,10 @@ go_library( | ||||
|     deps = [ | ||||
|         "//cmd/kube-aggregator/pkg/apis/apiregistration:go_default_library", | ||||
|         "//cmd/kube-aggregator/pkg/apis/apiregistration/v1alpha1:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/api/errors", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/labels", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -22,9 +22,9 @@ import ( | ||||
| 	"k8s.io/apimachinery/pkg/api/errors" | ||||
| 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/labels" | ||||
| 	"k8s.io/client-go/tools/cache" | ||||
| 	apiregistration "k8s.io/kubernetes/cmd/kube-aggregator/pkg/apis/apiregistration" | ||||
| 	v1alpha1 "k8s.io/kubernetes/cmd/kube-aggregator/pkg/apis/apiregistration/v1alpha1" | ||||
| 	"k8s.io/kubernetes/pkg/client/cache" | ||||
| ) | ||||
|  | ||||
| // APIServiceLister helps list APIServices. | ||||
|   | ||||
| @@ -17,9 +17,9 @@ go_test( | ||||
|     library = ":go_default_library", | ||||
|     tags = ["automanaged"], | ||||
|     deps = [ | ||||
|         "//pkg/apis/componentconfig:go_default_library", | ||||
|         "//pkg/kubelet:go_default_library", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/util/diff", | ||||
|         "//vendor:k8s.io/apiserver/pkg/util/flag", | ||||
|         "//vendor:k8s.io/client-go/rest", | ||||
|     ], | ||||
| ) | ||||
|   | ||||
| @@ -19,7 +19,7 @@ package app | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	utilflag "k8s.io/apiserver/pkg/util/flag" | ||||
| 	"k8s.io/kubernetes/pkg/apis/componentconfig" | ||||
| 	"k8s.io/kubernetes/pkg/kubelet" | ||||
| ) | ||||
|  | ||||
| @@ -51,8 +51,8 @@ func TestValueOfAllocatableResources(t *testing.T) { | ||||
| 	} | ||||
|  | ||||
| 	for _, test := range testCases { | ||||
| 		kubeReservedCM := make(utilflag.ConfigurationMap) | ||||
| 		systemReservedCM := make(utilflag.ConfigurationMap) | ||||
| 		kubeReservedCM := make(componentconfig.ConfigurationMap) | ||||
| 		systemReservedCM := make(componentconfig.ConfigurationMap) | ||||
|  | ||||
| 		kubeReservedCM.Set(test.kubeReserved) | ||||
| 		systemReservedCM.Set(test.systemReserved) | ||||
|   | ||||
| @@ -20,14 +20,14 @@ import "k8s.io/gengo/types" | ||||
|  | ||||
| var ( | ||||
| 	apiScheme                   = types.Name{Package: "k8s.io/kubernetes/pkg/api", Name: "Scheme"} | ||||
| 	cacheGenericLister          = types.Name{Package: "k8s.io/kubernetes/pkg/client/cache", Name: "GenericLister"} | ||||
| 	cacheIndexers               = types.Name{Package: "k8s.io/kubernetes/pkg/client/cache", Name: "Indexers"} | ||||
| 	cacheListWatch              = types.Name{Package: "k8s.io/kubernetes/pkg/client/cache", Name: "ListWatch"} | ||||
| 	cacheMetaNamespaceIndexFunc = types.Name{Package: "k8s.io/kubernetes/pkg/client/cache", Name: "MetaNamespaceIndexFunc"} | ||||
| 	cacheNamespaceIndex         = types.Name{Package: "k8s.io/kubernetes/pkg/client/cache", Name: "NamespaceIndex"} | ||||
| 	cacheNewGenericLister       = types.Name{Package: "k8s.io/kubernetes/pkg/client/cache", Name: "NewGenericLister"} | ||||
| 	cacheNewSharedIndexInformer = types.Name{Package: "k8s.io/kubernetes/pkg/client/cache", Name: "NewSharedIndexInformer"} | ||||
| 	cacheSharedIndexInformer    = types.Name{Package: "k8s.io/kubernetes/pkg/client/cache", Name: "SharedIndexInformer"} | ||||
| 	cacheGenericLister          = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "GenericLister"} | ||||
| 	cacheIndexers               = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "Indexers"} | ||||
| 	cacheListWatch              = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "ListWatch"} | ||||
| 	cacheMetaNamespaceIndexFunc = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "MetaNamespaceIndexFunc"} | ||||
| 	cacheNamespaceIndex         = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "NamespaceIndex"} | ||||
| 	cacheNewGenericLister       = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "NewGenericLister"} | ||||
| 	cacheNewSharedIndexInformer = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "NewSharedIndexInformer"} | ||||
| 	cacheSharedIndexInformer    = types.Name{Package: "k8s.io/client-go/tools/cache", Name: "SharedIndexInformer"} | ||||
| 	listOptions                 = types.Name{Package: "k8s.io/kubernetes/pkg/api", Name: "ListOptions"} | ||||
| 	reflectType                 = types.Name{Package: "reflect", Name: "Type"} | ||||
| 	runtimeObject               = types.Name{Package: "k8s.io/apimachinery/pkg/runtime", Name: "Object"} | ||||
|   | ||||
| @@ -219,7 +219,7 @@ func (g *listerGenerator) Imports(c *generator.Context) (imports []string) { | ||||
| 	imports = append(imports, "k8s.io/apimachinery/pkg/api/errors") | ||||
| 	imports = append(imports, "k8s.io/apimachinery/pkg/labels") | ||||
| 	// for Indexer | ||||
| 	imports = append(imports, "k8s.io/kubernetes/pkg/client/cache") | ||||
| 	imports = append(imports, "k8s.io/client-go/tools/cache") | ||||
| 	return | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										2
									
								
								federation/client/cache/BUILD
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								federation/client/cache/BUILD
									
									
									
									
										vendored
									
									
								
							| @@ -13,8 +13,8 @@ go_library( | ||||
|     tags = ["automanaged"], | ||||
|     deps = [ | ||||
|         "//federation/apis/federation/v1beta1:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//vendor:github.com/golang/glog", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
|   | ||||
							
								
								
									
										2
									
								
								federation/client/cache/cluster_cache.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								federation/client/cache/cluster_cache.go
									
									
									
									
										vendored
									
									
								
							| @@ -18,8 +18,8 @@ package cache | ||||
|  | ||||
| import ( | ||||
| 	"github.com/golang/glog" | ||||
| 	kubecache "k8s.io/client-go/tools/cache" | ||||
| 	"k8s.io/kubernetes/federation/apis/federation/v1beta1" | ||||
| 	kubecache "k8s.io/kubernetes/pkg/client/cache" | ||||
| ) | ||||
|  | ||||
| // StoreToClusterLister makes a Store have the List method of the metav1.ClusterInterface | ||||
|   | ||||
| @@ -23,7 +23,6 @@ go_library( | ||||
|         "//federation/pkg/federation-controller/util:go_default_library", | ||||
|         "//pkg/api:go_default_library", | ||||
|         "//pkg/api/v1:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//pkg/client/clientset_generated/internalclientset:go_default_library", | ||||
|         "//pkg/client/typed/discovery:go_default_library", | ||||
|         "//pkg/controller:go_default_library", | ||||
| @@ -35,6 +34,7 @@ go_library( | ||||
|         "//vendor:k8s.io/apimachinery/pkg/util/wait", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", | ||||
|         "//vendor:k8s.io/client-go/rest", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -27,10 +27,10 @@ import ( | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| 	"k8s.io/apimachinery/pkg/util/wait" | ||||
| 	"k8s.io/apimachinery/pkg/watch" | ||||
| 	"k8s.io/client-go/tools/cache" | ||||
| 	federationv1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" | ||||
| 	clustercache "k8s.io/kubernetes/federation/client/cache" | ||||
| 	federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" | ||||
| 	"k8s.io/kubernetes/pkg/client/cache" | ||||
| 	"k8s.io/kubernetes/pkg/controller" | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -19,7 +19,6 @@ go_library( | ||||
|         "//federation/pkg/federation-controller/util/eventsink:go_default_library", | ||||
|         "//pkg/api:go_default_library", | ||||
|         "//pkg/api/v1:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//pkg/client/clientset_generated/clientset:go_default_library", | ||||
|         "//pkg/client/record:go_default_library", | ||||
|         "//pkg/controller:go_default_library", | ||||
| @@ -28,6 +27,7 @@ go_library( | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/types", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|         "//vendor:k8s.io/client-go/util/flowcontrol", | ||||
|     ], | ||||
| ) | ||||
|   | ||||
| @@ -23,6 +23,7 @@ import ( | ||||
| 	pkgruntime "k8s.io/apimachinery/pkg/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/types" | ||||
| 	"k8s.io/apimachinery/pkg/watch" | ||||
| 	"k8s.io/client-go/tools/cache" | ||||
| 	"k8s.io/client-go/util/flowcontrol" | ||||
| 	federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" | ||||
| 	federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" | ||||
| @@ -30,7 +31,6 @@ import ( | ||||
| 	"k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink" | ||||
| 	"k8s.io/kubernetes/pkg/api" | ||||
| 	apiv1 "k8s.io/kubernetes/pkg/api/v1" | ||||
| 	"k8s.io/kubernetes/pkg/client/cache" | ||||
| 	kubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" | ||||
| 	"k8s.io/kubernetes/pkg/client/record" | ||||
| 	"k8s.io/kubernetes/pkg/controller" | ||||
|   | ||||
| @@ -21,7 +21,6 @@ go_library( | ||||
|         "//pkg/api:go_default_library", | ||||
|         "//pkg/api/v1:go_default_library", | ||||
|         "//pkg/apis/extensions/v1beta1:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//pkg/client/clientset_generated/clientset:go_default_library", | ||||
|         "//pkg/client/record:go_default_library", | ||||
|         "//pkg/controller:go_default_library", | ||||
| @@ -31,6 +30,7 @@ go_library( | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/types", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|         "//vendor:k8s.io/client-go/util/flowcontrol", | ||||
|     ], | ||||
| ) | ||||
|   | ||||
| @@ -26,6 +26,7 @@ import ( | ||||
| 	pkgruntime "k8s.io/apimachinery/pkg/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/types" | ||||
| 	"k8s.io/apimachinery/pkg/watch" | ||||
| 	"k8s.io/client-go/tools/cache" | ||||
| 	"k8s.io/client-go/util/flowcontrol" | ||||
| 	federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" | ||||
| 	federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" | ||||
| @@ -35,7 +36,6 @@ import ( | ||||
| 	"k8s.io/kubernetes/pkg/api" | ||||
| 	apiv1 "k8s.io/kubernetes/pkg/api/v1" | ||||
| 	extensionsv1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" | ||||
| 	"k8s.io/kubernetes/pkg/client/cache" | ||||
| 	kubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" | ||||
| 	"k8s.io/kubernetes/pkg/client/record" | ||||
| 	"k8s.io/kubernetes/pkg/controller" | ||||
|   | ||||
| @@ -24,7 +24,6 @@ go_library( | ||||
|         "//pkg/api:go_default_library", | ||||
|         "//pkg/api/v1:go_default_library", | ||||
|         "//pkg/apis/extensions/v1beta1:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//pkg/client/clientset_generated/clientset:go_default_library", | ||||
|         "//pkg/client/record:go_default_library", | ||||
|         "//pkg/controller:go_default_library", | ||||
| @@ -35,6 +34,7 @@ go_library( | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/util/wait", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|         "//vendor:k8s.io/client-go/util/flowcontrol", | ||||
|     ], | ||||
| ) | ||||
|   | ||||
| @@ -30,6 +30,7 @@ import ( | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/util/wait" | ||||
| 	"k8s.io/apimachinery/pkg/watch" | ||||
| 	"k8s.io/client-go/tools/cache" | ||||
| 	"k8s.io/client-go/util/flowcontrol" | ||||
| 	fed "k8s.io/kubernetes/federation/apis/federation" | ||||
| 	fedv1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" | ||||
| @@ -42,7 +43,6 @@ import ( | ||||
| 	"k8s.io/kubernetes/pkg/api" | ||||
| 	apiv1 "k8s.io/kubernetes/pkg/api/v1" | ||||
| 	extensionsv1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" | ||||
| 	"k8s.io/kubernetes/pkg/client/cache" | ||||
| 	kubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" | ||||
| 	"k8s.io/kubernetes/pkg/client/record" | ||||
| 	"k8s.io/kubernetes/pkg/controller" | ||||
|   | ||||
| @@ -21,7 +21,6 @@ go_library( | ||||
|         "//pkg/api:go_default_library", | ||||
|         "//pkg/api/v1:go_default_library", | ||||
|         "//pkg/apis/extensions/v1beta1:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//pkg/client/clientset_generated/clientset:go_default_library", | ||||
|         "//pkg/client/record:go_default_library", | ||||
|         "//pkg/controller:go_default_library", | ||||
| @@ -32,6 +31,7 @@ go_library( | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime/schema", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/types", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|         "//vendor:k8s.io/client-go/util/flowcontrol", | ||||
|     ], | ||||
| ) | ||||
| @@ -49,7 +49,6 @@ go_test( | ||||
|         "//federation/pkg/federation-controller/util/test:go_default_library", | ||||
|         "//pkg/api/v1:go_default_library", | ||||
|         "//pkg/apis/extensions/v1beta1:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//pkg/client/clientset_generated/clientset:go_default_library", | ||||
|         "//pkg/client/clientset_generated/clientset/fake:go_default_library", | ||||
|         "//vendor:github.com/golang/glog", | ||||
| @@ -59,6 +58,7 @@ go_test( | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/types", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/util/wait", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -27,6 +27,7 @@ import ( | ||||
| 	"k8s.io/apimachinery/pkg/runtime/schema" | ||||
| 	"k8s.io/apimachinery/pkg/types" | ||||
| 	"k8s.io/apimachinery/pkg/watch" | ||||
| 	"k8s.io/client-go/tools/cache" | ||||
| 	"k8s.io/client-go/util/flowcontrol" | ||||
| 	federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" | ||||
| 	federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" | ||||
| @@ -36,7 +37,6 @@ import ( | ||||
| 	"k8s.io/kubernetes/pkg/api" | ||||
| 	"k8s.io/kubernetes/pkg/api/v1" | ||||
| 	extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" | ||||
| 	"k8s.io/kubernetes/pkg/client/cache" | ||||
| 	kubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" | ||||
| 	"k8s.io/kubernetes/pkg/client/record" | ||||
| 	"k8s.io/kubernetes/pkg/controller" | ||||
|   | ||||
| @@ -27,6 +27,7 @@ import ( | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/types" | ||||
| 	"k8s.io/apimachinery/pkg/util/wait" | ||||
| 	"k8s.io/client-go/tools/cache" | ||||
| 	federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" | ||||
| 	fakefedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/fake" | ||||
| 	"k8s.io/kubernetes/federation/pkg/federation-controller/util" | ||||
| @@ -34,7 +35,6 @@ import ( | ||||
| 	. "k8s.io/kubernetes/federation/pkg/federation-controller/util/test" | ||||
| 	apiv1 "k8s.io/kubernetes/pkg/api/v1" | ||||
| 	extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" | ||||
| 	"k8s.io/kubernetes/pkg/client/cache" | ||||
| 	kubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" | ||||
| 	fakekubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/fake" | ||||
|  | ||||
|   | ||||
| @@ -20,7 +20,6 @@ go_library( | ||||
|         "//federation/pkg/federation-controller/util/eventsink:go_default_library", | ||||
|         "//pkg/api:go_default_library", | ||||
|         "//pkg/api/v1:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//pkg/client/clientset_generated/clientset:go_default_library", | ||||
|         "//pkg/client/record:go_default_library", | ||||
|         "//pkg/controller:go_default_library", | ||||
| @@ -29,6 +28,7 @@ go_library( | ||||
|         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|         "//vendor:k8s.io/client-go/util/flowcontrol", | ||||
|     ], | ||||
| ) | ||||
|   | ||||
| @@ -24,6 +24,7 @@ import ( | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/watch" | ||||
| 	"k8s.io/client-go/tools/cache" | ||||
| 	"k8s.io/client-go/util/flowcontrol" | ||||
| 	federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" | ||||
| 	federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" | ||||
| @@ -32,7 +33,6 @@ import ( | ||||
| 	"k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink" | ||||
| 	"k8s.io/kubernetes/pkg/api" | ||||
| 	apiv1 "k8s.io/kubernetes/pkg/api/v1" | ||||
| 	"k8s.io/kubernetes/pkg/client/cache" | ||||
| 	kubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" | ||||
| 	"k8s.io/kubernetes/pkg/client/record" | ||||
| 	"k8s.io/kubernetes/pkg/controller" | ||||
|   | ||||
| @@ -24,7 +24,6 @@ go_library( | ||||
|         "//pkg/api:go_default_library", | ||||
|         "//pkg/api/v1:go_default_library", | ||||
|         "//pkg/apis/extensions/v1beta1:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//pkg/client/clientset_generated/clientset:go_default_library", | ||||
|         "//pkg/client/legacylisters:go_default_library", | ||||
|         "//pkg/client/record:go_default_library", | ||||
| @@ -36,6 +35,7 @@ go_library( | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/util/wait", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|         "//vendor:k8s.io/client-go/util/flowcontrol", | ||||
|     ], | ||||
| ) | ||||
|   | ||||
| @@ -30,6 +30,7 @@ import ( | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/util/wait" | ||||
| 	"k8s.io/apimachinery/pkg/watch" | ||||
| 	"k8s.io/client-go/tools/cache" | ||||
| 	"k8s.io/client-go/util/flowcontrol" | ||||
| 	fed "k8s.io/kubernetes/federation/apis/federation" | ||||
| 	fedv1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" | ||||
| @@ -42,7 +43,6 @@ import ( | ||||
| 	"k8s.io/kubernetes/pkg/api" | ||||
| 	apiv1 "k8s.io/kubernetes/pkg/api/v1" | ||||
| 	extensionsv1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" | ||||
| 	"k8s.io/kubernetes/pkg/client/cache" | ||||
| 	kubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" | ||||
| 	"k8s.io/kubernetes/pkg/client/legacylisters" | ||||
| 	"k8s.io/kubernetes/pkg/client/record" | ||||
|   | ||||
| @@ -20,7 +20,6 @@ go_library( | ||||
|         "//federation/pkg/federation-controller/util/eventsink:go_default_library", | ||||
|         "//pkg/api:go_default_library", | ||||
|         "//pkg/api/v1:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//pkg/client/clientset_generated/clientset:go_default_library", | ||||
|         "//pkg/client/record:go_default_library", | ||||
|         "//pkg/controller:go_default_library", | ||||
| @@ -30,6 +29,7 @@ go_library( | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/types", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|         "//vendor:k8s.io/client-go/util/flowcontrol", | ||||
|     ], | ||||
| ) | ||||
|   | ||||
| @@ -25,6 +25,7 @@ import ( | ||||
| 	pkgruntime "k8s.io/apimachinery/pkg/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/types" | ||||
| 	"k8s.io/apimachinery/pkg/watch" | ||||
| 	"k8s.io/client-go/tools/cache" | ||||
| 	"k8s.io/client-go/util/flowcontrol" | ||||
| 	federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" | ||||
| 	federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" | ||||
| @@ -33,7 +34,6 @@ import ( | ||||
| 	"k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink" | ||||
| 	"k8s.io/kubernetes/pkg/api" | ||||
| 	apiv1 "k8s.io/kubernetes/pkg/api/v1" | ||||
| 	"k8s.io/kubernetes/pkg/client/cache" | ||||
| 	kubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" | ||||
| 	"k8s.io/kubernetes/pkg/client/record" | ||||
| 	"k8s.io/kubernetes/pkg/controller" | ||||
|   | ||||
| @@ -29,7 +29,6 @@ go_library( | ||||
|         "//federation/pkg/federation-controller/util/deletionhelper:go_default_library", | ||||
|         "//pkg/api:go_default_library", | ||||
|         "//pkg/api/v1:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//pkg/client/clientset_generated/clientset:go_default_library", | ||||
|         "//pkg/client/legacylisters:go_default_library", | ||||
|         "//pkg/client/record:go_default_library", | ||||
| @@ -45,6 +44,7 @@ go_library( | ||||
|         "//vendor:k8s.io/apimachinery/pkg/util/wait", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", | ||||
|         "//vendor:k8s.io/client-go/rest", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -24,9 +24,9 @@ import ( | ||||
| 	"k8s.io/apimachinery/pkg/util/wait" | ||||
| 	"k8s.io/apimachinery/pkg/watch" | ||||
| 	restclient "k8s.io/client-go/rest" | ||||
| 	cache "k8s.io/client-go/tools/cache" | ||||
| 	v1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" | ||||
| 	v1 "k8s.io/kubernetes/pkg/api/v1" | ||||
| 	cache "k8s.io/kubernetes/pkg/client/cache" | ||||
| 	kubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" | ||||
| 	"k8s.io/kubernetes/pkg/client/legacylisters" | ||||
| 	"k8s.io/kubernetes/pkg/util/workqueue" | ||||
|   | ||||
| @@ -20,9 +20,9 @@ import ( | ||||
| 	"fmt" | ||||
| 	"time" | ||||
|  | ||||
| 	cache "k8s.io/client-go/tools/cache" | ||||
| 	fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" | ||||
| 	v1 "k8s.io/kubernetes/pkg/api/v1" | ||||
| 	cache "k8s.io/kubernetes/pkg/client/cache" | ||||
| 	"k8s.io/kubernetes/pkg/controller" | ||||
|  | ||||
| 	"github.com/golang/glog" | ||||
|   | ||||
| @@ -22,9 +22,9 @@ import ( | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/api/errors" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	cache "k8s.io/client-go/tools/cache" | ||||
| 	fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" | ||||
| 	v1 "k8s.io/kubernetes/pkg/api/v1" | ||||
| 	cache "k8s.io/kubernetes/pkg/client/cache" | ||||
| 	"k8s.io/kubernetes/pkg/controller" | ||||
|  | ||||
| 	"reflect" | ||||
|   | ||||
| @@ -32,6 +32,7 @@ import ( | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| 	"k8s.io/apimachinery/pkg/util/wait" | ||||
| 	"k8s.io/apimachinery/pkg/watch" | ||||
| 	cache "k8s.io/client-go/tools/cache" | ||||
| 	v1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1" | ||||
| 	federationcache "k8s.io/kubernetes/federation/client/cache" | ||||
| 	fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" | ||||
| @@ -41,7 +42,6 @@ import ( | ||||
| 	"k8s.io/kubernetes/federation/pkg/federation-controller/util/deletionhelper" | ||||
| 	"k8s.io/kubernetes/pkg/api" | ||||
| 	v1 "k8s.io/kubernetes/pkg/api/v1" | ||||
| 	cache "k8s.io/kubernetes/pkg/client/cache" | ||||
| 	kubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" | ||||
| 	"k8s.io/kubernetes/pkg/client/legacylisters" | ||||
| 	"k8s.io/kubernetes/pkg/client/record" | ||||
|   | ||||
| @@ -29,7 +29,6 @@ go_library( | ||||
|         "//pkg/api:go_default_library", | ||||
|         "//pkg/api/v1:go_default_library", | ||||
|         "//pkg/apis/extensions/v1beta1:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//pkg/client/clientset_generated/clientset:go_default_library", | ||||
|         "//pkg/client/clientset_generated/internalclientset:go_default_library", | ||||
|         "//pkg/controller/deployment/util:go_default_library", | ||||
| @@ -40,6 +39,7 @@ go_library( | ||||
|         "//vendor:k8s.io/apimachinery/pkg/util/wait", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", | ||||
|         "//vendor:k8s.io/client-go/rest", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|         "//vendor:k8s.io/client-go/tools/clientcmd", | ||||
|         "//vendor:k8s.io/client-go/tools/clientcmd/api", | ||||
|         "//vendor:k8s.io/client-go/util/flowcontrol", | ||||
| @@ -63,7 +63,6 @@ go_test( | ||||
|         "//federation/client/clientset_generated/federation_clientset/fake:go_default_library", | ||||
|         "//pkg/api/v1:go_default_library", | ||||
|         "//pkg/apis/extensions/v1beta1:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//pkg/client/clientset_generated/clientset:go_default_library", | ||||
|         "//pkg/client/clientset_generated/clientset/fake:go_default_library", | ||||
|         "//pkg/client/testing/core:go_default_library", | ||||
| @@ -72,6 +71,7 @@ go_test( | ||||
|         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -26,10 +26,10 @@ import ( | ||||
| 	pkgruntime "k8s.io/apimachinery/pkg/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/watch" | ||||
| 	restclient "k8s.io/client-go/rest" | ||||
| 	"k8s.io/client-go/tools/cache" | ||||
| 	federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" | ||||
| 	federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" | ||||
| 	apiv1 "k8s.io/kubernetes/pkg/api/v1" | ||||
| 	"k8s.io/kubernetes/pkg/client/cache" | ||||
| 	kubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" | ||||
|  | ||||
| 	"github.com/golang/glog" | ||||
|   | ||||
| @@ -23,10 +23,10 @@ import ( | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/watch" | ||||
| 	"k8s.io/client-go/tools/cache" | ||||
| 	federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" | ||||
| 	fakefederationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/fake" | ||||
| 	apiv1 "k8s.io/kubernetes/pkg/api/v1" | ||||
| 	"k8s.io/kubernetes/pkg/client/cache" | ||||
| 	kubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" | ||||
| 	fakekubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/fake" | ||||
| 	"k8s.io/kubernetes/pkg/client/testing/core" | ||||
|   | ||||
| @@ -22,7 +22,7 @@ import ( | ||||
|  | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	pkgruntime "k8s.io/apimachinery/pkg/runtime" | ||||
| 	"k8s.io/kubernetes/pkg/client/cache" | ||||
| 	"k8s.io/client-go/tools/cache" | ||||
| ) | ||||
|  | ||||
| // Returns cache.ResourceEventHandlerFuncs that trigger the given function | ||||
|   | ||||
| @@ -53,13 +53,13 @@ go_test( | ||||
|         "//pkg/api:go_default_library", | ||||
|         "//pkg/api/testapi:go_default_library", | ||||
|         "//pkg/api/v1:go_default_library", | ||||
|         "//pkg/client/restclient/fake:go_default_library", | ||||
|         "//pkg/client/typed/dynamic:go_default_library", | ||||
|         "//pkg/kubectl/cmd/testing:go_default_library", | ||||
|         "//pkg/kubectl/cmd/util:go_default_library", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/api/errors", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/util/diff", | ||||
|         "//vendor:k8s.io/client-go/rest/fake", | ||||
|         "//vendor:k8s.io/client-go/tools/clientcmd", | ||||
|         "//vendor:k8s.io/client-go/tools/clientcmd/api", | ||||
|     ], | ||||
|   | ||||
| @@ -46,7 +46,6 @@ go_test( | ||||
|         "//pkg/api/testapi:go_default_library", | ||||
|         "//pkg/api/v1:go_default_library", | ||||
|         "//pkg/apis/extensions/v1beta1:go_default_library", | ||||
|         "//pkg/client/restclient/fake:go_default_library", | ||||
|         "//pkg/client/typed/dynamic:go_default_library", | ||||
|         "//pkg/kubectl/cmd/testing:go_default_library", | ||||
|         "//pkg/kubectl/cmd/util:go_default_library", | ||||
| @@ -54,6 +53,7 @@ go_test( | ||||
|         "//vendor:k8s.io/apimachinery/pkg/api/errors", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/util/diff", | ||||
|         "//vendor:k8s.io/client-go/rest/fake", | ||||
|         "//vendor:k8s.io/client-go/tools/clientcmd", | ||||
|     ], | ||||
| ) | ||||
|   | ||||
| @@ -33,6 +33,7 @@ import ( | ||||
| 	"k8s.io/apimachinery/pkg/api/errors" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/util/diff" | ||||
| 	"k8s.io/client-go/rest/fake" | ||||
| 	"k8s.io/client-go/tools/clientcmd" | ||||
| 	kubefedtesting "k8s.io/kubernetes/federation/pkg/kubefed/testing" | ||||
| 	"k8s.io/kubernetes/federation/pkg/kubefed/util" | ||||
| @@ -41,7 +42,6 @@ import ( | ||||
| 	"k8s.io/kubernetes/pkg/api/testapi" | ||||
| 	"k8s.io/kubernetes/pkg/api/v1" | ||||
| 	"k8s.io/kubernetes/pkg/apis/extensions/v1beta1" | ||||
| 	"k8s.io/kubernetes/pkg/client/restclient/fake" | ||||
| 	"k8s.io/kubernetes/pkg/client/typed/dynamic" | ||||
| 	cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" | ||||
| 	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" | ||||
| @@ -751,6 +751,7 @@ func fakeInitHostFactory(federationName, namespaceName, ip, dnsZoneName, image, | ||||
| 	ns := dynamic.ContentConfig().NegotiatedSerializer | ||||
| 	tf.ClientConfig = kubefedtesting.DefaultClientConfig() | ||||
| 	tf.Client = &fake.RESTClient{ | ||||
| 		APIRegistry:          api.Registry, | ||||
| 		NegotiatedSerializer: ns, | ||||
| 		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { | ||||
| 			switch p, m := req.URL.Path, req.Method; { | ||||
|   | ||||
| @@ -25,6 +25,7 @@ import ( | ||||
|  | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/util/diff" | ||||
| 	"k8s.io/client-go/rest/fake" | ||||
| 	"k8s.io/client-go/tools/clientcmd" | ||||
| 	clientcmdapi "k8s.io/client-go/tools/clientcmd/api" | ||||
| 	federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1" | ||||
| @@ -33,7 +34,6 @@ import ( | ||||
| 	"k8s.io/kubernetes/pkg/api" | ||||
| 	"k8s.io/kubernetes/pkg/api/testapi" | ||||
| 	"k8s.io/kubernetes/pkg/api/v1" | ||||
| 	"k8s.io/kubernetes/pkg/client/restclient/fake" | ||||
| 	"k8s.io/kubernetes/pkg/client/typed/dynamic" | ||||
| 	cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" | ||||
| 	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" | ||||
| @@ -175,6 +175,7 @@ func testJoinFederationFactory(clusterName, secretName, server string) cmdutil.F | ||||
| 	codec := testapi.Federation.Codec() | ||||
| 	ns := dynamic.ContentConfig().NegotiatedSerializer | ||||
| 	tf.Client = &fake.RESTClient{ | ||||
| 		APIRegistry:          api.Registry, | ||||
| 		NegotiatedSerializer: ns, | ||||
| 		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { | ||||
| 			switch p, m := req.URL.Path, req.Method; { | ||||
| @@ -250,6 +251,7 @@ func fakeJoinHostFactory(clusterName, clusterCtx, secretName, server, token stri | ||||
| 	ns := dynamic.ContentConfig().NegotiatedSerializer | ||||
| 	tf.ClientConfig = kubefedtesting.DefaultClientConfig() | ||||
| 	tf.Client = &fake.RESTClient{ | ||||
| 		APIRegistry:          api.Registry, | ||||
| 		NegotiatedSerializer: ns, | ||||
| 		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { | ||||
| 			switch p, m := req.URL.Path, req.Method; { | ||||
|   | ||||
| @@ -25,11 +25,11 @@ import ( | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/api/errors" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/client-go/rest/fake" | ||||
| 	federationapi "k8s.io/kubernetes/federation/apis/federation" | ||||
| 	kubefedtesting "k8s.io/kubernetes/federation/pkg/kubefed/testing" | ||||
| 	"k8s.io/kubernetes/pkg/api" | ||||
| 	"k8s.io/kubernetes/pkg/api/testapi" | ||||
| 	"k8s.io/kubernetes/pkg/client/restclient/fake" | ||||
| 	"k8s.io/kubernetes/pkg/client/typed/dynamic" | ||||
| 	cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" | ||||
| 	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" | ||||
| @@ -169,6 +169,7 @@ func testUnjoinFederationFactory(name, server, secret string) cmdutil.Factory { | ||||
| 	tf.ClientConfig = kubefedtesting.DefaultClientConfig() | ||||
| 	ns := testapi.Federation.NegotiatedSerializer() | ||||
| 	tf.Client = &fake.RESTClient{ | ||||
| 		APIRegistry:          api.Registry, | ||||
| 		NegotiatedSerializer: ns, | ||||
| 		GroupName:            "federation", | ||||
| 		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { | ||||
| @@ -205,6 +206,7 @@ func fakeUnjoinHostFactory(name string) cmdutil.Factory { | ||||
| 	ns := dynamic.ContentConfig().NegotiatedSerializer | ||||
| 	tf.ClientConfig = kubefedtesting.DefaultClientConfig() | ||||
| 	tf.Client = &fake.RESTClient{ | ||||
| 		APIRegistry:          api.Registry, | ||||
| 		NegotiatedSerializer: ns, | ||||
| 		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { | ||||
| 			switch p, m := req.URL.Path, req.Method; { | ||||
|   | ||||
| @@ -86,7 +86,6 @@ pkg/apis/rbac/install | ||||
| pkg/apis/rbac/v1alpha1 | ||||
| pkg/apis/storage/install | ||||
| pkg/apis/storage/validation | ||||
| pkg/client/cache | ||||
| pkg/client/conditions | ||||
| pkg/client/informers/informers_generated/apps | ||||
| pkg/client/informers/informers_generated/apps/internalversion | ||||
| @@ -297,6 +296,7 @@ staging/src/k8s.io/client-go/plugin/pkg/client/auth | ||||
| staging/src/k8s.io/client-go/plugin/pkg/client/auth/gcp | ||||
| staging/src/k8s.io/client-go/rest/watch | ||||
| staging/src/k8s.io/client-go/tools/auth | ||||
| staging/src/k8s.io/client-go/tools/cache | ||||
| staging/src/k8s.io/client-go/tools/metrics | ||||
| staging/src/k8s.io/client-go/util/cert | ||||
| staging/src/k8s.io/client-go/util/homedir | ||||
|   | ||||
| @@ -32,7 +32,6 @@ filegroup( | ||||
|         "//pkg/auth/authorizer/abac:all-srcs", | ||||
|         "//pkg/auth/user:all-srcs", | ||||
|         "//pkg/capabilities:all-srcs", | ||||
|         "//pkg/client/cache:all-srcs", | ||||
|         "//pkg/client/chaosclient:all-srcs", | ||||
|         "//pkg/client/clientset_generated/clientset:all-srcs", | ||||
|         "//pkg/client/clientset_generated/internalclientset:all-srcs", | ||||
| @@ -69,11 +68,11 @@ filegroup( | ||||
|         "//pkg/client/listers/storage/v1beta1:all-srcs", | ||||
|         "//pkg/client/metrics:all-srcs", | ||||
|         "//pkg/client/record:all-srcs", | ||||
|         "//pkg/client/restclient/fake:all-srcs", | ||||
|         "//pkg/client/retry:all-srcs", | ||||
|         "//pkg/client/testdata:all-srcs", | ||||
|         "//pkg/client/testing/cache:all-srcs", | ||||
|         "//pkg/client/testing/core:all-srcs", | ||||
|         "//pkg/client/tests:all-srcs", | ||||
|         "//pkg/client/typed/discovery:all-srcs", | ||||
|         "//pkg/client/typed/dynamic:all-srcs", | ||||
|         "//pkg/client/unversioned:all-srcs", | ||||
|   | ||||
| @@ -25,7 +25,6 @@ go_library( | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime/schema", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/util/net", | ||||
|         "//vendor:k8s.io/apiserver/pkg/util/flag", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -17,9 +17,12 @@ limitations under the License. | ||||
| package componentconfig | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
|  | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	runtime "k8s.io/apimachinery/pkg/runtime" | ||||
| 	utilflag "k8s.io/apiserver/pkg/util/flag" | ||||
| 	"k8s.io/kubernetes/pkg/api" | ||||
| ) | ||||
|  | ||||
| @@ -445,12 +448,12 @@ type KubeletConfiguration struct { | ||||
| 	// that describe resources reserved for non-kubernetes components. | ||||
| 	// Currently only cpu and memory are supported. [default=none] | ||||
| 	// See http://kubernetes.io/docs/user-guide/compute-resources for more detail. | ||||
| 	SystemReserved utilflag.ConfigurationMap | ||||
| 	SystemReserved ConfigurationMap | ||||
| 	// A set of ResourceName=ResourceQuantity (e.g. cpu=200m,memory=150G) pairs | ||||
| 	// that describe resources reserved for kubernetes system components. | ||||
| 	// Currently only cpu and memory are supported. [default=none] | ||||
| 	// See http://kubernetes.io/docs/user-guide/compute-resources for more detail. | ||||
| 	KubeReserved utilflag.ConfigurationMap | ||||
| 	KubeReserved ConfigurationMap | ||||
| 	// Default behaviour for kernel tuning | ||||
| 	ProtectKernelDefaults bool | ||||
| 	// If true, Kubelet ensures a set of iptables rules are present on host. | ||||
| @@ -863,3 +866,33 @@ type AdmissionPluginConfiguration struct { | ||||
| 	// +optional | ||||
| 	Configuration runtime.Object | ||||
| } | ||||
|  | ||||
| type ConfigurationMap map[string]string | ||||
|  | ||||
| func (m *ConfigurationMap) String() string { | ||||
| 	pairs := []string{} | ||||
| 	for k, v := range *m { | ||||
| 		pairs = append(pairs, fmt.Sprintf("%s=%s", k, v)) | ||||
| 	} | ||||
| 	sort.Strings(pairs) | ||||
| 	return strings.Join(pairs, ",") | ||||
| } | ||||
|  | ||||
| func (m *ConfigurationMap) Set(value string) error { | ||||
| 	for _, s := range strings.Split(value, ",") { | ||||
| 		if len(s) == 0 { | ||||
| 			continue | ||||
| 		} | ||||
| 		arr := strings.SplitN(s, "=", 2) | ||||
| 		if len(arr) == 2 { | ||||
| 			(*m)[strings.TrimSpace(arr[0])] = strings.TrimSpace(arr[1]) | ||||
| 		} else { | ||||
| 			(*m)[strings.TrimSpace(arr[0])] = "" | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (*ConfigurationMap) Type() string { | ||||
| 	return "mapStringString" | ||||
| } | ||||
|   | ||||
| @@ -30,7 +30,6 @@ go_library( | ||||
|         "//vendor:k8s.io/apimachinery/pkg/conversion", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime/schema", | ||||
|         "//vendor:k8s.io/apiserver/pkg/util/flag", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -23,7 +23,6 @@ package v1alpha1 | ||||
| import ( | ||||
| 	conversion "k8s.io/apimachinery/pkg/conversion" | ||||
| 	runtime "k8s.io/apimachinery/pkg/runtime" | ||||
| 	flag "k8s.io/apiserver/pkg/util/flag" | ||||
| 	api "k8s.io/kubernetes/pkg/api" | ||||
| 	v1 "k8s.io/kubernetes/pkg/api/v1" | ||||
| 	componentconfig "k8s.io/kubernetes/pkg/apis/componentconfig" | ||||
| @@ -464,8 +463,8 @@ func autoConvert_v1alpha1_KubeletConfiguration_To_componentconfig_KubeletConfigu | ||||
| 	if err := api.Convert_Pointer_bool_To_bool(&in.EnableControllerAttachDetach, &out.EnableControllerAttachDetach, s); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	out.SystemReserved = *(*flag.ConfigurationMap)(unsafe.Pointer(&in.SystemReserved)) | ||||
| 	out.KubeReserved = *(*flag.ConfigurationMap)(unsafe.Pointer(&in.KubeReserved)) | ||||
| 	out.SystemReserved = *(*componentconfig.ConfigurationMap)(unsafe.Pointer(&in.SystemReserved)) | ||||
| 	out.KubeReserved = *(*componentconfig.ConfigurationMap)(unsafe.Pointer(&in.KubeReserved)) | ||||
| 	out.ProtectKernelDefaults = in.ProtectKernelDefaults | ||||
| 	if err := api.Convert_Pointer_bool_To_bool(&in.MakeIPTablesUtilChains, &out.MakeIPTablesUtilChains, s); err != nil { | ||||
| 		return err | ||||
|   | ||||
| @@ -23,7 +23,6 @@ package componentconfig | ||||
| import ( | ||||
| 	conversion "k8s.io/apimachinery/pkg/conversion" | ||||
| 	runtime "k8s.io/apimachinery/pkg/runtime" | ||||
| 	flag "k8s.io/apiserver/pkg/util/flag" | ||||
| 	api "k8s.io/kubernetes/pkg/api" | ||||
| 	reflect "reflect" | ||||
| ) | ||||
| @@ -208,14 +207,14 @@ func DeepCopy_componentconfig_KubeletConfiguration(in interface{}, out interface | ||||
| 		} | ||||
| 		if in.SystemReserved != nil { | ||||
| 			in, out := &in.SystemReserved, &out.SystemReserved | ||||
| 			*out = make(flag.ConfigurationMap) | ||||
| 			*out = make(ConfigurationMap) | ||||
| 			for key, val := range *in { | ||||
| 				(*out)[key] = val | ||||
| 			} | ||||
| 		} | ||||
| 		if in.KubeReserved != nil { | ||||
| 			in, out := &in.KubeReserved, &out.KubeReserved | ||||
| 			*out = make(flag.ConfigurationMap) | ||||
| 			*out = make(ConfigurationMap) | ||||
| 			for key, val := range *in { | ||||
| 				(*out)[key] = val | ||||
| 			} | ||||
|   | ||||
							
								
								
									
										112
									
								
								pkg/client/cache/BUILD
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										112
									
								
								pkg/client/cache/BUILD
									
									
									
									
										vendored
									
									
								
							| @@ -1,112 +0,0 @@ | ||||
| package(default_visibility = ["//visibility:public"]) | ||||
|  | ||||
| licenses(["notice"]) | ||||
|  | ||||
| load( | ||||
|     "@io_bazel_rules_go//go:def.bzl", | ||||
|     "go_library", | ||||
|     "go_test", | ||||
| ) | ||||
|  | ||||
| go_library( | ||||
|     name = "go_default_library", | ||||
|     srcs = [ | ||||
|         "controller.go", | ||||
|         "delta_fifo.go", | ||||
|         "doc.go", | ||||
|         "expiration_cache.go", | ||||
|         "expiration_cache_fakes.go", | ||||
|         "fake_custom_store.go", | ||||
|         "fifo.go", | ||||
|         "index.go", | ||||
|         "listers.go", | ||||
|         "listwatch.go", | ||||
|         "mutation_detector.go", | ||||
|         "reflector.go", | ||||
|         "shared_informer.go", | ||||
|         "store.go", | ||||
|         "thread_safe_store.go", | ||||
|         "undelta_store.go", | ||||
|     ], | ||||
|     tags = ["automanaged"], | ||||
|     deps = [ | ||||
|         "//pkg/api:go_default_library", | ||||
|         "//vendor:github.com/golang/glog", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/api/errors", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/api/meta", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/fields", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/labels", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime/schema", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/util/diff", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/util/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/util/sets", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/util/wait", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", | ||||
|         "//vendor:k8s.io/client-go/rest", | ||||
|         "//vendor:k8s.io/client-go/util/clock", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
| go_test( | ||||
|     name = "go_default_test", | ||||
|     srcs = [ | ||||
|         "controller_test.go", | ||||
|         "delta_fifo_test.go", | ||||
|         "expiration_cache_test.go", | ||||
|         "fifo_test.go", | ||||
|         "index_test.go", | ||||
|         "mutation_detector_test.go", | ||||
|         "processor_listener_test.go", | ||||
|         "reflector_test.go", | ||||
|         "store_test.go", | ||||
|         "undelta_store_test.go", | ||||
|     ], | ||||
|     library = ":go_default_library", | ||||
|     tags = ["automanaged"], | ||||
|     deps = [ | ||||
|         "//pkg/api:go_default_library", | ||||
|         "//pkg/api/v1:go_default_library", | ||||
|         "//pkg/client/testing/cache:go_default_library", | ||||
|         "//vendor:github.com/google/gofuzz", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/util/sets", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/util/wait", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", | ||||
|         "//vendor:k8s.io/client-go/util/clock", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
| filegroup( | ||||
|     name = "package-srcs", | ||||
|     srcs = glob(["**"]), | ||||
|     tags = ["automanaged"], | ||||
|     visibility = ["//visibility:private"], | ||||
| ) | ||||
|  | ||||
| filegroup( | ||||
|     name = "all-srcs", | ||||
|     srcs = [":package-srcs"], | ||||
|     tags = ["automanaged"], | ||||
| ) | ||||
|  | ||||
| go_test( | ||||
|     name = "go_default_xtest", | ||||
|     srcs = ["listwatch_test.go"], | ||||
|     tags = ["automanaged"], | ||||
|     deps = [ | ||||
|         "//pkg/api:go_default_library", | ||||
|         "//pkg/api/testapi:go_default_library", | ||||
|         "//pkg/api/v1:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//pkg/client/clientset_generated/internalclientset:go_default_library", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/fields", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", | ||||
|         "//vendor:k8s.io/client-go/rest", | ||||
|         "//vendor:k8s.io/client-go/util/testing", | ||||
|     ], | ||||
| ) | ||||
							
								
								
									
										41
									
								
								pkg/client/cache/OWNERS
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										41
									
								
								pkg/client/cache/OWNERS
									
									
									
									
										vendored
									
									
								
							| @@ -1,41 +0,0 @@ | ||||
| reviewers: | ||||
| - thockin | ||||
| - lavalamp | ||||
| - smarterclayton | ||||
| - wojtek-t | ||||
| - deads2k | ||||
| - brendandburns | ||||
| - derekwaynecarr | ||||
| - caesarxuchao | ||||
| - mikedanese | ||||
| - liggitt | ||||
| - nikhiljindal | ||||
| - bprashanth | ||||
| - erictune | ||||
| - davidopp | ||||
| - pmorie | ||||
| - kargakis | ||||
| - janetkuo | ||||
| - justinsb | ||||
| - eparis | ||||
| - soltysh | ||||
| - jsafrane | ||||
| - dims | ||||
| - madhusudancs | ||||
| - hongchaodeng | ||||
| - krousey | ||||
| - markturansky | ||||
| - fgrzadkowski | ||||
| - xiang90 | ||||
| - mml | ||||
| - ingvagabund | ||||
| - resouer | ||||
| - jessfraz | ||||
| - david-mcmahon | ||||
| - mfojtik | ||||
| - '249043822' | ||||
| - lixiaobing10051267 | ||||
| - ddysher | ||||
| - mqliang | ||||
| - feihujiang | ||||
| - sdminonne | ||||
							
								
								
									
										327
									
								
								pkg/client/cache/controller.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										327
									
								
								pkg/client/cache/controller.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,327 +0,0 @@ | ||||
| /* | ||||
| Copyright 2015 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	utilruntime "k8s.io/apimachinery/pkg/util/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/util/wait" | ||||
| ) | ||||
|  | ||||
| // Config contains all the settings for a Controller. | ||||
| type Config struct { | ||||
| 	// The queue for your objects; either a FIFO or | ||||
| 	// a DeltaFIFO. Your Process() function should accept | ||||
| 	// the output of this Queue's Pop() method. | ||||
| 	Queue | ||||
|  | ||||
| 	// Something that can list and watch your objects. | ||||
| 	ListerWatcher | ||||
|  | ||||
| 	// Something that can process your objects. | ||||
| 	Process ProcessFunc | ||||
|  | ||||
| 	// The type of your objects. | ||||
| 	ObjectType runtime.Object | ||||
|  | ||||
| 	// Reprocess everything at least this often. | ||||
| 	// Note that if it takes longer for you to clear the queue than this | ||||
| 	// period, you will end up processing items in the order determined | ||||
| 	// by FIFO.Replace(). Currently, this is random. If this is a | ||||
| 	// problem, we can change that replacement policy to append new | ||||
| 	// things to the end of the queue instead of replacing the entire | ||||
| 	// queue. | ||||
| 	FullResyncPeriod time.Duration | ||||
|  | ||||
| 	// If true, when Process() returns an error, re-enqueue the object. | ||||
| 	// TODO: add interface to let you inject a delay/backoff or drop | ||||
| 	//       the object completely if desired. Pass the object in | ||||
| 	//       question to this interface as a parameter. | ||||
| 	RetryOnError bool | ||||
| } | ||||
|  | ||||
| // ProcessFunc processes a single object. | ||||
| type ProcessFunc func(obj interface{}) error | ||||
|  | ||||
| // Controller is a generic controller framework. | ||||
| type controller struct { | ||||
| 	config         Config | ||||
| 	reflector      *Reflector | ||||
| 	reflectorMutex sync.RWMutex | ||||
| } | ||||
|  | ||||
| type Controller interface { | ||||
| 	Run(stopCh <-chan struct{}) | ||||
| 	HasSynced() bool | ||||
| 	LastSyncResourceVersion() string | ||||
| } | ||||
|  | ||||
| // New makes a new Controller from the given Config. | ||||
| func New(c *Config) Controller { | ||||
| 	ctlr := &controller{ | ||||
| 		config: *c, | ||||
| 	} | ||||
| 	return ctlr | ||||
| } | ||||
|  | ||||
| // Run begins processing items, and will continue until a value is sent down stopCh. | ||||
| // It's an error to call Run more than once. | ||||
| // Run blocks; call via go. | ||||
| func (c *controller) Run(stopCh <-chan struct{}) { | ||||
| 	defer utilruntime.HandleCrash() | ||||
| 	r := NewReflector( | ||||
| 		c.config.ListerWatcher, | ||||
| 		c.config.ObjectType, | ||||
| 		c.config.Queue, | ||||
| 		c.config.FullResyncPeriod, | ||||
| 	) | ||||
|  | ||||
| 	c.reflectorMutex.Lock() | ||||
| 	c.reflector = r | ||||
| 	c.reflectorMutex.Unlock() | ||||
|  | ||||
| 	r.RunUntil(stopCh) | ||||
|  | ||||
| 	wait.Until(c.processLoop, time.Second, stopCh) | ||||
| } | ||||
|  | ||||
| // Returns true once this controller has completed an initial resource listing | ||||
| func (c *controller) HasSynced() bool { | ||||
| 	return c.config.Queue.HasSynced() | ||||
| } | ||||
|  | ||||
| func (c *controller) LastSyncResourceVersion() string { | ||||
| 	if c.reflector == nil { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return c.reflector.LastSyncResourceVersion() | ||||
| } | ||||
|  | ||||
| // processLoop drains the work queue. | ||||
| // TODO: Consider doing the processing in parallel. This will require a little thought | ||||
| // to make sure that we don't end up processing the same object multiple times | ||||
| // concurrently. | ||||
| // | ||||
| // TODO: Plumb through the stopCh here (and down to the queue) so that this can | ||||
| // actually exit when the controller is stopped. Or just give up on this stuff | ||||
| // ever being stoppable. Converting this whole package to use Context would | ||||
| // also be helpful. | ||||
| func (c *controller) processLoop() { | ||||
| 	for { | ||||
| 		obj, err := c.config.Queue.Pop(PopProcessFunc(c.config.Process)) | ||||
| 		if err != nil { | ||||
| 			if c.config.RetryOnError { | ||||
| 				// This is the safe way to re-enqueue. | ||||
| 				c.config.Queue.AddIfNotPresent(obj) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ResourceEventHandler can handle notifications for events that happen to a | ||||
| // resource. The events are informational only, so you can't return an | ||||
| // error. | ||||
| //  * OnAdd is called when an object is added. | ||||
| //  * OnUpdate is called when an object is modified. Note that oldObj is the | ||||
| //      last known state of the object-- it is possible that several changes | ||||
| //      were combined together, so you can't use this to see every single | ||||
| //      change. OnUpdate is also called when a re-list happens, and it will | ||||
| //      get called even if nothing changed. This is useful for periodically | ||||
| //      evaluating or syncing something. | ||||
| //  * OnDelete will get the final state of the item if it is known, otherwise | ||||
| //      it will get an object of type DeletedFinalStateUnknown. This can | ||||
| //      happen if the watch is closed and misses the delete event and we don't | ||||
| //      notice the deletion until the subsequent re-list. | ||||
| type ResourceEventHandler interface { | ||||
| 	OnAdd(obj interface{}) | ||||
| 	OnUpdate(oldObj, newObj interface{}) | ||||
| 	OnDelete(obj interface{}) | ||||
| } | ||||
|  | ||||
| // ResourceEventHandlerFuncs is an adaptor to let you easily specify as many or | ||||
| // as few of the notification functions as you want while still implementing | ||||
| // ResourceEventHandler. | ||||
| type ResourceEventHandlerFuncs struct { | ||||
| 	AddFunc    func(obj interface{}) | ||||
| 	UpdateFunc func(oldObj, newObj interface{}) | ||||
| 	DeleteFunc func(obj interface{}) | ||||
| } | ||||
|  | ||||
| // OnAdd calls AddFunc if it's not nil. | ||||
| func (r ResourceEventHandlerFuncs) OnAdd(obj interface{}) { | ||||
| 	if r.AddFunc != nil { | ||||
| 		r.AddFunc(obj) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // OnUpdate calls UpdateFunc if it's not nil. | ||||
| func (r ResourceEventHandlerFuncs) OnUpdate(oldObj, newObj interface{}) { | ||||
| 	if r.UpdateFunc != nil { | ||||
| 		r.UpdateFunc(oldObj, newObj) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // OnDelete calls DeleteFunc if it's not nil. | ||||
| func (r ResourceEventHandlerFuncs) OnDelete(obj interface{}) { | ||||
| 	if r.DeleteFunc != nil { | ||||
| 		r.DeleteFunc(obj) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // DeletionHandlingMetaNamespaceKeyFunc checks for | ||||
| // DeletedFinalStateUnknown objects before calling | ||||
| // MetaNamespaceKeyFunc. | ||||
| func DeletionHandlingMetaNamespaceKeyFunc(obj interface{}) (string, error) { | ||||
| 	if d, ok := obj.(DeletedFinalStateUnknown); ok { | ||||
| 		return d.Key, nil | ||||
| 	} | ||||
| 	return MetaNamespaceKeyFunc(obj) | ||||
| } | ||||
|  | ||||
| // NewInformer returns a Store and a controller for populating the store | ||||
| // while also providing event notifications. You should only used the returned | ||||
| // Store for Get/List operations; Add/Modify/Deletes will cause the event | ||||
| // notifications to be faulty. | ||||
| // | ||||
| // Parameters: | ||||
| //  * lw is list and watch functions for the source of the resource you want to | ||||
| //    be informed of. | ||||
| //  * objType is an object of the type that you expect to receive. | ||||
| //  * resyncPeriod: if non-zero, will re-list this often (you will get OnUpdate | ||||
| //    calls, even if nothing changed). Otherwise, re-list will be delayed as | ||||
| //    long as possible (until the upstream source closes the watch or times out, | ||||
| //    or you stop the controller). | ||||
| //  * h is the object you want notifications sent to. | ||||
| // | ||||
| func NewInformer( | ||||
| 	lw ListerWatcher, | ||||
| 	objType runtime.Object, | ||||
| 	resyncPeriod time.Duration, | ||||
| 	h ResourceEventHandler, | ||||
| ) (Store, Controller) { | ||||
| 	// This will hold the client state, as we know it. | ||||
| 	clientState := NewStore(DeletionHandlingMetaNamespaceKeyFunc) | ||||
|  | ||||
| 	// This will hold incoming changes. Note how we pass clientState in as a | ||||
| 	// KeyLister, that way resync operations will result in the correct set | ||||
| 	// of update/delete deltas. | ||||
| 	fifo := NewDeltaFIFO(MetaNamespaceKeyFunc, nil, clientState) | ||||
|  | ||||
| 	cfg := &Config{ | ||||
| 		Queue:            fifo, | ||||
| 		ListerWatcher:    lw, | ||||
| 		ObjectType:       objType, | ||||
| 		FullResyncPeriod: resyncPeriod, | ||||
| 		RetryOnError:     false, | ||||
|  | ||||
| 		Process: func(obj interface{}) error { | ||||
| 			// from oldest to newest | ||||
| 			for _, d := range obj.(Deltas) { | ||||
| 				switch d.Type { | ||||
| 				case Sync, Added, Updated: | ||||
| 					if old, exists, err := clientState.Get(d.Object); err == nil && exists { | ||||
| 						if err := clientState.Update(d.Object); err != nil { | ||||
| 							return err | ||||
| 						} | ||||
| 						h.OnUpdate(old, d.Object) | ||||
| 					} else { | ||||
| 						if err := clientState.Add(d.Object); err != nil { | ||||
| 							return err | ||||
| 						} | ||||
| 						h.OnAdd(d.Object) | ||||
| 					} | ||||
| 				case Deleted: | ||||
| 					if err := clientState.Delete(d.Object); err != nil { | ||||
| 						return err | ||||
| 					} | ||||
| 					h.OnDelete(d.Object) | ||||
| 				} | ||||
| 			} | ||||
| 			return nil | ||||
| 		}, | ||||
| 	} | ||||
| 	return clientState, New(cfg) | ||||
| } | ||||
|  | ||||
| // NewIndexerInformer returns a Indexer and a controller for populating the index | ||||
| // while also providing event notifications. You should only used the returned | ||||
| // Index for Get/List operations; Add/Modify/Deletes will cause the event | ||||
| // notifications to be faulty. | ||||
| // | ||||
| // Parameters: | ||||
| //  * lw is list and watch functions for the source of the resource you want to | ||||
| //    be informed of. | ||||
| //  * objType is an object of the type that you expect to receive. | ||||
| //  * resyncPeriod: if non-zero, will re-list this often (you will get OnUpdate | ||||
| //    calls, even if nothing changed). Otherwise, re-list will be delayed as | ||||
| //    long as possible (until the upstream source closes the watch or times out, | ||||
| //    or you stop the controller). | ||||
| //  * h is the object you want notifications sent to. | ||||
| // | ||||
| func NewIndexerInformer( | ||||
| 	lw ListerWatcher, | ||||
| 	objType runtime.Object, | ||||
| 	resyncPeriod time.Duration, | ||||
| 	h ResourceEventHandler, | ||||
| 	indexers Indexers, | ||||
| ) (Indexer, Controller) { | ||||
| 	// This will hold the client state, as we know it. | ||||
| 	clientState := NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, indexers) | ||||
|  | ||||
| 	// This will hold incoming changes. Note how we pass clientState in as a | ||||
| 	// KeyLister, that way resync operations will result in the correct set | ||||
| 	// of update/delete deltas. | ||||
| 	fifo := NewDeltaFIFO(MetaNamespaceKeyFunc, nil, clientState) | ||||
|  | ||||
| 	cfg := &Config{ | ||||
| 		Queue:            fifo, | ||||
| 		ListerWatcher:    lw, | ||||
| 		ObjectType:       objType, | ||||
| 		FullResyncPeriod: resyncPeriod, | ||||
| 		RetryOnError:     false, | ||||
|  | ||||
| 		Process: func(obj interface{}) error { | ||||
| 			// from oldest to newest | ||||
| 			for _, d := range obj.(Deltas) { | ||||
| 				switch d.Type { | ||||
| 				case Sync, Added, Updated: | ||||
| 					if old, exists, err := clientState.Get(d.Object); err == nil && exists { | ||||
| 						if err := clientState.Update(d.Object); err != nil { | ||||
| 							return err | ||||
| 						} | ||||
| 						h.OnUpdate(old, d.Object) | ||||
| 					} else { | ||||
| 						if err := clientState.Add(d.Object); err != nil { | ||||
| 							return err | ||||
| 						} | ||||
| 						h.OnAdd(d.Object) | ||||
| 					} | ||||
| 				case Deleted: | ||||
| 					if err := clientState.Delete(d.Object); err != nil { | ||||
| 						return err | ||||
| 					} | ||||
| 					h.OnDelete(d.Object) | ||||
| 				} | ||||
| 			} | ||||
| 			return nil | ||||
| 		}, | ||||
| 	} | ||||
| 	return clientState, New(cfg) | ||||
| } | ||||
							
								
								
									
										405
									
								
								pkg/client/cache/controller_test.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										405
									
								
								pkg/client/cache/controller_test.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,405 +0,0 @@ | ||||
| /* | ||||
| Copyright 2015 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"math/rand" | ||||
| 	"sync" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| 	"k8s.io/apimachinery/pkg/util/wait" | ||||
| 	"k8s.io/apimachinery/pkg/watch" | ||||
| 	"k8s.io/kubernetes/pkg/api/v1" | ||||
| 	fcache "k8s.io/kubernetes/pkg/client/testing/cache" | ||||
|  | ||||
| 	"github.com/google/gofuzz" | ||||
| ) | ||||
|  | ||||
| func Example() { | ||||
| 	// source simulates an apiserver object endpoint. | ||||
| 	source := fcache.NewFakeControllerSource() | ||||
|  | ||||
| 	// This will hold the downstream state, as we know it. | ||||
| 	downstream := NewStore(DeletionHandlingMetaNamespaceKeyFunc) | ||||
|  | ||||
| 	// This will hold incoming changes. Note how we pass downstream in as a | ||||
| 	// KeyLister, that way resync operations will result in the correct set | ||||
| 	// of update/delete deltas. | ||||
| 	fifo := NewDeltaFIFO(MetaNamespaceKeyFunc, nil, downstream) | ||||
|  | ||||
| 	// Let's do threadsafe output to get predictable test results. | ||||
| 	deletionCounter := make(chan string, 1000) | ||||
|  | ||||
| 	cfg := &Config{ | ||||
| 		Queue:            fifo, | ||||
| 		ListerWatcher:    source, | ||||
| 		ObjectType:       &v1.Pod{}, | ||||
| 		FullResyncPeriod: time.Millisecond * 100, | ||||
| 		RetryOnError:     false, | ||||
|  | ||||
| 		// Let's implement a simple controller that just deletes | ||||
| 		// everything that comes in. | ||||
| 		Process: func(obj interface{}) error { | ||||
| 			// Obj is from the Pop method of the Queue we make above. | ||||
| 			newest := obj.(Deltas).Newest() | ||||
|  | ||||
| 			if newest.Type != Deleted { | ||||
| 				// Update our downstream store. | ||||
| 				err := downstream.Add(newest.Object) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
|  | ||||
| 				// Delete this object. | ||||
| 				source.Delete(newest.Object.(runtime.Object)) | ||||
| 			} else { | ||||
| 				// Update our downstream store. | ||||
| 				err := downstream.Delete(newest.Object) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
|  | ||||
| 				// fifo's KeyOf is easiest, because it handles | ||||
| 				// DeletedFinalStateUnknown markers. | ||||
| 				key, err := fifo.KeyOf(newest.Object) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
|  | ||||
| 				// Report this deletion. | ||||
| 				deletionCounter <- key | ||||
| 			} | ||||
| 			return nil | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	// Create the controller and run it until we close stop. | ||||
| 	stop := make(chan struct{}) | ||||
| 	defer close(stop) | ||||
| 	go New(cfg).Run(stop) | ||||
|  | ||||
| 	// Let's add a few objects to the source. | ||||
| 	testIDs := []string{"a-hello", "b-controller", "c-framework"} | ||||
| 	for _, name := range testIDs { | ||||
| 		// Note that these pods are not valid-- the fake source doesn't | ||||
| 		// call validation or anything. | ||||
| 		source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: name}}) | ||||
| 	} | ||||
|  | ||||
| 	// Let's wait for the controller to process the things we just added. | ||||
| 	outputSet := sets.String{} | ||||
| 	for i := 0; i < len(testIDs); i++ { | ||||
| 		outputSet.Insert(<-deletionCounter) | ||||
| 	} | ||||
|  | ||||
| 	for _, key := range outputSet.List() { | ||||
| 		fmt.Println(key) | ||||
| 	} | ||||
| 	// Output: | ||||
| 	// a-hello | ||||
| 	// b-controller | ||||
| 	// c-framework | ||||
| } | ||||
|  | ||||
| func ExampleNewInformer() { | ||||
| 	// source simulates an apiserver object endpoint. | ||||
| 	source := fcache.NewFakeControllerSource() | ||||
|  | ||||
| 	// Let's do threadsafe output to get predictable test results. | ||||
| 	deletionCounter := make(chan string, 1000) | ||||
|  | ||||
| 	// Make a controller that immediately deletes anything added to it, and | ||||
| 	// logs anything deleted. | ||||
| 	_, controller := NewInformer( | ||||
| 		source, | ||||
| 		&v1.Pod{}, | ||||
| 		time.Millisecond*100, | ||||
| 		ResourceEventHandlerFuncs{ | ||||
| 			AddFunc: func(obj interface{}) { | ||||
| 				source.Delete(obj.(runtime.Object)) | ||||
| 			}, | ||||
| 			DeleteFunc: func(obj interface{}) { | ||||
| 				key, err := DeletionHandlingMetaNamespaceKeyFunc(obj) | ||||
| 				if err != nil { | ||||
| 					key = "oops something went wrong with the key" | ||||
| 				} | ||||
|  | ||||
| 				// Report this deletion. | ||||
| 				deletionCounter <- key | ||||
| 			}, | ||||
| 		}, | ||||
| 	) | ||||
|  | ||||
| 	// Run the controller and run it until we close stop. | ||||
| 	stop := make(chan struct{}) | ||||
| 	defer close(stop) | ||||
| 	go controller.Run(stop) | ||||
|  | ||||
| 	// Let's add a few objects to the source. | ||||
| 	testIDs := []string{"a-hello", "b-controller", "c-framework"} | ||||
| 	for _, name := range testIDs { | ||||
| 		// Note that these pods are not valid-- the fake source doesn't | ||||
| 		// call validation or anything. | ||||
| 		source.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: name}}) | ||||
| 	} | ||||
|  | ||||
| 	// Let's wait for the controller to process the things we just added. | ||||
| 	outputSet := sets.String{} | ||||
| 	for i := 0; i < len(testIDs); i++ { | ||||
| 		outputSet.Insert(<-deletionCounter) | ||||
| 	} | ||||
|  | ||||
| 	for _, key := range outputSet.List() { | ||||
| 		fmt.Println(key) | ||||
| 	} | ||||
| 	// Output: | ||||
| 	// a-hello | ||||
| 	// b-controller | ||||
| 	// c-framework | ||||
| } | ||||
|  | ||||
| func TestHammerController(t *testing.T) { | ||||
| 	// This test executes a bunch of requests through the fake source and | ||||
| 	// controller framework to make sure there's no locking/threading | ||||
| 	// errors. If an error happens, it should hang forever or trigger the | ||||
| 	// race detector. | ||||
|  | ||||
| 	// source simulates an apiserver object endpoint. | ||||
| 	source := fcache.NewFakeControllerSource() | ||||
|  | ||||
| 	// Let's do threadsafe output to get predictable test results. | ||||
| 	outputSetLock := sync.Mutex{} | ||||
| 	// map of key to operations done on the key | ||||
| 	outputSet := map[string][]string{} | ||||
|  | ||||
| 	recordFunc := func(eventType string, obj interface{}) { | ||||
| 		key, err := DeletionHandlingMetaNamespaceKeyFunc(obj) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("something wrong with key: %v", err) | ||||
| 			key = "oops something went wrong with the key" | ||||
| 		} | ||||
|  | ||||
| 		// Record some output when items are deleted. | ||||
| 		outputSetLock.Lock() | ||||
| 		defer outputSetLock.Unlock() | ||||
| 		outputSet[key] = append(outputSet[key], eventType) | ||||
| 	} | ||||
|  | ||||
| 	// Make a controller which just logs all the changes it gets. | ||||
| 	_, controller := NewInformer( | ||||
| 		source, | ||||
| 		&v1.Pod{}, | ||||
| 		time.Millisecond*100, | ||||
| 		ResourceEventHandlerFuncs{ | ||||
| 			AddFunc:    func(obj interface{}) { recordFunc("add", obj) }, | ||||
| 			UpdateFunc: func(oldObj, newObj interface{}) { recordFunc("update", newObj) }, | ||||
| 			DeleteFunc: func(obj interface{}) { recordFunc("delete", obj) }, | ||||
| 		}, | ||||
| 	) | ||||
|  | ||||
| 	if controller.HasSynced() { | ||||
| 		t.Errorf("Expected HasSynced() to return false before we started the controller") | ||||
| 	} | ||||
|  | ||||
| 	// Run the controller and run it until we close stop. | ||||
| 	stop := make(chan struct{}) | ||||
| 	go controller.Run(stop) | ||||
|  | ||||
| 	// Let's wait for the controller to do its initial sync | ||||
| 	wait.Poll(100*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) { | ||||
| 		return controller.HasSynced(), nil | ||||
| 	}) | ||||
| 	if !controller.HasSynced() { | ||||
| 		t.Errorf("Expected HasSynced() to return true after the initial sync") | ||||
| 	} | ||||
|  | ||||
| 	wg := sync.WaitGroup{} | ||||
| 	const threads = 3 | ||||
| 	wg.Add(threads) | ||||
| 	for i := 0; i < threads; i++ { | ||||
| 		go func() { | ||||
| 			defer wg.Done() | ||||
| 			// Let's add a few objects to the source. | ||||
| 			currentNames := sets.String{} | ||||
| 			rs := rand.NewSource(rand.Int63()) | ||||
| 			f := fuzz.New().NilChance(.5).NumElements(0, 2).RandSource(rs) | ||||
| 			r := rand.New(rs) // Mustn't use r and f concurrently! | ||||
| 			for i := 0; i < 100; i++ { | ||||
| 				var name string | ||||
| 				var isNew bool | ||||
| 				if currentNames.Len() == 0 || r.Intn(3) == 1 { | ||||
| 					f.Fuzz(&name) | ||||
| 					isNew = true | ||||
| 				} else { | ||||
| 					l := currentNames.List() | ||||
| 					name = l[r.Intn(len(l))] | ||||
| 				} | ||||
|  | ||||
| 				pod := &v1.Pod{} | ||||
| 				f.Fuzz(pod) | ||||
| 				pod.ObjectMeta.Name = name | ||||
| 				pod.ObjectMeta.Namespace = "default" | ||||
| 				// Add, update, or delete randomly. | ||||
| 				// Note that these pods are not valid-- the fake source doesn't | ||||
| 				// call validation or perform any other checking. | ||||
| 				if isNew { | ||||
| 					currentNames.Insert(name) | ||||
| 					source.Add(pod) | ||||
| 					continue | ||||
| 				} | ||||
| 				switch r.Intn(2) { | ||||
| 				case 0: | ||||
| 					currentNames.Insert(name) | ||||
| 					source.Modify(pod) | ||||
| 				case 1: | ||||
| 					currentNames.Delete(name) | ||||
| 					source.Delete(pod) | ||||
| 				} | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
| 	wg.Wait() | ||||
|  | ||||
| 	// Let's wait for the controller to finish processing the things we just added. | ||||
| 	// TODO: look in the queue to see how many items need to be processed. | ||||
| 	time.Sleep(100 * time.Millisecond) | ||||
| 	close(stop) | ||||
|  | ||||
| 	// TODO: Verify that no goroutines were leaked here and that everything shut | ||||
| 	// down cleanly. | ||||
|  | ||||
| 	outputSetLock.Lock() | ||||
| 	t.Logf("got: %#v", outputSet) | ||||
| } | ||||
|  | ||||
| func TestUpdate(t *testing.T) { | ||||
| 	// This test is going to exercise the various paths that result in a | ||||
| 	// call to update. | ||||
|  | ||||
| 	// source simulates an apiserver object endpoint. | ||||
| 	source := fcache.NewFakeControllerSource() | ||||
|  | ||||
| 	const ( | ||||
| 		FROM = "from" | ||||
| 		TO   = "to" | ||||
| 	) | ||||
|  | ||||
| 	// These are the transitions we expect to see; because this is | ||||
| 	// asynchronous, there are a lot of valid possibilities. | ||||
| 	type pair struct{ from, to string } | ||||
| 	allowedTransitions := map[pair]bool{ | ||||
| 		pair{FROM, TO}: true, | ||||
|  | ||||
| 		// Because a resync can happen when we've already observed one | ||||
| 		// of the above but before the item is deleted. | ||||
| 		pair{TO, TO}: true, | ||||
| 		// Because a resync could happen before we observe an update. | ||||
| 		pair{FROM, FROM}: true, | ||||
| 	} | ||||
|  | ||||
| 	pod := func(name, check string, final bool) *v1.Pod { | ||||
| 		p := &v1.Pod{ | ||||
| 			ObjectMeta: metav1.ObjectMeta{ | ||||
| 				Name:   name, | ||||
| 				Labels: map[string]string{"check": check}, | ||||
| 			}, | ||||
| 		} | ||||
| 		if final { | ||||
| 			p.Labels["final"] = "true" | ||||
| 		} | ||||
| 		return p | ||||
| 	} | ||||
| 	deletePod := func(p *v1.Pod) bool { | ||||
| 		return p.Labels["final"] == "true" | ||||
| 	} | ||||
|  | ||||
| 	tests := []func(string){ | ||||
| 		func(name string) { | ||||
| 			name = "a-" + name | ||||
| 			source.Add(pod(name, FROM, false)) | ||||
| 			source.Modify(pod(name, TO, true)) | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	const threads = 3 | ||||
|  | ||||
| 	var testDoneWG sync.WaitGroup | ||||
| 	testDoneWG.Add(threads * len(tests)) | ||||
|  | ||||
| 	// Make a controller that deletes things once it observes an update. | ||||
| 	// It calls Done() on the wait group on deletions so we can tell when | ||||
| 	// everything we've added has been deleted. | ||||
| 	watchCh := make(chan struct{}) | ||||
| 	_, controller := NewInformer( | ||||
| 		&testLW{ | ||||
| 			WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { | ||||
| 				watch, err := source.Watch(options) | ||||
| 				close(watchCh) | ||||
| 				return watch, err | ||||
| 			}, | ||||
| 			ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { | ||||
| 				return source.List(options) | ||||
| 			}, | ||||
| 		}, | ||||
| 		&v1.Pod{}, | ||||
| 		0, | ||||
| 		ResourceEventHandlerFuncs{ | ||||
| 			UpdateFunc: func(oldObj, newObj interface{}) { | ||||
| 				o, n := oldObj.(*v1.Pod), newObj.(*v1.Pod) | ||||
| 				from, to := o.Labels["check"], n.Labels["check"] | ||||
| 				if !allowedTransitions[pair{from, to}] { | ||||
| 					t.Errorf("observed transition %q -> %q for %v", from, to, n.Name) | ||||
| 				} | ||||
| 				if deletePod(n) { | ||||
| 					source.Delete(n) | ||||
| 				} | ||||
| 			}, | ||||
| 			DeleteFunc: func(obj interface{}) { | ||||
| 				testDoneWG.Done() | ||||
| 			}, | ||||
| 		}, | ||||
| 	) | ||||
|  | ||||
| 	// Run the controller and run it until we close stop. | ||||
| 	// Once Run() is called, calls to testDoneWG.Done() might start, so | ||||
| 	// all testDoneWG.Add() calls must happen before this point | ||||
| 	stop := make(chan struct{}) | ||||
| 	go controller.Run(stop) | ||||
| 	<-watchCh | ||||
|  | ||||
| 	// run every test a few times, in parallel | ||||
| 	var wg sync.WaitGroup | ||||
| 	wg.Add(threads * len(tests)) | ||||
| 	for i := 0; i < threads; i++ { | ||||
| 		for j, f := range tests { | ||||
| 			go func(name string, f func(string)) { | ||||
| 				defer wg.Done() | ||||
| 				f(name) | ||||
| 			}(fmt.Sprintf("%v-%v", i, j), f) | ||||
| 		} | ||||
| 	} | ||||
| 	wg.Wait() | ||||
|  | ||||
| 	// Let's wait for the controller to process the things we just added. | ||||
| 	testDoneWG.Wait() | ||||
| 	close(stop) | ||||
| } | ||||
							
								
								
									
										647
									
								
								pkg/client/cache/delta_fifo.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										647
									
								
								pkg/client/cache/delta_fifo.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,647 +0,0 @@ | ||||
| /* | ||||
| Copyright 2014 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"sync" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
|  | ||||
| 	"github.com/golang/glog" | ||||
| ) | ||||
|  | ||||
| // NewDeltaFIFO returns a Store which can be used process changes to items. | ||||
| // | ||||
| // keyFunc is used to figure out what key an object should have. (It's | ||||
| // exposed in the returned DeltaFIFO's KeyOf() method, with bonus features.) | ||||
| // | ||||
| // 'compressor' may compress as many or as few items as it wants | ||||
| // (including returning an empty slice), but it should do what it | ||||
| // does quickly since it is called while the queue is locked. | ||||
| // 'compressor' may be nil if you don't want any delta compression. | ||||
| // | ||||
| // 'keyLister' is expected to return a list of keys that the consumer of | ||||
| // this queue "knows about". It is used to decide which items are missing | ||||
| // when Replace() is called; 'Deleted' deltas are produced for these items. | ||||
| // It may be nil if you don't need to detect all deletions. | ||||
| // TODO: consider merging keyLister with this object, tracking a list of | ||||
| //       "known" keys when Pop() is called. Have to think about how that | ||||
| //       affects error retrying. | ||||
| // TODO(lavalamp): I believe there is a possible race only when using an | ||||
| //                 external known object source that the above TODO would | ||||
| //                 fix. | ||||
| // | ||||
| // Also see the comment on DeltaFIFO. | ||||
| func NewDeltaFIFO(keyFunc KeyFunc, compressor DeltaCompressor, knownObjects KeyListerGetter) *DeltaFIFO { | ||||
| 	f := &DeltaFIFO{ | ||||
| 		items:           map[string]Deltas{}, | ||||
| 		queue:           []string{}, | ||||
| 		keyFunc:         keyFunc, | ||||
| 		deltaCompressor: compressor, | ||||
| 		knownObjects:    knownObjects, | ||||
| 	} | ||||
| 	f.cond.L = &f.lock | ||||
| 	return f | ||||
| } | ||||
|  | ||||
| // DeltaFIFO is like FIFO, but allows you to process deletes. | ||||
| // | ||||
| // DeltaFIFO is a producer-consumer queue, where a Reflector is | ||||
| // intended to be the producer, and the consumer is whatever calls | ||||
| // the Pop() method. | ||||
| // | ||||
| // DeltaFIFO solves this use case: | ||||
| //  * You want to process every object change (delta) at most once. | ||||
| //  * When you process an object, you want to see everything | ||||
| //    that's happened to it since you last processed it. | ||||
| //  * You want to process the deletion of objects. | ||||
| //  * You might want to periodically reprocess objects. | ||||
| // | ||||
| // DeltaFIFO's Pop(), Get(), and GetByKey() methods return | ||||
| // interface{} to satisfy the Store/Queue interfaces, but it | ||||
| // will always return an object of type Deltas. | ||||
| // | ||||
| // A note on threading: If you call Pop() in parallel from multiple | ||||
| // threads, you could end up with multiple threads processing slightly | ||||
| // different versions of the same object. | ||||
| // | ||||
| // A note on the KeyLister used by the DeltaFIFO: It's main purpose is | ||||
| // to list keys that are "known", for the purpose of figuring out which | ||||
| // items have been deleted when Replace() or Delete() are called. The deleted | ||||
| // object will be included in the DeleteFinalStateUnknown markers. These objects | ||||
| // could be stale. | ||||
| // | ||||
| // You may provide a function to compress deltas (e.g., represent a | ||||
| // series of Updates as a single Update). | ||||
| type DeltaFIFO struct { | ||||
| 	// lock/cond protects access to 'items' and 'queue'. | ||||
| 	lock sync.RWMutex | ||||
| 	cond sync.Cond | ||||
|  | ||||
| 	// We depend on the property that items in the set are in | ||||
| 	// the queue and vice versa, and that all Deltas in this | ||||
| 	// map have at least one Delta. | ||||
| 	items map[string]Deltas | ||||
| 	queue []string | ||||
|  | ||||
| 	// populated is true if the first batch of items inserted by Replace() has been populated | ||||
| 	// or Delete/Add/Update was called first. | ||||
| 	populated bool | ||||
| 	// initialPopulationCount is the number of items inserted by the first call of Replace() | ||||
| 	initialPopulationCount int | ||||
|  | ||||
| 	// keyFunc is used to make the key used for queued item | ||||
| 	// insertion and retrieval, and should be deterministic. | ||||
| 	keyFunc KeyFunc | ||||
|  | ||||
| 	// deltaCompressor tells us how to combine two or more | ||||
| 	// deltas. It may be nil. | ||||
| 	deltaCompressor DeltaCompressor | ||||
|  | ||||
| 	// knownObjects list keys that are "known", for the | ||||
| 	// purpose of figuring out which items have been deleted | ||||
| 	// when Replace() or Delete() is called. | ||||
| 	knownObjects KeyListerGetter | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	_ = Queue(&DeltaFIFO{}) // DeltaFIFO is a Queue | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// ErrZeroLengthDeltasObject is returned in a KeyError if a Deltas | ||||
| 	// object with zero length is encountered (should be impossible, | ||||
| 	// even if such an object is accidentally produced by a DeltaCompressor-- | ||||
| 	// but included for completeness). | ||||
| 	ErrZeroLengthDeltasObject = errors.New("0 length Deltas object; can't get key") | ||||
| ) | ||||
|  | ||||
| // KeyOf exposes f's keyFunc, but also detects the key of a Deltas object or | ||||
| // DeletedFinalStateUnknown objects. | ||||
| func (f *DeltaFIFO) KeyOf(obj interface{}) (string, error) { | ||||
| 	if d, ok := obj.(Deltas); ok { | ||||
| 		if len(d) == 0 { | ||||
| 			return "", KeyError{obj, ErrZeroLengthDeltasObject} | ||||
| 		} | ||||
| 		obj = d.Newest().Object | ||||
| 	} | ||||
| 	if d, ok := obj.(DeletedFinalStateUnknown); ok { | ||||
| 		return d.Key, nil | ||||
| 	} | ||||
| 	return f.keyFunc(obj) | ||||
| } | ||||
|  | ||||
| // Return true if an Add/Update/Delete/AddIfNotPresent are called first, | ||||
| // or an Update called first but the first batch of items inserted by Replace() has been popped | ||||
| func (f *DeltaFIFO) HasSynced() bool { | ||||
| 	f.lock.Lock() | ||||
| 	defer f.lock.Unlock() | ||||
| 	return f.populated && f.initialPopulationCount == 0 | ||||
| } | ||||
|  | ||||
| // Add inserts an item, and puts it in the queue. The item is only enqueued | ||||
| // if it doesn't already exist in the set. | ||||
| func (f *DeltaFIFO) Add(obj interface{}) error { | ||||
| 	f.lock.Lock() | ||||
| 	defer f.lock.Unlock() | ||||
| 	f.populated = true | ||||
| 	return f.queueActionLocked(Added, obj) | ||||
| } | ||||
|  | ||||
| // Update is just like Add, but makes an Updated Delta. | ||||
| func (f *DeltaFIFO) Update(obj interface{}) error { | ||||
| 	f.lock.Lock() | ||||
| 	defer f.lock.Unlock() | ||||
| 	f.populated = true | ||||
| 	return f.queueActionLocked(Updated, obj) | ||||
| } | ||||
|  | ||||
| // Delete is just like Add, but makes an Deleted Delta. If the item does not | ||||
| // already exist, it will be ignored. (It may have already been deleted by a | ||||
| // Replace (re-list), for example. | ||||
| func (f *DeltaFIFO) Delete(obj interface{}) error { | ||||
| 	id, err := f.KeyOf(obj) | ||||
| 	if err != nil { | ||||
| 		return KeyError{obj, err} | ||||
| 	} | ||||
| 	f.lock.Lock() | ||||
| 	defer f.lock.Unlock() | ||||
| 	f.populated = true | ||||
| 	if f.knownObjects == nil { | ||||
| 		if _, exists := f.items[id]; !exists { | ||||
| 			// Presumably, this was deleted when a relist happened. | ||||
| 			// Don't provide a second report of the same deletion. | ||||
| 			return nil | ||||
| 		} | ||||
| 	} else { | ||||
| 		// We only want to skip the "deletion" action if the object doesn't | ||||
| 		// exist in knownObjects and it doesn't have corresponding item in items. | ||||
| 		// Note that even if there is a "deletion" action in items, we can ignore it, | ||||
| 		// because it will be deduped automatically in "queueActionLocked" | ||||
| 		_, exists, err := f.knownObjects.GetByKey(id) | ||||
| 		_, itemsExist := f.items[id] | ||||
| 		if err == nil && !exists && !itemsExist { | ||||
| 			// Presumably, this was deleted when a relist happened. | ||||
| 			// Don't provide a second report of the same deletion. | ||||
| 			// TODO(lavalamp): This may be racy-- we aren't properly locked | ||||
| 			// with knownObjects. | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return f.queueActionLocked(Deleted, obj) | ||||
| } | ||||
|  | ||||
| // AddIfNotPresent inserts an item, and puts it in the queue. If the item is already | ||||
| // present in the set, it is neither enqueued nor added to the set. | ||||
| // | ||||
| // This is useful in a single producer/consumer scenario so that the consumer can | ||||
| // safely retry items without contending with the producer and potentially enqueueing | ||||
| // stale items. | ||||
| // | ||||
| // Important: obj must be a Deltas (the output of the Pop() function). Yes, this is | ||||
| // different from the Add/Update/Delete functions. | ||||
| func (f *DeltaFIFO) AddIfNotPresent(obj interface{}) error { | ||||
| 	deltas, ok := obj.(Deltas) | ||||
| 	if !ok { | ||||
| 		return fmt.Errorf("object must be of type deltas, but got: %#v", obj) | ||||
| 	} | ||||
| 	id, err := f.KeyOf(deltas.Newest().Object) | ||||
| 	if err != nil { | ||||
| 		return KeyError{obj, err} | ||||
| 	} | ||||
| 	f.lock.Lock() | ||||
| 	defer f.lock.Unlock() | ||||
| 	f.addIfNotPresent(id, deltas) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // addIfNotPresent inserts deltas under id if it does not exist, and assumes the caller | ||||
| // already holds the fifo lock. | ||||
| func (f *DeltaFIFO) addIfNotPresent(id string, deltas Deltas) { | ||||
| 	f.populated = true | ||||
| 	if _, exists := f.items[id]; exists { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	f.queue = append(f.queue, id) | ||||
| 	f.items[id] = deltas | ||||
| 	f.cond.Broadcast() | ||||
| } | ||||
|  | ||||
| // re-listing and watching can deliver the same update multiple times in any | ||||
| // order. This will combine the most recent two deltas if they are the same. | ||||
| func dedupDeltas(deltas Deltas) Deltas { | ||||
| 	n := len(deltas) | ||||
| 	if n < 2 { | ||||
| 		return deltas | ||||
| 	} | ||||
| 	a := &deltas[n-1] | ||||
| 	b := &deltas[n-2] | ||||
| 	if out := isDup(a, b); out != nil { | ||||
| 		d := append(Deltas{}, deltas[:n-2]...) | ||||
| 		return append(d, *out) | ||||
| 	} | ||||
| 	return deltas | ||||
| } | ||||
|  | ||||
| // If a & b represent the same event, returns the delta that ought to be kept. | ||||
| // Otherwise, returns nil. | ||||
| // TODO: is there anything other than deletions that need deduping? | ||||
| func isDup(a, b *Delta) *Delta { | ||||
| 	if out := isDeletionDup(a, b); out != nil { | ||||
| 		return out | ||||
| 	} | ||||
| 	// TODO: Detect other duplicate situations? Are there any? | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // keep the one with the most information if both are deletions. | ||||
| func isDeletionDup(a, b *Delta) *Delta { | ||||
| 	if b.Type != Deleted || a.Type != Deleted { | ||||
| 		return nil | ||||
| 	} | ||||
| 	// Do more sophisticated checks, or is this sufficient? | ||||
| 	if _, ok := b.Object.(DeletedFinalStateUnknown); ok { | ||||
| 		return a | ||||
| 	} | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| // willObjectBeDeletedLocked returns true only if the last delta for the | ||||
| // given object is Delete. Caller must lock first. | ||||
| func (f *DeltaFIFO) willObjectBeDeletedLocked(id string) bool { | ||||
| 	deltas := f.items[id] | ||||
| 	return len(deltas) > 0 && deltas[len(deltas)-1].Type == Deleted | ||||
| } | ||||
|  | ||||
| // queueActionLocked appends to the delta list for the object, calling | ||||
| // f.deltaCompressor if needed. Caller must lock first. | ||||
| func (f *DeltaFIFO) queueActionLocked(actionType DeltaType, obj interface{}) error { | ||||
| 	id, err := f.KeyOf(obj) | ||||
| 	if err != nil { | ||||
| 		return KeyError{obj, err} | ||||
| 	} | ||||
|  | ||||
| 	// If object is supposed to be deleted (last event is Deleted), | ||||
| 	// then we should ignore Sync events, because it would result in | ||||
| 	// recreation of this object. | ||||
| 	if actionType == Sync && f.willObjectBeDeletedLocked(id) { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	newDeltas := append(f.items[id], Delta{actionType, obj}) | ||||
| 	newDeltas = dedupDeltas(newDeltas) | ||||
| 	if f.deltaCompressor != nil { | ||||
| 		newDeltas = f.deltaCompressor.Compress(newDeltas) | ||||
| 	} | ||||
|  | ||||
| 	_, exists := f.items[id] | ||||
| 	if len(newDeltas) > 0 { | ||||
| 		if !exists { | ||||
| 			f.queue = append(f.queue, id) | ||||
| 		} | ||||
| 		f.items[id] = newDeltas | ||||
| 		f.cond.Broadcast() | ||||
| 	} else if exists { | ||||
| 		// The compression step removed all deltas, so | ||||
| 		// we need to remove this from our map (extra items | ||||
| 		// in the queue are ignored if they are not in the | ||||
| 		// map). | ||||
| 		delete(f.items, id) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // List returns a list of all the items; it returns the object | ||||
| // from the most recent Delta. | ||||
| // You should treat the items returned inside the deltas as immutable. | ||||
| func (f *DeltaFIFO) List() []interface{} { | ||||
| 	f.lock.RLock() | ||||
| 	defer f.lock.RUnlock() | ||||
| 	return f.listLocked() | ||||
| } | ||||
|  | ||||
| func (f *DeltaFIFO) listLocked() []interface{} { | ||||
| 	list := make([]interface{}, 0, len(f.items)) | ||||
| 	for _, item := range f.items { | ||||
| 		// Copy item's slice so operations on this slice (delta | ||||
| 		// compression) won't interfere with the object we return. | ||||
| 		item = copyDeltas(item) | ||||
| 		list = append(list, item.Newest().Object) | ||||
| 	} | ||||
| 	return list | ||||
| } | ||||
|  | ||||
| // ListKeys returns a list of all the keys of the objects currently | ||||
| // in the FIFO. | ||||
| func (f *DeltaFIFO) ListKeys() []string { | ||||
| 	f.lock.RLock() | ||||
| 	defer f.lock.RUnlock() | ||||
| 	list := make([]string, 0, len(f.items)) | ||||
| 	for key := range f.items { | ||||
| 		list = append(list, key) | ||||
| 	} | ||||
| 	return list | ||||
| } | ||||
|  | ||||
| // Get returns the complete list of deltas for the requested item, | ||||
| // or sets exists=false. | ||||
| // You should treat the items returned inside the deltas as immutable. | ||||
| func (f *DeltaFIFO) Get(obj interface{}) (item interface{}, exists bool, err error) { | ||||
| 	key, err := f.KeyOf(obj) | ||||
| 	if err != nil { | ||||
| 		return nil, false, KeyError{obj, err} | ||||
| 	} | ||||
| 	return f.GetByKey(key) | ||||
| } | ||||
|  | ||||
| // GetByKey returns the complete list of deltas for the requested item, | ||||
| // setting exists=false if that list is empty. | ||||
| // You should treat the items returned inside the deltas as immutable. | ||||
| func (f *DeltaFIFO) GetByKey(key string) (item interface{}, exists bool, err error) { | ||||
| 	f.lock.RLock() | ||||
| 	defer f.lock.RUnlock() | ||||
| 	d, exists := f.items[key] | ||||
| 	if exists { | ||||
| 		// Copy item's slice so operations on this slice (delta | ||||
| 		// compression) won't interfere with the object we return. | ||||
| 		d = copyDeltas(d) | ||||
| 	} | ||||
| 	return d, exists, nil | ||||
| } | ||||
|  | ||||
| // Pop blocks until an item is added to the queue, and then returns it.  If | ||||
| // multiple items are ready, they are returned in the order in which they were | ||||
| // added/updated. The item is removed from the queue (and the store) before it | ||||
| // is returned, so if you don't successfully process it, you need to add it back | ||||
| // with AddIfNotPresent(). | ||||
| // process function is called under lock, so it is safe update data structures | ||||
| // in it that need to be in sync with the queue (e.g. knownKeys). The PopProcessFunc | ||||
| // may return an instance of ErrRequeue with a nested error to indicate the current | ||||
| // item should be requeued (equivalent to calling AddIfNotPresent under the lock). | ||||
| // | ||||
| // Pop returns a 'Deltas', which has a complete list of all the things | ||||
| // that happened to the object (deltas) while it was sitting in the queue. | ||||
| func (f *DeltaFIFO) Pop(process PopProcessFunc) (interface{}, error) { | ||||
| 	f.lock.Lock() | ||||
| 	defer f.lock.Unlock() | ||||
| 	for { | ||||
| 		for len(f.queue) == 0 { | ||||
| 			f.cond.Wait() | ||||
| 		} | ||||
| 		id := f.queue[0] | ||||
| 		f.queue = f.queue[1:] | ||||
| 		item, ok := f.items[id] | ||||
| 		if f.initialPopulationCount > 0 { | ||||
| 			f.initialPopulationCount-- | ||||
| 		} | ||||
| 		if !ok { | ||||
| 			// Item may have been deleted subsequently. | ||||
| 			continue | ||||
| 		} | ||||
| 		delete(f.items, id) | ||||
| 		err := process(item) | ||||
| 		if e, ok := err.(ErrRequeue); ok { | ||||
| 			f.addIfNotPresent(id, item) | ||||
| 			err = e.Err | ||||
| 		} | ||||
| 		// Don't need to copyDeltas here, because we're transferring | ||||
| 		// ownership to the caller. | ||||
| 		return item, err | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Replace will delete the contents of 'f', using instead the given map. | ||||
| // 'f' takes ownership of the map, you should not reference the map again | ||||
| // after calling this function. f's queue is reset, too; upon return, it | ||||
| // will contain the items in the map, in no particular order. | ||||
| func (f *DeltaFIFO) Replace(list []interface{}, resourceVersion string) error { | ||||
| 	f.lock.Lock() | ||||
| 	defer f.lock.Unlock() | ||||
| 	keys := make(sets.String, len(list)) | ||||
|  | ||||
| 	for _, item := range list { | ||||
| 		key, err := f.KeyOf(item) | ||||
| 		if err != nil { | ||||
| 			return KeyError{item, err} | ||||
| 		} | ||||
| 		keys.Insert(key) | ||||
| 		if err := f.queueActionLocked(Sync, item); err != nil { | ||||
| 			return fmt.Errorf("couldn't enqueue object: %v", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if f.knownObjects == nil { | ||||
| 		// Do deletion detection against our own list. | ||||
| 		for k, oldItem := range f.items { | ||||
| 			if keys.Has(k) { | ||||
| 				continue | ||||
| 			} | ||||
| 			var deletedObj interface{} | ||||
| 			if n := oldItem.Newest(); n != nil { | ||||
| 				deletedObj = n.Object | ||||
| 			} | ||||
| 			if err := f.queueActionLocked(Deleted, DeletedFinalStateUnknown{k, deletedObj}); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if !f.populated { | ||||
| 			f.populated = true | ||||
| 			f.initialPopulationCount = len(list) | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	// Detect deletions not already in the queue. | ||||
| 	// TODO(lavalamp): This may be racy-- we aren't properly locked | ||||
| 	// with knownObjects. Unproven. | ||||
| 	knownKeys := f.knownObjects.ListKeys() | ||||
| 	queuedDeletions := 0 | ||||
| 	for _, k := range knownKeys { | ||||
| 		if keys.Has(k) { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		deletedObj, exists, err := f.knownObjects.GetByKey(k) | ||||
| 		if err != nil { | ||||
| 			deletedObj = nil | ||||
| 			glog.Errorf("Unexpected error %v during lookup of key %v, placing DeleteFinalStateUnknown marker without object", err, k) | ||||
| 		} else if !exists { | ||||
| 			deletedObj = nil | ||||
| 			glog.Infof("Key %v does not exist in known objects store, placing DeleteFinalStateUnknown marker without object", k) | ||||
| 		} | ||||
| 		queuedDeletions++ | ||||
| 		if err := f.queueActionLocked(Deleted, DeletedFinalStateUnknown{k, deletedObj}); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if !f.populated { | ||||
| 		f.populated = true | ||||
| 		f.initialPopulationCount = len(list) + queuedDeletions | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Resync will send a sync event for each item | ||||
| func (f *DeltaFIFO) Resync() error { | ||||
| 	var keys []string | ||||
| 	func() { | ||||
| 		f.lock.RLock() | ||||
| 		defer f.lock.RUnlock() | ||||
| 		keys = f.knownObjects.ListKeys() | ||||
| 	}() | ||||
| 	for _, k := range keys { | ||||
| 		if err := f.syncKey(k); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (f *DeltaFIFO) syncKey(key string) error { | ||||
| 	f.lock.Lock() | ||||
| 	defer f.lock.Unlock() | ||||
| 	obj, exists, err := f.knownObjects.GetByKey(key) | ||||
| 	if err != nil { | ||||
| 		glog.Errorf("Unexpected error %v during lookup of key %v, unable to queue object for sync", err, key) | ||||
| 		return nil | ||||
| 	} else if !exists { | ||||
| 		glog.Infof("Key %v does not exist in known objects store, unable to queue object for sync", key) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	// If we are doing Resync() and there is already an event queued for that object, | ||||
| 	// we ignore the Resync for it. This is to avoid the race, in which the resync | ||||
| 	// comes with the previous value of object (since queueing an event for the object | ||||
| 	// doesn't trigger changing the underlying store <knownObjects>. | ||||
| 	id, err := f.KeyOf(obj) | ||||
| 	if err != nil { | ||||
| 		return KeyError{obj, err} | ||||
| 	} | ||||
| 	if len(f.items[id]) > 0 { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	if err := f.queueActionLocked(Sync, obj); err != nil { | ||||
| 		return fmt.Errorf("couldn't queue object: %v", err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // A KeyListerGetter is anything that knows how to list its keys and look up by key. | ||||
| type KeyListerGetter interface { | ||||
| 	KeyLister | ||||
| 	KeyGetter | ||||
| } | ||||
|  | ||||
| // A KeyLister is anything that knows how to list its keys. | ||||
| type KeyLister interface { | ||||
| 	ListKeys() []string | ||||
| } | ||||
|  | ||||
| // A KeyGetter is anything that knows how to get the value stored under a given key. | ||||
| type KeyGetter interface { | ||||
| 	GetByKey(key string) (interface{}, bool, error) | ||||
| } | ||||
|  | ||||
| // DeltaCompressor is an algorithm that removes redundant changes. | ||||
| type DeltaCompressor interface { | ||||
| 	Compress(Deltas) Deltas | ||||
| } | ||||
|  | ||||
| // DeltaCompressorFunc should remove redundant changes; but changes that | ||||
| // are redundant depend on one's desired semantics, so this is an | ||||
| // injectable function. | ||||
| // | ||||
| // DeltaCompressorFunc adapts a raw function to be a DeltaCompressor. | ||||
| type DeltaCompressorFunc func(Deltas) Deltas | ||||
|  | ||||
| // Compress just calls dc. | ||||
| func (dc DeltaCompressorFunc) Compress(d Deltas) Deltas { | ||||
| 	return dc(d) | ||||
| } | ||||
|  | ||||
| // DeltaType is the type of a change (addition, deletion, etc) | ||||
| type DeltaType string | ||||
|  | ||||
| const ( | ||||
| 	Added   DeltaType = "Added" | ||||
| 	Updated DeltaType = "Updated" | ||||
| 	Deleted DeltaType = "Deleted" | ||||
| 	// The other types are obvious. You'll get Sync deltas when: | ||||
| 	//  * A watch expires/errors out and a new list/watch cycle is started. | ||||
| 	//  * You've turned on periodic syncs. | ||||
| 	// (Anything that trigger's DeltaFIFO's Replace() method.) | ||||
| 	Sync DeltaType = "Sync" | ||||
| ) | ||||
|  | ||||
| // Delta is the type stored by a DeltaFIFO. It tells you what change | ||||
| // happened, and the object's state after* that change. | ||||
| // | ||||
| // [*] Unless the change is a deletion, and then you'll get the final | ||||
| //     state of the object before it was deleted. | ||||
| type Delta struct { | ||||
| 	Type   DeltaType | ||||
| 	Object interface{} | ||||
| } | ||||
|  | ||||
| // Deltas is a list of one or more 'Delta's to an individual object. | ||||
| // The oldest delta is at index 0, the newest delta is the last one. | ||||
| type Deltas []Delta | ||||
|  | ||||
| // Oldest is a convenience function that returns the oldest delta, or | ||||
| // nil if there are no deltas. | ||||
| func (d Deltas) Oldest() *Delta { | ||||
| 	if len(d) > 0 { | ||||
| 		return &d[0] | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Newest is a convenience function that returns the newest delta, or | ||||
| // nil if there are no deltas. | ||||
| func (d Deltas) Newest() *Delta { | ||||
| 	if n := len(d); n > 0 { | ||||
| 		return &d[n-1] | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // copyDeltas returns a shallow copy of d; that is, it copies the slice but not | ||||
| // the objects in the slice. This allows Get/List to return an object that we | ||||
| // know won't be clobbered by a subsequent call to a delta compressor. | ||||
| func copyDeltas(d Deltas) Deltas { | ||||
| 	d2 := make(Deltas, len(d)) | ||||
| 	copy(d2, d) | ||||
| 	return d2 | ||||
| } | ||||
|  | ||||
| // DeletedFinalStateUnknown is placed into a DeltaFIFO in the case where | ||||
| // an object was deleted but the watch deletion event was missed. In this | ||||
| // case we don't know the final "resting" state of the object, so there's | ||||
| // a chance the included `Obj` is stale. | ||||
| type DeletedFinalStateUnknown struct { | ||||
| 	Key string | ||||
| 	Obj interface{} | ||||
| } | ||||
							
								
								
									
										533
									
								
								pkg/client/cache/delta_fifo_test.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										533
									
								
								pkg/client/cache/delta_fifo_test.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,533 +0,0 @@ | ||||
| /* | ||||
| Copyright 2014 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // helper function to reduce stuttering | ||||
| func testPop(f *DeltaFIFO) testFifoObject { | ||||
| 	return Pop(f).(Deltas).Newest().Object.(testFifoObject) | ||||
| } | ||||
|  | ||||
| // keyLookupFunc adapts a raw function to be a KeyLookup. | ||||
| type keyLookupFunc func() []testFifoObject | ||||
|  | ||||
| // ListKeys just calls kl. | ||||
| func (kl keyLookupFunc) ListKeys() []string { | ||||
| 	result := []string{} | ||||
| 	for _, fifoObj := range kl() { | ||||
| 		result = append(result, fifoObj.name) | ||||
| 	} | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| // GetByKey returns the key if it exists in the list returned by kl. | ||||
| func (kl keyLookupFunc) GetByKey(key string) (interface{}, bool, error) { | ||||
| 	for _, v := range kl() { | ||||
| 		if v.name == key { | ||||
| 			return v, true, nil | ||||
| 		} | ||||
| 	} | ||||
| 	return nil, false, nil | ||||
| } | ||||
|  | ||||
| func TestDeltaFIFO_basic(t *testing.T) { | ||||
| 	f := NewDeltaFIFO(testFifoObjectKeyFunc, nil, nil) | ||||
| 	const amount = 500 | ||||
| 	go func() { | ||||
| 		for i := 0; i < amount; i++ { | ||||
| 			f.Add(mkFifoObj(string([]rune{'a', rune(i)}), i+1)) | ||||
| 		} | ||||
| 	}() | ||||
| 	go func() { | ||||
| 		for u := uint64(0); u < amount; u++ { | ||||
| 			f.Add(mkFifoObj(string([]rune{'b', rune(u)}), u+1)) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	lastInt := int(0) | ||||
| 	lastUint := uint64(0) | ||||
| 	for i := 0; i < amount*2; i++ { | ||||
| 		switch obj := testPop(f).val.(type) { | ||||
| 		case int: | ||||
| 			if obj <= lastInt { | ||||
| 				t.Errorf("got %v (int) out of order, last was %v", obj, lastInt) | ||||
| 			} | ||||
| 			lastInt = obj | ||||
| 		case uint64: | ||||
| 			if obj <= lastUint { | ||||
| 				t.Errorf("got %v (uint) out of order, last was %v", obj, lastUint) | ||||
| 			} else { | ||||
| 				lastUint = obj | ||||
| 			} | ||||
| 		default: | ||||
| 			t.Fatalf("unexpected type %#v", obj) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDeltaFIFO_requeueOnPop(t *testing.T) { | ||||
| 	f := NewDeltaFIFO(testFifoObjectKeyFunc, nil, nil) | ||||
|  | ||||
| 	f.Add(mkFifoObj("foo", 10)) | ||||
| 	_, err := f.Pop(func(obj interface{}) error { | ||||
| 		if obj.(Deltas)[0].Object.(testFifoObject).name != "foo" { | ||||
| 			t.Fatalf("unexpected object: %#v", obj) | ||||
| 		} | ||||
| 		return ErrRequeue{Err: nil} | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error: %v", err) | ||||
| 	} | ||||
| 	if _, ok, err := f.GetByKey("foo"); !ok || err != nil { | ||||
| 		t.Fatalf("object should have been requeued: %t %v", ok, err) | ||||
| 	} | ||||
|  | ||||
| 	_, err = f.Pop(func(obj interface{}) error { | ||||
| 		if obj.(Deltas)[0].Object.(testFifoObject).name != "foo" { | ||||
| 			t.Fatalf("unexpected object: %#v", obj) | ||||
| 		} | ||||
| 		return ErrRequeue{Err: fmt.Errorf("test error")} | ||||
| 	}) | ||||
| 	if err == nil || err.Error() != "test error" { | ||||
| 		t.Fatalf("unexpected error: %v", err) | ||||
| 	} | ||||
| 	if _, ok, err := f.GetByKey("foo"); !ok || err != nil { | ||||
| 		t.Fatalf("object should have been requeued: %t %v", ok, err) | ||||
| 	} | ||||
|  | ||||
| 	_, err = f.Pop(func(obj interface{}) error { | ||||
| 		if obj.(Deltas)[0].Object.(testFifoObject).name != "foo" { | ||||
| 			t.Fatalf("unexpected object: %#v", obj) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error: %v", err) | ||||
| 	} | ||||
| 	if _, ok, err := f.GetByKey("foo"); ok || err != nil { | ||||
| 		t.Fatalf("object should have been removed: %t %v", ok, err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDeltaFIFO_compressorWorks(t *testing.T) { | ||||
| 	oldestTypes := []DeltaType{} | ||||
| 	f := NewDeltaFIFO( | ||||
| 		testFifoObjectKeyFunc, | ||||
| 		// This function just keeps the most recent delta | ||||
| 		// and puts deleted ones in the list. | ||||
| 		DeltaCompressorFunc(func(d Deltas) Deltas { | ||||
| 			if n := len(d); n > 1 { | ||||
| 				oldestTypes = append(oldestTypes, d[0].Type) | ||||
| 				d = d[1:] | ||||
| 			} | ||||
| 			return d | ||||
| 		}), | ||||
| 		nil, | ||||
| 	) | ||||
| 	if f.HasSynced() { | ||||
| 		t.Errorf("Expected HasSynced to be false before completion of initial population") | ||||
| 	} | ||||
| 	f.Add(mkFifoObj("foo", 10)) | ||||
| 	f.Update(mkFifoObj("foo", 12)) | ||||
| 	f.Replace([]interface{}{mkFifoObj("foo", 20)}, "0") | ||||
| 	f.Delete(mkFifoObj("foo", 22)) | ||||
| 	f.Add(mkFifoObj("foo", 25)) // flush the last one out | ||||
| 	expect := []DeltaType{Added, Updated, Sync, Deleted} | ||||
| 	if e, a := expect, oldestTypes; !reflect.DeepEqual(e, a) { | ||||
| 		t.Errorf("Expected %#v, got %#v", e, a) | ||||
| 	} | ||||
| 	if e, a := (Deltas{{Added, mkFifoObj("foo", 25)}}), Pop(f).(Deltas); !reflect.DeepEqual(e, a) { | ||||
| 		t.Fatalf("Expected %#v, got %#v", e, a) | ||||
| 	} | ||||
| 	if !f.HasSynced() { | ||||
| 		t.Errorf("Expected HasSynced to be true after completion of initial population") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDeltaFIFO_addUpdate(t *testing.T) { | ||||
| 	f := NewDeltaFIFO(testFifoObjectKeyFunc, nil, nil) | ||||
| 	f.Add(mkFifoObj("foo", 10)) | ||||
| 	f.Update(mkFifoObj("foo", 12)) | ||||
| 	f.Delete(mkFifoObj("foo", 15)) | ||||
|  | ||||
| 	if e, a := []interface{}{mkFifoObj("foo", 15)}, f.List(); !reflect.DeepEqual(e, a) { | ||||
| 		t.Errorf("Expected %+v, got %+v", e, a) | ||||
| 	} | ||||
| 	if e, a := []string{"foo"}, f.ListKeys(); !reflect.DeepEqual(e, a) { | ||||
| 		t.Errorf("Expected %+v, got %+v", e, a) | ||||
| 	} | ||||
|  | ||||
| 	got := make(chan testFifoObject, 2) | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			obj := testPop(f) | ||||
| 			t.Logf("got a thing %#v", obj) | ||||
| 			t.Logf("D len: %v", len(f.queue)) | ||||
| 			got <- obj | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	first := <-got | ||||
| 	if e, a := 15, first.val; e != a { | ||||
| 		t.Errorf("Didn't get updated value (%v), got %v", e, a) | ||||
| 	} | ||||
| 	select { | ||||
| 	case unexpected := <-got: | ||||
| 		t.Errorf("Got second value %v", unexpected.val) | ||||
| 	case <-time.After(50 * time.Millisecond): | ||||
| 	} | ||||
| 	_, exists, _ := f.Get(mkFifoObj("foo", "")) | ||||
| 	if exists { | ||||
| 		t.Errorf("item did not get removed") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDeltaFIFO_enqueueingNoLister(t *testing.T) { | ||||
| 	f := NewDeltaFIFO(testFifoObjectKeyFunc, nil, nil) | ||||
| 	f.Add(mkFifoObj("foo", 10)) | ||||
| 	f.Update(mkFifoObj("bar", 15)) | ||||
| 	f.Add(mkFifoObj("qux", 17)) | ||||
| 	f.Delete(mkFifoObj("qux", 18)) | ||||
|  | ||||
| 	// This delete does not enqueue anything because baz doesn't exist. | ||||
| 	f.Delete(mkFifoObj("baz", 20)) | ||||
|  | ||||
| 	expectList := []int{10, 15, 18} | ||||
| 	for _, expect := range expectList { | ||||
| 		if e, a := expect, testPop(f).val; e != a { | ||||
| 			t.Errorf("Didn't get updated value (%v), got %v", e, a) | ||||
| 		} | ||||
| 	} | ||||
| 	if e, a := 0, len(f.items); e != a { | ||||
| 		t.Errorf("queue unexpectedly not empty: %v != %v\n%#v", e, a, f.items) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDeltaFIFO_enqueueingWithLister(t *testing.T) { | ||||
| 	f := NewDeltaFIFO( | ||||
| 		testFifoObjectKeyFunc, | ||||
| 		nil, | ||||
| 		keyLookupFunc(func() []testFifoObject { | ||||
| 			return []testFifoObject{mkFifoObj("foo", 5), mkFifoObj("bar", 6), mkFifoObj("baz", 7)} | ||||
| 		}), | ||||
| 	) | ||||
| 	f.Add(mkFifoObj("foo", 10)) | ||||
| 	f.Update(mkFifoObj("bar", 15)) | ||||
|  | ||||
| 	// This delete does enqueue the deletion, because "baz" is in the key lister. | ||||
| 	f.Delete(mkFifoObj("baz", 20)) | ||||
|  | ||||
| 	expectList := []int{10, 15, 20} | ||||
| 	for _, expect := range expectList { | ||||
| 		if e, a := expect, testPop(f).val; e != a { | ||||
| 			t.Errorf("Didn't get updated value (%v), got %v", e, a) | ||||
| 		} | ||||
| 	} | ||||
| 	if e, a := 0, len(f.items); e != a { | ||||
| 		t.Errorf("queue unexpectedly not empty: %v != %v", e, a) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDeltaFIFO_addReplace(t *testing.T) { | ||||
| 	f := NewDeltaFIFO(testFifoObjectKeyFunc, nil, nil) | ||||
| 	f.Add(mkFifoObj("foo", 10)) | ||||
| 	f.Replace([]interface{}{mkFifoObj("foo", 15)}, "0") | ||||
| 	got := make(chan testFifoObject, 2) | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			got <- testPop(f) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	first := <-got | ||||
| 	if e, a := 15, first.val; e != a { | ||||
| 		t.Errorf("Didn't get updated value (%v), got %v", e, a) | ||||
| 	} | ||||
| 	select { | ||||
| 	case unexpected := <-got: | ||||
| 		t.Errorf("Got second value %v", unexpected.val) | ||||
| 	case <-time.After(50 * time.Millisecond): | ||||
| 	} | ||||
| 	_, exists, _ := f.Get(mkFifoObj("foo", "")) | ||||
| 	if exists { | ||||
| 		t.Errorf("item did not get removed") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDeltaFIFO_ResyncNonExisting(t *testing.T) { | ||||
| 	f := NewDeltaFIFO( | ||||
| 		testFifoObjectKeyFunc, | ||||
| 		nil, | ||||
| 		keyLookupFunc(func() []testFifoObject { | ||||
| 			return []testFifoObject{mkFifoObj("foo", 5)} | ||||
| 		}), | ||||
| 	) | ||||
| 	f.Delete(mkFifoObj("foo", 10)) | ||||
| 	f.Resync() | ||||
|  | ||||
| 	deltas := f.items["foo"] | ||||
| 	if len(deltas) != 1 { | ||||
| 		t.Fatalf("unexpected deltas length: %v", deltas) | ||||
| 	} | ||||
| 	if deltas[0].Type != Deleted { | ||||
| 		t.Errorf("unexpected delta: %v", deltas[0]) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDeltaFIFO_DeleteExistingNonPropagated(t *testing.T) { | ||||
| 	f := NewDeltaFIFO( | ||||
| 		testFifoObjectKeyFunc, | ||||
| 		nil, | ||||
| 		keyLookupFunc(func() []testFifoObject { | ||||
| 			return []testFifoObject{} | ||||
| 		}), | ||||
| 	) | ||||
| 	f.Add(mkFifoObj("foo", 5)) | ||||
| 	f.Delete(mkFifoObj("foo", 6)) | ||||
|  | ||||
| 	deltas := f.items["foo"] | ||||
| 	if len(deltas) != 2 { | ||||
| 		t.Fatalf("unexpected deltas length: %v", deltas) | ||||
| 	} | ||||
| 	if deltas[len(deltas)-1].Type != Deleted { | ||||
| 		t.Errorf("unexpected delta: %v", deltas[len(deltas)-1]) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDeltaFIFO_ReplaceMakesDeletions(t *testing.T) { | ||||
| 	f := NewDeltaFIFO( | ||||
| 		testFifoObjectKeyFunc, | ||||
| 		nil, | ||||
| 		keyLookupFunc(func() []testFifoObject { | ||||
| 			return []testFifoObject{mkFifoObj("foo", 5), mkFifoObj("bar", 6), mkFifoObj("baz", 7)} | ||||
| 		}), | ||||
| 	) | ||||
| 	f.Delete(mkFifoObj("baz", 10)) | ||||
| 	f.Replace([]interface{}{mkFifoObj("foo", 5)}, "0") | ||||
|  | ||||
| 	expectedList := []Deltas{ | ||||
| 		{{Deleted, mkFifoObj("baz", 10)}}, | ||||
| 		{{Sync, mkFifoObj("foo", 5)}}, | ||||
| 		// Since "bar" didn't have a delete event and wasn't in the Replace list | ||||
| 		// it should get a tombstone key with the right Obj. | ||||
| 		{{Deleted, DeletedFinalStateUnknown{Key: "bar", Obj: mkFifoObj("bar", 6)}}}, | ||||
| 	} | ||||
|  | ||||
| 	for _, expected := range expectedList { | ||||
| 		cur := Pop(f).(Deltas) | ||||
| 		if e, a := expected, cur; !reflect.DeepEqual(e, a) { | ||||
| 			t.Errorf("Expected %#v, got %#v", e, a) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDeltaFIFO_UpdateResyncRace(t *testing.T) { | ||||
| 	f := NewDeltaFIFO( | ||||
| 		testFifoObjectKeyFunc, | ||||
| 		nil, | ||||
| 		keyLookupFunc(func() []testFifoObject { | ||||
| 			return []testFifoObject{mkFifoObj("foo", 5)} | ||||
| 		}), | ||||
| 	) | ||||
| 	f.Update(mkFifoObj("foo", 6)) | ||||
| 	f.Resync() | ||||
|  | ||||
| 	expectedList := []Deltas{ | ||||
| 		{{Updated, mkFifoObj("foo", 6)}}, | ||||
| 	} | ||||
|  | ||||
| 	for _, expected := range expectedList { | ||||
| 		cur := Pop(f).(Deltas) | ||||
| 		if e, a := expected, cur; !reflect.DeepEqual(e, a) { | ||||
| 			t.Errorf("Expected %#v, got %#v", e, a) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDeltaFIFO_HasSyncedCorrectOnDeletion(t *testing.T) { | ||||
| 	f := NewDeltaFIFO( | ||||
| 		testFifoObjectKeyFunc, | ||||
| 		nil, | ||||
| 		keyLookupFunc(func() []testFifoObject { | ||||
| 			return []testFifoObject{mkFifoObj("foo", 5), mkFifoObj("bar", 6), mkFifoObj("baz", 7)} | ||||
| 		}), | ||||
| 	) | ||||
| 	f.Replace([]interface{}{mkFifoObj("foo", 5)}, "0") | ||||
|  | ||||
| 	expectedList := []Deltas{ | ||||
| 		{{Sync, mkFifoObj("foo", 5)}}, | ||||
| 		// Since "bar" didn't have a delete event and wasn't in the Replace list | ||||
| 		// it should get a tombstone key with the right Obj. | ||||
| 		{{Deleted, DeletedFinalStateUnknown{Key: "bar", Obj: mkFifoObj("bar", 6)}}}, | ||||
| 	} | ||||
|  | ||||
| 	for _, expected := range expectedList { | ||||
| 		if f.HasSynced() { | ||||
| 			t.Errorf("Expected HasSynced to be false") | ||||
| 		} | ||||
| 		cur := Pop(f).(Deltas) | ||||
| 		if e, a := expected, cur; !reflect.DeepEqual(e, a) { | ||||
| 			t.Errorf("Expected %#v, got %#v", e, a) | ||||
| 		} | ||||
| 	} | ||||
| 	if f.HasSynced() { | ||||
| 		t.Errorf("Expected HasSynced to be true") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDeltaFIFO_detectLineJumpers(t *testing.T) { | ||||
| 	f := NewDeltaFIFO(testFifoObjectKeyFunc, nil, nil) | ||||
|  | ||||
| 	f.Add(mkFifoObj("foo", 10)) | ||||
| 	f.Add(mkFifoObj("bar", 1)) | ||||
| 	f.Add(mkFifoObj("foo", 11)) | ||||
| 	f.Add(mkFifoObj("foo", 13)) | ||||
| 	f.Add(mkFifoObj("zab", 30)) | ||||
|  | ||||
| 	if e, a := 13, testPop(f).val; a != e { | ||||
| 		t.Fatalf("expected %d, got %d", e, a) | ||||
| 	} | ||||
|  | ||||
| 	f.Add(mkFifoObj("foo", 14)) // ensure foo doesn't jump back in line | ||||
|  | ||||
| 	if e, a := 1, testPop(f).val; a != e { | ||||
| 		t.Fatalf("expected %d, got %d", e, a) | ||||
| 	} | ||||
|  | ||||
| 	if e, a := 30, testPop(f).val; a != e { | ||||
| 		t.Fatalf("expected %d, got %d", e, a) | ||||
| 	} | ||||
|  | ||||
| 	if e, a := 14, testPop(f).val; a != e { | ||||
| 		t.Fatalf("expected %d, got %d", e, a) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDeltaFIFO_addIfNotPresent(t *testing.T) { | ||||
| 	f := NewDeltaFIFO(testFifoObjectKeyFunc, nil, nil) | ||||
|  | ||||
| 	f.Add(mkFifoObj("b", 3)) | ||||
| 	b3 := Pop(f) | ||||
| 	f.Add(mkFifoObj("c", 4)) | ||||
| 	c4 := Pop(f) | ||||
| 	if e, a := 0, len(f.items); e != a { | ||||
| 		t.Fatalf("Expected %v, got %v items in queue", e, a) | ||||
| 	} | ||||
|  | ||||
| 	f.Add(mkFifoObj("a", 1)) | ||||
| 	f.Add(mkFifoObj("b", 2)) | ||||
| 	f.AddIfNotPresent(b3) | ||||
| 	f.AddIfNotPresent(c4) | ||||
|  | ||||
| 	if e, a := 3, len(f.items); a != e { | ||||
| 		t.Fatalf("expected queue length %d, got %d", e, a) | ||||
| 	} | ||||
|  | ||||
| 	expectedValues := []int{1, 2, 4} | ||||
| 	for _, expected := range expectedValues { | ||||
| 		if actual := testPop(f).val; actual != expected { | ||||
| 			t.Fatalf("expected value %d, got %d", expected, actual) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDeltaFIFO_KeyOf(t *testing.T) { | ||||
| 	f := DeltaFIFO{keyFunc: testFifoObjectKeyFunc} | ||||
|  | ||||
| 	table := []struct { | ||||
| 		obj interface{} | ||||
| 		key string | ||||
| 	}{ | ||||
| 		{obj: testFifoObject{name: "A"}, key: "A"}, | ||||
| 		{obj: DeletedFinalStateUnknown{Key: "B", Obj: nil}, key: "B"}, | ||||
| 		{obj: Deltas{{Object: testFifoObject{name: "C"}}}, key: "C"}, | ||||
| 		{obj: Deltas{{Object: DeletedFinalStateUnknown{Key: "D", Obj: nil}}}, key: "D"}, | ||||
| 	} | ||||
|  | ||||
| 	for _, item := range table { | ||||
| 		got, err := f.KeyOf(item.obj) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("Unexpected error for %q: %v", item.obj, err) | ||||
| 			continue | ||||
| 		} | ||||
| 		if e, a := item.key, got; e != a { | ||||
| 			t.Errorf("Expected %v, got %v", e, a) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDeltaFIFO_HasSynced(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		actions        []func(f *DeltaFIFO) | ||||
| 		expectedSynced bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			actions:        []func(f *DeltaFIFO){}, | ||||
| 			expectedSynced: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			actions: []func(f *DeltaFIFO){ | ||||
| 				func(f *DeltaFIFO) { f.Add(mkFifoObj("a", 1)) }, | ||||
| 			}, | ||||
| 			expectedSynced: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			actions: []func(f *DeltaFIFO){ | ||||
| 				func(f *DeltaFIFO) { f.Replace([]interface{}{}, "0") }, | ||||
| 			}, | ||||
| 			expectedSynced: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			actions: []func(f *DeltaFIFO){ | ||||
| 				func(f *DeltaFIFO) { f.Replace([]interface{}{mkFifoObj("a", 1), mkFifoObj("b", 2)}, "0") }, | ||||
| 			}, | ||||
| 			expectedSynced: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			actions: []func(f *DeltaFIFO){ | ||||
| 				func(f *DeltaFIFO) { f.Replace([]interface{}{mkFifoObj("a", 1), mkFifoObj("b", 2)}, "0") }, | ||||
| 				func(f *DeltaFIFO) { Pop(f) }, | ||||
| 			}, | ||||
| 			expectedSynced: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			actions: []func(f *DeltaFIFO){ | ||||
| 				func(f *DeltaFIFO) { f.Replace([]interface{}{mkFifoObj("a", 1), mkFifoObj("b", 2)}, "0") }, | ||||
| 				func(f *DeltaFIFO) { Pop(f) }, | ||||
| 				func(f *DeltaFIFO) { Pop(f) }, | ||||
| 			}, | ||||
| 			expectedSynced: true, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for i, test := range tests { | ||||
| 		f := NewDeltaFIFO(testFifoObjectKeyFunc, nil, nil) | ||||
|  | ||||
| 		for _, action := range test.actions { | ||||
| 			action(f) | ||||
| 		} | ||||
| 		if e, a := test.expectedSynced, f.HasSynced(); a != e { | ||||
| 			t.Errorf("test case %v failed, expected: %v , got %v", i, e, a) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										24
									
								
								pkg/client/cache/doc.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								pkg/client/cache/doc.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,24 +0,0 @@ | ||||
| /* | ||||
| Copyright 2014 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 cache is a client-side caching mechanism. It is useful for | ||||
| // reducing the number of server calls you'd otherwise need to make. | ||||
| // Reflector watches a server and updates a Store. Two stores are provided; | ||||
| // one that simply caches objects (for example, to allow a scheduler to | ||||
| // list currently available nodes), and one that additionally acts as | ||||
| // a FIFO queue (for example, to allow a scheduler to process incoming | ||||
| // pods). | ||||
| package cache // import "k8s.io/kubernetes/pkg/client/cache" | ||||
							
								
								
									
										208
									
								
								pkg/client/cache/expiration_cache.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										208
									
								
								pkg/client/cache/expiration_cache.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,208 +0,0 @@ | ||||
| /* | ||||
| Copyright 2014 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/golang/glog" | ||||
| 	"k8s.io/client-go/util/clock" | ||||
| ) | ||||
|  | ||||
| // ExpirationCache implements the store interface | ||||
| //	1. All entries are automatically time stamped on insert | ||||
| //		a. The key is computed based off the original item/keyFunc | ||||
| //		b. The value inserted under that key is the timestamped item | ||||
| //	2. Expiration happens lazily on read based on the expiration policy | ||||
| //      a. No item can be inserted into the store while we're expiring | ||||
| //		   *any* item in the cache. | ||||
| //	3. Time-stamps are stripped off unexpired entries before return | ||||
| // Note that the ExpirationCache is inherently slower than a normal | ||||
| // threadSafeStore because it takes a write lock every time it checks if | ||||
| // an item has expired. | ||||
| type ExpirationCache struct { | ||||
| 	cacheStorage     ThreadSafeStore | ||||
| 	keyFunc          KeyFunc | ||||
| 	clock            clock.Clock | ||||
| 	expirationPolicy ExpirationPolicy | ||||
| 	// expirationLock is a write lock used to guarantee that we don't clobber | ||||
| 	// newly inserted objects because of a stale expiration timestamp comparison | ||||
| 	expirationLock sync.Mutex | ||||
| } | ||||
|  | ||||
| // ExpirationPolicy dictates when an object expires. Currently only abstracted out | ||||
| // so unittests don't rely on the system clock. | ||||
| type ExpirationPolicy interface { | ||||
| 	IsExpired(obj *timestampedEntry) bool | ||||
| } | ||||
|  | ||||
| // TTLPolicy implements a ttl based ExpirationPolicy. | ||||
| type TTLPolicy struct { | ||||
| 	//	 >0: Expire entries with an age > ttl | ||||
| 	//	<=0: Don't expire any entry | ||||
| 	Ttl time.Duration | ||||
|  | ||||
| 	// Clock used to calculate ttl expiration | ||||
| 	Clock clock.Clock | ||||
| } | ||||
|  | ||||
| // IsExpired returns true if the given object is older than the ttl, or it can't | ||||
| // determine its age. | ||||
| func (p *TTLPolicy) IsExpired(obj *timestampedEntry) bool { | ||||
| 	return p.Ttl > 0 && p.Clock.Since(obj.timestamp) > p.Ttl | ||||
| } | ||||
|  | ||||
| // timestampedEntry is the only type allowed in a ExpirationCache. | ||||
| type timestampedEntry struct { | ||||
| 	obj       interface{} | ||||
| 	timestamp time.Time | ||||
| } | ||||
|  | ||||
| // getTimestampedEntry returns the timestampedEntry stored under the given key. | ||||
| func (c *ExpirationCache) getTimestampedEntry(key string) (*timestampedEntry, bool) { | ||||
| 	item, _ := c.cacheStorage.Get(key) | ||||
| 	if tsEntry, ok := item.(*timestampedEntry); ok { | ||||
| 		return tsEntry, true | ||||
| 	} | ||||
| 	return nil, false | ||||
| } | ||||
|  | ||||
| // getOrExpire retrieves the object from the timestampedEntry if and only if it hasn't | ||||
| // already expired. It holds a write lock across deletion. | ||||
| func (c *ExpirationCache) getOrExpire(key string) (interface{}, bool) { | ||||
| 	// Prevent all inserts from the time we deem an item as "expired" to when we | ||||
| 	// delete it, so an un-expired item doesn't sneak in under the same key, just | ||||
| 	// before the Delete. | ||||
| 	c.expirationLock.Lock() | ||||
| 	defer c.expirationLock.Unlock() | ||||
| 	timestampedItem, exists := c.getTimestampedEntry(key) | ||||
| 	if !exists { | ||||
| 		return nil, false | ||||
| 	} | ||||
| 	if c.expirationPolicy.IsExpired(timestampedItem) { | ||||
| 		glog.V(4).Infof("Entry %v: %+v has expired", key, timestampedItem.obj) | ||||
| 		c.cacheStorage.Delete(key) | ||||
| 		return nil, false | ||||
| 	} | ||||
| 	return timestampedItem.obj, true | ||||
| } | ||||
|  | ||||
| // GetByKey returns the item stored under the key, or sets exists=false. | ||||
| func (c *ExpirationCache) GetByKey(key string) (interface{}, bool, error) { | ||||
| 	obj, exists := c.getOrExpire(key) | ||||
| 	return obj, exists, nil | ||||
| } | ||||
|  | ||||
| // Get returns unexpired items. It purges the cache of expired items in the | ||||
| // process. | ||||
| func (c *ExpirationCache) Get(obj interface{}) (interface{}, bool, error) { | ||||
| 	key, err := c.keyFunc(obj) | ||||
| 	if err != nil { | ||||
| 		return nil, false, KeyError{obj, err} | ||||
| 	} | ||||
| 	obj, exists := c.getOrExpire(key) | ||||
| 	return obj, exists, nil | ||||
| } | ||||
|  | ||||
| // List retrieves a list of unexpired items. It purges the cache of expired | ||||
| // items in the process. | ||||
| func (c *ExpirationCache) List() []interface{} { | ||||
| 	items := c.cacheStorage.List() | ||||
|  | ||||
| 	list := make([]interface{}, 0, len(items)) | ||||
| 	for _, item := range items { | ||||
| 		obj := item.(*timestampedEntry).obj | ||||
| 		if key, err := c.keyFunc(obj); err != nil { | ||||
| 			list = append(list, obj) | ||||
| 		} else if obj, exists := c.getOrExpire(key); exists { | ||||
| 			list = append(list, obj) | ||||
| 		} | ||||
| 	} | ||||
| 	return list | ||||
| } | ||||
|  | ||||
| // ListKeys returns a list of all keys in the expiration cache. | ||||
| func (c *ExpirationCache) ListKeys() []string { | ||||
| 	return c.cacheStorage.ListKeys() | ||||
| } | ||||
|  | ||||
| // Add timestamps an item and inserts it into the cache, overwriting entries | ||||
| // that might exist under the same key. | ||||
| func (c *ExpirationCache) Add(obj interface{}) error { | ||||
| 	c.expirationLock.Lock() | ||||
| 	defer c.expirationLock.Unlock() | ||||
|  | ||||
| 	key, err := c.keyFunc(obj) | ||||
| 	if err != nil { | ||||
| 		return KeyError{obj, err} | ||||
| 	} | ||||
| 	c.cacheStorage.Add(key, ×tampedEntry{obj, c.clock.Now()}) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Update has not been implemented yet for lack of a use case, so this method | ||||
| // simply calls `Add`. This effectively refreshes the timestamp. | ||||
| func (c *ExpirationCache) Update(obj interface{}) error { | ||||
| 	return c.Add(obj) | ||||
| } | ||||
|  | ||||
| // Delete removes an item from the cache. | ||||
| func (c *ExpirationCache) Delete(obj interface{}) error { | ||||
| 	c.expirationLock.Lock() | ||||
| 	defer c.expirationLock.Unlock() | ||||
| 	key, err := c.keyFunc(obj) | ||||
| 	if err != nil { | ||||
| 		return KeyError{obj, err} | ||||
| 	} | ||||
| 	c.cacheStorage.Delete(key) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Replace will convert all items in the given list to TimestampedEntries | ||||
| // before attempting the replace operation. The replace operation will | ||||
| // delete the contents of the ExpirationCache `c`. | ||||
| func (c *ExpirationCache) Replace(list []interface{}, resourceVersion string) error { | ||||
| 	c.expirationLock.Lock() | ||||
| 	defer c.expirationLock.Unlock() | ||||
| 	items := map[string]interface{}{} | ||||
| 	ts := c.clock.Now() | ||||
| 	for _, item := range list { | ||||
| 		key, err := c.keyFunc(item) | ||||
| 		if err != nil { | ||||
| 			return KeyError{item, err} | ||||
| 		} | ||||
| 		items[key] = ×tampedEntry{item, ts} | ||||
| 	} | ||||
| 	c.cacheStorage.Replace(items, resourceVersion) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Resync will touch all objects to put them into the processing queue | ||||
| func (c *ExpirationCache) Resync() error { | ||||
| 	return c.cacheStorage.Resync() | ||||
| } | ||||
|  | ||||
| // NewTTLStore creates and returns a ExpirationCache with a TTLPolicy | ||||
| func NewTTLStore(keyFunc KeyFunc, ttl time.Duration) Store { | ||||
| 	return &ExpirationCache{ | ||||
| 		cacheStorage:     NewThreadSafeStore(Indexers{}, Indices{}), | ||||
| 		keyFunc:          keyFunc, | ||||
| 		clock:            clock.RealClock{}, | ||||
| 		expirationPolicy: &TTLPolicy{ttl, clock.RealClock{}}, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										54
									
								
								pkg/client/cache/expiration_cache_fakes.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										54
									
								
								pkg/client/cache/expiration_cache_fakes.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,54 +0,0 @@ | ||||
| /* | ||||
| Copyright 2014 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| 	"k8s.io/client-go/util/clock" | ||||
| ) | ||||
|  | ||||
| type fakeThreadSafeMap struct { | ||||
| 	ThreadSafeStore | ||||
| 	deletedKeys chan<- string | ||||
| } | ||||
|  | ||||
| func (c *fakeThreadSafeMap) Delete(key string) { | ||||
| 	if c.deletedKeys != nil { | ||||
| 		c.ThreadSafeStore.Delete(key) | ||||
| 		c.deletedKeys <- key | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type FakeExpirationPolicy struct { | ||||
| 	NeverExpire     sets.String | ||||
| 	RetrieveKeyFunc KeyFunc | ||||
| } | ||||
|  | ||||
| func (p *FakeExpirationPolicy) IsExpired(obj *timestampedEntry) bool { | ||||
| 	key, _ := p.RetrieveKeyFunc(obj) | ||||
| 	return !p.NeverExpire.Has(key) | ||||
| } | ||||
|  | ||||
| func NewFakeExpirationStore(keyFunc KeyFunc, deletedKeys chan<- string, expirationPolicy ExpirationPolicy, cacheClock clock.Clock) Store { | ||||
| 	cacheStorage := NewThreadSafeStore(Indexers{}, Indices{}) | ||||
| 	return &ExpirationCache{ | ||||
| 		cacheStorage:     &fakeThreadSafeMap{cacheStorage, deletedKeys}, | ||||
| 		keyFunc:          keyFunc, | ||||
| 		clock:            cacheClock, | ||||
| 		expirationPolicy: expirationPolicy, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										189
									
								
								pkg/client/cache/expiration_cache_test.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										189
									
								
								pkg/client/cache/expiration_cache_test.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,189 +0,0 @@ | ||||
| /* | ||||
| Copyright 2014 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| 	"k8s.io/apimachinery/pkg/util/wait" | ||||
| 	"k8s.io/client-go/util/clock" | ||||
| ) | ||||
|  | ||||
| func TestTTLExpirationBasic(t *testing.T) { | ||||
| 	testObj := testStoreObject{id: "foo", val: "bar"} | ||||
| 	deleteChan := make(chan string, 1) | ||||
| 	ttlStore := NewFakeExpirationStore( | ||||
| 		testStoreKeyFunc, deleteChan, | ||||
| 		&FakeExpirationPolicy{ | ||||
| 			NeverExpire: sets.NewString(), | ||||
| 			RetrieveKeyFunc: func(obj interface{}) (string, error) { | ||||
| 				return obj.(*timestampedEntry).obj.(testStoreObject).id, nil | ||||
| 			}, | ||||
| 		}, | ||||
| 		clock.RealClock{}, | ||||
| 	) | ||||
| 	err := ttlStore.Add(testObj) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Unable to add obj %#v", testObj) | ||||
| 	} | ||||
| 	item, exists, err := ttlStore.Get(testObj) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Failed to get from store, %v", err) | ||||
| 	} | ||||
| 	if exists || item != nil { | ||||
| 		t.Errorf("Got unexpected item %#v", item) | ||||
| 	} | ||||
| 	key, _ := testStoreKeyFunc(testObj) | ||||
| 	select { | ||||
| 	case delKey := <-deleteChan: | ||||
| 		if delKey != key { | ||||
| 			t.Errorf("Unexpected delete for key %s", key) | ||||
| 		} | ||||
| 	case <-time.After(wait.ForeverTestTimeout): | ||||
| 		t.Errorf("Unexpected timeout waiting on delete") | ||||
| 	} | ||||
| 	close(deleteChan) | ||||
| } | ||||
|  | ||||
| func TestReAddExpiredItem(t *testing.T) { | ||||
| 	deleteChan := make(chan string, 1) | ||||
| 	exp := &FakeExpirationPolicy{ | ||||
| 		NeverExpire: sets.NewString(), | ||||
| 		RetrieveKeyFunc: func(obj interface{}) (string, error) { | ||||
| 			return obj.(*timestampedEntry).obj.(testStoreObject).id, nil | ||||
| 		}, | ||||
| 	} | ||||
| 	ttlStore := NewFakeExpirationStore( | ||||
| 		testStoreKeyFunc, deleteChan, exp, clock.RealClock{}) | ||||
| 	testKey := "foo" | ||||
| 	testObj := testStoreObject{id: testKey, val: "bar"} | ||||
| 	err := ttlStore.Add(testObj) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Unable to add obj %#v", testObj) | ||||
| 	} | ||||
|  | ||||
| 	// This get will expire the item. | ||||
| 	item, exists, err := ttlStore.Get(testObj) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Failed to get from store, %v", err) | ||||
| 	} | ||||
| 	if exists || item != nil { | ||||
| 		t.Errorf("Got unexpected item %#v", item) | ||||
| 	} | ||||
|  | ||||
| 	key, _ := testStoreKeyFunc(testObj) | ||||
| 	differentValue := "different_bar" | ||||
| 	err = ttlStore.Add( | ||||
| 		testStoreObject{id: testKey, val: differentValue}) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Failed to add second value") | ||||
| 	} | ||||
|  | ||||
| 	select { | ||||
| 	case delKey := <-deleteChan: | ||||
| 		if delKey != key { | ||||
| 			t.Errorf("Unexpected delete for key %s", key) | ||||
| 		} | ||||
| 	case <-time.After(wait.ForeverTestTimeout): | ||||
| 		t.Errorf("Unexpected timeout waiting on delete") | ||||
| 	} | ||||
| 	exp.NeverExpire = sets.NewString(testKey) | ||||
| 	item, exists, err = ttlStore.GetByKey(testKey) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Failed to get from store, %v", err) | ||||
| 	} | ||||
| 	if !exists || item == nil || item.(testStoreObject).val != differentValue { | ||||
| 		t.Errorf("Got unexpected item %#v", item) | ||||
| 	} | ||||
| 	close(deleteChan) | ||||
| } | ||||
|  | ||||
| func TestTTLList(t *testing.T) { | ||||
| 	testObjs := []testStoreObject{ | ||||
| 		{id: "foo", val: "bar"}, | ||||
| 		{id: "foo1", val: "bar1"}, | ||||
| 		{id: "foo2", val: "bar2"}, | ||||
| 	} | ||||
| 	expireKeys := sets.NewString(testObjs[0].id, testObjs[2].id) | ||||
| 	deleteChan := make(chan string, len(testObjs)) | ||||
| 	defer close(deleteChan) | ||||
|  | ||||
| 	ttlStore := NewFakeExpirationStore( | ||||
| 		testStoreKeyFunc, deleteChan, | ||||
| 		&FakeExpirationPolicy{ | ||||
| 			NeverExpire: sets.NewString(testObjs[1].id), | ||||
| 			RetrieveKeyFunc: func(obj interface{}) (string, error) { | ||||
| 				return obj.(*timestampedEntry).obj.(testStoreObject).id, nil | ||||
| 			}, | ||||
| 		}, | ||||
| 		clock.RealClock{}, | ||||
| 	) | ||||
| 	for _, obj := range testObjs { | ||||
| 		err := ttlStore.Add(obj) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("Unable to add obj %#v", obj) | ||||
| 		} | ||||
| 	} | ||||
| 	listObjs := ttlStore.List() | ||||
| 	if len(listObjs) != 1 || !reflect.DeepEqual(listObjs[0], testObjs[1]) { | ||||
| 		t.Errorf("List returned unexpected results %#v", listObjs) | ||||
| 	} | ||||
|  | ||||
| 	// Make sure all our deletes come through in an acceptable rate (1/100ms) | ||||
| 	for expireKeys.Len() != 0 { | ||||
| 		select { | ||||
| 		case delKey := <-deleteChan: | ||||
| 			if !expireKeys.Has(delKey) { | ||||
| 				t.Errorf("Unexpected delete for key %s", delKey) | ||||
| 			} | ||||
| 			expireKeys.Delete(delKey) | ||||
| 		case <-time.After(wait.ForeverTestTimeout): | ||||
| 			t.Errorf("Unexpected timeout waiting on delete") | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTTLPolicy(t *testing.T) { | ||||
| 	fakeTime := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) | ||||
| 	ttl := 30 * time.Second | ||||
| 	exactlyOnTTL := fakeTime.Add(-ttl) | ||||
| 	expiredTime := fakeTime.Add(-(ttl + 1)) | ||||
|  | ||||
| 	policy := TTLPolicy{ttl, clock.NewFakeClock(fakeTime)} | ||||
| 	fakeTimestampedEntry := ×tampedEntry{obj: struct{}{}, timestamp: exactlyOnTTL} | ||||
| 	if policy.IsExpired(fakeTimestampedEntry) { | ||||
| 		t.Errorf("TTL cache should not expire entries exactly on ttl") | ||||
| 	} | ||||
| 	fakeTimestampedEntry.timestamp = fakeTime | ||||
| 	if policy.IsExpired(fakeTimestampedEntry) { | ||||
| 		t.Errorf("TTL Cache should not expire entries before ttl") | ||||
| 	} | ||||
| 	fakeTimestampedEntry.timestamp = expiredTime | ||||
| 	if !policy.IsExpired(fakeTimestampedEntry) { | ||||
| 		t.Errorf("TTL Cache should expire entries older than ttl") | ||||
| 	} | ||||
| 	for _, ttl = range []time.Duration{0, -1} { | ||||
| 		policy.Ttl = ttl | ||||
| 		if policy.IsExpired(fakeTimestampedEntry) { | ||||
| 			t.Errorf("TTL policy should only expire entries when initialized with a ttl > 0") | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										102
									
								
								pkg/client/cache/fake_custom_store.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										102
									
								
								pkg/client/cache/fake_custom_store.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,102 +0,0 @@ | ||||
| /* | ||||
| 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 cache | ||||
|  | ||||
| // FakeStore lets you define custom functions for store operations | ||||
| type FakeCustomStore struct { | ||||
| 	AddFunc      func(obj interface{}) error | ||||
| 	UpdateFunc   func(obj interface{}) error | ||||
| 	DeleteFunc   func(obj interface{}) error | ||||
| 	ListFunc     func() []interface{} | ||||
| 	ListKeysFunc func() []string | ||||
| 	GetFunc      func(obj interface{}) (item interface{}, exists bool, err error) | ||||
| 	GetByKeyFunc func(key string) (item interface{}, exists bool, err error) | ||||
| 	ReplaceFunc  func(list []interface{}, resourceVerion string) error | ||||
| 	ResyncFunc   func() error | ||||
| } | ||||
|  | ||||
| // Add calls the custom Add function if defined | ||||
| func (f *FakeCustomStore) Add(obj interface{}) error { | ||||
| 	if f.AddFunc != nil { | ||||
| 		return f.AddFunc(obj) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Update calls the custom Update function if defined | ||||
| func (f *FakeCustomStore) Update(obj interface{}) error { | ||||
| 	if f.UpdateFunc != nil { | ||||
| 		return f.Update(obj) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Delete calls the custom Delete function if defined | ||||
| func (f *FakeCustomStore) Delete(obj interface{}) error { | ||||
| 	if f.DeleteFunc != nil { | ||||
| 		return f.DeleteFunc(obj) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // List calls the custom List function if defined | ||||
| func (f *FakeCustomStore) List() []interface{} { | ||||
| 	if f.ListFunc != nil { | ||||
| 		return f.ListFunc() | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // ListKeys calls the custom ListKeys function if defined | ||||
| func (f *FakeCustomStore) ListKeys() []string { | ||||
| 	if f.ListKeysFunc != nil { | ||||
| 		return f.ListKeysFunc() | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Get calls the custom Get function if defined | ||||
| func (f *FakeCustomStore) Get(obj interface{}) (item interface{}, exists bool, err error) { | ||||
| 	if f.GetFunc != nil { | ||||
| 		return f.GetFunc(obj) | ||||
| 	} | ||||
| 	return nil, false, nil | ||||
| } | ||||
|  | ||||
| // GetByKey calls the custom GetByKey function if defined | ||||
| func (f *FakeCustomStore) GetByKey(key string) (item interface{}, exists bool, err error) { | ||||
| 	if f.GetByKeyFunc != nil { | ||||
| 		return f.GetByKeyFunc(key) | ||||
| 	} | ||||
| 	return nil, false, nil | ||||
| } | ||||
|  | ||||
| // Replace calls the custom Replace function if defined | ||||
| func (f *FakeCustomStore) Replace(list []interface{}, resourceVersion string) error { | ||||
| 	if f.ReplaceFunc != nil { | ||||
| 		return f.ReplaceFunc(list, resourceVersion) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Resync calls the custom Resync function if defined | ||||
| func (f *FakeCustomStore) Resync() error { | ||||
| 	if f.ResyncFunc != nil { | ||||
| 		return f.ResyncFunc() | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										321
									
								
								pkg/client/cache/fifo.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										321
									
								
								pkg/client/cache/fifo.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,321 +0,0 @@ | ||||
| /* | ||||
| Copyright 2014 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"sync" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| ) | ||||
|  | ||||
| // PopProcessFunc is passed to Pop() method of Queue interface. | ||||
| // It is supposed to process the element popped from the queue. | ||||
| type PopProcessFunc func(interface{}) error | ||||
|  | ||||
| // ErrRequeue may be returned by a PopProcessFunc to safely requeue | ||||
| // the current item. The value of Err will be returned from Pop. | ||||
| type ErrRequeue struct { | ||||
| 	// Err is returned by the Pop function | ||||
| 	Err error | ||||
| } | ||||
|  | ||||
| func (e ErrRequeue) Error() string { | ||||
| 	if e.Err == nil { | ||||
| 		return "the popped item should be requeued without returning an error" | ||||
| 	} | ||||
| 	return e.Err.Error() | ||||
| } | ||||
|  | ||||
| // Queue is exactly like a Store, but has a Pop() method too. | ||||
| type Queue interface { | ||||
| 	Store | ||||
|  | ||||
| 	// Pop blocks until it has something to process. | ||||
| 	// It returns the object that was process and the result of processing. | ||||
| 	// The PopProcessFunc may return an ErrRequeue{...} to indicate the item | ||||
| 	// should be requeued before releasing the lock on the queue. | ||||
| 	Pop(PopProcessFunc) (interface{}, error) | ||||
|  | ||||
| 	// AddIfNotPresent adds a value previously | ||||
| 	// returned by Pop back into the queue as long | ||||
| 	// as nothing else (presumably more recent) | ||||
| 	// has since been added. | ||||
| 	AddIfNotPresent(interface{}) error | ||||
|  | ||||
| 	// Return true if the first batch of items has been popped | ||||
| 	HasSynced() bool | ||||
| } | ||||
|  | ||||
| // Helper function for popping from Queue. | ||||
| // WARNING: Do NOT use this function in non-test code to avoid races | ||||
| // unless you really really really really know what you are doing. | ||||
| func Pop(queue Queue) interface{} { | ||||
| 	var result interface{} | ||||
| 	queue.Pop(func(obj interface{}) error { | ||||
| 		result = obj | ||||
| 		return nil | ||||
| 	}) | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| // FIFO receives adds and updates from a Reflector, and puts them in a queue for | ||||
| // FIFO order processing. If multiple adds/updates of a single item happen while | ||||
| // an item is in the queue before it has been processed, it will only be | ||||
| // processed once, and when it is processed, the most recent version will be | ||||
| // processed. This can't be done with a channel. | ||||
| // | ||||
| // FIFO solves this use case: | ||||
| //  * You want to process every object (exactly) once. | ||||
| //  * You want to process the most recent version of the object when you process it. | ||||
| //  * You do not want to process deleted objects, they should be removed from the queue. | ||||
| //  * You do not want to periodically reprocess objects. | ||||
| // Compare with DeltaFIFO for other use cases. | ||||
| type FIFO struct { | ||||
| 	lock sync.RWMutex | ||||
| 	cond sync.Cond | ||||
| 	// We depend on the property that items in the set are in the queue and vice versa. | ||||
| 	items map[string]interface{} | ||||
| 	queue []string | ||||
|  | ||||
| 	// populated is true if the first batch of items inserted by Replace() has been populated | ||||
| 	// or Delete/Add/Update was called first. | ||||
| 	populated bool | ||||
| 	// initialPopulationCount is the number of items inserted by the first call of Replace() | ||||
| 	initialPopulationCount int | ||||
|  | ||||
| 	// keyFunc is used to make the key used for queued item insertion and retrieval, and | ||||
| 	// should be deterministic. | ||||
| 	keyFunc KeyFunc | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	_ = Queue(&FIFO{}) // FIFO is a Queue | ||||
| ) | ||||
|  | ||||
| // Return true if an Add/Update/Delete/AddIfNotPresent are called first, | ||||
| // or an Update called first but the first batch of items inserted by Replace() has been popped | ||||
| func (f *FIFO) HasSynced() bool { | ||||
| 	f.lock.Lock() | ||||
| 	defer f.lock.Unlock() | ||||
| 	return f.populated && f.initialPopulationCount == 0 | ||||
| } | ||||
|  | ||||
| // Add inserts an item, and puts it in the queue. The item is only enqueued | ||||
| // if it doesn't already exist in the set. | ||||
| func (f *FIFO) Add(obj interface{}) error { | ||||
| 	id, err := f.keyFunc(obj) | ||||
| 	if err != nil { | ||||
| 		return KeyError{obj, err} | ||||
| 	} | ||||
| 	f.lock.Lock() | ||||
| 	defer f.lock.Unlock() | ||||
| 	f.populated = true | ||||
| 	if _, exists := f.items[id]; !exists { | ||||
| 		f.queue = append(f.queue, id) | ||||
| 	} | ||||
| 	f.items[id] = obj | ||||
| 	f.cond.Broadcast() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // AddIfNotPresent inserts an item, and puts it in the queue. If the item is already | ||||
| // present in the set, it is neither enqueued nor added to the set. | ||||
| // | ||||
| // This is useful in a single producer/consumer scenario so that the consumer can | ||||
| // safely retry items without contending with the producer and potentially enqueueing | ||||
| // stale items. | ||||
| func (f *FIFO) AddIfNotPresent(obj interface{}) error { | ||||
| 	id, err := f.keyFunc(obj) | ||||
| 	if err != nil { | ||||
| 		return KeyError{obj, err} | ||||
| 	} | ||||
| 	f.lock.Lock() | ||||
| 	defer f.lock.Unlock() | ||||
| 	f.addIfNotPresent(id, obj) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // addIfNotPresent assumes the fifo lock is already held and adds the the provided | ||||
| // item to the queue under id if it does not already exist. | ||||
| func (f *FIFO) addIfNotPresent(id string, obj interface{}) { | ||||
| 	f.populated = true | ||||
| 	if _, exists := f.items[id]; exists { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	f.queue = append(f.queue, id) | ||||
| 	f.items[id] = obj | ||||
| 	f.cond.Broadcast() | ||||
| } | ||||
|  | ||||
| // Update is the same as Add in this implementation. | ||||
| func (f *FIFO) Update(obj interface{}) error { | ||||
| 	return f.Add(obj) | ||||
| } | ||||
|  | ||||
| // Delete removes an item. It doesn't add it to the queue, because | ||||
| // this implementation assumes the consumer only cares about the objects, | ||||
| // not the order in which they were created/added. | ||||
| func (f *FIFO) Delete(obj interface{}) error { | ||||
| 	id, err := f.keyFunc(obj) | ||||
| 	if err != nil { | ||||
| 		return KeyError{obj, err} | ||||
| 	} | ||||
| 	f.lock.Lock() | ||||
| 	defer f.lock.Unlock() | ||||
| 	f.populated = true | ||||
| 	delete(f.items, id) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // List returns a list of all the items. | ||||
| func (f *FIFO) List() []interface{} { | ||||
| 	f.lock.RLock() | ||||
| 	defer f.lock.RUnlock() | ||||
| 	list := make([]interface{}, 0, len(f.items)) | ||||
| 	for _, item := range f.items { | ||||
| 		list = append(list, item) | ||||
| 	} | ||||
| 	return list | ||||
| } | ||||
|  | ||||
| // ListKeys returns a list of all the keys of the objects currently | ||||
| // in the FIFO. | ||||
| func (f *FIFO) ListKeys() []string { | ||||
| 	f.lock.RLock() | ||||
| 	defer f.lock.RUnlock() | ||||
| 	list := make([]string, 0, len(f.items)) | ||||
| 	for key := range f.items { | ||||
| 		list = append(list, key) | ||||
| 	} | ||||
| 	return list | ||||
| } | ||||
|  | ||||
| // Get returns the requested item, or sets exists=false. | ||||
| func (f *FIFO) Get(obj interface{}) (item interface{}, exists bool, err error) { | ||||
| 	key, err := f.keyFunc(obj) | ||||
| 	if err != nil { | ||||
| 		return nil, false, KeyError{obj, err} | ||||
| 	} | ||||
| 	return f.GetByKey(key) | ||||
| } | ||||
|  | ||||
| // GetByKey returns the requested item, or sets exists=false. | ||||
| func (f *FIFO) GetByKey(key string) (item interface{}, exists bool, err error) { | ||||
| 	f.lock.RLock() | ||||
| 	defer f.lock.RUnlock() | ||||
| 	item, exists = f.items[key] | ||||
| 	return item, exists, nil | ||||
| } | ||||
|  | ||||
| // Pop waits until an item is ready and processes it. If multiple items are | ||||
| // ready, they are returned in the order in which they were added/updated. | ||||
| // The item is removed from the queue (and the store) before it is processed, | ||||
| // so if you don't successfully process it, it should be added back with | ||||
| // AddIfNotPresent(). process function is called under lock, so it is safe | ||||
| // update data structures in it that need to be in sync with the queue. | ||||
| func (f *FIFO) Pop(process PopProcessFunc) (interface{}, error) { | ||||
| 	f.lock.Lock() | ||||
| 	defer f.lock.Unlock() | ||||
| 	for { | ||||
| 		for len(f.queue) == 0 { | ||||
| 			f.cond.Wait() | ||||
| 		} | ||||
| 		id := f.queue[0] | ||||
| 		f.queue = f.queue[1:] | ||||
| 		if f.initialPopulationCount > 0 { | ||||
| 			f.initialPopulationCount-- | ||||
| 		} | ||||
| 		item, ok := f.items[id] | ||||
| 		if !ok { | ||||
| 			// Item may have been deleted subsequently. | ||||
| 			continue | ||||
| 		} | ||||
| 		delete(f.items, id) | ||||
| 		err := process(item) | ||||
| 		if e, ok := err.(ErrRequeue); ok { | ||||
| 			f.addIfNotPresent(id, item) | ||||
| 			err = e.Err | ||||
| 		} | ||||
| 		return item, err | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Replace will delete the contents of 'f', using instead the given map. | ||||
| // 'f' takes ownership of the map, you should not reference the map again | ||||
| // after calling this function. f's queue is reset, too; upon return, it | ||||
| // will contain the items in the map, in no particular order. | ||||
| func (f *FIFO) Replace(list []interface{}, resourceVersion string) error { | ||||
| 	items := map[string]interface{}{} | ||||
| 	for _, item := range list { | ||||
| 		key, err := f.keyFunc(item) | ||||
| 		if err != nil { | ||||
| 			return KeyError{item, err} | ||||
| 		} | ||||
| 		items[key] = item | ||||
| 	} | ||||
|  | ||||
| 	f.lock.Lock() | ||||
| 	defer f.lock.Unlock() | ||||
|  | ||||
| 	if !f.populated { | ||||
| 		f.populated = true | ||||
| 		f.initialPopulationCount = len(items) | ||||
| 	} | ||||
|  | ||||
| 	f.items = items | ||||
| 	f.queue = f.queue[:0] | ||||
| 	for id := range items { | ||||
| 		f.queue = append(f.queue, id) | ||||
| 	} | ||||
| 	if len(f.queue) > 0 { | ||||
| 		f.cond.Broadcast() | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Resync will touch all objects to put them into the processing queue | ||||
| func (f *FIFO) Resync() error { | ||||
| 	f.lock.Lock() | ||||
| 	defer f.lock.Unlock() | ||||
|  | ||||
| 	inQueue := sets.NewString() | ||||
| 	for _, id := range f.queue { | ||||
| 		inQueue.Insert(id) | ||||
| 	} | ||||
| 	for id := range f.items { | ||||
| 		if !inQueue.Has(id) { | ||||
| 			f.queue = append(f.queue, id) | ||||
| 		} | ||||
| 	} | ||||
| 	if len(f.queue) > 0 { | ||||
| 		f.cond.Broadcast() | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // NewFIFO returns a Store which can be used to queue up items to | ||||
| // process. | ||||
| func NewFIFO(keyFunc KeyFunc) *FIFO { | ||||
| 	f := &FIFO{ | ||||
| 		items:   map[string]interface{}{}, | ||||
| 		queue:   []string{}, | ||||
| 		keyFunc: keyFunc, | ||||
| 	} | ||||
| 	f.cond.L = &f.lock | ||||
| 	return f | ||||
| } | ||||
							
								
								
									
										280
									
								
								pkg/client/cache/fifo_test.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										280
									
								
								pkg/client/cache/fifo_test.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,280 +0,0 @@ | ||||
| /* | ||||
| Copyright 2014 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| func testFifoObjectKeyFunc(obj interface{}) (string, error) { | ||||
| 	return obj.(testFifoObject).name, nil | ||||
| } | ||||
|  | ||||
| type testFifoObject struct { | ||||
| 	name string | ||||
| 	val  interface{} | ||||
| } | ||||
|  | ||||
| func mkFifoObj(name string, val interface{}) testFifoObject { | ||||
| 	return testFifoObject{name: name, val: val} | ||||
| } | ||||
|  | ||||
| func TestFIFO_basic(t *testing.T) { | ||||
| 	f := NewFIFO(testFifoObjectKeyFunc) | ||||
| 	const amount = 500 | ||||
| 	go func() { | ||||
| 		for i := 0; i < amount; i++ { | ||||
| 			f.Add(mkFifoObj(string([]rune{'a', rune(i)}), i+1)) | ||||
| 		} | ||||
| 	}() | ||||
| 	go func() { | ||||
| 		for u := uint64(0); u < amount; u++ { | ||||
| 			f.Add(mkFifoObj(string([]rune{'b', rune(u)}), u+1)) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	lastInt := int(0) | ||||
| 	lastUint := uint64(0) | ||||
| 	for i := 0; i < amount*2; i++ { | ||||
| 		switch obj := Pop(f).(testFifoObject).val.(type) { | ||||
| 		case int: | ||||
| 			if obj <= lastInt { | ||||
| 				t.Errorf("got %v (int) out of order, last was %v", obj, lastInt) | ||||
| 			} | ||||
| 			lastInt = obj | ||||
| 		case uint64: | ||||
| 			if obj <= lastUint { | ||||
| 				t.Errorf("got %v (uint) out of order, last was %v", obj, lastUint) | ||||
| 			} else { | ||||
| 				lastUint = obj | ||||
| 			} | ||||
| 		default: | ||||
| 			t.Fatalf("unexpected type %#v", obj) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestFIFO_requeueOnPop(t *testing.T) { | ||||
| 	f := NewFIFO(testFifoObjectKeyFunc) | ||||
|  | ||||
| 	f.Add(mkFifoObj("foo", 10)) | ||||
| 	_, err := f.Pop(func(obj interface{}) error { | ||||
| 		if obj.(testFifoObject).name != "foo" { | ||||
| 			t.Fatalf("unexpected object: %#v", obj) | ||||
| 		} | ||||
| 		return ErrRequeue{Err: nil} | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error: %v", err) | ||||
| 	} | ||||
| 	if _, ok, err := f.GetByKey("foo"); !ok || err != nil { | ||||
| 		t.Fatalf("object should have been requeued: %t %v", ok, err) | ||||
| 	} | ||||
|  | ||||
| 	_, err = f.Pop(func(obj interface{}) error { | ||||
| 		if obj.(testFifoObject).name != "foo" { | ||||
| 			t.Fatalf("unexpected object: %#v", obj) | ||||
| 		} | ||||
| 		return ErrRequeue{Err: fmt.Errorf("test error")} | ||||
| 	}) | ||||
| 	if err == nil || err.Error() != "test error" { | ||||
| 		t.Fatalf("unexpected error: %v", err) | ||||
| 	} | ||||
| 	if _, ok, err := f.GetByKey("foo"); !ok || err != nil { | ||||
| 		t.Fatalf("object should have been requeued: %t %v", ok, err) | ||||
| 	} | ||||
|  | ||||
| 	_, err = f.Pop(func(obj interface{}) error { | ||||
| 		if obj.(testFifoObject).name != "foo" { | ||||
| 			t.Fatalf("unexpected object: %#v", obj) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("unexpected error: %v", err) | ||||
| 	} | ||||
| 	if _, ok, err := f.GetByKey("foo"); ok || err != nil { | ||||
| 		t.Fatalf("object should have been removed: %t %v", ok, err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestFIFO_addUpdate(t *testing.T) { | ||||
| 	f := NewFIFO(testFifoObjectKeyFunc) | ||||
| 	f.Add(mkFifoObj("foo", 10)) | ||||
| 	f.Update(mkFifoObj("foo", 15)) | ||||
|  | ||||
| 	if e, a := []interface{}{mkFifoObj("foo", 15)}, f.List(); !reflect.DeepEqual(e, a) { | ||||
| 		t.Errorf("Expected %+v, got %+v", e, a) | ||||
| 	} | ||||
| 	if e, a := []string{"foo"}, f.ListKeys(); !reflect.DeepEqual(e, a) { | ||||
| 		t.Errorf("Expected %+v, got %+v", e, a) | ||||
| 	} | ||||
|  | ||||
| 	got := make(chan testFifoObject, 2) | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			got <- Pop(f).(testFifoObject) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	first := <-got | ||||
| 	if e, a := 15, first.val; e != a { | ||||
| 		t.Errorf("Didn't get updated value (%v), got %v", e, a) | ||||
| 	} | ||||
| 	select { | ||||
| 	case unexpected := <-got: | ||||
| 		t.Errorf("Got second value %v", unexpected.val) | ||||
| 	case <-time.After(50 * time.Millisecond): | ||||
| 	} | ||||
| 	_, exists, _ := f.Get(mkFifoObj("foo", "")) | ||||
| 	if exists { | ||||
| 		t.Errorf("item did not get removed") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestFIFO_addReplace(t *testing.T) { | ||||
| 	f := NewFIFO(testFifoObjectKeyFunc) | ||||
| 	f.Add(mkFifoObj("foo", 10)) | ||||
| 	f.Replace([]interface{}{mkFifoObj("foo", 15)}, "15") | ||||
| 	got := make(chan testFifoObject, 2) | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			got <- Pop(f).(testFifoObject) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	first := <-got | ||||
| 	if e, a := 15, first.val; e != a { | ||||
| 		t.Errorf("Didn't get updated value (%v), got %v", e, a) | ||||
| 	} | ||||
| 	select { | ||||
| 	case unexpected := <-got: | ||||
| 		t.Errorf("Got second value %v", unexpected.val) | ||||
| 	case <-time.After(50 * time.Millisecond): | ||||
| 	} | ||||
| 	_, exists, _ := f.Get(mkFifoObj("foo", "")) | ||||
| 	if exists { | ||||
| 		t.Errorf("item did not get removed") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestFIFO_detectLineJumpers(t *testing.T) { | ||||
| 	f := NewFIFO(testFifoObjectKeyFunc) | ||||
|  | ||||
| 	f.Add(mkFifoObj("foo", 10)) | ||||
| 	f.Add(mkFifoObj("bar", 1)) | ||||
| 	f.Add(mkFifoObj("foo", 11)) | ||||
| 	f.Add(mkFifoObj("foo", 13)) | ||||
| 	f.Add(mkFifoObj("zab", 30)) | ||||
|  | ||||
| 	if e, a := 13, Pop(f).(testFifoObject).val; a != e { | ||||
| 		t.Fatalf("expected %d, got %d", e, a) | ||||
| 	} | ||||
|  | ||||
| 	f.Add(mkFifoObj("foo", 14)) // ensure foo doesn't jump back in line | ||||
|  | ||||
| 	if e, a := 1, Pop(f).(testFifoObject).val; a != e { | ||||
| 		t.Fatalf("expected %d, got %d", e, a) | ||||
| 	} | ||||
|  | ||||
| 	if e, a := 30, Pop(f).(testFifoObject).val; a != e { | ||||
| 		t.Fatalf("expected %d, got %d", e, a) | ||||
| 	} | ||||
|  | ||||
| 	if e, a := 14, Pop(f).(testFifoObject).val; a != e { | ||||
| 		t.Fatalf("expected %d, got %d", e, a) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestFIFO_addIfNotPresent(t *testing.T) { | ||||
| 	f := NewFIFO(testFifoObjectKeyFunc) | ||||
|  | ||||
| 	f.Add(mkFifoObj("a", 1)) | ||||
| 	f.Add(mkFifoObj("b", 2)) | ||||
| 	f.AddIfNotPresent(mkFifoObj("b", 3)) | ||||
| 	f.AddIfNotPresent(mkFifoObj("c", 4)) | ||||
|  | ||||
| 	if e, a := 3, len(f.items); a != e { | ||||
| 		t.Fatalf("expected queue length %d, got %d", e, a) | ||||
| 	} | ||||
|  | ||||
| 	expectedValues := []int{1, 2, 4} | ||||
| 	for _, expected := range expectedValues { | ||||
| 		if actual := Pop(f).(testFifoObject).val; actual != expected { | ||||
| 			t.Fatalf("expected value %d, got %d", expected, actual) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestFIFO_HasSynced(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		actions        []func(f *FIFO) | ||||
| 		expectedSynced bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			actions:        []func(f *FIFO){}, | ||||
| 			expectedSynced: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			actions: []func(f *FIFO){ | ||||
| 				func(f *FIFO) { f.Add(mkFifoObj("a", 1)) }, | ||||
| 			}, | ||||
| 			expectedSynced: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			actions: []func(f *FIFO){ | ||||
| 				func(f *FIFO) { f.Replace([]interface{}{}, "0") }, | ||||
| 			}, | ||||
| 			expectedSynced: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			actions: []func(f *FIFO){ | ||||
| 				func(f *FIFO) { f.Replace([]interface{}{mkFifoObj("a", 1), mkFifoObj("b", 2)}, "0") }, | ||||
| 			}, | ||||
| 			expectedSynced: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			actions: []func(f *FIFO){ | ||||
| 				func(f *FIFO) { f.Replace([]interface{}{mkFifoObj("a", 1), mkFifoObj("b", 2)}, "0") }, | ||||
| 				func(f *FIFO) { Pop(f) }, | ||||
| 			}, | ||||
| 			expectedSynced: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			actions: []func(f *FIFO){ | ||||
| 				func(f *FIFO) { f.Replace([]interface{}{mkFifoObj("a", 1), mkFifoObj("b", 2)}, "0") }, | ||||
| 				func(f *FIFO) { Pop(f) }, | ||||
| 				func(f *FIFO) { Pop(f) }, | ||||
| 			}, | ||||
| 			expectedSynced: true, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for i, test := range tests { | ||||
| 		f := NewFIFO(testFifoObjectKeyFunc) | ||||
|  | ||||
| 		for _, action := range test.actions { | ||||
| 			action(f) | ||||
| 		} | ||||
| 		if e, a := test.expectedSynced, f.HasSynced(); a != e { | ||||
| 			t.Errorf("test case %v failed, expected: %v , got %v", i, e, a) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										85
									
								
								pkg/client/cache/index.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										85
									
								
								pkg/client/cache/index.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,85 +0,0 @@ | ||||
| /* | ||||
| Copyright 2014 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/api/meta" | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| ) | ||||
|  | ||||
| // Indexer is a storage interface that lets you list objects using multiple indexing functions | ||||
| type Indexer interface { | ||||
| 	Store | ||||
| 	// Retrieve list of objects that match on the named indexing function | ||||
| 	Index(indexName string, obj interface{}) ([]interface{}, error) | ||||
| 	// ListIndexFuncValues returns the list of generated values of an Index func | ||||
| 	ListIndexFuncValues(indexName string) []string | ||||
| 	// ByIndex lists object that match on the named indexing function with the exact key | ||||
| 	ByIndex(indexName, indexKey string) ([]interface{}, error) | ||||
| 	// GetIndexer return the indexers | ||||
| 	GetIndexers() Indexers | ||||
|  | ||||
| 	// AddIndexers adds more indexers to this store.  If you call this after you already have data | ||||
| 	// in the store, the results are undefined. | ||||
| 	AddIndexers(newIndexers Indexers) error | ||||
| } | ||||
|  | ||||
| // IndexFunc knows how to provide an indexed value for an object. | ||||
| type IndexFunc func(obj interface{}) ([]string, error) | ||||
|  | ||||
| // IndexFuncToKeyFuncAdapter adapts an indexFunc to a keyFunc.  This is only useful if your index function returns | ||||
| // unique values for every object.  This is conversion can create errors when more than one key is found.  You | ||||
| // should prefer to make proper key and index functions. | ||||
| func IndexFuncToKeyFuncAdapter(indexFunc IndexFunc) KeyFunc { | ||||
| 	return func(obj interface{}) (string, error) { | ||||
| 		indexKeys, err := indexFunc(obj) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
| 		if len(indexKeys) > 1 { | ||||
| 			return "", fmt.Errorf("too many keys: %v", indexKeys) | ||||
| 		} | ||||
| 		if len(indexKeys) == 0 { | ||||
| 			return "", fmt.Errorf("unexpected empty indexKeys") | ||||
| 		} | ||||
| 		return indexKeys[0], nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	NamespaceIndex string = "namespace" | ||||
| ) | ||||
|  | ||||
| // MetaNamespaceIndexFunc is a default index function that indexes based on an object's namespace | ||||
| func MetaNamespaceIndexFunc(obj interface{}) ([]string, error) { | ||||
| 	meta, err := meta.Accessor(obj) | ||||
| 	if err != nil { | ||||
| 		return []string{""}, fmt.Errorf("object has no meta: %v", err) | ||||
| 	} | ||||
| 	return []string{meta.GetNamespace()}, nil | ||||
| } | ||||
|  | ||||
| // Index maps the indexed value to a set of keys in the store that match on that value | ||||
| type Index map[string]sets.String | ||||
|  | ||||
| // Indexers maps a name to a IndexFunc | ||||
| type Indexers map[string]IndexFunc | ||||
|  | ||||
| // Indices maps a name to an Index | ||||
| type Indices map[string]Index | ||||
							
								
								
									
										137
									
								
								pkg/client/cache/index_test.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										137
									
								
								pkg/client/cache/index_test.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,137 +0,0 @@ | ||||
| /* | ||||
| Copyright 2015 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/kubernetes/pkg/api" | ||||
| 	"k8s.io/kubernetes/pkg/api/v1" | ||||
| ) | ||||
|  | ||||
| func testIndexFunc(obj interface{}) ([]string, error) { | ||||
| 	pod := obj.(*v1.Pod) | ||||
| 	return []string{pod.Labels["foo"]}, nil | ||||
| } | ||||
|  | ||||
| func TestGetIndexFuncValues(t *testing.T) { | ||||
| 	index := NewIndexer(MetaNamespaceKeyFunc, Indexers{"testmodes": testIndexFunc}) | ||||
|  | ||||
| 	pod1 := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "one", Labels: map[string]string{"foo": "bar"}}} | ||||
| 	pod2 := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "two", Labels: map[string]string{"foo": "bar"}}} | ||||
| 	pod3 := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "tre", Labels: map[string]string{"foo": "biz"}}} | ||||
|  | ||||
| 	index.Add(pod1) | ||||
| 	index.Add(pod2) | ||||
| 	index.Add(pod3) | ||||
|  | ||||
| 	keys := index.ListIndexFuncValues("testmodes") | ||||
| 	if len(keys) != 2 { | ||||
| 		t.Errorf("Expected 2 keys but got %v", len(keys)) | ||||
| 	} | ||||
|  | ||||
| 	for _, key := range keys { | ||||
| 		if key != "bar" && key != "biz" { | ||||
| 			t.Errorf("Expected only 'bar' or 'biz' but got %s", key) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func testUsersIndexFunc(obj interface{}) ([]string, error) { | ||||
| 	pod := obj.(*v1.Pod) | ||||
| 	usersString := pod.Annotations["users"] | ||||
|  | ||||
| 	return strings.Split(usersString, ","), nil | ||||
| } | ||||
|  | ||||
| func TestMultiIndexKeys(t *testing.T) { | ||||
| 	index := NewIndexer(MetaNamespaceKeyFunc, Indexers{"byUser": testUsersIndexFunc}) | ||||
|  | ||||
| 	pod1 := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "one", Annotations: map[string]string{"users": "ernie,bert"}}} | ||||
| 	pod2 := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "two", Annotations: map[string]string{"users": "bert,oscar"}}} | ||||
| 	pod3 := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "tre", Annotations: map[string]string{"users": "ernie,elmo"}}} | ||||
|  | ||||
| 	index.Add(pod1) | ||||
| 	index.Add(pod2) | ||||
| 	index.Add(pod3) | ||||
|  | ||||
| 	erniePods, err := index.ByIndex("byUser", "ernie") | ||||
| 	if err != nil { | ||||
| 		t.Errorf("unexpected error: %v", err) | ||||
| 	} | ||||
| 	if len(erniePods) != 2 { | ||||
| 		t.Errorf("Expected 2 pods but got %v", len(erniePods)) | ||||
| 	} | ||||
|  | ||||
| 	bertPods, err := index.ByIndex("byUser", "bert") | ||||
| 	if err != nil { | ||||
| 		t.Errorf("unexpected error: %v", err) | ||||
| 	} | ||||
| 	if len(bertPods) != 2 { | ||||
| 		t.Errorf("Expected 2 pods but got %v", len(bertPods)) | ||||
| 	} | ||||
|  | ||||
| 	oscarPods, err := index.ByIndex("byUser", "oscar") | ||||
| 	if err != nil { | ||||
| 		t.Errorf("unexpected error: %v", err) | ||||
| 	} | ||||
| 	if len(oscarPods) != 1 { | ||||
| 		t.Errorf("Expected 1 pods but got %v", len(erniePods)) | ||||
| 	} | ||||
|  | ||||
| 	ernieAndBertKeys, err := index.Index("byUser", pod1) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("unexpected error: %v", err) | ||||
| 	} | ||||
| 	if len(ernieAndBertKeys) != 3 { | ||||
| 		t.Errorf("Expected 3 pods but got %v", len(ernieAndBertKeys)) | ||||
| 	} | ||||
|  | ||||
| 	index.Delete(pod3) | ||||
| 	erniePods, err = index.ByIndex("byUser", "ernie") | ||||
| 	if err != nil { | ||||
| 		t.Errorf("unexpected error: %v", err) | ||||
| 	} | ||||
| 	if len(erniePods) != 1 { | ||||
| 		t.Errorf("Expected 1 pods but got %v", len(erniePods)) | ||||
| 	} | ||||
| 	elmoPods, err := index.ByIndex("byUser", "elmo") | ||||
| 	if err != nil { | ||||
| 		t.Errorf("unexpected error: %v", err) | ||||
| 	} | ||||
| 	if len(elmoPods) != 0 { | ||||
| 		t.Errorf("Expected 0 pods but got %v", len(elmoPods)) | ||||
| 	} | ||||
|  | ||||
| 	obj, err := api.Scheme.DeepCopy(pod2) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("unexpected error: %v", err) | ||||
| 	} | ||||
| 	copyOfPod2 := obj.(*v1.Pod) | ||||
| 	copyOfPod2.Annotations["users"] = "oscar" | ||||
| 	index.Update(copyOfPod2) | ||||
| 	bertPods, err = index.ByIndex("byUser", "bert") | ||||
| 	if err != nil { | ||||
| 		t.Errorf("unexpected error: %v", err) | ||||
| 	} | ||||
| 	if len(bertPods) != 1 { | ||||
| 		t.Errorf("Expected 1 pods but got %v", len(bertPods)) | ||||
| 	} | ||||
|  | ||||
| } | ||||
							
								
								
									
										160
									
								
								pkg/client/cache/listers.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										160
									
								
								pkg/client/cache/listers.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,160 +0,0 @@ | ||||
| /* | ||||
| Copyright 2014 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"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/labels" | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/runtime/schema" | ||||
| ) | ||||
|  | ||||
| // AppendFunc is used to add a matching item to whatever list the caller is using | ||||
| type AppendFunc func(interface{}) | ||||
|  | ||||
| func ListAll(store Store, selector labels.Selector, appendFn AppendFunc) error { | ||||
| 	for _, m := range store.List() { | ||||
| 		metadata, err := meta.Accessor(m) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if selector.Matches(labels.Set(metadata.GetLabels())) { | ||||
| 			appendFn(m) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func ListAllByNamespace(indexer Indexer, namespace string, selector labels.Selector, appendFn AppendFunc) error { | ||||
| 	if namespace == metav1.NamespaceAll { | ||||
| 		for _, m := range indexer.List() { | ||||
| 			metadata, err := meta.Accessor(m) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			if selector.Matches(labels.Set(metadata.GetLabels())) { | ||||
| 				appendFn(m) | ||||
| 			} | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	items, err := indexer.Index(NamespaceIndex, &metav1.ObjectMeta{Namespace: namespace}) | ||||
| 	if err != nil { | ||||
| 		// Ignore error; do slow search without index. | ||||
| 		glog.Warningf("can not retrieve list of objects using index : %v", err) | ||||
| 		for _, m := range indexer.List() { | ||||
| 			metadata, err := meta.Accessor(m) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			if metadata.GetNamespace() == namespace && selector.Matches(labels.Set(metadata.GetLabels())) { | ||||
| 				appendFn(m) | ||||
| 			} | ||||
|  | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| 	for _, m := range items { | ||||
| 		metadata, err := meta.Accessor(m) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if selector.Matches(labels.Set(metadata.GetLabels())) { | ||||
| 			appendFn(m) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // GenericLister is a lister skin on a generic Indexer | ||||
| type GenericLister interface { | ||||
| 	// List will return all objects across namespaces | ||||
| 	List(selector labels.Selector) (ret []runtime.Object, err error) | ||||
| 	// Get will attempt to retrieve assuming that name==key | ||||
| 	Get(name string) (runtime.Object, error) | ||||
| 	// ByNamespace will give you a GenericNamespaceLister for one namespace | ||||
| 	ByNamespace(namespace string) GenericNamespaceLister | ||||
| } | ||||
|  | ||||
| // GenericNamespaceLister is a lister skin on a generic Indexer | ||||
| type GenericNamespaceLister interface { | ||||
| 	// List will return all objects in this namespace | ||||
| 	List(selector labels.Selector) (ret []runtime.Object, err error) | ||||
| 	// Get will attempt to retrieve by namespace and name | ||||
| 	Get(name string) (runtime.Object, error) | ||||
| } | ||||
|  | ||||
| func NewGenericLister(indexer Indexer, resource schema.GroupResource) GenericLister { | ||||
| 	return &genericLister{indexer: indexer, resource: resource} | ||||
| } | ||||
|  | ||||
| type genericLister struct { | ||||
| 	indexer  Indexer | ||||
| 	resource schema.GroupResource | ||||
| } | ||||
|  | ||||
| func (s *genericLister) List(selector labels.Selector) (ret []runtime.Object, err error) { | ||||
| 	err = ListAll(s.indexer, selector, func(m interface{}) { | ||||
| 		ret = append(ret, m.(runtime.Object)) | ||||
| 	}) | ||||
| 	return ret, err | ||||
| } | ||||
|  | ||||
| func (s *genericLister) ByNamespace(namespace string) GenericNamespaceLister { | ||||
| 	return &genericNamespaceLister{indexer: s.indexer, namespace: namespace, resource: s.resource} | ||||
| } | ||||
|  | ||||
| func (s *genericLister) Get(name string) (runtime.Object, error) { | ||||
| 	obj, exists, err := s.indexer.GetByKey(name) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if !exists { | ||||
| 		return nil, errors.NewNotFound(s.resource, name) | ||||
| 	} | ||||
| 	return obj.(runtime.Object), nil | ||||
| } | ||||
|  | ||||
| type genericNamespaceLister struct { | ||||
| 	indexer   Indexer | ||||
| 	namespace string | ||||
| 	resource  schema.GroupResource | ||||
| } | ||||
|  | ||||
| func (s *genericNamespaceLister) List(selector labels.Selector) (ret []runtime.Object, err error) { | ||||
| 	err = ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { | ||||
| 		ret = append(ret, m.(runtime.Object)) | ||||
| 	}) | ||||
| 	return ret, err | ||||
| } | ||||
|  | ||||
| func (s *genericNamespaceLister) Get(name string) (runtime.Object, error) { | ||||
| 	obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if !exists { | ||||
| 		return nil, errors.NewNotFound(s.resource, name) | ||||
| 	} | ||||
| 	return obj.(runtime.Object), nil | ||||
| } | ||||
							
								
								
									
										162
									
								
								pkg/client/cache/listwatch.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										162
									
								
								pkg/client/cache/listwatch.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,162 +0,0 @@ | ||||
| /* | ||||
| Copyright 2015 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/api/meta" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/fields" | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/watch" | ||||
| 	restclient "k8s.io/client-go/rest" | ||||
| ) | ||||
|  | ||||
| // ListerWatcher is any object that knows how to perform an initial list and start a watch on a resource. | ||||
| type ListerWatcher interface { | ||||
| 	// List should return a list type object; the Items field will be extracted, and the | ||||
| 	// ResourceVersion field will be used to start the watch in the right place. | ||||
| 	List(options metav1.ListOptions) (runtime.Object, error) | ||||
| 	// Watch should begin a watch at the specified version. | ||||
| 	Watch(options metav1.ListOptions) (watch.Interface, error) | ||||
| } | ||||
|  | ||||
| // ListFunc knows how to list resources | ||||
| type ListFunc func(options metav1.ListOptions) (runtime.Object, error) | ||||
|  | ||||
| // WatchFunc knows how to watch resources | ||||
| type WatchFunc func(options metav1.ListOptions) (watch.Interface, error) | ||||
|  | ||||
| // ListWatch knows how to list and watch a set of apiserver resources.  It satisfies the ListerWatcher interface. | ||||
| // It is a convenience function for users of NewReflector, etc. | ||||
| // ListFunc and WatchFunc must not be nil | ||||
| type ListWatch struct { | ||||
| 	ListFunc  ListFunc | ||||
| 	WatchFunc WatchFunc | ||||
| } | ||||
|  | ||||
| // Getter interface knows how to access Get method from RESTClient. | ||||
| type Getter interface { | ||||
| 	Get() *restclient.Request | ||||
| } | ||||
|  | ||||
| // NewListWatchFromClient creates a new ListWatch from the specified client, resource, namespace and field selector. | ||||
| func NewListWatchFromClient(c Getter, resource string, namespace string, fieldSelector fields.Selector) *ListWatch { | ||||
| 	listFunc := func(options metav1.ListOptions) (runtime.Object, error) { | ||||
| 		return c.Get(). | ||||
| 			Namespace(namespace). | ||||
| 			Resource(resource). | ||||
| 			VersionedParams(&options, metav1.ParameterCodec). | ||||
| 			FieldsSelectorParam(fieldSelector). | ||||
| 			Do(). | ||||
| 			Get() | ||||
| 	} | ||||
| 	watchFunc := func(options metav1.ListOptions) (watch.Interface, error) { | ||||
| 		return c.Get(). | ||||
| 			Prefix("watch"). | ||||
| 			Namespace(namespace). | ||||
| 			Resource(resource). | ||||
| 			VersionedParams(&options, metav1.ParameterCodec). | ||||
| 			FieldsSelectorParam(fieldSelector). | ||||
| 			Watch() | ||||
| 	} | ||||
| 	return &ListWatch{ListFunc: listFunc, WatchFunc: watchFunc} | ||||
| } | ||||
|  | ||||
| func timeoutFromListOptions(options metav1.ListOptions) time.Duration { | ||||
| 	if options.TimeoutSeconds != nil { | ||||
| 		return time.Duration(*options.TimeoutSeconds) * time.Second | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| // List a set of apiserver resources | ||||
| func (lw *ListWatch) List(options metav1.ListOptions) (runtime.Object, error) { | ||||
| 	return lw.ListFunc(options) | ||||
| } | ||||
|  | ||||
| // Watch a set of apiserver resources | ||||
| func (lw *ListWatch) Watch(options metav1.ListOptions) (watch.Interface, error) { | ||||
| 	return lw.WatchFunc(options) | ||||
| } | ||||
|  | ||||
| // TODO: check for watch expired error and retry watch from latest point?  Same issue exists for Until. | ||||
| func ListWatchUntil(timeout time.Duration, lw ListerWatcher, conditions ...watch.ConditionFunc) (*watch.Event, error) { | ||||
| 	if len(conditions) == 0 { | ||||
| 		return nil, nil | ||||
| 	} | ||||
|  | ||||
| 	list, err := lw.List(metav1.ListOptions{}) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	initialItems, err := meta.ExtractList(list) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// use the initial items as simulated "adds" | ||||
| 	var lastEvent *watch.Event | ||||
| 	currIndex := 0 | ||||
| 	passedConditions := 0 | ||||
| 	for _, condition := range conditions { | ||||
| 		// check the next condition against the previous event and short circuit waiting for the next watch | ||||
| 		if lastEvent != nil { | ||||
| 			done, err := condition(*lastEvent) | ||||
| 			if err != nil { | ||||
| 				return lastEvent, err | ||||
| 			} | ||||
| 			if done { | ||||
| 				passedConditions = passedConditions + 1 | ||||
| 				continue | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 	ConditionSucceeded: | ||||
| 		for currIndex < len(initialItems) { | ||||
| 			lastEvent = &watch.Event{Type: watch.Added, Object: initialItems[currIndex]} | ||||
| 			currIndex++ | ||||
|  | ||||
| 			done, err := condition(*lastEvent) | ||||
| 			if err != nil { | ||||
| 				return lastEvent, err | ||||
| 			} | ||||
| 			if done { | ||||
| 				passedConditions = passedConditions + 1 | ||||
| 				break ConditionSucceeded | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if passedConditions == len(conditions) { | ||||
| 		return lastEvent, nil | ||||
| 	} | ||||
| 	remainingConditions := conditions[passedConditions:] | ||||
|  | ||||
| 	metaObj, err := meta.ListAccessor(list) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	currResourceVersion := metaObj.GetResourceVersion() | ||||
|  | ||||
| 	watchInterface, err := lw.Watch(metav1.ListOptions{ResourceVersion: currResourceVersion}) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return watch.Until(timeout, watchInterface, remainingConditions...) | ||||
| } | ||||
							
								
								
									
										135
									
								
								pkg/client/cache/mutation_detector.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										135
									
								
								pkg/client/cache/mutation_detector.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,135 +0,0 @@ | ||||
| /* | ||||
| 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"reflect" | ||||
| 	"strconv" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/util/diff" | ||||
| 	"k8s.io/kubernetes/pkg/api" | ||||
| ) | ||||
|  | ||||
| var mutationDetectionEnabled = false | ||||
|  | ||||
| func init() { | ||||
| 	mutationDetectionEnabled, _ = strconv.ParseBool(os.Getenv("KUBE_CACHE_MUTATION_DETECTOR")) | ||||
| } | ||||
|  | ||||
| type CacheMutationDetector interface { | ||||
| 	AddObject(obj interface{}) | ||||
| 	Run(stopCh <-chan struct{}) | ||||
| } | ||||
|  | ||||
| func NewCacheMutationDetector(name string) CacheMutationDetector { | ||||
| 	if !mutationDetectionEnabled { | ||||
| 		return dummyMutationDetector{} | ||||
| 	} | ||||
| 	return &defaultCacheMutationDetector{name: name, period: 1 * time.Second} | ||||
| } | ||||
|  | ||||
| type dummyMutationDetector struct{} | ||||
|  | ||||
| func (dummyMutationDetector) Run(stopCh <-chan struct{}) { | ||||
| } | ||||
| func (dummyMutationDetector) AddObject(obj interface{}) { | ||||
| } | ||||
|  | ||||
| // defaultCacheMutationDetector gives a way to detect if a cached object has been mutated | ||||
| // It has a list of cached objects and their copies.  I haven't thought of a way | ||||
| // to see WHO is mutating it, just that it's getting mutated. | ||||
| type defaultCacheMutationDetector struct { | ||||
| 	name   string | ||||
| 	period time.Duration | ||||
|  | ||||
| 	lock       sync.Mutex | ||||
| 	cachedObjs []cacheObj | ||||
|  | ||||
| 	// failureFunc is injectable for unit testing.  If you don't have it, the process will panic. | ||||
| 	// This panic is intentional, since turning on this detection indicates you want a strong | ||||
| 	// failure signal.  This failure is effectively a p0 bug and you can't trust process results | ||||
| 	// after a mutation anyway. | ||||
| 	failureFunc func(message string) | ||||
| } | ||||
|  | ||||
| // cacheObj holds the actual object and a copy | ||||
| type cacheObj struct { | ||||
| 	cached interface{} | ||||
| 	copied interface{} | ||||
| } | ||||
|  | ||||
| func (d *defaultCacheMutationDetector) Run(stopCh <-chan struct{}) { | ||||
| 	// we DON'T want protection from panics.  If we're running this code, we want to die | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			d.CompareObjects() | ||||
|  | ||||
| 			select { | ||||
| 			case <-stopCh: | ||||
| 				return | ||||
| 			case <-time.After(d.period): | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
| } | ||||
|  | ||||
| // AddObject makes a deep copy of the object for later comparison.  It only works on runtime.Object | ||||
| // but that covers the vast majority of our cached objects | ||||
| func (d *defaultCacheMutationDetector) AddObject(obj interface{}) { | ||||
| 	if _, ok := obj.(DeletedFinalStateUnknown); ok { | ||||
| 		return | ||||
| 	} | ||||
| 	if _, ok := obj.(runtime.Object); !ok { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	copiedObj, err := api.Scheme.Copy(obj.(runtime.Object)) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	d.lock.Lock() | ||||
| 	defer d.lock.Unlock() | ||||
| 	d.cachedObjs = append(d.cachedObjs, cacheObj{cached: obj, copied: copiedObj}) | ||||
| } | ||||
|  | ||||
| func (d *defaultCacheMutationDetector) CompareObjects() { | ||||
| 	d.lock.Lock() | ||||
| 	defer d.lock.Unlock() | ||||
|  | ||||
| 	altered := false | ||||
| 	for i, obj := range d.cachedObjs { | ||||
| 		if !reflect.DeepEqual(obj.cached, obj.copied) { | ||||
| 			fmt.Printf("CACHE %s[%d] ALTERED!\n%v\n", d.name, i, diff.ObjectDiff(obj.cached, obj.copied)) | ||||
| 			altered = true | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if altered { | ||||
| 		msg := fmt.Sprintf("cache %s modified", d.name) | ||||
| 		if d.failureFunc != nil { | ||||
| 			d.failureFunc(msg) | ||||
| 			return | ||||
| 		} | ||||
| 		panic(msg) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										81
									
								
								pkg/client/cache/mutation_detector_test.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										81
									
								
								pkg/client/cache/mutation_detector_test.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,81 +0,0 @@ | ||||
| // +build !race | ||||
|  | ||||
| /* | ||||
| 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/watch" | ||||
| 	"k8s.io/kubernetes/pkg/api/v1" | ||||
| ) | ||||
|  | ||||
| func TestMutationDetector(t *testing.T) { | ||||
| 	fakeWatch := watch.NewFake() | ||||
| 	lw := &testLW{ | ||||
| 		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { | ||||
| 			return fakeWatch, nil | ||||
| 		}, | ||||
| 		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { | ||||
| 			return &v1.PodList{}, nil | ||||
| 		}, | ||||
| 	} | ||||
| 	pod := &v1.Pod{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name:   "anything", | ||||
| 			Labels: map[string]string{"check": "foo"}, | ||||
| 		}, | ||||
| 	} | ||||
| 	stopCh := make(chan struct{}) | ||||
| 	defer close(stopCh) | ||||
| 	addReceived := make(chan bool) | ||||
| 	mutationFound := make(chan bool) | ||||
|  | ||||
| 	informer := NewSharedInformer(lw, &v1.Pod{}, 1*time.Second).(*sharedIndexInformer) | ||||
| 	informer.cacheMutationDetector = &defaultCacheMutationDetector{ | ||||
| 		name:   "name", | ||||
| 		period: 1 * time.Second, | ||||
| 		failureFunc: func(message string) { | ||||
| 			mutationFound <- true | ||||
| 		}, | ||||
| 	} | ||||
| 	informer.AddEventHandler( | ||||
| 		ResourceEventHandlerFuncs{ | ||||
| 			AddFunc: func(obj interface{}) { | ||||
| 				addReceived <- true | ||||
| 			}, | ||||
| 		}, | ||||
| 	) | ||||
| 	go informer.Run(stopCh) | ||||
|  | ||||
| 	fakeWatch.Add(pod) | ||||
|  | ||||
| 	select { | ||||
| 	case <-addReceived: | ||||
| 	} | ||||
|  | ||||
| 	pod.Labels["change"] = "true" | ||||
|  | ||||
| 	select { | ||||
| 	case <-mutationFound: | ||||
| 	} | ||||
|  | ||||
| } | ||||
							
								
								
									
										48
									
								
								pkg/client/cache/processor_listener_test.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										48
									
								
								pkg/client/cache/processor_listener_test.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,48 +0,0 @@ | ||||
| /* | ||||
| 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/util/wait" | ||||
| ) | ||||
|  | ||||
| // TestPopReleaseLock tests that when processor listener blocks on chan, | ||||
| // it should release the lock for pendingNotifications. | ||||
| func TestPopReleaseLock(t *testing.T) { | ||||
| 	pl := newProcessListener(nil) | ||||
| 	stopCh := make(chan struct{}) | ||||
| 	defer close(stopCh) | ||||
| 	// make pop() block on nextCh: waiting for receiver to get notification. | ||||
| 	pl.add(1) | ||||
| 	go pl.pop(stopCh) | ||||
|  | ||||
| 	resultCh := make(chan struct{}) | ||||
| 	go func() { | ||||
| 		pl.lock.Lock() | ||||
| 		close(resultCh) | ||||
| 	}() | ||||
|  | ||||
| 	select { | ||||
| 	case <-resultCh: | ||||
| 	case <-time.After(wait.ForeverTestTimeout): | ||||
| 		t.Errorf("Timeout after %v", wait.ForeverTestTimeout) | ||||
| 	} | ||||
| 	pl.lock.Unlock() | ||||
| } | ||||
							
								
								
									
										417
									
								
								pkg/client/cache/reflector.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										417
									
								
								pkg/client/cache/reflector.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,417 +0,0 @@ | ||||
| /* | ||||
| Copyright 2014 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"math/rand" | ||||
| 	"net" | ||||
| 	"net/url" | ||||
| 	"reflect" | ||||
| 	"regexp" | ||||
| 	goruntime "runtime" | ||||
| 	"runtime/debug" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"syscall" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/golang/glog" | ||||
| 	apierrs "k8s.io/apimachinery/pkg/api/errors" | ||||
| 	"k8s.io/apimachinery/pkg/api/meta" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	utilruntime "k8s.io/apimachinery/pkg/util/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/util/wait" | ||||
| 	"k8s.io/apimachinery/pkg/watch" | ||||
| ) | ||||
|  | ||||
| // Reflector watches a specified resource and causes all changes to be reflected in the given store. | ||||
| type Reflector struct { | ||||
| 	// name identifies this reflector. By default it will be a file:line if possible. | ||||
| 	name string | ||||
|  | ||||
| 	// The type of object we expect to place in the store. | ||||
| 	expectedType reflect.Type | ||||
| 	// The destination to sync up with the watch source | ||||
| 	store Store | ||||
| 	// listerWatcher is used to perform lists and watches. | ||||
| 	listerWatcher ListerWatcher | ||||
| 	// period controls timing between one watch ending and | ||||
| 	// the beginning of the next one. | ||||
| 	period       time.Duration | ||||
| 	resyncPeriod time.Duration | ||||
| 	// now() returns current time - exposed for testing purposes | ||||
| 	now func() time.Time | ||||
| 	// lastSyncResourceVersion is the resource version token last | ||||
| 	// observed when doing a sync with the underlying store | ||||
| 	// it is thread safe, but not synchronized with the underlying store | ||||
| 	lastSyncResourceVersion string | ||||
| 	// lastSyncResourceVersionMutex guards read/write access to lastSyncResourceVersion | ||||
| 	lastSyncResourceVersionMutex sync.RWMutex | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	// We try to spread the load on apiserver by setting timeouts for | ||||
| 	// watch requests - it is random in [minWatchTimeout, 2*minWatchTimeout]. | ||||
| 	// However, it can be modified to avoid periodic resync to break the | ||||
| 	// TCP connection. | ||||
| 	minWatchTimeout = 5 * time.Minute | ||||
| ) | ||||
|  | ||||
| // NewNamespaceKeyedIndexerAndReflector creates an Indexer and a Reflector | ||||
| // The indexer is configured to key on namespace | ||||
| func NewNamespaceKeyedIndexerAndReflector(lw ListerWatcher, expectedType interface{}, resyncPeriod time.Duration) (indexer Indexer, reflector *Reflector) { | ||||
| 	indexer = NewIndexer(MetaNamespaceKeyFunc, Indexers{"namespace": MetaNamespaceIndexFunc}) | ||||
| 	reflector = NewReflector(lw, expectedType, indexer, resyncPeriod) | ||||
| 	return indexer, reflector | ||||
| } | ||||
|  | ||||
| // NewReflector creates a new Reflector object which will keep the given store up to | ||||
| // date with the server's contents for the given resource. Reflector promises to | ||||
| // only put things in the store that have the type of expectedType, unless expectedType | ||||
| // is nil. If resyncPeriod is non-zero, then lists will be executed after every | ||||
| // resyncPeriod, so that you can use reflectors to periodically process everything as | ||||
| // well as incrementally processing the things that change. | ||||
| func NewReflector(lw ListerWatcher, expectedType interface{}, store Store, resyncPeriod time.Duration) *Reflector { | ||||
| 	return NewNamedReflector(getDefaultReflectorName(internalPackages...), lw, expectedType, store, resyncPeriod) | ||||
| } | ||||
|  | ||||
| // NewNamedReflector same as NewReflector, but with a specified name for logging | ||||
| func NewNamedReflector(name string, lw ListerWatcher, expectedType interface{}, store Store, resyncPeriod time.Duration) *Reflector { | ||||
| 	r := &Reflector{ | ||||
| 		name:          name, | ||||
| 		listerWatcher: lw, | ||||
| 		store:         store, | ||||
| 		expectedType:  reflect.TypeOf(expectedType), | ||||
| 		period:        time.Second, | ||||
| 		resyncPeriod:  resyncPeriod, | ||||
| 		now:           time.Now, | ||||
| 	} | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // internalPackages are packages that ignored when creating a default reflector name. These packages are in the common | ||||
| // call chains to NewReflector, so they'd be low entropy names for reflectors | ||||
| var internalPackages = []string{"kubernetes/pkg/client/cache/", "/runtime/asm_"} | ||||
|  | ||||
| // getDefaultReflectorName walks back through the call stack until we find a caller from outside of the ignoredPackages | ||||
| // it returns back a shortpath/filename:line to aid in identification of this reflector when it starts logging | ||||
| func getDefaultReflectorName(ignoredPackages ...string) string { | ||||
| 	name := "????" | ||||
| 	const maxStack = 10 | ||||
| 	for i := 1; i < maxStack; i++ { | ||||
| 		_, file, line, ok := goruntime.Caller(i) | ||||
| 		if !ok { | ||||
| 			file, line, ok = extractStackCreator() | ||||
| 			if !ok { | ||||
| 				break | ||||
| 			} | ||||
| 			i += maxStack | ||||
| 		} | ||||
| 		if hasPackage(file, ignoredPackages) { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		file = trimPackagePrefix(file) | ||||
| 		name = fmt.Sprintf("%s:%d", file, line) | ||||
| 		break | ||||
| 	} | ||||
| 	return name | ||||
| } | ||||
|  | ||||
| // hasPackage returns true if the file is in one of the ignored packages. | ||||
| func hasPackage(file string, ignoredPackages []string) bool { | ||||
| 	for _, ignoredPackage := range ignoredPackages { | ||||
| 		if strings.Contains(file, ignoredPackage) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // trimPackagePrefix reduces duplicate values off the front of a package name. | ||||
| func trimPackagePrefix(file string) string { | ||||
| 	if l := strings.LastIndex(file, "k8s.io/kubernetes/pkg/"); l >= 0 { | ||||
| 		return file[l+len("k8s.io/kubernetes/"):] | ||||
| 	} | ||||
| 	if l := strings.LastIndex(file, "/src/"); l >= 0 { | ||||
| 		return file[l+5:] | ||||
| 	} | ||||
| 	if l := strings.LastIndex(file, "/pkg/"); l >= 0 { | ||||
| 		return file[l+1:] | ||||
| 	} | ||||
| 	return file | ||||
| } | ||||
|  | ||||
| var stackCreator = regexp.MustCompile(`(?m)^created by (.*)\n\s+(.*):(\d+) \+0x[[:xdigit:]]+$`) | ||||
|  | ||||
| // extractStackCreator retrieves the goroutine file and line that launched this stack. Returns false | ||||
| // if the creator cannot be located. | ||||
| // TODO: Go does not expose this via runtime https://github.com/golang/go/issues/11440 | ||||
| func extractStackCreator() (string, int, bool) { | ||||
| 	stack := debug.Stack() | ||||
| 	matches := stackCreator.FindStringSubmatch(string(stack)) | ||||
| 	if matches == nil || len(matches) != 4 { | ||||
| 		return "", 0, false | ||||
| 	} | ||||
| 	line, err := strconv.Atoi(matches[3]) | ||||
| 	if err != nil { | ||||
| 		return "", 0, false | ||||
| 	} | ||||
| 	return matches[2], line, true | ||||
| } | ||||
|  | ||||
| // Run starts a watch and handles watch events. Will restart the watch if it is closed. | ||||
| // Run starts a goroutine and returns immediately. | ||||
| func (r *Reflector) Run() { | ||||
| 	glog.V(3).Infof("Starting reflector %v (%s) from %s", r.expectedType, r.resyncPeriod, r.name) | ||||
| 	go wait.Until(func() { | ||||
| 		if err := r.ListAndWatch(wait.NeverStop); err != nil { | ||||
| 			utilruntime.HandleError(err) | ||||
| 		} | ||||
| 	}, r.period, wait.NeverStop) | ||||
| } | ||||
|  | ||||
| // RunUntil starts a watch and handles watch events. Will restart the watch if it is closed. | ||||
| // RunUntil starts a goroutine and returns immediately. It will exit when stopCh is closed. | ||||
| func (r *Reflector) RunUntil(stopCh <-chan struct{}) { | ||||
| 	glog.V(3).Infof("Starting reflector %v (%s) from %s", r.expectedType, r.resyncPeriod, r.name) | ||||
| 	go wait.Until(func() { | ||||
| 		if err := r.ListAndWatch(stopCh); err != nil { | ||||
| 			utilruntime.HandleError(err) | ||||
| 		} | ||||
| 	}, r.period, stopCh) | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	// nothing will ever be sent down this channel | ||||
| 	neverExitWatch <-chan time.Time = make(chan time.Time) | ||||
|  | ||||
| 	// Used to indicate that watching stopped so that a resync could happen. | ||||
| 	errorResyncRequested = errors.New("resync channel fired") | ||||
|  | ||||
| 	// Used to indicate that watching stopped because of a signal from the stop | ||||
| 	// channel passed in from a client of the reflector. | ||||
| 	errorStopRequested = errors.New("Stop requested") | ||||
| ) | ||||
|  | ||||
| // resyncChan returns a channel which will receive something when a resync is | ||||
| // required, and a cleanup function. | ||||
| func (r *Reflector) resyncChan() (<-chan time.Time, func() bool) { | ||||
| 	if r.resyncPeriod == 0 { | ||||
| 		return neverExitWatch, func() bool { return false } | ||||
| 	} | ||||
| 	// The cleanup function is required: imagine the scenario where watches | ||||
| 	// always fail so we end up listing frequently. Then, if we don't | ||||
| 	// manually stop the timer, we could end up with many timers active | ||||
| 	// concurrently. | ||||
| 	t := time.NewTimer(r.resyncPeriod) | ||||
| 	return t.C, t.Stop | ||||
| } | ||||
|  | ||||
| // ListAndWatch first lists all items and get the resource version at the moment of call, | ||||
| // and then use the resource version to watch. | ||||
| // It returns error if ListAndWatch didn't even try to initialize watch. | ||||
| func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error { | ||||
| 	glog.V(3).Infof("Listing and watching %v from %s", r.expectedType, r.name) | ||||
| 	var resourceVersion string | ||||
| 	resyncCh, cleanup := r.resyncChan() | ||||
| 	defer cleanup() | ||||
|  | ||||
| 	// Explicitly set "0" as resource version - it's fine for the List() | ||||
| 	// to be served from cache and potentially be delayed relative to | ||||
| 	// etcd contents. Reflector framework will catch up via Watch() eventually. | ||||
| 	options := metav1.ListOptions{ResourceVersion: "0"} | ||||
| 	list, err := r.listerWatcher.List(options) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("%s: Failed to list %v: %v", r.name, r.expectedType, err) | ||||
| 	} | ||||
| 	listMetaInterface, err := meta.ListAccessor(list) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("%s: Unable to understand list result %#v: %v", r.name, list, err) | ||||
| 	} | ||||
| 	resourceVersion = listMetaInterface.GetResourceVersion() | ||||
| 	items, err := meta.ExtractList(list) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("%s: Unable to understand list result %#v (%v)", r.name, list, err) | ||||
| 	} | ||||
| 	if err := r.syncWith(items, resourceVersion); err != nil { | ||||
| 		return fmt.Errorf("%s: Unable to sync list result: %v", r.name, err) | ||||
| 	} | ||||
| 	r.setLastSyncResourceVersion(resourceVersion) | ||||
|  | ||||
| 	resyncerrc := make(chan error, 1) | ||||
| 	cancelCh := make(chan struct{}) | ||||
| 	defer close(cancelCh) | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			select { | ||||
| 			case <-resyncCh: | ||||
| 			case <-stopCh: | ||||
| 				return | ||||
| 			case <-cancelCh: | ||||
| 				return | ||||
| 			} | ||||
| 			glog.V(4).Infof("%s: forcing resync", r.name) | ||||
| 			if err := r.store.Resync(); err != nil { | ||||
| 				resyncerrc <- err | ||||
| 				return | ||||
| 			} | ||||
| 			cleanup() | ||||
| 			resyncCh, cleanup = r.resyncChan() | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	for { | ||||
| 		timemoutseconds := int64(minWatchTimeout.Seconds() * (rand.Float64() + 1.0)) | ||||
| 		options = metav1.ListOptions{ | ||||
| 			ResourceVersion: resourceVersion, | ||||
| 			// We want to avoid situations of hanging watchers. Stop any wachers that do not | ||||
| 			// receive any events within the timeout window. | ||||
| 			TimeoutSeconds: &timemoutseconds, | ||||
| 		} | ||||
|  | ||||
| 		w, err := r.listerWatcher.Watch(options) | ||||
| 		if err != nil { | ||||
| 			switch err { | ||||
| 			case io.EOF: | ||||
| 				// watch closed normally | ||||
| 			case io.ErrUnexpectedEOF: | ||||
| 				glog.V(1).Infof("%s: Watch for %v closed with unexpected EOF: %v", r.name, r.expectedType, err) | ||||
| 			default: | ||||
| 				utilruntime.HandleError(fmt.Errorf("%s: Failed to watch %v: %v", r.name, r.expectedType, err)) | ||||
| 			} | ||||
| 			// If this is "connection refused" error, it means that most likely apiserver is not responsive. | ||||
| 			// It doesn't make sense to re-list all objects because most likely we will be able to restart | ||||
| 			// watch where we ended. | ||||
| 			// If that's the case wait and resend watch request. | ||||
| 			if urlError, ok := err.(*url.Error); ok { | ||||
| 				if opError, ok := urlError.Err.(*net.OpError); ok { | ||||
| 					if errno, ok := opError.Err.(syscall.Errno); ok && errno == syscall.ECONNREFUSED { | ||||
| 						time.Sleep(time.Second) | ||||
| 						continue | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			return nil | ||||
| 		} | ||||
|  | ||||
| 		if err := r.watchHandler(w, &resourceVersion, resyncerrc, stopCh); err != nil { | ||||
| 			if err != errorStopRequested { | ||||
| 				glog.Warningf("%s: watch of %v ended with: %v", r.name, r.expectedType, err) | ||||
| 			} | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // syncWith replaces the store's items with the given list. | ||||
| func (r *Reflector) syncWith(items []runtime.Object, resourceVersion string) error { | ||||
| 	found := make([]interface{}, 0, len(items)) | ||||
| 	for _, item := range items { | ||||
| 		found = append(found, item) | ||||
| 	} | ||||
| 	return r.store.Replace(found, resourceVersion) | ||||
| } | ||||
|  | ||||
| // watchHandler watches w and keeps *resourceVersion up to date. | ||||
| func (r *Reflector) watchHandler(w watch.Interface, resourceVersion *string, errc chan error, stopCh <-chan struct{}) error { | ||||
| 	start := time.Now() | ||||
| 	eventCount := 0 | ||||
|  | ||||
| 	// Stopping the watcher should be idempotent and if we return from this function there's no way | ||||
| 	// we're coming back in with the same watch interface. | ||||
| 	defer w.Stop() | ||||
|  | ||||
| loop: | ||||
| 	for { | ||||
| 		select { | ||||
| 		case <-stopCh: | ||||
| 			return errorStopRequested | ||||
| 		case err := <-errc: | ||||
| 			return err | ||||
| 		case event, ok := <-w.ResultChan(): | ||||
| 			if !ok { | ||||
| 				break loop | ||||
| 			} | ||||
| 			if event.Type == watch.Error { | ||||
| 				return apierrs.FromObject(event.Object) | ||||
| 			} | ||||
| 			if e, a := r.expectedType, reflect.TypeOf(event.Object); e != nil && e != a { | ||||
| 				utilruntime.HandleError(fmt.Errorf("%s: expected type %v, but watch event object had type %v", r.name, e, a)) | ||||
| 				continue | ||||
| 			} | ||||
| 			meta, err := meta.Accessor(event.Object) | ||||
| 			if err != nil { | ||||
| 				utilruntime.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event)) | ||||
| 				continue | ||||
| 			} | ||||
| 			newResourceVersion := meta.GetResourceVersion() | ||||
| 			switch event.Type { | ||||
| 			case watch.Added: | ||||
| 				err := r.store.Add(event.Object) | ||||
| 				if err != nil { | ||||
| 					utilruntime.HandleError(fmt.Errorf("%s: unable to add watch event object (%#v) to store: %v", r.name, event.Object, err)) | ||||
| 				} | ||||
| 			case watch.Modified: | ||||
| 				err := r.store.Update(event.Object) | ||||
| 				if err != nil { | ||||
| 					utilruntime.HandleError(fmt.Errorf("%s: unable to update watch event object (%#v) to store: %v", r.name, event.Object, err)) | ||||
| 				} | ||||
| 			case watch.Deleted: | ||||
| 				// TODO: Will any consumers need access to the "last known | ||||
| 				// state", which is passed in event.Object? If so, may need | ||||
| 				// to change this. | ||||
| 				err := r.store.Delete(event.Object) | ||||
| 				if err != nil { | ||||
| 					utilruntime.HandleError(fmt.Errorf("%s: unable to delete watch event object (%#v) from store: %v", r.name, event.Object, err)) | ||||
| 				} | ||||
| 			default: | ||||
| 				utilruntime.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event)) | ||||
| 			} | ||||
| 			*resourceVersion = newResourceVersion | ||||
| 			r.setLastSyncResourceVersion(newResourceVersion) | ||||
| 			eventCount++ | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	watchDuration := time.Now().Sub(start) | ||||
| 	if watchDuration < 1*time.Second && eventCount == 0 { | ||||
| 		glog.V(4).Infof("%s: Unexpected watch close - watch lasted less than a second and no items received", r.name) | ||||
| 		return errors.New("very short watch") | ||||
| 	} | ||||
| 	glog.V(4).Infof("%s: Watch close - %v total %v items received", r.name, r.expectedType, eventCount) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // LastSyncResourceVersion is the resource version observed when last sync with the underlying store | ||||
| // The value returned is not synchronized with access to the underlying store and is not thread-safe | ||||
| func (r *Reflector) LastSyncResourceVersion() string { | ||||
| 	r.lastSyncResourceVersionMutex.RLock() | ||||
| 	defer r.lastSyncResourceVersionMutex.RUnlock() | ||||
| 	return r.lastSyncResourceVersion | ||||
| } | ||||
|  | ||||
| func (r *Reflector) setLastSyncResourceVersion(v string) { | ||||
| 	r.lastSyncResourceVersionMutex.Lock() | ||||
| 	defer r.lastSyncResourceVersionMutex.Unlock() | ||||
| 	r.lastSyncResourceVersion = v | ||||
| } | ||||
							
								
								
									
										389
									
								
								pkg/client/cache/reflector_test.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										389
									
								
								pkg/client/cache/reflector_test.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,389 +0,0 @@ | ||||
| /* | ||||
| Copyright 2014 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"math/rand" | ||||
| 	"strconv" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/util/wait" | ||||
| 	"k8s.io/apimachinery/pkg/watch" | ||||
| 	"k8s.io/kubernetes/pkg/api/v1" | ||||
| ) | ||||
|  | ||||
| var nevererrc chan error | ||||
|  | ||||
| type testLW struct { | ||||
| 	ListFunc  func(options metav1.ListOptions) (runtime.Object, error) | ||||
| 	WatchFunc func(options metav1.ListOptions) (watch.Interface, error) | ||||
| } | ||||
|  | ||||
| func (t *testLW) List(options metav1.ListOptions) (runtime.Object, error) { | ||||
| 	return t.ListFunc(options) | ||||
| } | ||||
| func (t *testLW) Watch(options metav1.ListOptions) (watch.Interface, error) { | ||||
| 	return t.WatchFunc(options) | ||||
| } | ||||
|  | ||||
| func TestCloseWatchChannelOnError(t *testing.T) { | ||||
| 	r := NewReflector(&testLW{}, &v1.Pod{}, NewStore(MetaNamespaceKeyFunc), 0) | ||||
| 	pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}} | ||||
| 	fw := watch.NewFake() | ||||
| 	r.listerWatcher = &testLW{ | ||||
| 		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { | ||||
| 			return fw, nil | ||||
| 		}, | ||||
| 		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { | ||||
| 			return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "1"}}, nil | ||||
| 		}, | ||||
| 	} | ||||
| 	go r.ListAndWatch(wait.NeverStop) | ||||
| 	fw.Error(pod) | ||||
| 	select { | ||||
| 	case _, ok := <-fw.ResultChan(): | ||||
| 		if ok { | ||||
| 			t.Errorf("Watch channel left open after cancellation") | ||||
| 		} | ||||
| 	case <-time.After(wait.ForeverTestTimeout): | ||||
| 		t.Errorf("the cancellation is at least %s late", wait.ForeverTestTimeout.String()) | ||||
| 		break | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestRunUntil(t *testing.T) { | ||||
| 	stopCh := make(chan struct{}) | ||||
| 	store := NewStore(MetaNamespaceKeyFunc) | ||||
| 	r := NewReflector(&testLW{}, &v1.Pod{}, store, 0) | ||||
| 	fw := watch.NewFake() | ||||
| 	r.listerWatcher = &testLW{ | ||||
| 		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { | ||||
| 			return fw, nil | ||||
| 		}, | ||||
| 		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { | ||||
| 			return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "1"}}, nil | ||||
| 		}, | ||||
| 	} | ||||
| 	r.RunUntil(stopCh) | ||||
| 	// Synchronously add a dummy pod into the watch channel so we | ||||
| 	// know the RunUntil go routine is in the watch handler. | ||||
| 	fw.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}}) | ||||
| 	close(stopCh) | ||||
| 	select { | ||||
| 	case _, ok := <-fw.ResultChan(): | ||||
| 		if ok { | ||||
| 			t.Errorf("Watch channel left open after stopping the watch") | ||||
| 		} | ||||
| 	case <-time.After(wait.ForeverTestTimeout): | ||||
| 		t.Errorf("the cancellation is at least %s late", wait.ForeverTestTimeout.String()) | ||||
| 		break | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestReflectorResyncChan(t *testing.T) { | ||||
| 	s := NewStore(MetaNamespaceKeyFunc) | ||||
| 	g := NewReflector(&testLW{}, &v1.Pod{}, s, time.Millisecond) | ||||
| 	a, _ := g.resyncChan() | ||||
| 	b := time.After(wait.ForeverTestTimeout) | ||||
| 	select { | ||||
| 	case <-a: | ||||
| 		t.Logf("got timeout as expected") | ||||
| 	case <-b: | ||||
| 		t.Errorf("resyncChan() is at least 99 milliseconds late??") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func BenchmarkReflectorResyncChanMany(b *testing.B) { | ||||
| 	s := NewStore(MetaNamespaceKeyFunc) | ||||
| 	g := NewReflector(&testLW{}, &v1.Pod{}, s, 25*time.Millisecond) | ||||
| 	// The improvement to this (calling the timer's Stop() method) makes | ||||
| 	// this benchmark about 40% faster. | ||||
| 	for i := 0; i < b.N; i++ { | ||||
| 		g.resyncPeriod = time.Duration(rand.Float64() * float64(time.Millisecond) * 25) | ||||
| 		_, stop := g.resyncChan() | ||||
| 		stop() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestReflectorWatchHandlerError(t *testing.T) { | ||||
| 	s := NewStore(MetaNamespaceKeyFunc) | ||||
| 	g := NewReflector(&testLW{}, &v1.Pod{}, s, 0) | ||||
| 	fw := watch.NewFake() | ||||
| 	go func() { | ||||
| 		fw.Stop() | ||||
| 	}() | ||||
| 	var resumeRV string | ||||
| 	err := g.watchHandler(fw, &resumeRV, nevererrc, wait.NeverStop) | ||||
| 	if err == nil { | ||||
| 		t.Errorf("unexpected non-error") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestReflectorWatchHandler(t *testing.T) { | ||||
| 	s := NewStore(MetaNamespaceKeyFunc) | ||||
| 	g := NewReflector(&testLW{}, &v1.Pod{}, s, 0) | ||||
| 	fw := watch.NewFake() | ||||
| 	s.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}) | ||||
| 	s.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}}) | ||||
| 	go func() { | ||||
| 		fw.Add(&v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "rejected"}}) | ||||
| 		fw.Delete(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}) | ||||
| 		fw.Modify(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar", ResourceVersion: "55"}}) | ||||
| 		fw.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "baz", ResourceVersion: "32"}}) | ||||
| 		fw.Stop() | ||||
| 	}() | ||||
| 	var resumeRV string | ||||
| 	err := g.watchHandler(fw, &resumeRV, nevererrc, wait.NeverStop) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("unexpected error %v", err) | ||||
| 	} | ||||
|  | ||||
| 	mkPod := func(id string, rv string) *v1.Pod { | ||||
| 		return &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: id, ResourceVersion: rv}} | ||||
| 	} | ||||
|  | ||||
| 	table := []struct { | ||||
| 		Pod    *v1.Pod | ||||
| 		exists bool | ||||
| 	}{ | ||||
| 		{mkPod("foo", ""), false}, | ||||
| 		{mkPod("rejected", ""), false}, | ||||
| 		{mkPod("bar", "55"), true}, | ||||
| 		{mkPod("baz", "32"), true}, | ||||
| 	} | ||||
| 	for _, item := range table { | ||||
| 		obj, exists, _ := s.Get(item.Pod) | ||||
| 		if e, a := item.exists, exists; e != a { | ||||
| 			t.Errorf("%v: expected %v, got %v", item.Pod, e, a) | ||||
| 		} | ||||
| 		if !exists { | ||||
| 			continue | ||||
| 		} | ||||
| 		if e, a := item.Pod.ResourceVersion, obj.(*v1.Pod).ResourceVersion; e != a { | ||||
| 			t.Errorf("%v: expected %v, got %v", item.Pod, e, a) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// RV should send the last version we see. | ||||
| 	if e, a := "32", resumeRV; e != a { | ||||
| 		t.Errorf("expected %v, got %v", e, a) | ||||
| 	} | ||||
|  | ||||
| 	// last sync resource version should be the last version synced with store | ||||
| 	if e, a := "32", g.LastSyncResourceVersion(); e != a { | ||||
| 		t.Errorf("expected %v, got %v", e, a) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestReflectorStopWatch(t *testing.T) { | ||||
| 	s := NewStore(MetaNamespaceKeyFunc) | ||||
| 	g := NewReflector(&testLW{}, &v1.Pod{}, s, 0) | ||||
| 	fw := watch.NewFake() | ||||
| 	var resumeRV string | ||||
| 	stopWatch := make(chan struct{}, 1) | ||||
| 	stopWatch <- struct{}{} | ||||
| 	err := g.watchHandler(fw, &resumeRV, nevererrc, stopWatch) | ||||
| 	if err != errorStopRequested { | ||||
| 		t.Errorf("expected stop error, got %q", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestReflectorListAndWatch(t *testing.T) { | ||||
| 	createdFakes := make(chan *watch.FakeWatcher) | ||||
|  | ||||
| 	// The ListFunc says that it's at revision 1. Therefore, we expect our WatchFunc | ||||
| 	// to get called at the beginning of the watch with 1, and again with 3 when we | ||||
| 	// inject an error. | ||||
| 	expectedRVs := []string{"1", "3"} | ||||
| 	lw := &testLW{ | ||||
| 		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { | ||||
| 			rv := options.ResourceVersion | ||||
| 			fw := watch.NewFake() | ||||
| 			if e, a := expectedRVs[0], rv; e != a { | ||||
| 				t.Errorf("Expected rv %v, but got %v", e, a) | ||||
| 			} | ||||
| 			expectedRVs = expectedRVs[1:] | ||||
| 			// channel is not buffered because the for loop below needs to block. But | ||||
| 			// we don't want to block here, so report the new fake via a go routine. | ||||
| 			go func() { createdFakes <- fw }() | ||||
| 			return fw, nil | ||||
| 		}, | ||||
| 		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { | ||||
| 			return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "1"}}, nil | ||||
| 		}, | ||||
| 	} | ||||
| 	s := NewFIFO(MetaNamespaceKeyFunc) | ||||
| 	r := NewReflector(lw, &v1.Pod{}, s, 0) | ||||
| 	go r.ListAndWatch(wait.NeverStop) | ||||
|  | ||||
| 	ids := []string{"foo", "bar", "baz", "qux", "zoo"} | ||||
| 	var fw *watch.FakeWatcher | ||||
| 	for i, id := range ids { | ||||
| 		if fw == nil { | ||||
| 			fw = <-createdFakes | ||||
| 		} | ||||
| 		sendingRV := strconv.FormatUint(uint64(i+2), 10) | ||||
| 		fw.Add(&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: id, ResourceVersion: sendingRV}}) | ||||
| 		if sendingRV == "3" { | ||||
| 			// Inject a failure. | ||||
| 			fw.Stop() | ||||
| 			fw = nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Verify we received the right ids with the right resource versions. | ||||
| 	for i, id := range ids { | ||||
| 		pod := Pop(s).(*v1.Pod) | ||||
| 		if e, a := id, pod.Name; e != a { | ||||
| 			t.Errorf("%v: Expected %v, got %v", i, e, a) | ||||
| 		} | ||||
| 		if e, a := strconv.FormatUint(uint64(i+2), 10), pod.ResourceVersion; e != a { | ||||
| 			t.Errorf("%v: Expected %v, got %v", i, e, a) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(expectedRVs) != 0 { | ||||
| 		t.Error("called watchStarter an unexpected number of times") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestReflectorListAndWatchWithErrors(t *testing.T) { | ||||
| 	mkPod := func(id string, rv string) *v1.Pod { | ||||
| 		return &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: id, ResourceVersion: rv}} | ||||
| 	} | ||||
| 	mkList := func(rv string, pods ...*v1.Pod) *v1.PodList { | ||||
| 		list := &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: rv}} | ||||
| 		for _, pod := range pods { | ||||
| 			list.Items = append(list.Items, *pod) | ||||
| 		} | ||||
| 		return list | ||||
| 	} | ||||
| 	table := []struct { | ||||
| 		list     *v1.PodList | ||||
| 		listErr  error | ||||
| 		events   []watch.Event | ||||
| 		watchErr error | ||||
| 	}{ | ||||
| 		{ | ||||
| 			list: mkList("1"), | ||||
| 			events: []watch.Event{ | ||||
| 				{Type: watch.Added, Object: mkPod("foo", "2")}, | ||||
| 				{Type: watch.Added, Object: mkPod("bar", "3")}, | ||||
| 			}, | ||||
| 		}, { | ||||
| 			list: mkList("3", mkPod("foo", "2"), mkPod("bar", "3")), | ||||
| 			events: []watch.Event{ | ||||
| 				{Type: watch.Deleted, Object: mkPod("foo", "4")}, | ||||
| 				{Type: watch.Added, Object: mkPod("qux", "5")}, | ||||
| 			}, | ||||
| 		}, { | ||||
| 			listErr: fmt.Errorf("a list error"), | ||||
| 		}, { | ||||
| 			list:     mkList("5", mkPod("bar", "3"), mkPod("qux", "5")), | ||||
| 			watchErr: fmt.Errorf("a watch error"), | ||||
| 		}, { | ||||
| 			list: mkList("5", mkPod("bar", "3"), mkPod("qux", "5")), | ||||
| 			events: []watch.Event{ | ||||
| 				{Type: watch.Added, Object: mkPod("baz", "6")}, | ||||
| 			}, | ||||
| 		}, { | ||||
| 			list: mkList("6", mkPod("bar", "3"), mkPod("qux", "5"), mkPod("baz", "6")), | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	s := NewFIFO(MetaNamespaceKeyFunc) | ||||
| 	for line, item := range table { | ||||
| 		if item.list != nil { | ||||
| 			// Test that the list is what currently exists in the store. | ||||
| 			current := s.List() | ||||
| 			checkMap := map[string]string{} | ||||
| 			for _, item := range current { | ||||
| 				pod := item.(*v1.Pod) | ||||
| 				checkMap[pod.Name] = pod.ResourceVersion | ||||
| 			} | ||||
| 			for _, pod := range item.list.Items { | ||||
| 				if e, a := pod.ResourceVersion, checkMap[pod.Name]; e != a { | ||||
| 					t.Errorf("%v: expected %v, got %v for pod %v", line, e, a, pod.Name) | ||||
| 				} | ||||
| 			} | ||||
| 			if e, a := len(item.list.Items), len(checkMap); e != a { | ||||
| 				t.Errorf("%v: expected %v, got %v", line, e, a) | ||||
| 			} | ||||
| 		} | ||||
| 		watchRet, watchErr := item.events, item.watchErr | ||||
| 		lw := &testLW{ | ||||
| 			WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { | ||||
| 				if watchErr != nil { | ||||
| 					return nil, watchErr | ||||
| 				} | ||||
| 				watchErr = fmt.Errorf("second watch") | ||||
| 				fw := watch.NewFake() | ||||
| 				go func() { | ||||
| 					for _, e := range watchRet { | ||||
| 						fw.Action(e.Type, e.Object) | ||||
| 					} | ||||
| 					fw.Stop() | ||||
| 				}() | ||||
| 				return fw, nil | ||||
| 			}, | ||||
| 			ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { | ||||
| 				return item.list, item.listErr | ||||
| 			}, | ||||
| 		} | ||||
| 		r := NewReflector(lw, &v1.Pod{}, s, 0) | ||||
| 		r.ListAndWatch(wait.NeverStop) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestReflectorResync(t *testing.T) { | ||||
| 	iteration := 0 | ||||
| 	stopCh := make(chan struct{}) | ||||
| 	rerr := errors.New("expected resync reached") | ||||
| 	s := &FakeCustomStore{ | ||||
| 		ResyncFunc: func() error { | ||||
| 			iteration++ | ||||
| 			if iteration == 2 { | ||||
| 				return rerr | ||||
| 			} | ||||
| 			return nil | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	lw := &testLW{ | ||||
| 		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { | ||||
| 			fw := watch.NewFake() | ||||
| 			return fw, nil | ||||
| 		}, | ||||
| 		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { | ||||
| 			return &v1.PodList{ListMeta: metav1.ListMeta{ResourceVersion: "0"}}, nil | ||||
| 		}, | ||||
| 	} | ||||
| 	resyncPeriod := 1 * time.Millisecond | ||||
| 	r := NewReflector(lw, &v1.Pod{}, s, resyncPeriod) | ||||
| 	if err := r.ListAndWatch(stopCh); err != nil { | ||||
| 		// error from Resync is not propaged up to here. | ||||
| 		t.Errorf("expected error %v", err) | ||||
| 	} | ||||
| 	if iteration != 2 { | ||||
| 		t.Errorf("exactly 2 iterations were expected, got: %v", iteration) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										421
									
								
								pkg/client/cache/shared_informer.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										421
									
								
								pkg/client/cache/shared_informer.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,421 +0,0 @@ | ||||
| /* | ||||
| Copyright 2015 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	utilruntime "k8s.io/apimachinery/pkg/util/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/util/wait" | ||||
|  | ||||
| 	"github.com/golang/glog" | ||||
| ) | ||||
|  | ||||
| // if you use this, there is one behavior change compared to a standard Informer. | ||||
| // When you receive a notification, the cache will be AT LEAST as fresh as the | ||||
| // notification, but it MAY be more fresh.  You should NOT depend on the contents | ||||
| // of the cache exactly matching the notification you've received in handler | ||||
| // functions.  If there was a create, followed by a delete, the cache may NOT | ||||
| // have your item.  This has advantages over the broadcaster since it allows us | ||||
| // to share a common cache across many controllers. Extending the broadcaster | ||||
| // would have required us keep duplicate caches for each watch. | ||||
| type SharedInformer interface { | ||||
| 	// events to a single handler are delivered sequentially, but there is no coordination between different handlers | ||||
| 	// You may NOT add a handler *after* the SharedInformer is running.  That will result in an error being returned. | ||||
| 	// TODO we should try to remove this restriction eventually. | ||||
| 	AddEventHandler(handler ResourceEventHandler) error | ||||
| 	GetStore() Store | ||||
| 	// GetController gives back a synthetic interface that "votes" to start the informer | ||||
| 	GetController() Controller | ||||
| 	Run(stopCh <-chan struct{}) | ||||
| 	HasSynced() bool | ||||
| 	LastSyncResourceVersion() string | ||||
| } | ||||
|  | ||||
| type SharedIndexInformer interface { | ||||
| 	SharedInformer | ||||
| 	// AddIndexers add indexers to the informer before it starts. | ||||
| 	AddIndexers(indexers Indexers) error | ||||
| 	GetIndexer() Indexer | ||||
| } | ||||
|  | ||||
| // NewSharedInformer creates a new instance for the listwatcher. | ||||
| // TODO: create a cache/factory of these at a higher level for the list all, watch all of a given resource that can | ||||
| // be shared amongst all consumers. | ||||
| func NewSharedInformer(lw ListerWatcher, objType runtime.Object, resyncPeriod time.Duration) SharedInformer { | ||||
| 	return NewSharedIndexInformer(lw, objType, resyncPeriod, Indexers{}) | ||||
| } | ||||
|  | ||||
| // NewSharedIndexInformer creates a new instance for the listwatcher. | ||||
| // TODO: create a cache/factory of these at a higher level for the list all, watch all of a given resource that can | ||||
| // be shared amongst all consumers. | ||||
| func NewSharedIndexInformer(lw ListerWatcher, objType runtime.Object, resyncPeriod time.Duration, indexers Indexers) SharedIndexInformer { | ||||
| 	sharedIndexInformer := &sharedIndexInformer{ | ||||
| 		processor:             &sharedProcessor{}, | ||||
| 		indexer:               NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, indexers), | ||||
| 		listerWatcher:         lw, | ||||
| 		objectType:            objType, | ||||
| 		fullResyncPeriod:      resyncPeriod, | ||||
| 		cacheMutationDetector: NewCacheMutationDetector(fmt.Sprintf("%T", objType)), | ||||
| 	} | ||||
| 	return sharedIndexInformer | ||||
| } | ||||
|  | ||||
| // InformerSynced is a function that can be used to determine if an informer has synced.  This is useful for determining if caches have synced. | ||||
| type InformerSynced func() bool | ||||
|  | ||||
| // syncedPollPeriod controls how often you look at the status of your sync funcs | ||||
| const syncedPollPeriod = 100 * time.Millisecond | ||||
|  | ||||
| // WaitForCacheSync waits for caches to populate.  It returns true if it was successful, false | ||||
| // if the contoller should shutdown | ||||
| func WaitForCacheSync(stopCh <-chan struct{}, cacheSyncs ...InformerSynced) bool { | ||||
| 	err := wait.PollUntil(syncedPollPeriod, | ||||
| 		func() (bool, error) { | ||||
| 			for _, syncFunc := range cacheSyncs { | ||||
| 				if !syncFunc() { | ||||
| 					return false, nil | ||||
| 				} | ||||
| 			} | ||||
| 			return true, nil | ||||
| 		}, | ||||
| 		stopCh) | ||||
| 	if err != nil { | ||||
| 		glog.V(2).Infof("stop requested") | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	glog.V(4).Infof("caches populated") | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| type sharedIndexInformer struct { | ||||
| 	indexer    Indexer | ||||
| 	controller Controller | ||||
|  | ||||
| 	processor             *sharedProcessor | ||||
| 	cacheMutationDetector CacheMutationDetector | ||||
|  | ||||
| 	// This block is tracked to handle late initialization of the controller | ||||
| 	listerWatcher    ListerWatcher | ||||
| 	objectType       runtime.Object | ||||
| 	fullResyncPeriod time.Duration | ||||
|  | ||||
| 	started     bool | ||||
| 	startedLock sync.Mutex | ||||
|  | ||||
| 	// blockDeltas gives a way to stop all event distribution so that a late event handler | ||||
| 	// can safely join the shared informer. | ||||
| 	blockDeltas sync.Mutex | ||||
| 	// stopCh is the channel used to stop the main Run process.  We have to track it so that | ||||
| 	// late joiners can have a proper stop | ||||
| 	stopCh <-chan struct{} | ||||
| } | ||||
|  | ||||
| // dummyController hides the fact that a SharedInformer is different from a dedicated one | ||||
| // where a caller can `Run`.  The run method is disonnected in this case, because higher | ||||
| // level logic will decide when to start the SharedInformer and related controller. | ||||
| // Because returning information back is always asynchronous, the legacy callers shouldn't | ||||
| // notice any change in behavior. | ||||
| type dummyController struct { | ||||
| 	informer *sharedIndexInformer | ||||
| } | ||||
|  | ||||
| func (v *dummyController) Run(stopCh <-chan struct{}) { | ||||
| } | ||||
|  | ||||
| func (v *dummyController) HasSynced() bool { | ||||
| 	return v.informer.HasSynced() | ||||
| } | ||||
|  | ||||
| func (c *dummyController) LastSyncResourceVersion() string { | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| type updateNotification struct { | ||||
| 	oldObj interface{} | ||||
| 	newObj interface{} | ||||
| } | ||||
|  | ||||
| type addNotification struct { | ||||
| 	newObj interface{} | ||||
| } | ||||
|  | ||||
| type deleteNotification struct { | ||||
| 	oldObj interface{} | ||||
| } | ||||
|  | ||||
| func (s *sharedIndexInformer) Run(stopCh <-chan struct{}) { | ||||
| 	defer utilruntime.HandleCrash() | ||||
|  | ||||
| 	fifo := NewDeltaFIFO(MetaNamespaceKeyFunc, nil, s.indexer) | ||||
|  | ||||
| 	cfg := &Config{ | ||||
| 		Queue:            fifo, | ||||
| 		ListerWatcher:    s.listerWatcher, | ||||
| 		ObjectType:       s.objectType, | ||||
| 		FullResyncPeriod: s.fullResyncPeriod, | ||||
| 		RetryOnError:     false, | ||||
|  | ||||
| 		Process: s.HandleDeltas, | ||||
| 	} | ||||
|  | ||||
| 	func() { | ||||
| 		s.startedLock.Lock() | ||||
| 		defer s.startedLock.Unlock() | ||||
|  | ||||
| 		s.controller = New(cfg) | ||||
| 		s.started = true | ||||
| 	}() | ||||
|  | ||||
| 	s.stopCh = stopCh | ||||
| 	s.cacheMutationDetector.Run(stopCh) | ||||
| 	s.processor.run(stopCh) | ||||
| 	s.controller.Run(stopCh) | ||||
| } | ||||
|  | ||||
| func (s *sharedIndexInformer) isStarted() bool { | ||||
| 	s.startedLock.Lock() | ||||
| 	defer s.startedLock.Unlock() | ||||
| 	return s.started | ||||
| } | ||||
|  | ||||
| func (s *sharedIndexInformer) HasSynced() bool { | ||||
| 	s.startedLock.Lock() | ||||
| 	defer s.startedLock.Unlock() | ||||
|  | ||||
| 	if s.controller == nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	return s.controller.HasSynced() | ||||
| } | ||||
|  | ||||
| func (s *sharedIndexInformer) LastSyncResourceVersion() string { | ||||
| 	s.startedLock.Lock() | ||||
| 	defer s.startedLock.Unlock() | ||||
|  | ||||
| 	if s.controller == nil { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return s.controller.LastSyncResourceVersion() | ||||
| } | ||||
|  | ||||
| func (s *sharedIndexInformer) GetStore() Store { | ||||
| 	return s.indexer | ||||
| } | ||||
|  | ||||
| func (s *sharedIndexInformer) GetIndexer() Indexer { | ||||
| 	return s.indexer | ||||
| } | ||||
|  | ||||
| func (s *sharedIndexInformer) AddIndexers(indexers Indexers) error { | ||||
| 	s.startedLock.Lock() | ||||
| 	defer s.startedLock.Unlock() | ||||
|  | ||||
| 	if s.started { | ||||
| 		return fmt.Errorf("informer has already started") | ||||
| 	} | ||||
|  | ||||
| 	return s.indexer.AddIndexers(indexers) | ||||
| } | ||||
|  | ||||
| func (s *sharedIndexInformer) GetController() Controller { | ||||
| 	return &dummyController{informer: s} | ||||
| } | ||||
|  | ||||
| func (s *sharedIndexInformer) AddEventHandler(handler ResourceEventHandler) error { | ||||
| 	s.startedLock.Lock() | ||||
| 	defer s.startedLock.Unlock() | ||||
|  | ||||
| 	if !s.started { | ||||
| 		listener := newProcessListener(handler) | ||||
| 		s.processor.listeners = append(s.processor.listeners, listener) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	// in order to safely join, we have to | ||||
| 	// 1. stop sending add/update/delete notifications | ||||
| 	// 2. do a list against the store | ||||
| 	// 3. send synthetic "Add" events to the new handler | ||||
| 	// 4. unblock | ||||
| 	s.blockDeltas.Lock() | ||||
| 	defer s.blockDeltas.Unlock() | ||||
|  | ||||
| 	listener := newProcessListener(handler) | ||||
| 	s.processor.listeners = append(s.processor.listeners, listener) | ||||
|  | ||||
| 	go listener.run(s.stopCh) | ||||
| 	go listener.pop(s.stopCh) | ||||
|  | ||||
| 	items := s.indexer.List() | ||||
| 	for i := range items { | ||||
| 		listener.add(addNotification{newObj: items[i]}) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (s *sharedIndexInformer) HandleDeltas(obj interface{}) error { | ||||
| 	s.blockDeltas.Lock() | ||||
| 	defer s.blockDeltas.Unlock() | ||||
|  | ||||
| 	// from oldest to newest | ||||
| 	for _, d := range obj.(Deltas) { | ||||
| 		switch d.Type { | ||||
| 		case Sync, Added, Updated: | ||||
| 			s.cacheMutationDetector.AddObject(d.Object) | ||||
| 			if old, exists, err := s.indexer.Get(d.Object); err == nil && exists { | ||||
| 				if err := s.indexer.Update(d.Object); err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 				s.processor.distribute(updateNotification{oldObj: old, newObj: d.Object}) | ||||
| 			} else { | ||||
| 				if err := s.indexer.Add(d.Object); err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 				s.processor.distribute(addNotification{newObj: d.Object}) | ||||
| 			} | ||||
| 		case Deleted: | ||||
| 			if err := s.indexer.Delete(d.Object); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			s.processor.distribute(deleteNotification{oldObj: d.Object}) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type sharedProcessor struct { | ||||
| 	listeners []*processorListener | ||||
| } | ||||
|  | ||||
| func (p *sharedProcessor) distribute(obj interface{}) { | ||||
| 	for _, listener := range p.listeners { | ||||
| 		listener.add(obj) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *sharedProcessor) run(stopCh <-chan struct{}) { | ||||
| 	for _, listener := range p.listeners { | ||||
| 		go listener.run(stopCh) | ||||
| 		go listener.pop(stopCh) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| type processorListener struct { | ||||
| 	// lock/cond protects access to 'pendingNotifications'. | ||||
| 	lock sync.RWMutex | ||||
| 	cond sync.Cond | ||||
|  | ||||
| 	// pendingNotifications is an unbounded slice that holds all notifications not yet distributed | ||||
| 	// there is one per listener, but a failing/stalled listener will have infinite pendingNotifications | ||||
| 	// added until we OOM. | ||||
| 	// TODO This is no worse that before, since reflectors were backed by unbounded DeltaFIFOs, but | ||||
| 	// we should try to do something better | ||||
| 	pendingNotifications []interface{} | ||||
|  | ||||
| 	nextCh chan interface{} | ||||
|  | ||||
| 	handler ResourceEventHandler | ||||
| } | ||||
|  | ||||
| func newProcessListener(handler ResourceEventHandler) *processorListener { | ||||
| 	ret := &processorListener{ | ||||
| 		pendingNotifications: []interface{}{}, | ||||
| 		nextCh:               make(chan interface{}), | ||||
| 		handler:              handler, | ||||
| 	} | ||||
|  | ||||
| 	ret.cond.L = &ret.lock | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| func (p *processorListener) add(notification interface{}) { | ||||
| 	p.lock.Lock() | ||||
| 	defer p.lock.Unlock() | ||||
|  | ||||
| 	p.pendingNotifications = append(p.pendingNotifications, notification) | ||||
| 	p.cond.Broadcast() | ||||
| } | ||||
|  | ||||
| func (p *processorListener) pop(stopCh <-chan struct{}) { | ||||
| 	defer utilruntime.HandleCrash() | ||||
|  | ||||
| 	for { | ||||
| 		blockingGet := func() (interface{}, bool) { | ||||
| 			p.lock.Lock() | ||||
| 			defer p.lock.Unlock() | ||||
|  | ||||
| 			for len(p.pendingNotifications) == 0 { | ||||
| 				// check if we're shutdown | ||||
| 				select { | ||||
| 				case <-stopCh: | ||||
| 					return nil, true | ||||
| 				default: | ||||
| 				} | ||||
| 				p.cond.Wait() | ||||
| 			} | ||||
|  | ||||
| 			nt := p.pendingNotifications[0] | ||||
| 			p.pendingNotifications = p.pendingNotifications[1:] | ||||
| 			return nt, false | ||||
| 		} | ||||
|  | ||||
| 		notification, stopped := blockingGet() | ||||
| 		if stopped { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		select { | ||||
| 		case <-stopCh: | ||||
| 			return | ||||
| 		case p.nextCh <- notification: | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *processorListener) run(stopCh <-chan struct{}) { | ||||
| 	defer utilruntime.HandleCrash() | ||||
|  | ||||
| 	for { | ||||
| 		var next interface{} | ||||
| 		select { | ||||
| 		case <-stopCh: | ||||
| 			func() { | ||||
| 				p.lock.Lock() | ||||
| 				defer p.lock.Unlock() | ||||
| 				p.cond.Broadcast() | ||||
| 			}() | ||||
| 			return | ||||
| 		case next = <-p.nextCh: | ||||
| 		} | ||||
|  | ||||
| 		switch notification := next.(type) { | ||||
| 		case updateNotification: | ||||
| 			p.handler.OnUpdate(notification.oldObj, notification.newObj) | ||||
| 		case addNotification: | ||||
| 			p.handler.OnAdd(notification.newObj) | ||||
| 		case deleteNotification: | ||||
| 			p.handler.OnDelete(notification.oldObj) | ||||
| 		default: | ||||
| 			utilruntime.HandleError(fmt.Errorf("unrecognized notification: %#v", next)) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										240
									
								
								pkg/client/cache/store.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										240
									
								
								pkg/client/cache/store.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,240 +0,0 @@ | ||||
| /* | ||||
| Copyright 2014 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/api/meta" | ||||
| ) | ||||
|  | ||||
| // Store is a generic object storage interface. Reflector knows how to watch a server | ||||
| // and update a store. A generic store is provided, which allows Reflector to be used | ||||
| // as a local caching system, and an LRU store, which allows Reflector to work like a | ||||
| // queue of items yet to be processed. | ||||
| // | ||||
| // Store makes no assumptions about stored object identity; it is the responsibility | ||||
| // of a Store implementation to provide a mechanism to correctly key objects and to | ||||
| // define the contract for obtaining objects by some arbitrary key type. | ||||
| type Store interface { | ||||
| 	Add(obj interface{}) error | ||||
| 	Update(obj interface{}) error | ||||
| 	Delete(obj interface{}) error | ||||
| 	List() []interface{} | ||||
| 	ListKeys() []string | ||||
| 	Get(obj interface{}) (item interface{}, exists bool, err error) | ||||
| 	GetByKey(key string) (item interface{}, exists bool, err error) | ||||
|  | ||||
| 	// Replace will delete the contents of the store, using instead the | ||||
| 	// given list. Store takes ownership of the list, you should not reference | ||||
| 	// it after calling this function. | ||||
| 	Replace([]interface{}, string) error | ||||
| 	Resync() error | ||||
| } | ||||
|  | ||||
| // KeyFunc knows how to make a key from an object. Implementations should be deterministic. | ||||
| type KeyFunc func(obj interface{}) (string, error) | ||||
|  | ||||
| // KeyError will be returned any time a KeyFunc gives an error; it includes the object | ||||
| // at fault. | ||||
| type KeyError struct { | ||||
| 	Obj interface{} | ||||
| 	Err error | ||||
| } | ||||
|  | ||||
| // Error gives a human-readable description of the error. | ||||
| func (k KeyError) Error() string { | ||||
| 	return fmt.Sprintf("couldn't create key for object %+v: %v", k.Obj, k.Err) | ||||
| } | ||||
|  | ||||
| // ExplicitKey can be passed to MetaNamespaceKeyFunc if you have the key for | ||||
| // the object but not the object itself. | ||||
| type ExplicitKey string | ||||
|  | ||||
| // MetaNamespaceKeyFunc is a convenient default KeyFunc which knows how to make | ||||
| // keys for API objects which implement meta.Interface. | ||||
| // The key uses the format <namespace>/<name> unless <namespace> is empty, then | ||||
| // it's just <name>. | ||||
| // | ||||
| // TODO: replace key-as-string with a key-as-struct so that this | ||||
| // packing/unpacking won't be necessary. | ||||
| func MetaNamespaceKeyFunc(obj interface{}) (string, error) { | ||||
| 	if key, ok := obj.(ExplicitKey); ok { | ||||
| 		return string(key), nil | ||||
| 	} | ||||
| 	meta, err := meta.Accessor(obj) | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("object has no meta: %v", err) | ||||
| 	} | ||||
| 	if len(meta.GetNamespace()) > 0 { | ||||
| 		return meta.GetNamespace() + "/" + meta.GetName(), nil | ||||
| 	} | ||||
| 	return meta.GetName(), nil | ||||
| } | ||||
|  | ||||
| // SplitMetaNamespaceKey returns the namespace and name that | ||||
| // MetaNamespaceKeyFunc encoded into key. | ||||
| // | ||||
| // TODO: replace key-as-string with a key-as-struct so that this | ||||
| // packing/unpacking won't be necessary. | ||||
| func SplitMetaNamespaceKey(key string) (namespace, name string, err error) { | ||||
| 	parts := strings.Split(key, "/") | ||||
| 	switch len(parts) { | ||||
| 	case 1: | ||||
| 		// name only, no namespace | ||||
| 		return "", parts[0], nil | ||||
| 	case 2: | ||||
| 		// namespace and name | ||||
| 		return parts[0], parts[1], nil | ||||
| 	} | ||||
|  | ||||
| 	return "", "", fmt.Errorf("unexpected key format: %q", key) | ||||
| } | ||||
|  | ||||
| // cache responsibilities are limited to: | ||||
| //	1. Computing keys for objects via keyFunc | ||||
| //  2. Invoking methods of a ThreadSafeStorage interface | ||||
| type cache struct { | ||||
| 	// cacheStorage bears the burden of thread safety for the cache | ||||
| 	cacheStorage ThreadSafeStore | ||||
| 	// keyFunc is used to make the key for objects stored in and retrieved from items, and | ||||
| 	// should be deterministic. | ||||
| 	keyFunc KeyFunc | ||||
| } | ||||
|  | ||||
| var _ Store = &cache{} | ||||
|  | ||||
| // Add inserts an item into the cache. | ||||
| func (c *cache) Add(obj interface{}) error { | ||||
| 	key, err := c.keyFunc(obj) | ||||
| 	if err != nil { | ||||
| 		return KeyError{obj, err} | ||||
| 	} | ||||
| 	c.cacheStorage.Add(key, obj) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Update sets an item in the cache to its updated state. | ||||
| func (c *cache) Update(obj interface{}) error { | ||||
| 	key, err := c.keyFunc(obj) | ||||
| 	if err != nil { | ||||
| 		return KeyError{obj, err} | ||||
| 	} | ||||
| 	c.cacheStorage.Update(key, obj) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Delete removes an item from the cache. | ||||
| func (c *cache) Delete(obj interface{}) error { | ||||
| 	key, err := c.keyFunc(obj) | ||||
| 	if err != nil { | ||||
| 		return KeyError{obj, err} | ||||
| 	} | ||||
| 	c.cacheStorage.Delete(key) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // List returns a list of all the items. | ||||
| // List is completely threadsafe as long as you treat all items as immutable. | ||||
| func (c *cache) List() []interface{} { | ||||
| 	return c.cacheStorage.List() | ||||
| } | ||||
|  | ||||
| // ListKeys returns a list of all the keys of the objects currently | ||||
| // in the cache. | ||||
| func (c *cache) ListKeys() []string { | ||||
| 	return c.cacheStorage.ListKeys() | ||||
| } | ||||
|  | ||||
| // GetIndexers returns the indexers of cache | ||||
| func (c *cache) GetIndexers() Indexers { | ||||
| 	return c.cacheStorage.GetIndexers() | ||||
| } | ||||
|  | ||||
| // Index returns a list of items that match on the index function | ||||
| // Index is thread-safe so long as you treat all items as immutable | ||||
| func (c *cache) Index(indexName string, obj interface{}) ([]interface{}, error) { | ||||
| 	return c.cacheStorage.Index(indexName, obj) | ||||
| } | ||||
|  | ||||
| // ListIndexFuncValues returns the list of generated values of an Index func | ||||
| func (c *cache) ListIndexFuncValues(indexName string) []string { | ||||
| 	return c.cacheStorage.ListIndexFuncValues(indexName) | ||||
| } | ||||
|  | ||||
| func (c *cache) ByIndex(indexName, indexKey string) ([]interface{}, error) { | ||||
| 	return c.cacheStorage.ByIndex(indexName, indexKey) | ||||
| } | ||||
|  | ||||
| func (c *cache) AddIndexers(newIndexers Indexers) error { | ||||
| 	return c.cacheStorage.AddIndexers(newIndexers) | ||||
| } | ||||
|  | ||||
| // Get returns the requested item, or sets exists=false. | ||||
| // Get is completely threadsafe as long as you treat all items as immutable. | ||||
| func (c *cache) Get(obj interface{}) (item interface{}, exists bool, err error) { | ||||
| 	key, err := c.keyFunc(obj) | ||||
| 	if err != nil { | ||||
| 		return nil, false, KeyError{obj, err} | ||||
| 	} | ||||
| 	return c.GetByKey(key) | ||||
| } | ||||
|  | ||||
| // GetByKey returns the request item, or exists=false. | ||||
| // GetByKey is completely threadsafe as long as you treat all items as immutable. | ||||
| func (c *cache) GetByKey(key string) (item interface{}, exists bool, err error) { | ||||
| 	item, exists = c.cacheStorage.Get(key) | ||||
| 	return item, exists, nil | ||||
| } | ||||
|  | ||||
| // Replace will delete the contents of 'c', using instead the given list. | ||||
| // 'c' takes ownership of the list, you should not reference the list again | ||||
| // after calling this function. | ||||
| func (c *cache) Replace(list []interface{}, resourceVersion string) error { | ||||
| 	items := map[string]interface{}{} | ||||
| 	for _, item := range list { | ||||
| 		key, err := c.keyFunc(item) | ||||
| 		if err != nil { | ||||
| 			return KeyError{item, err} | ||||
| 		} | ||||
| 		items[key] = item | ||||
| 	} | ||||
| 	c.cacheStorage.Replace(items, resourceVersion) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Resync touches all items in the store to force processing | ||||
| func (c *cache) Resync() error { | ||||
| 	return c.cacheStorage.Resync() | ||||
| } | ||||
|  | ||||
| // NewStore returns a Store implemented simply with a map and a lock. | ||||
| func NewStore(keyFunc KeyFunc) Store { | ||||
| 	return &cache{ | ||||
| 		cacheStorage: NewThreadSafeStore(Indexers{}, Indices{}), | ||||
| 		keyFunc:      keyFunc, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewIndexer returns an Indexer implemented simply with a map and a lock. | ||||
| func NewIndexer(keyFunc KeyFunc, indexers Indexers) Indexer { | ||||
| 	return &cache{ | ||||
| 		cacheStorage: NewThreadSafeStore(indexers, Indices{}), | ||||
| 		keyFunc:      keyFunc, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										156
									
								
								pkg/client/cache/store_test.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										156
									
								
								pkg/client/cache/store_test.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,156 +0,0 @@ | ||||
| /* | ||||
| Copyright 2014 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| ) | ||||
|  | ||||
| // Test public interface | ||||
| func doTestStore(t *testing.T, store Store) { | ||||
| 	mkObj := func(id string, val string) testStoreObject { | ||||
| 		return testStoreObject{id: id, val: val} | ||||
| 	} | ||||
|  | ||||
| 	store.Add(mkObj("foo", "bar")) | ||||
| 	if item, ok, _ := store.Get(mkObj("foo", "")); !ok { | ||||
| 		t.Errorf("didn't find inserted item") | ||||
| 	} else { | ||||
| 		if e, a := "bar", item.(testStoreObject).val; e != a { | ||||
| 			t.Errorf("expected %v, got %v", e, a) | ||||
| 		} | ||||
| 	} | ||||
| 	store.Update(mkObj("foo", "baz")) | ||||
| 	if item, ok, _ := store.Get(mkObj("foo", "")); !ok { | ||||
| 		t.Errorf("didn't find inserted item") | ||||
| 	} else { | ||||
| 		if e, a := "baz", item.(testStoreObject).val; e != a { | ||||
| 			t.Errorf("expected %v, got %v", e, a) | ||||
| 		} | ||||
| 	} | ||||
| 	store.Delete(mkObj("foo", "")) | ||||
| 	if _, ok, _ := store.Get(mkObj("foo", "")); ok { | ||||
| 		t.Errorf("found deleted item??") | ||||
| 	} | ||||
|  | ||||
| 	// Test List. | ||||
| 	store.Add(mkObj("a", "b")) | ||||
| 	store.Add(mkObj("c", "d")) | ||||
| 	store.Add(mkObj("e", "e")) | ||||
| 	{ | ||||
| 		found := sets.String{} | ||||
| 		for _, item := range store.List() { | ||||
| 			found.Insert(item.(testStoreObject).val) | ||||
| 		} | ||||
| 		if !found.HasAll("b", "d", "e") { | ||||
| 			t.Errorf("missing items, found: %v", found) | ||||
| 		} | ||||
| 		if len(found) != 3 { | ||||
| 			t.Errorf("extra items") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Test Replace. | ||||
| 	store.Replace([]interface{}{ | ||||
| 		mkObj("foo", "foo"), | ||||
| 		mkObj("bar", "bar"), | ||||
| 	}, "0") | ||||
|  | ||||
| 	{ | ||||
| 		found := sets.String{} | ||||
| 		for _, item := range store.List() { | ||||
| 			found.Insert(item.(testStoreObject).val) | ||||
| 		} | ||||
| 		if !found.HasAll("foo", "bar") { | ||||
| 			t.Errorf("missing items") | ||||
| 		} | ||||
| 		if len(found) != 2 { | ||||
| 			t.Errorf("extra items") | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Test public interface | ||||
| func doTestIndex(t *testing.T, indexer Indexer) { | ||||
| 	mkObj := func(id string, val string) testStoreObject { | ||||
| 		return testStoreObject{id: id, val: val} | ||||
| 	} | ||||
|  | ||||
| 	// Test Index | ||||
| 	expected := map[string]sets.String{} | ||||
| 	expected["b"] = sets.NewString("a", "c") | ||||
| 	expected["f"] = sets.NewString("e") | ||||
| 	expected["h"] = sets.NewString("g") | ||||
| 	indexer.Add(mkObj("a", "b")) | ||||
| 	indexer.Add(mkObj("c", "b")) | ||||
| 	indexer.Add(mkObj("e", "f")) | ||||
| 	indexer.Add(mkObj("g", "h")) | ||||
| 	{ | ||||
| 		for k, v := range expected { | ||||
| 			found := sets.String{} | ||||
| 			indexResults, err := indexer.Index("by_val", mkObj("", k)) | ||||
| 			if err != nil { | ||||
| 				t.Errorf("Unexpected error %v", err) | ||||
| 			} | ||||
| 			for _, item := range indexResults { | ||||
| 				found.Insert(item.(testStoreObject).id) | ||||
| 			} | ||||
| 			items := v.List() | ||||
| 			if !found.HasAll(items...) { | ||||
| 				t.Errorf("missing items, index %s, expected %v but found %v", k, items, found.List()) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func testStoreKeyFunc(obj interface{}) (string, error) { | ||||
| 	return obj.(testStoreObject).id, nil | ||||
| } | ||||
|  | ||||
| func testStoreIndexFunc(obj interface{}) ([]string, error) { | ||||
| 	return []string{obj.(testStoreObject).val}, nil | ||||
| } | ||||
|  | ||||
| func testStoreIndexers() Indexers { | ||||
| 	indexers := Indexers{} | ||||
| 	indexers["by_val"] = testStoreIndexFunc | ||||
| 	return indexers | ||||
| } | ||||
|  | ||||
| type testStoreObject struct { | ||||
| 	id  string | ||||
| 	val string | ||||
| } | ||||
|  | ||||
| func TestCache(t *testing.T) { | ||||
| 	doTestStore(t, NewStore(testStoreKeyFunc)) | ||||
| } | ||||
|  | ||||
| func TestFIFOCache(t *testing.T) { | ||||
| 	doTestStore(t, NewFIFO(testStoreKeyFunc)) | ||||
| } | ||||
|  | ||||
| func TestUndeltaStore(t *testing.T) { | ||||
| 	nop := func([]interface{}) {} | ||||
| 	doTestStore(t, NewUndeltaStore(nop, testStoreKeyFunc)) | ||||
| } | ||||
|  | ||||
| func TestIndex(t *testing.T) { | ||||
| 	doTestIndex(t, NewIndexer(testStoreKeyFunc, testStoreIndexers())) | ||||
| } | ||||
							
								
								
									
										288
									
								
								pkg/client/cache/thread_safe_store.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										288
									
								
								pkg/client/cache/thread_safe_store.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,288 +0,0 @@ | ||||
| /* | ||||
| Copyright 2014 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"sync" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| ) | ||||
|  | ||||
| // ThreadSafeStore is an interface that allows concurrent access to a storage backend. | ||||
| // TL;DR caveats: you must not modify anything returned by Get or List as it will break | ||||
| // the indexing feature in addition to not being thread safe. | ||||
| // | ||||
| // The guarantees of thread safety provided by List/Get are only valid if the caller | ||||
| // treats returned items as read-only. For example, a pointer inserted in the store | ||||
| // through `Add` will be returned as is by `Get`. Multiple clients might invoke `Get` | ||||
| // on the same key and modify the pointer in a non-thread-safe way. Also note that | ||||
| // modifying objects stored by the indexers (if any) will *not* automatically lead | ||||
| // to a re-index. So it's not a good idea to directly modify the objects returned by | ||||
| // Get/List, in general. | ||||
| type ThreadSafeStore interface { | ||||
| 	Add(key string, obj interface{}) | ||||
| 	Update(key string, obj interface{}) | ||||
| 	Delete(key string) | ||||
| 	Get(key string) (item interface{}, exists bool) | ||||
| 	List() []interface{} | ||||
| 	ListKeys() []string | ||||
| 	Replace(map[string]interface{}, string) | ||||
| 	Index(indexName string, obj interface{}) ([]interface{}, error) | ||||
| 	ListIndexFuncValues(name string) []string | ||||
| 	ByIndex(indexName, indexKey string) ([]interface{}, error) | ||||
| 	GetIndexers() Indexers | ||||
|  | ||||
| 	// AddIndexers adds more indexers to this store.  If you call this after you already have data | ||||
| 	// in the store, the results are undefined. | ||||
| 	AddIndexers(newIndexers Indexers) error | ||||
| 	Resync() error | ||||
| } | ||||
|  | ||||
| // threadSafeMap implements ThreadSafeStore | ||||
| type threadSafeMap struct { | ||||
| 	lock  sync.RWMutex | ||||
| 	items map[string]interface{} | ||||
|  | ||||
| 	// indexers maps a name to an IndexFunc | ||||
| 	indexers Indexers | ||||
| 	// indices maps a name to an Index | ||||
| 	indices Indices | ||||
| } | ||||
|  | ||||
| func (c *threadSafeMap) Add(key string, obj interface{}) { | ||||
| 	c.lock.Lock() | ||||
| 	defer c.lock.Unlock() | ||||
| 	oldObject := c.items[key] | ||||
| 	c.items[key] = obj | ||||
| 	c.updateIndices(oldObject, obj, key) | ||||
| } | ||||
|  | ||||
| func (c *threadSafeMap) Update(key string, obj interface{}) { | ||||
| 	c.lock.Lock() | ||||
| 	defer c.lock.Unlock() | ||||
| 	oldObject := c.items[key] | ||||
| 	c.items[key] = obj | ||||
| 	c.updateIndices(oldObject, obj, key) | ||||
| } | ||||
|  | ||||
| func (c *threadSafeMap) Delete(key string) { | ||||
| 	c.lock.Lock() | ||||
| 	defer c.lock.Unlock() | ||||
| 	if obj, exists := c.items[key]; exists { | ||||
| 		c.deleteFromIndices(obj, key) | ||||
| 		delete(c.items, key) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *threadSafeMap) Get(key string) (item interface{}, exists bool) { | ||||
| 	c.lock.RLock() | ||||
| 	defer c.lock.RUnlock() | ||||
| 	item, exists = c.items[key] | ||||
| 	return item, exists | ||||
| } | ||||
|  | ||||
| func (c *threadSafeMap) List() []interface{} { | ||||
| 	c.lock.RLock() | ||||
| 	defer c.lock.RUnlock() | ||||
| 	list := make([]interface{}, 0, len(c.items)) | ||||
| 	for _, item := range c.items { | ||||
| 		list = append(list, item) | ||||
| 	} | ||||
| 	return list | ||||
| } | ||||
|  | ||||
| // ListKeys returns a list of all the keys of the objects currently | ||||
| // in the threadSafeMap. | ||||
| func (c *threadSafeMap) ListKeys() []string { | ||||
| 	c.lock.RLock() | ||||
| 	defer c.lock.RUnlock() | ||||
| 	list := make([]string, 0, len(c.items)) | ||||
| 	for key := range c.items { | ||||
| 		list = append(list, key) | ||||
| 	} | ||||
| 	return list | ||||
| } | ||||
|  | ||||
| func (c *threadSafeMap) Replace(items map[string]interface{}, resourceVersion string) { | ||||
| 	c.lock.Lock() | ||||
| 	defer c.lock.Unlock() | ||||
| 	c.items = items | ||||
|  | ||||
| 	// rebuild any index | ||||
| 	c.indices = Indices{} | ||||
| 	for key, item := range c.items { | ||||
| 		c.updateIndices(nil, item, key) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Index returns a list of items that match on the index function | ||||
| // Index is thread-safe so long as you treat all items as immutable | ||||
| func (c *threadSafeMap) Index(indexName string, obj interface{}) ([]interface{}, error) { | ||||
| 	c.lock.RLock() | ||||
| 	defer c.lock.RUnlock() | ||||
|  | ||||
| 	indexFunc := c.indexers[indexName] | ||||
| 	if indexFunc == nil { | ||||
| 		return nil, fmt.Errorf("Index with name %s does not exist", indexName) | ||||
| 	} | ||||
|  | ||||
| 	indexKeys, err := indexFunc(obj) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	index := c.indices[indexName] | ||||
|  | ||||
| 	// need to de-dupe the return list.  Since multiple keys are allowed, this can happen. | ||||
| 	returnKeySet := sets.String{} | ||||
| 	for _, indexKey := range indexKeys { | ||||
| 		set := index[indexKey] | ||||
| 		for _, key := range set.UnsortedList() { | ||||
| 			returnKeySet.Insert(key) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	list := make([]interface{}, 0, returnKeySet.Len()) | ||||
| 	for absoluteKey := range returnKeySet { | ||||
| 		list = append(list, c.items[absoluteKey]) | ||||
| 	} | ||||
| 	return list, nil | ||||
| } | ||||
|  | ||||
| // ByIndex returns a list of items that match an exact value on the index function | ||||
| func (c *threadSafeMap) ByIndex(indexName, indexKey string) ([]interface{}, error) { | ||||
| 	c.lock.RLock() | ||||
| 	defer c.lock.RUnlock() | ||||
|  | ||||
| 	indexFunc := c.indexers[indexName] | ||||
| 	if indexFunc == nil { | ||||
| 		return nil, fmt.Errorf("Index with name %s does not exist", indexName) | ||||
| 	} | ||||
|  | ||||
| 	index := c.indices[indexName] | ||||
|  | ||||
| 	set := index[indexKey] | ||||
| 	list := make([]interface{}, 0, set.Len()) | ||||
| 	for _, key := range set.List() { | ||||
| 		list = append(list, c.items[key]) | ||||
| 	} | ||||
|  | ||||
| 	return list, nil | ||||
| } | ||||
|  | ||||
| func (c *threadSafeMap) ListIndexFuncValues(indexName string) []string { | ||||
| 	c.lock.RLock() | ||||
| 	defer c.lock.RUnlock() | ||||
|  | ||||
| 	index := c.indices[indexName] | ||||
| 	names := make([]string, 0, len(index)) | ||||
| 	for key := range index { | ||||
| 		names = append(names, key) | ||||
| 	} | ||||
| 	return names | ||||
| } | ||||
|  | ||||
| func (c *threadSafeMap) GetIndexers() Indexers { | ||||
| 	return c.indexers | ||||
| } | ||||
|  | ||||
| func (c *threadSafeMap) AddIndexers(newIndexers Indexers) error { | ||||
| 	c.lock.Lock() | ||||
| 	defer c.lock.Unlock() | ||||
|  | ||||
| 	if len(c.items) > 0 { | ||||
| 		return fmt.Errorf("cannot add indexers to running index") | ||||
| 	} | ||||
|  | ||||
| 	oldKeys := sets.StringKeySet(c.indexers) | ||||
| 	newKeys := sets.StringKeySet(newIndexers) | ||||
|  | ||||
| 	if oldKeys.HasAny(newKeys.List()...) { | ||||
| 		return fmt.Errorf("indexer conflict: %v", oldKeys.Intersection(newKeys)) | ||||
| 	} | ||||
|  | ||||
| 	for k, v := range newIndexers { | ||||
| 		c.indexers[k] = v | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // updateIndices modifies the objects location in the managed indexes, if this is an update, you must provide an oldObj | ||||
| // updateIndices must be called from a function that already has a lock on the cache | ||||
| func (c *threadSafeMap) updateIndices(oldObj interface{}, newObj interface{}, key string) error { | ||||
| 	// if we got an old object, we need to remove it before we add it again | ||||
| 	if oldObj != nil { | ||||
| 		c.deleteFromIndices(oldObj, key) | ||||
| 	} | ||||
| 	for name, indexFunc := range c.indexers { | ||||
| 		indexValues, err := indexFunc(newObj) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		index := c.indices[name] | ||||
| 		if index == nil { | ||||
| 			index = Index{} | ||||
| 			c.indices[name] = index | ||||
| 		} | ||||
|  | ||||
| 		for _, indexValue := range indexValues { | ||||
| 			set := index[indexValue] | ||||
| 			if set == nil { | ||||
| 				set = sets.String{} | ||||
| 				index[indexValue] = set | ||||
| 			} | ||||
| 			set.Insert(key) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // deleteFromIndices removes the object from each of the managed indexes | ||||
| // it is intended to be called from a function that already has a lock on the cache | ||||
| func (c *threadSafeMap) deleteFromIndices(obj interface{}, key string) error { | ||||
| 	for name, indexFunc := range c.indexers { | ||||
| 		indexValues, err := indexFunc(obj) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		index := c.indices[name] | ||||
| 		if index == nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		for _, indexValue := range indexValues { | ||||
| 			set := index[indexValue] | ||||
| 			if set != nil { | ||||
| 				set.Delete(key) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (c *threadSafeMap) Resync() error { | ||||
| 	// Nothing to do | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func NewThreadSafeStore(indexers Indexers, indices Indices) ThreadSafeStore { | ||||
| 	return &threadSafeMap{ | ||||
| 		items:    map[string]interface{}{}, | ||||
| 		indexers: indexers, | ||||
| 		indices:  indices, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										83
									
								
								pkg/client/cache/undelta_store.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										83
									
								
								pkg/client/cache/undelta_store.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,83 +0,0 @@ | ||||
| /* | ||||
| Copyright 2015 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 cache | ||||
|  | ||||
| // UndeltaStore listens to incremental updates and sends complete state on every change. | ||||
| // It implements the Store interface so that it can receive a stream of mirrored objects | ||||
| // from Reflector.  Whenever it receives any complete (Store.Replace) or incremental change | ||||
| // (Store.Add, Store.Update, Store.Delete), it sends the complete state by calling PushFunc. | ||||
| // It is thread-safe.  It guarantees that every change (Add, Update, Replace, Delete) results | ||||
| // in one call to PushFunc, but sometimes PushFunc may be called twice with the same values. | ||||
| // PushFunc should be thread safe. | ||||
| type UndeltaStore struct { | ||||
| 	Store | ||||
| 	PushFunc func([]interface{}) | ||||
| } | ||||
|  | ||||
| // Assert that it implements the Store interface. | ||||
| var _ Store = &UndeltaStore{} | ||||
|  | ||||
| // Note about thread safety.  The Store implementation (cache.cache) uses a lock for all methods. | ||||
| // In the functions below, the lock gets released and reacquired betweend the {Add,Delete,etc} | ||||
| // and the List.  So, the following can happen, resulting in two identical calls to PushFunc. | ||||
| // time            thread 1                  thread 2 | ||||
| // 0               UndeltaStore.Add(a) | ||||
| // 1                                         UndeltaStore.Add(b) | ||||
| // 2               Store.Add(a) | ||||
| // 3                                         Store.Add(b) | ||||
| // 4               Store.List() -> [a,b] | ||||
| // 5                                         Store.List() -> [a,b] | ||||
|  | ||||
| func (u *UndeltaStore) Add(obj interface{}) error { | ||||
| 	if err := u.Store.Add(obj); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	u.PushFunc(u.Store.List()) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (u *UndeltaStore) Update(obj interface{}) error { | ||||
| 	if err := u.Store.Update(obj); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	u.PushFunc(u.Store.List()) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (u *UndeltaStore) Delete(obj interface{}) error { | ||||
| 	if err := u.Store.Delete(obj); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	u.PushFunc(u.Store.List()) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (u *UndeltaStore) Replace(list []interface{}, resourceVersion string) error { | ||||
| 	if err := u.Store.Replace(list, resourceVersion); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	u.PushFunc(u.Store.List()) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // NewUndeltaStore returns an UndeltaStore implemented with a Store. | ||||
| func NewUndeltaStore(pushFunc func([]interface{}), keyFunc KeyFunc) *UndeltaStore { | ||||
| 	return &UndeltaStore{ | ||||
| 		Store:    NewStore(keyFunc), | ||||
| 		PushFunc: pushFunc, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										131
									
								
								pkg/client/cache/undelta_store_test.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										131
									
								
								pkg/client/cache/undelta_store_test.go
									
									
									
									
										vendored
									
									
								
							| @@ -1,131 +0,0 @@ | ||||
| /* | ||||
| Copyright 2015 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 cache | ||||
|  | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| // store_test.go checks that UndeltaStore conforms to the Store interface | ||||
| // behavior.  This test just tests that it calls the push func in addition. | ||||
|  | ||||
| type testUndeltaObject struct { | ||||
| 	name string | ||||
| 	val  interface{} | ||||
| } | ||||
|  | ||||
| func testUndeltaKeyFunc(obj interface{}) (string, error) { | ||||
| 	return obj.(testUndeltaObject).name, nil | ||||
| } | ||||
|  | ||||
| /* | ||||
| var ( | ||||
| 	o1 interface{}   = t{1} | ||||
| 	o2 interface{}   = t{2} | ||||
| 	l1 []interface{} = []interface{}{t{1}} | ||||
| ) | ||||
| */ | ||||
|  | ||||
| func TestUpdateCallsPush(t *testing.T) { | ||||
| 	mkObj := func(name string, val interface{}) testUndeltaObject { | ||||
| 		return testUndeltaObject{name: name, val: val} | ||||
| 	} | ||||
|  | ||||
| 	var got []interface{} | ||||
| 	var callcount int = 0 | ||||
| 	push := func(m []interface{}) { | ||||
| 		callcount++ | ||||
| 		got = m | ||||
| 	} | ||||
|  | ||||
| 	u := NewUndeltaStore(push, testUndeltaKeyFunc) | ||||
|  | ||||
| 	u.Add(mkObj("a", 2)) | ||||
| 	u.Update(mkObj("a", 1)) | ||||
| 	if callcount != 2 { | ||||
| 		t.Errorf("Expected 2 calls, got %d", callcount) | ||||
| 	} | ||||
|  | ||||
| 	l := []interface{}{mkObj("a", 1)} | ||||
| 	if !reflect.DeepEqual(l, got) { | ||||
| 		t.Errorf("Expected %#v, Got %#v", l, got) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDeleteCallsPush(t *testing.T) { | ||||
| 	mkObj := func(name string, val interface{}) testUndeltaObject { | ||||
| 		return testUndeltaObject{name: name, val: val} | ||||
| 	} | ||||
|  | ||||
| 	var got []interface{} | ||||
| 	var callcount int = 0 | ||||
| 	push := func(m []interface{}) { | ||||
| 		callcount++ | ||||
| 		got = m | ||||
| 	} | ||||
|  | ||||
| 	u := NewUndeltaStore(push, testUndeltaKeyFunc) | ||||
|  | ||||
| 	u.Add(mkObj("a", 2)) | ||||
| 	u.Delete(mkObj("a", "")) | ||||
| 	if callcount != 2 { | ||||
| 		t.Errorf("Expected 2 calls, got %d", callcount) | ||||
| 	} | ||||
| 	expected := []interface{}{} | ||||
| 	if !reflect.DeepEqual(expected, got) { | ||||
| 		t.Errorf("Expected %#v, Got %#v", expected, got) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestReadsDoNotCallPush(t *testing.T) { | ||||
| 	push := func(m []interface{}) { | ||||
| 		t.Errorf("Unexpected call to push!") | ||||
| 	} | ||||
|  | ||||
| 	u := NewUndeltaStore(push, testUndeltaKeyFunc) | ||||
|  | ||||
| 	// These should not call push. | ||||
| 	_ = u.List() | ||||
| 	_, _, _ = u.Get(testUndeltaObject{"a", ""}) | ||||
| } | ||||
|  | ||||
| func TestReplaceCallsPush(t *testing.T) { | ||||
| 	mkObj := func(name string, val interface{}) testUndeltaObject { | ||||
| 		return testUndeltaObject{name: name, val: val} | ||||
| 	} | ||||
|  | ||||
| 	var got []interface{} | ||||
| 	var callcount int = 0 | ||||
| 	push := func(m []interface{}) { | ||||
| 		callcount++ | ||||
| 		got = m | ||||
| 	} | ||||
|  | ||||
| 	u := NewUndeltaStore(push, testUndeltaKeyFunc) | ||||
|  | ||||
| 	m := []interface{}{mkObj("a", 1)} | ||||
|  | ||||
| 	u.Replace(m, "0") | ||||
| 	if callcount != 1 { | ||||
| 		t.Errorf("Expected 1 calls, got %d", callcount) | ||||
| 	} | ||||
| 	expected := []interface{}{mkObj("a", 1)} | ||||
| 	if !reflect.DeepEqual(expected, got) { | ||||
| 		t.Errorf("Expected %#v, Got %#v", expected, got) | ||||
| 	} | ||||
| } | ||||
| @@ -35,7 +35,6 @@ go_library( | ||||
|         "//pkg/apis/rbac/v1beta1:go_default_library", | ||||
|         "//pkg/apis/storage:go_default_library", | ||||
|         "//pkg/apis/storage/v1beta1:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//pkg/client/clientset_generated/clientset:go_default_library", | ||||
|         "//pkg/client/clientset_generated/internalclientset:go_default_library", | ||||
|         "//pkg/client/informers/informers_generated/apps:go_default_library", | ||||
| @@ -50,6 +49,7 @@ go_library( | ||||
|         "//pkg/client/informers/informers_generated/storage:go_default_library", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime/schema", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -16,13 +16,13 @@ go_library( | ||||
|     tags = ["automanaged"], | ||||
|     deps = [ | ||||
|         "//pkg/apis/apps:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//pkg/client/clientset_generated/internalclientset:go_default_library", | ||||
|         "//pkg/client/informers/informers_generated/internalinterfaces:go_default_library", | ||||
|         "//pkg/client/listers/apps/internalversion:go_default_library", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -22,8 +22,8 @@ import ( | ||||
| 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	runtime "k8s.io/apimachinery/pkg/runtime" | ||||
| 	watch "k8s.io/apimachinery/pkg/watch" | ||||
| 	cache "k8s.io/client-go/tools/cache" | ||||
| 	apps "k8s.io/kubernetes/pkg/apis/apps" | ||||
| 	cache "k8s.io/kubernetes/pkg/client/cache" | ||||
| 	internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" | ||||
| 	internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalinterfaces" | ||||
| 	internalversion "k8s.io/kubernetes/pkg/client/listers/apps/internalversion" | ||||
|   | ||||
| @@ -16,13 +16,13 @@ go_library( | ||||
|     tags = ["automanaged"], | ||||
|     deps = [ | ||||
|         "//pkg/apis/apps/v1beta1:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//pkg/client/clientset_generated/clientset:go_default_library", | ||||
|         "//pkg/client/informers/informers_generated/internalinterfaces:go_default_library", | ||||
|         "//pkg/client/listers/apps/v1beta1:go_default_library", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -22,8 +22,8 @@ import ( | ||||
| 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	runtime "k8s.io/apimachinery/pkg/runtime" | ||||
| 	watch "k8s.io/apimachinery/pkg/watch" | ||||
| 	cache "k8s.io/client-go/tools/cache" | ||||
| 	apps_v1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1" | ||||
| 	cache "k8s.io/kubernetes/pkg/client/cache" | ||||
| 	clientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" | ||||
| 	internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalinterfaces" | ||||
| 	v1beta1 "k8s.io/kubernetes/pkg/client/listers/apps/v1beta1" | ||||
|   | ||||
| @@ -16,13 +16,13 @@ go_library( | ||||
|     tags = ["automanaged"], | ||||
|     deps = [ | ||||
|         "//pkg/apis/autoscaling:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//pkg/client/clientset_generated/internalclientset:go_default_library", | ||||
|         "//pkg/client/informers/informers_generated/internalinterfaces:go_default_library", | ||||
|         "//pkg/client/listers/autoscaling/internalversion:go_default_library", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -22,8 +22,8 @@ import ( | ||||
| 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	runtime "k8s.io/apimachinery/pkg/runtime" | ||||
| 	watch "k8s.io/apimachinery/pkg/watch" | ||||
| 	cache "k8s.io/client-go/tools/cache" | ||||
| 	autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling" | ||||
| 	cache "k8s.io/kubernetes/pkg/client/cache" | ||||
| 	internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" | ||||
| 	internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalinterfaces" | ||||
| 	internalversion "k8s.io/kubernetes/pkg/client/listers/autoscaling/internalversion" | ||||
|   | ||||
| @@ -16,13 +16,13 @@ go_library( | ||||
|     tags = ["automanaged"], | ||||
|     deps = [ | ||||
|         "//pkg/apis/autoscaling/v1:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//pkg/client/clientset_generated/clientset:go_default_library", | ||||
|         "//pkg/client/informers/informers_generated/internalinterfaces:go_default_library", | ||||
|         "//pkg/client/listers/autoscaling/v1:go_default_library", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -22,8 +22,8 @@ import ( | ||||
| 	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	runtime "k8s.io/apimachinery/pkg/runtime" | ||||
| 	watch "k8s.io/apimachinery/pkg/watch" | ||||
| 	cache "k8s.io/client-go/tools/cache" | ||||
| 	autoscaling_v1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1" | ||||
| 	cache "k8s.io/kubernetes/pkg/client/cache" | ||||
| 	clientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" | ||||
| 	internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalinterfaces" | ||||
| 	v1 "k8s.io/kubernetes/pkg/client/listers/autoscaling/v1" | ||||
|   | ||||
| @@ -17,13 +17,13 @@ go_library( | ||||
|     tags = ["automanaged"], | ||||
|     deps = [ | ||||
|         "//pkg/apis/batch:go_default_library", | ||||
|         "//pkg/client/cache:go_default_library", | ||||
|         "//pkg/client/clientset_generated/internalclientset:go_default_library", | ||||
|         "//pkg/client/informers/informers_generated/internalinterfaces:go_default_library", | ||||
|         "//pkg/client/listers/batch/internalversion:go_default_library", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/runtime", | ||||
|         "//vendor:k8s.io/apimachinery/pkg/watch", | ||||
|         "//vendor:k8s.io/client-go/tools/cache", | ||||
|     ], | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -22,8 +22,8 @@ import ( | ||||
| 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	runtime "k8s.io/apimachinery/pkg/runtime" | ||||
| 	watch "k8s.io/apimachinery/pkg/watch" | ||||
| 	cache "k8s.io/client-go/tools/cache" | ||||
| 	batch "k8s.io/kubernetes/pkg/apis/batch" | ||||
| 	cache "k8s.io/kubernetes/pkg/client/cache" | ||||
| 	internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" | ||||
| 	internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalinterfaces" | ||||
| 	internalversion "k8s.io/kubernetes/pkg/client/listers/batch/internalversion" | ||||
|   | ||||
| @@ -22,8 +22,8 @@ import ( | ||||
| 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	runtime "k8s.io/apimachinery/pkg/runtime" | ||||
| 	watch "k8s.io/apimachinery/pkg/watch" | ||||
| 	cache "k8s.io/client-go/tools/cache" | ||||
| 	batch "k8s.io/kubernetes/pkg/apis/batch" | ||||
| 	cache "k8s.io/kubernetes/pkg/client/cache" | ||||
| 	internalclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" | ||||
| 	internalinterfaces "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalinterfaces" | ||||
| 	internalversion "k8s.io/kubernetes/pkg/client/listers/batch/internalversion" | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 Kubernetes Submit Queue
					Kubernetes Submit Queue