func startNamespaceController(ctx ControllerContext) (bool, error) { // TODO: should use a dynamic RESTMapper built from the discovery results. restMapper := api.Registry.RESTMapper() // Find the list of namespaced resources via discovery that the namespace controller must manage namespaceKubeClient := ctx.ClientBuilder.ClientOrDie("namespace-controller") namespaceClientPool := dynamic.NewClientPool(ctx.ClientBuilder.ConfigOrDie("namespace-controller"), restMapper, dynamic.LegacyAPIPathResolverFunc) // TODO: consider using a list-watch + cache here rather than polling resources, err := namespaceKubeClient.Discovery().ServerResources() if err != nil { return true, fmt.Errorf("failed to get preferred server resources: %v", err) } gvrs, err := discovery.GroupVersionResources(resources) if err != nil { return true, fmt.Errorf("failed to parse preferred server resources: %v", err) } discoverResourcesFn := namespaceKubeClient.Discovery().ServerPreferredNamespacedResources if _, found := gvrs[extensions.SchemeGroupVersion.WithResource("thirdpartyresource")]; !found { // make discovery static snapshot, err := discoverResourcesFn() if err != nil { return true, fmt.Errorf("failed to get server resources: %v", err) } discoverResourcesFn = func() ([]*metav1.APIResourceList, error) { return snapshot, nil } } namespaceController := namespacecontroller.NewNamespaceController(namespaceKubeClient, namespaceClientPool, discoverResourcesFn, ctx.Options.NamespaceSyncPeriod.Duration, v1.FinalizerKubernetes) go namespaceController.Run(int(ctx.Options.ConcurrentNamespaceSyncs), ctx.Stop) return true, nil }
func startGarbageCollectorController(ctx ControllerContext) (bool, error) { if !ctx.Options.EnableGarbageCollector { return false, nil } // TODO: should use a dynamic RESTMapper built from the discovery results. restMapper := api.Registry.RESTMapper() gcClientset := ctx.ClientBuilder.ClientOrDie("generic-garbage-collector") preferredResources, err := gcClientset.Discovery().ServerPreferredResources() if err != nil { return true, fmt.Errorf("failed to get supported resources from server: %v", err) } deletableResources := discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"delete"}}, preferredResources) deletableGroupVersionResources, err := discovery.GroupVersionResources(deletableResources) if err != nil { return true, fmt.Errorf("Failed to parse resources from server: %v", err) } config := ctx.ClientBuilder.ConfigOrDie("generic-garbage-collector") config.ContentConfig.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: metaonly.NewMetadataCodecFactory()} metaOnlyClientPool := dynamic.NewClientPool(config, restMapper, dynamic.LegacyAPIPathResolverFunc) config.ContentConfig = dynamic.ContentConfig() clientPool := dynamic.NewClientPool(config, restMapper, dynamic.LegacyAPIPathResolverFunc) garbageCollector, err := garbagecollector.NewGarbageCollector(metaOnlyClientPool, clientPool, restMapper, deletableGroupVersionResources) if err != nil { return true, fmt.Errorf("Failed to start the generic garbage collector: %v", err) } workers := int(ctx.Options.ConcurrentGCSyncs) go garbageCollector.Run(workers, ctx.Stop) return true, nil }
func setup(t *testing.T) (*httptest.Server, *garbagecollector.GarbageCollector, clientset.Interface) { masterConfig := framework.NewIntegrationTestMasterConfig() masterConfig.EnableCoreControllers = false masterConfig.GenericConfig.EnableGarbageCollection = true _, s := framework.RunAMaster(masterConfig) clientSet, err := clientset.NewForConfig(&restclient.Config{Host: s.URL}) if err != nil { t.Fatalf("Error in create clientset: %v", err) } preferredResources, err := clientSet.Discovery().ServerPreferredResources() if err != nil { t.Fatalf("Failed to get supported resources from server: %v", err) } deletableResources := discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"delete"}}, preferredResources) deletableGroupVersionResources, err := discovery.GroupVersionResources(deletableResources) if err != nil { t.Fatalf("Failed to parse supported resources from server: %v", err) } config := &restclient.Config{Host: s.URL} config.ContentConfig.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: metaonly.NewMetadataCodecFactory()} metaOnlyClientPool := dynamic.NewClientPool(config, registered.RESTMapper(), dynamic.LegacyAPIPathResolverFunc) config.ContentConfig.NegotiatedSerializer = nil clientPool := dynamic.NewClientPool(config, registered.RESTMapper(), dynamic.LegacyAPIPathResolverFunc) gc, err := garbagecollector.NewGarbageCollector(metaOnlyClientPool, clientPool, registered.RESTMapper(), deletableGroupVersionResources) if err != nil { t.Fatalf("Failed to create garbage collector") } return s, gc, clientSet }
func (e ShortcutExpander) getAll() []schema.GroupResource { if e.discoveryClient == nil { return e.All } // Check if we have access to server resources apiResources, err := e.discoveryClient.ServerResources() if err != nil { return e.All } availableResources, err := discovery.GroupVersionResources(apiResources) if err != nil { return e.All } availableAll := []schema.GroupResource{} for _, requestedResource := range e.All { for availableResource := range availableResources { if requestedResource.Group == availableResource.Group && requestedResource.Resource == availableResource.Resource { availableAll = append(availableAll, requestedResource) break } } } return availableAll }
// syncNamespace orchestrates deletion of a Namespace and its associated content. func syncNamespace( kubeClient clientset.Interface, clientPool dynamic.ClientPool, opCache *operationNotSupportedCache, discoverResourcesFn func() ([]*metav1.APIResourceList, error), namespace *v1.Namespace, finalizerToken v1.FinalizerName, ) error { if namespace.DeletionTimestamp == nil { return nil } // multiple controllers may edit a namespace during termination // first get the latest state of the namespace before proceeding // if the namespace was deleted already, don't do anything namespace, err := kubeClient.Core().Namespaces().Get(namespace.Name) if err != nil { if errors.IsNotFound(err) { return nil } return err } glog.V(5).Infof("namespace controller - syncNamespace - namespace: %s, finalizerToken: %s", namespace.Name, finalizerToken) // ensure that the status is up to date on the namespace // if we get a not found error, we assume the namespace is truly gone namespace, err = retryOnConflictError(kubeClient, namespace, updateNamespaceStatusFunc) if err != nil { if errors.IsNotFound(err) { return nil } return err } // the latest view of the namespace asserts that namespace is no longer deleting.. if namespace.DeletionTimestamp.IsZero() { return nil } // if the namespace is already finalized, delete it if finalized(namespace) { var opts *v1.DeleteOptions uid := namespace.UID if len(uid) > 0 { opts = &v1.DeleteOptions{Preconditions: &v1.Preconditions{UID: &uid}} } err = kubeClient.Core().Namespaces().Delete(namespace.Name, opts) if err != nil && !errors.IsNotFound(err) { return err } return nil } // there may still be content for us to remove resources, err := discoverResourcesFn() if err != nil { return err } // TODO(sttts): get rid of opCache and pass the verbs (especially "deletecollection") down into the deleter deletableResources := discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"delete"}}, resources) groupVersionResources, err := discovery.GroupVersionResources(deletableResources) if err != nil { return err } estimate, err := deleteAllContent(kubeClient, clientPool, opCache, groupVersionResources, namespace.Name, *namespace.DeletionTimestamp) if err != nil { return err } if estimate > 0 { return &contentRemainingError{estimate} } // we have removed content, so mark it finalized by us result, err := retryOnConflictError(kubeClient, namespace, finalizeNamespaceFunc(finalizerToken)) if err != nil { // in normal practice, this should not be possible, but if a deployment is running // two controllers to do namespace deletion that share a common finalizer token it's // possible that a not found could occur since the other controller would have finished the delete. if errors.IsNotFound(err) { return nil } return err } // now check if all finalizers have reported that we delete now if finalized(result) { err = kubeClient.Core().Namespaces().Delete(namespace.Name, nil) if err != nil && !errors.IsNotFound(err) { return err } } return nil }
func testSyncNamespaceThatIsTerminating(t *testing.T, versions *metav1.APIVersions) { now := metav1.Now() namespaceName := "test" testNamespacePendingFinalize := &v1.Namespace{ ObjectMeta: v1.ObjectMeta{ Name: namespaceName, ResourceVersion: "1", DeletionTimestamp: &now, }, Spec: v1.NamespaceSpec{ Finalizers: []v1.FinalizerName{"kubernetes"}, }, Status: v1.NamespaceStatus{ Phase: v1.NamespaceTerminating, }, } testNamespaceFinalizeComplete := &v1.Namespace{ ObjectMeta: v1.ObjectMeta{ Name: namespaceName, ResourceVersion: "1", DeletionTimestamp: &now, }, Spec: v1.NamespaceSpec{}, Status: v1.NamespaceStatus{ Phase: v1.NamespaceTerminating, }, } // when doing a delete all of content, we will do a GET of a collection, and DELETE of a collection by default dynamicClientActionSet := sets.NewString() resources := testResources() groupVersionResources, _ := discovery.GroupVersionResources(resources) for groupVersionResource := range groupVersionResources { urlPath := path.Join([]string{ dynamic.LegacyAPIPathResolverFunc(schema.GroupVersionKind{Group: groupVersionResource.Group, Version: groupVersionResource.Version}), groupVersionResource.Group, groupVersionResource.Version, "namespaces", namespaceName, groupVersionResource.Resource, }...) dynamicClientActionSet.Insert((&fakeAction{method: "GET", path: urlPath}).String()) dynamicClientActionSet.Insert((&fakeAction{method: "DELETE", path: urlPath}).String()) } scenarios := map[string]struct { testNamespace *v1.Namespace kubeClientActionSet sets.String dynamicClientActionSet sets.String gvrError error }{ "pending-finalize": { testNamespace: testNamespacePendingFinalize, kubeClientActionSet: sets.NewString( strings.Join([]string{"get", "namespaces", ""}, "-"), strings.Join([]string{"create", "namespaces", "finalize"}, "-"), strings.Join([]string{"list", "pods", ""}, "-"), strings.Join([]string{"delete", "namespaces", ""}, "-"), ), dynamicClientActionSet: dynamicClientActionSet, }, "complete-finalize": { testNamespace: testNamespaceFinalizeComplete, kubeClientActionSet: sets.NewString( strings.Join([]string{"get", "namespaces", ""}, "-"), strings.Join([]string{"delete", "namespaces", ""}, "-"), ), dynamicClientActionSet: sets.NewString(), }, "groupVersionResourceErr": { testNamespace: testNamespaceFinalizeComplete, kubeClientActionSet: sets.NewString( strings.Join([]string{"get", "namespaces", ""}, "-"), strings.Join([]string{"delete", "namespaces", ""}, "-"), ), dynamicClientActionSet: sets.NewString(), gvrError: fmt.Errorf("test error"), }, } for scenario, testInput := range scenarios { testHandler := &fakeActionHandler{statusCode: 200} srv, clientConfig := testServerAndClientConfig(testHandler.ServeHTTP) defer srv.Close() mockClient := fake.NewSimpleClientset(testInput.testNamespace) clientPool := dynamic.NewClientPool(clientConfig, registered.RESTMapper(), dynamic.LegacyAPIPathResolverFunc) fn := func() ([]*metav1.APIResourceList, error) { return resources, nil } err := syncNamespace(mockClient, clientPool, &operationNotSupportedCache{m: make(map[operationKey]bool)}, fn, testInput.testNamespace, v1.FinalizerKubernetes) if err != nil { t.Errorf("scenario %s - Unexpected error when synching namespace %v", scenario, err) } // validate traffic from kube client actionSet := sets.NewString() for _, action := range mockClient.Actions() { actionSet.Insert(strings.Join([]string{action.GetVerb(), action.GetResource().Resource, action.GetSubresource()}, "-")) } if !actionSet.Equal(testInput.kubeClientActionSet) { t.Errorf("scenario %s - mock client expected actions:\n%v\n but got:\n%v\nDifference:\n%v", scenario, testInput.kubeClientActionSet, actionSet, testInput.kubeClientActionSet.Difference(actionSet)) } // validate traffic from dynamic client actionSet = sets.NewString() for _, action := range testHandler.actions { actionSet.Insert(action.String()) } if !actionSet.Equal(testInput.dynamicClientActionSet) { t.Errorf("scenario %s - dynamic client expected actions:\n%v\n but got:\n%v\nDifference:\n%v", scenario, testInput.dynamicClientActionSet, actionSet, testInput.dynamicClientActionSet.Difference(actionSet)) } } }
func StartControllers(controllers map[string]InitFunc, s *options.CMServer, rootClientBuilder, clientBuilder controller.ControllerClientBuilder, stop <-chan struct{}) error { sharedInformers := informers.NewSharedInformerFactory(rootClientBuilder.ClientOrDie("shared-informers"), nil, ResyncPeriod(s)()) // always start the SA token controller first using a full-power client, since it needs to mint tokens for the rest if len(s.ServiceAccountKeyFile) > 0 { privateKey, err := serviceaccount.ReadPrivateKey(s.ServiceAccountKeyFile) if err != nil { return fmt.Errorf("error reading key for service account token controller: %v", err) } else { var rootCA []byte if s.RootCAFile != "" { rootCA, err = ioutil.ReadFile(s.RootCAFile) if err != nil { return fmt.Errorf("error reading root-ca-file at %s: %v", s.RootCAFile, err) } if _, err := certutil.ParseCertsPEM(rootCA); err != nil { return fmt.Errorf("error parsing root-ca-file at %s: %v", s.RootCAFile, err) } } else { rootCA = rootClientBuilder.ConfigOrDie("tokens-controller").CAData } go serviceaccountcontroller.NewTokensController( rootClientBuilder.ClientOrDie("tokens-controller"), serviceaccountcontroller.TokensControllerOptions{ TokenGenerator: serviceaccount.JWTTokenGenerator(privateKey), RootCA: rootCA, }, ).Run(int(s.ConcurrentSATokenSyncs), stop) time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter)) } } availableResources, err := getAvailableResources(clientBuilder) if err != nil { return err } ctx := ControllerContext{ ClientBuilder: clientBuilder, InformerFactory: sharedInformers, Options: *s, AvailableResources: availableResources, Stop: stop, } for controllerName, initFn := range controllers { time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter)) glog.V(1).Infof("Starting %q", controllerName) started, err := initFn(ctx) if err != nil { glog.Errorf("Error starting %q", controllerName) return err } if !started { glog.Warningf("Skipping %q", controllerName) } glog.Infof("Started %q", controllerName) } cloud, err := cloudprovider.InitCloudProvider(s.CloudProvider, s.CloudConfigFile) if err != nil { return fmt.Errorf("cloud provider could not be initialized: %v", err) } _, clusterCIDR, err := net.ParseCIDR(s.ClusterCIDR) if err != nil { glog.Warningf("Unsuccessful parsing of cluster CIDR %v: %v", s.ClusterCIDR, err) } _, serviceCIDR, err := net.ParseCIDR(s.ServiceCIDR) if err != nil { glog.Warningf("Unsuccessful parsing of service CIDR %v: %v", s.ServiceCIDR, err) } nodeController, err := nodecontroller.NewNodeController( sharedInformers.Pods(), sharedInformers.Nodes(), sharedInformers.DaemonSets(), cloud, clientBuilder.ClientOrDie("node-controller"), s.PodEvictionTimeout.Duration, s.NodeEvictionRate, s.SecondaryNodeEvictionRate, s.LargeClusterSizeThreshold, s.UnhealthyZoneThreshold, s.NodeMonitorGracePeriod.Duration, s.NodeStartupGracePeriod.Duration, s.NodeMonitorPeriod.Duration, clusterCIDR, serviceCIDR, int(s.NodeCIDRMaskSize), s.AllocateNodeCIDRs) if err != nil { return fmt.Errorf("failed to initialize nodecontroller: %v", err) } nodeController.Run() time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter)) serviceController, err := servicecontroller.New(cloud, clientBuilder.ClientOrDie("service-controller"), s.ClusterName) if err != nil { glog.Errorf("Failed to start service controller: %v", err) } else { serviceController.Run(int(s.ConcurrentServiceSyncs)) } time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter)) if s.AllocateNodeCIDRs && s.ConfigureCloudRoutes { if cloud == nil { glog.Warning("configure-cloud-routes is set, but no cloud provider specified. Will not configure cloud provider routes.") } else if routes, ok := cloud.Routes(); !ok { glog.Warning("configure-cloud-routes is set, but cloud provider does not support routes. Will not configure cloud provider routes.") } else { routeController := routecontroller.New(routes, clientBuilder.ClientOrDie("route-controller"), s.ClusterName, clusterCIDR) routeController.Run(s.RouteReconciliationPeriod.Duration) time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter)) } } else { glog.Infof("Will not configure cloud provider routes for allocate-node-cidrs: %v, configure-cloud-routes: %v.", s.AllocateNodeCIDRs, s.ConfigureCloudRoutes) } if availableResources[schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "daemonsets"}] { go daemon.NewDaemonSetsController(sharedInformers.DaemonSets(), sharedInformers.Pods(), sharedInformers.Nodes(), clientBuilder.ClientOrDie("daemon-set-controller"), int(s.LookupCacheSizeForDaemonSet)). Run(int(s.ConcurrentDaemonSetSyncs), stop) time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter)) } if availableResources[schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "jobs"}] { glog.Infof("Starting job controller") go job.NewJobController(sharedInformers.Pods().Informer(), sharedInformers.Jobs(), clientBuilder.ClientOrDie("job-controller")). Run(int(s.ConcurrentJobSyncs), stop) time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter)) } if availableResources[schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"}] { glog.Infof("Starting deployment controller") go deployment.NewDeploymentController(sharedInformers.Deployments(), sharedInformers.ReplicaSets(), sharedInformers.Pods(), clientBuilder.ClientOrDie("deployment-controller")). Run(int(s.ConcurrentDeploymentSyncs), stop) time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter)) } if availableResources[schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "replicasets"}] { glog.Infof("Starting ReplicaSet controller") go replicaset.NewReplicaSetController(sharedInformers.ReplicaSets(), sharedInformers.Pods(), clientBuilder.ClientOrDie("replicaset-controller"), replicaset.BurstReplicas, int(s.LookupCacheSizeForRS), s.EnableGarbageCollector). Run(int(s.ConcurrentRSSyncs), stop) time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter)) } if availableResources[schema.GroupVersionResource{Group: "autoscaling", Version: "v1", Resource: "horizontalpodautoscalers"}] { glog.Infof("Starting horizontal pod autoscaler controller.") hpaClient := clientBuilder.ClientOrDie("horizontal-pod-autoscaler") metricsClient := metrics.NewHeapsterMetricsClient( hpaClient, metrics.DefaultHeapsterNamespace, metrics.DefaultHeapsterScheme, metrics.DefaultHeapsterService, metrics.DefaultHeapsterPort, ) replicaCalc := podautoscaler.NewReplicaCalculator(metricsClient, hpaClient.Core()) go podautoscaler.NewHorizontalController(hpaClient.Core(), hpaClient.Extensions(), hpaClient.Autoscaling(), replicaCalc, s.HorizontalPodAutoscalerSyncPeriod.Duration). Run(stop) time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter)) } if availableResources[schema.GroupVersionResource{Group: "policy", Version: "v1beta1", Resource: "poddisruptionbudgets"}] { glog.Infof("Starting disruption controller") go disruption.NewDisruptionController(sharedInformers.Pods().Informer(), clientBuilder.ClientOrDie("disruption-controller")).Run(stop) time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter)) } if availableResources[schema.GroupVersionResource{Group: "apps", Version: "v1beta1", Resource: "statefulsets"}] { glog.Infof("Starting StatefulSet controller") resyncPeriod := ResyncPeriod(s)() go petset.NewStatefulSetController( sharedInformers.Pods().Informer(), clientBuilder.ClientOrDie("statefulset-controller"), resyncPeriod, ).Run(1, stop) time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter)) } if availableResources[schema.GroupVersionResource{Group: "batch", Version: "v2alpha1", Resource: "cronjobs"}] { glog.Infof("Starting cronjob controller") // TODO: this is a temp fix for allowing kubeClient list v2alpha1 sj, should switch to using clientset cronjobConfig := rootClientBuilder.ConfigOrDie("cronjob-controller") cronjobConfig.ContentConfig.GroupVersion = &schema.GroupVersion{Group: batch.GroupName, Version: "v2alpha1"} go cronjob.NewCronJobController(clientset.NewForConfigOrDie(cronjobConfig)).Run(stop) time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter)) } alphaProvisioner, err := NewAlphaVolumeProvisioner(cloud, s.VolumeConfiguration) if err != nil { return fmt.Errorf("an backward-compatible provisioner could not be created: %v, but one was expected. Provisioning will not work. This functionality is considered an early Alpha version.", err) } params := persistentvolumecontroller.ControllerParameters{ KubeClient: clientBuilder.ClientOrDie("persistent-volume-binder"), SyncPeriod: s.PVClaimBinderSyncPeriod.Duration, AlphaProvisioner: alphaProvisioner, VolumePlugins: ProbeControllerVolumePlugins(cloud, s.VolumeConfiguration), Cloud: cloud, ClusterName: s.ClusterName, EnableDynamicProvisioning: s.VolumeConfiguration.EnableDynamicProvisioning, } volumeController := persistentvolumecontroller.NewController(params) volumeController.Run(stop) time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter)) attachDetachController, attachDetachControllerErr := attachdetach.NewAttachDetachController( clientBuilder.ClientOrDie("attachdetach-controller"), sharedInformers.Pods().Informer(), sharedInformers.Nodes().Informer(), sharedInformers.PersistentVolumeClaims().Informer(), sharedInformers.PersistentVolumes().Informer(), cloud, ProbeAttachableVolumePlugins(s.VolumeConfiguration)) if attachDetachControllerErr != nil { return fmt.Errorf("failed to start attach/detach controller: %v", attachDetachControllerErr) } go attachDetachController.Run(stop) time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter)) if availableResources[schema.GroupVersionResource{Group: "certificates.k8s.io", Version: "v1alpha1", Resource: "certificatesigningrequests"}] { glog.Infof("Starting certificate request controller") resyncPeriod := ResyncPeriod(s)() c := clientBuilder.ClientOrDie("certificate-controller") certController, err := certcontroller.NewCertificateController( c, resyncPeriod, s.ClusterSigningCertFile, s.ClusterSigningKeyFile, certcontroller.NewGroupApprover(c.Certificates().CertificateSigningRequests(), s.ApproveAllKubeletCSRsForGroup), ) if err != nil { glog.Errorf("Failed to start certificate controller: %v", err) } else { go certController.Run(1, stop) } time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter)) } go serviceaccountcontroller.NewServiceAccountsController( sharedInformers.ServiceAccounts(), sharedInformers.Namespaces(), clientBuilder.ClientOrDie("service-account-controller"), serviceaccountcontroller.DefaultServiceAccountsControllerOptions(), ).Run(1, stop) time.Sleep(wait.Jitter(s.ControllerStartInterval.Duration, ControllerStartJitter)) if s.EnableGarbageCollector { // TODO: should use a dynamic RESTMapper built from the discovery results. restMapper := registered.RESTMapper() gcClientset := clientBuilder.ClientOrDie("generic-garbage-collector") preferredResources, err := gcClientset.Discovery().ServerPreferredResources() if err != nil { return fmt.Errorf("failed to get supported resources from server: %v", err) } deletableResources := discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"delete"}}, preferredResources) deletableGroupVersionResources, err := discovery.GroupVersionResources(deletableResources) if err != nil { glog.Errorf("Failed to parse resources from server: %v", err) } config := rootClientBuilder.ConfigOrDie("generic-garbage-collector") config.ContentConfig.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: metaonly.NewMetadataCodecFactory()} metaOnlyClientPool := dynamic.NewClientPool(config, restMapper, dynamic.LegacyAPIPathResolverFunc) config.ContentConfig = dynamic.ContentConfig() clientPool := dynamic.NewClientPool(config, restMapper, dynamic.LegacyAPIPathResolverFunc) garbageCollector, err := garbagecollector.NewGarbageCollector(metaOnlyClientPool, clientPool, restMapper, deletableGroupVersionResources) if err != nil { glog.Errorf("Failed to start the generic garbage collector: %v", err) } else { workers := int(s.ConcurrentGCSyncs) go garbageCollector.Run(workers, stop) } } sharedInformers.Start(stop) select {} }