Merge pull request #41050 from marun/fed-ctlr-refactor

Automatic merge from submit-queue

[Federation] refactor controller for secrets

First pass of a simple federation controller that can be configured via an adapter to support multiple types.  The controller is a strict refactor of the secrets controller.

Targets #40989 

cc: @kubernetes/sig-federation-pr-reviews
This commit is contained in:
Kubernetes Submit Queue 2017-04-07 01:38:34 -07:00 committed by GitHub
commit fce31358c5
6 changed files with 277 additions and 202 deletions

View File

@ -18,6 +18,7 @@ go_library(
"//federation/pkg/federation-controller/util:go_default_library", "//federation/pkg/federation-controller/util:go_default_library",
"//federation/pkg/federation-controller/util/deletionhelper:go_default_library", "//federation/pkg/federation-controller/util/deletionhelper:go_default_library",
"//federation/pkg/federation-controller/util/eventsink:go_default_library", "//federation/pkg/federation-controller/util/eventsink:go_default_library",
"//federation/pkg/typeadapters:go_default_library",
"//pkg/api:go_default_library", "//pkg/api:go_default_library",
"//pkg/api/v1:go_default_library", "//pkg/api/v1:go_default_library",
"//pkg/client/clientset_generated/clientset:go_default_library", "//pkg/client/clientset_generated/clientset:go_default_library",

View File

@ -18,6 +18,7 @@ package secret
import ( import (
"fmt" "fmt"
"strings"
"time" "time"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
@ -36,6 +37,7 @@ import (
"k8s.io/kubernetes/federation/pkg/federation-controller/util" "k8s.io/kubernetes/federation/pkg/federation-controller/util"
"k8s.io/kubernetes/federation/pkg/federation-controller/util/deletionhelper" "k8s.io/kubernetes/federation/pkg/federation-controller/util/deletionhelper"
"k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink" "k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink"
"k8s.io/kubernetes/federation/pkg/typeadapters"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
apiv1 "k8s.io/kubernetes/pkg/api/v1" apiv1 "k8s.io/kubernetes/pkg/api/v1"
kubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" kubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
@ -53,185 +55,194 @@ var (
RequiredResources = []schema.GroupVersionResource{apiv1.SchemeGroupVersion.WithResource("secrets")} RequiredResources = []schema.GroupVersionResource{apiv1.SchemeGroupVersion.WithResource("secrets")}
) )
type SecretController struct { // FederationSyncController synchronizes the state of a federated type
// For triggering single secret reconciliation. This is used when there is an // to clusters that are members of the federation.
// add/update/delete operation on a secret in either federated API server or type FederationSyncController struct {
// in some member of the federation. // For triggering reconciliation of a single resource. This is
secretDeliverer *util.DelayingDeliverer // used when there is an add/update/delete operation on a resource
// in either federated API server or in some member of the
// federation.
deliverer *util.DelayingDeliverer
// For triggering all secrets reconciliation. This is used when // For triggering reconciliation of all target resources. This is
// a new cluster becomes available. // used when a new cluster becomes available.
clusterDeliverer *util.DelayingDeliverer clusterDeliverer *util.DelayingDeliverer
// Contains secrets present in members of federation. // Contains resources present in members of federation.
secretFederatedInformer util.FederatedInformer informer util.FederatedInformer
// For updating members of federation. // For updating members of federation.
federatedUpdater util.FederatedUpdater updater util.FederatedUpdater
// Definitions of secrets that should be federated. // Definitions of resources that should be federated.
secretInformerStore cache.Store store cache.Store
// Informer controller for secrets that should be federated. // Informer controller for resources that should be federated.
secretInformerController cache.Controller controller cache.Controller
// Client to federated api server. // Backoff manager
federatedApiClient federationclientset.Interface backoff *flowcontrol.Backoff
// Backoff manager for secrets
secretBackoff *flowcontrol.Backoff
// For events // For events
eventRecorder record.EventRecorder eventRecorder record.EventRecorder
deletionHelper *deletionhelper.DeletionHelper deletionHelper *deletionhelper.DeletionHelper
secretReviewDelay time.Duration reviewDelay time.Duration
clusterAvailableDelay time.Duration clusterAvailableDelay time.Duration
smallDelay time.Duration smallDelay time.Duration
updateTimeout time.Duration updateTimeout time.Duration
adapter typeadapters.FederatedTypeAdapter
} }
// StartSecretController starts a new secret controller // StartSecretController starts a new secret controller
func StartSecretController(config *restclient.Config, stopChan <-chan struct{}, minimizeLatency bool) { func StartSecretController(config *restclient.Config, stopChan <-chan struct{}, minimizeLatency bool) {
restclient.AddUserAgent(config, "secret-controller") startFederationSyncController(&typeadapters.SecretAdapter{}, config, stopChan, minimizeLatency)
client := federationclientset.NewForConfigOrDie(config)
controller := newSecretController(client)
if minimizeLatency {
controller.minimizeLatency()
}
glog.Infof("Starting Secret controller")
controller.Run(stopChan)
} }
// newSecretController returns a new secret controller // newSecretController returns a new secret controller
func newSecretController(client federationclientset.Interface) *SecretController { func newSecretController(client federationclientset.Interface) *FederationSyncController {
return newFederationSyncController(client, typeadapters.NewSecretAdapter(client))
}
// startFederationSyncController starts a new sync controller for the given type adapter
func startFederationSyncController(adapter typeadapters.FederatedTypeAdapter, config *restclient.Config, stopChan <-chan struct{}, minimizeLatency bool) {
restclient.AddUserAgent(config, fmt.Sprintf("%s-controller", adapter.Kind()))
client := federationclientset.NewForConfigOrDie(config)
adapter.SetClient(client)
controller := newFederationSyncController(client, adapter)
if minimizeLatency {
controller.minimizeLatency()
}
glog.Infof(fmt.Sprintf("Starting federated sync controller for %s resources", adapter.Kind()))
controller.Run(stopChan)
}
// newFederationSyncController returns a new sync controller for the given client and type adapter
func newFederationSyncController(client federationclientset.Interface, adapter typeadapters.FederatedTypeAdapter) *FederationSyncController {
broadcaster := record.NewBroadcaster() broadcaster := record.NewBroadcaster()
broadcaster.StartRecordingToSink(eventsink.NewFederatedEventSink(client)) broadcaster.StartRecordingToSink(eventsink.NewFederatedEventSink(client))
recorder := broadcaster.NewRecorder(api.Scheme, clientv1.EventSource{Component: "federated-secrets-controller"}) recorder := broadcaster.NewRecorder(api.Scheme, clientv1.EventSource{Component: fmt.Sprintf("federated-%v-controller", adapter.Kind())})
secretcontroller := &SecretController{ s := &FederationSyncController{
federatedApiClient: client, reviewDelay: time.Second * 10,
secretReviewDelay: time.Second * 10,
clusterAvailableDelay: time.Second * 20, clusterAvailableDelay: time.Second * 20,
smallDelay: time.Second * 3, smallDelay: time.Second * 3,
updateTimeout: time.Second * 30, updateTimeout: time.Second * 30,
secretBackoff: flowcontrol.NewBackOff(5*time.Second, time.Minute), backoff: flowcontrol.NewBackOff(5*time.Second, time.Minute),
eventRecorder: recorder, eventRecorder: recorder,
adapter: adapter,
} }
// Build delivereres for triggering reconciliations. // Build delivereres for triggering reconciliations.
secretcontroller.secretDeliverer = util.NewDelayingDeliverer() s.deliverer = util.NewDelayingDeliverer()
secretcontroller.clusterDeliverer = util.NewDelayingDeliverer() s.clusterDeliverer = util.NewDelayingDeliverer()
// Start informer in federated API servers on secrets that should be federated. // Start informer in federated API servers on the resource type that should be federated.
secretcontroller.secretInformerStore, secretcontroller.secretInformerController = cache.NewInformer( s.store, s.controller = cache.NewInformer(
&cache.ListWatch{ &cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (pkgruntime.Object, error) { ListFunc: func(options metav1.ListOptions) (pkgruntime.Object, error) {
return client.Core().Secrets(metav1.NamespaceAll).List(options) return adapter.FedList(metav1.NamespaceAll, options)
}, },
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
return client.Core().Secrets(metav1.NamespaceAll).Watch(options) return adapter.FedWatch(metav1.NamespaceAll, options)
}, },
}, },
&apiv1.Secret{}, adapter.ObjectType(),
controller.NoResyncPeriodFunc(), controller.NoResyncPeriodFunc(),
util.NewTriggerOnAllChanges(func(obj pkgruntime.Object) { secretcontroller.deliverSecretObj(obj, 0, false) })) util.NewTriggerOnAllChanges(func(obj pkgruntime.Object) { s.deliverObj(obj, 0, false) }))
// Federated informer on secrets in members of federation. // Federated informer on the resource type in members of federation.
secretcontroller.secretFederatedInformer = util.NewFederatedInformer( s.informer = util.NewFederatedInformer(
client, client,
func(cluster *federationapi.Cluster, targetClient kubeclientset.Interface) (cache.Store, cache.Controller) { func(cluster *federationapi.Cluster, targetClient kubeclientset.Interface) (cache.Store, cache.Controller) {
return cache.NewInformer( return cache.NewInformer(
&cache.ListWatch{ &cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (pkgruntime.Object, error) { ListFunc: func(options metav1.ListOptions) (pkgruntime.Object, error) {
return targetClient.Core().Secrets(metav1.NamespaceAll).List(options) return adapter.ClusterList(targetClient, metav1.NamespaceAll, options)
}, },
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
return targetClient.Core().Secrets(metav1.NamespaceAll).Watch(options) return adapter.ClusterWatch(targetClient, metav1.NamespaceAll, options)
}, },
}, },
&apiv1.Secret{}, adapter.ObjectType(),
controller.NoResyncPeriodFunc(), controller.NoResyncPeriodFunc(),
// Trigger reconciliation whenever something in federated cluster is changed. In most cases it // Trigger reconciliation whenever something in federated cluster is changed. In most cases it
// would be just confirmation that some secret operation succeeded. // would be just confirmation that some operation on the target resource type had succeeded.
util.NewTriggerOnAllChanges( util.NewTriggerOnAllChanges(
func(obj pkgruntime.Object) { func(obj pkgruntime.Object) {
secretcontroller.deliverSecretObj(obj, secretcontroller.secretReviewDelay, false) s.deliverObj(obj, s.reviewDelay, false)
}, },
)) ))
}, },
&util.ClusterLifecycleHandlerFuncs{ &util.ClusterLifecycleHandlerFuncs{
ClusterAvailable: func(cluster *federationapi.Cluster) { ClusterAvailable: func(cluster *federationapi.Cluster) {
// When new cluster becomes available process all the secrets again. // When new cluster becomes available process all the target resources again.
secretcontroller.clusterDeliverer.DeliverAt(allClustersKey, nil, time.Now().Add(secretcontroller.clusterAvailableDelay)) s.clusterDeliverer.DeliverAt(allClustersKey, nil, time.Now().Add(s.clusterAvailableDelay))
}, },
}, },
) )
// Federated updeater along with Create/Update/Delete operations. // Federated updeater along with Create/Update/Delete operations.
secretcontroller.federatedUpdater = util.NewFederatedUpdater(secretcontroller.secretFederatedInformer, s.updater = util.NewFederatedUpdater(s.informer,
func(client kubeclientset.Interface, obj pkgruntime.Object) error { func(client kubeclientset.Interface, obj pkgruntime.Object) error {
secret := obj.(*apiv1.Secret) _, err := adapter.ClusterCreate(client, obj)
_, err := client.Core().Secrets(secret.Namespace).Create(secret)
return err return err
}, },
func(client kubeclientset.Interface, obj pkgruntime.Object) error { func(client kubeclientset.Interface, obj pkgruntime.Object) error {
secret := obj.(*apiv1.Secret) _, err := adapter.ClusterUpdate(client, obj)
_, err := client.Core().Secrets(secret.Namespace).Update(secret)
return err return err
}, },
func(client kubeclientset.Interface, obj pkgruntime.Object) error { func(client kubeclientset.Interface, obj pkgruntime.Object) error {
secret := obj.(*apiv1.Secret) namespacedName := adapter.NamespacedName(obj)
orphanDependents := false orphanDependents := false
err := client.Core().Secrets(secret.Namespace).Delete(secret.Name, &metav1.DeleteOptions{OrphanDependents: &orphanDependents}) err := adapter.ClusterDelete(client, namespacedName, &metav1.DeleteOptions{OrphanDependents: &orphanDependents})
return err return err
}) })
secretcontroller.deletionHelper = deletionhelper.NewDeletionHelper( s.deletionHelper = deletionhelper.NewDeletionHelper(
secretcontroller.hasFinalizerFunc, s.hasFinalizerFunc,
secretcontroller.removeFinalizerFunc, s.removeFinalizerFunc,
secretcontroller.addFinalizerFunc, s.addFinalizerFunc,
// objNameFunc // objNameFunc
func(obj pkgruntime.Object) string { func(obj pkgruntime.Object) string {
secret := obj.(*apiv1.Secret) return adapter.ObjectMeta(obj).Name
return secret.Name
}, },
secretcontroller.updateTimeout, s.updateTimeout,
secretcontroller.eventRecorder, s.eventRecorder,
secretcontroller.secretFederatedInformer, s.informer,
secretcontroller.federatedUpdater, s.updater,
) )
return secretcontroller return s
} }
// minimizeLatency reduces delays and timeouts to make the controller more responsive (useful for testing). // minimizeLatency reduces delays and timeouts to make the controller more responsive (useful for testing).
func (secretcontroller *SecretController) minimizeLatency() { func (s *FederationSyncController) minimizeLatency() {
secretcontroller.clusterAvailableDelay = time.Second s.clusterAvailableDelay = time.Second
secretcontroller.secretReviewDelay = 50 * time.Millisecond s.reviewDelay = 50 * time.Millisecond
secretcontroller.smallDelay = 20 * time.Millisecond s.smallDelay = 20 * time.Millisecond
secretcontroller.updateTimeout = 5 * time.Second s.updateTimeout = 5 * time.Second
} }
// Returns true if the given object has the given finalizer in its ObjectMeta. // Returns true if the given object has the given finalizer in its ObjectMeta.
func (secretcontroller *SecretController) hasFinalizerFunc(obj pkgruntime.Object, finalizer string) bool { func (s *FederationSyncController) hasFinalizerFunc(obj pkgruntime.Object, finalizer string) bool {
secret := obj.(*apiv1.Secret) meta := s.adapter.ObjectMeta(obj)
for i := range secret.ObjectMeta.Finalizers { for i := range meta.Finalizers {
if string(secret.ObjectMeta.Finalizers[i]) == finalizer { if string(meta.Finalizers[i]) == finalizer {
return true return true
} }
} }
return false return false
} }
// Removes the finalizers from the given objects ObjectMeta. // Removes the finalizer from the given objects ObjectMeta.
// Assumes that the given object is a secret. func (s *FederationSyncController) removeFinalizerFunc(obj pkgruntime.Object, finalizers []string) (pkgruntime.Object, error) {
func (secretcontroller *SecretController) removeFinalizerFunc(obj pkgruntime.Object, finalizers []string) (pkgruntime.Object, error) { meta := s.adapter.ObjectMeta(obj)
secret := obj.(*apiv1.Secret)
newFinalizers := []string{} newFinalizers := []string{}
hasFinalizer := false hasFinalizer := false
for i := range secret.ObjectMeta.Finalizers { for i := range meta.Finalizers {
if !deletionhelper.ContainsString(finalizers, secret.ObjectMeta.Finalizers[i]) { if !deletionhelper.ContainsString(finalizers, meta.Finalizers[i]) {
newFinalizers = append(newFinalizers, secret.ObjectMeta.Finalizers[i]) newFinalizers = append(newFinalizers, meta.Finalizers[i])
} else { } else {
hasFinalizer = true hasFinalizer = true
} }
@ -240,184 +251,185 @@ func (secretcontroller *SecretController) removeFinalizerFunc(obj pkgruntime.Obj
// Nothing to do. // Nothing to do.
return obj, nil return obj, nil
} }
secret.ObjectMeta.Finalizers = newFinalizers meta.Finalizers = newFinalizers
secret, err := secretcontroller.federatedApiClient.Core().Secrets(secret.Namespace).Update(secret) secret, err := s.adapter.FedUpdate(obj)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to remove finalizers %v from secret %s: %v", finalizers, secret.Name, err) return nil, fmt.Errorf("failed to remove finalizers %v from %s %s: %v", finalizers, s.adapter.Kind(), meta.Name, err)
} }
return secret, nil return secret, nil
} }
// Adds the given finalizers to the given objects ObjectMeta. // Adds the given finalizers to the given objects ObjectMeta.
// Assumes that the given object is a secret. func (s *FederationSyncController) addFinalizerFunc(obj pkgruntime.Object, finalizers []string) (pkgruntime.Object, error) {
func (secretcontroller *SecretController) addFinalizerFunc(obj pkgruntime.Object, finalizers []string) (pkgruntime.Object, error) { meta := s.adapter.ObjectMeta(obj)
secret := obj.(*apiv1.Secret) meta.Finalizers = append(meta.Finalizers, finalizers...)
secret.ObjectMeta.Finalizers = append(secret.ObjectMeta.Finalizers, finalizers...) secret, err := s.adapter.FedUpdate(obj)
secret, err := secretcontroller.federatedApiClient.Core().Secrets(secret.Namespace).Update(secret)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to add finalizers %v to secret %s: %v", finalizers, secret.Name, err) return nil, fmt.Errorf("failed to add finalizers %v to %s %s: %v", finalizers, s.adapter.Kind(), meta.Name, err)
} }
return secret, nil return secret, nil
} }
func (secretcontroller *SecretController) Run(stopChan <-chan struct{}) { func (s *FederationSyncController) Run(stopChan <-chan struct{}) {
go secretcontroller.secretInformerController.Run(stopChan) go s.controller.Run(stopChan)
secretcontroller.secretFederatedInformer.Start() s.informer.Start()
go func() { go func() {
<-stopChan <-stopChan
secretcontroller.secretFederatedInformer.Stop() s.informer.Stop()
}() }()
secretcontroller.secretDeliverer.StartWithHandler(func(item *util.DelayingDelivererItem) { s.deliverer.StartWithHandler(func(item *util.DelayingDelivererItem) {
secret := item.Value.(*types.NamespacedName) namespacedName := item.Value.(*types.NamespacedName)
secretcontroller.reconcileSecret(*secret) s.reconcile(*namespacedName)
}) })
secretcontroller.clusterDeliverer.StartWithHandler(func(_ *util.DelayingDelivererItem) { s.clusterDeliverer.StartWithHandler(func(_ *util.DelayingDelivererItem) {
secretcontroller.reconcileSecretsOnClusterChange() s.reconcileOnClusterChange()
}) })
util.StartBackoffGC(secretcontroller.secretBackoff, stopChan) util.StartBackoffGC(s.backoff, stopChan)
} }
func (secretcontroller *SecretController) deliverSecretObj(obj interface{}, delay time.Duration, failed bool) { func (s *FederationSyncController) deliverObj(obj pkgruntime.Object, delay time.Duration, failed bool) {
secret := obj.(*apiv1.Secret) namespacedName := s.adapter.NamespacedName(obj)
secretcontroller.deliverSecret(types.NamespacedName{Namespace: secret.Namespace, Name: secret.Name}, delay, failed) s.deliver(namespacedName, delay, failed)
} }
// Adds backoff to delay if this delivery is related to some failure. Resets backoff if there was no failure. // Adds backoff to delay if this delivery is related to some failure. Resets backoff if there was no failure.
func (secretcontroller *SecretController) deliverSecret(secret types.NamespacedName, delay time.Duration, failed bool) { func (s *FederationSyncController) deliver(namespacedName types.NamespacedName, delay time.Duration, failed bool) {
key := secret.String() key := namespacedName.String()
if failed { if failed {
secretcontroller.secretBackoff.Next(key, time.Now()) s.backoff.Next(key, time.Now())
delay = delay + secretcontroller.secretBackoff.Get(key) delay = delay + s.backoff.Get(key)
} else { } else {
secretcontroller.secretBackoff.Reset(key) s.backoff.Reset(key)
} }
secretcontroller.secretDeliverer.DeliverAfter(key, &secret, delay) s.deliverer.DeliverAfter(key, &namespacedName, delay)
} }
// Check whether all data stores are in sync. False is returned if any of the informer/stores is not yet // Check whether all data stores are in sync. False is returned if any of the informer/stores is not yet
// synced with the corresponding api server. // synced with the corresponding api server.
func (secretcontroller *SecretController) isSynced() bool { func (s *FederationSyncController) isSynced() bool {
if !secretcontroller.secretFederatedInformer.ClustersSynced() { if !s.informer.ClustersSynced() {
glog.V(2).Infof("Cluster list not synced") glog.V(2).Infof("Cluster list not synced")
return false return false
} }
clusters, err := secretcontroller.secretFederatedInformer.GetReadyClusters() clusters, err := s.informer.GetReadyClusters()
if err != nil { if err != nil {
glog.Errorf("Failed to get ready clusters: %v", err) glog.Errorf("Failed to get ready clusters: %v", err)
return false return false
} }
if !secretcontroller.secretFederatedInformer.GetTargetStore().ClustersSynced(clusters) { if !s.informer.GetTargetStore().ClustersSynced(clusters) {
return false return false
} }
return true return true
} }
// The function triggers reconciliation of all federated secrets. // The function triggers reconciliation of all target federated resources.
func (secretcontroller *SecretController) reconcileSecretsOnClusterChange() { func (s *FederationSyncController) reconcileOnClusterChange() {
if !secretcontroller.isSynced() { if !s.isSynced() {
secretcontroller.clusterDeliverer.DeliverAt(allClustersKey, nil, time.Now().Add(secretcontroller.clusterAvailableDelay)) s.clusterDeliverer.DeliverAt(allClustersKey, nil, time.Now().Add(s.clusterAvailableDelay))
} }
for _, obj := range secretcontroller.secretInformerStore.List() { for _, obj := range s.store.List() {
secret := obj.(*apiv1.Secret) namespacedName := s.adapter.NamespacedName(obj.(pkgruntime.Object))
secretcontroller.deliverSecret(types.NamespacedName{Namespace: secret.Namespace, Name: secret.Name}, secretcontroller.smallDelay, false) s.deliver(namespacedName, s.smallDelay, false)
} }
} }
func (secretcontroller *SecretController) reconcileSecret(secret types.NamespacedName) { func (s *FederationSyncController) reconcile(namespacedName types.NamespacedName) {
if !secretcontroller.isSynced() { if !s.isSynced() {
secretcontroller.deliverSecret(secret, secretcontroller.clusterAvailableDelay, false) s.deliver(namespacedName, s.clusterAvailableDelay, false)
return return
} }
key := secret.String() key := namespacedName.String()
baseSecretObjFromStore, exist, err := secretcontroller.secretInformerStore.GetByKey(key) kind := s.adapter.Kind()
cachedObj, exist, err := s.store.GetByKey(key)
if err != nil { if err != nil {
glog.Errorf("Failed to query main secret store for %v: %v", key, err) glog.Errorf("Failed to query main %s store for %v: %v", kind, key, err)
secretcontroller.deliverSecret(secret, 0, true) s.deliver(namespacedName, 0, true)
return return
} }
if !exist { if !exist {
// Not federated secret, ignoring. // Not federated, ignoring.
return return
} }
// Create a copy before modifying the obj to prevent race condition with // Create a copy before modifying the resource to prevent racing
// other readers of obj from store. // with other readers.
baseSecretObj, err := api.Scheme.DeepCopy(baseSecretObjFromStore) copiedObj, err := api.Scheme.DeepCopy(cachedObj)
baseSecret, ok := baseSecretObj.(*apiv1.Secret)
if err != nil || !ok {
glog.Errorf("Error in retrieving obj from store: %v, %v", ok, err)
secretcontroller.deliverSecret(secret, 0, true)
return
}
if baseSecret.DeletionTimestamp != nil {
if err := secretcontroller.delete(baseSecret); err != nil {
glog.Errorf("Failed to delete %s: %v", secret, err)
secretcontroller.eventRecorder.Eventf(baseSecret, api.EventTypeNormal, "DeleteFailed",
"Secret delete failed: %v", err)
secretcontroller.deliverSecret(secret, 0, true)
}
return
}
glog.V(3).Infof("Ensuring delete object from underlying clusters finalizer for secret: %s",
baseSecret.Name)
// Add the required finalizers before creating a secret in underlying clusters.
updatedSecretObj, err := secretcontroller.deletionHelper.EnsureFinalizers(baseSecret)
if err != nil { if err != nil {
glog.Errorf("Failed to ensure delete object from underlying clusters finalizer in secret %s: %v", glog.Errorf("Error in retrieving %s from store: %v", kind, err)
baseSecret.Name, err) s.deliver(namespacedName, 0, true)
secretcontroller.deliverSecret(secret, 0, false)
return return
} }
baseSecret = updatedSecretObj.(*apiv1.Secret) if !s.adapter.IsExpectedType(copiedObj) {
glog.Errorf("Object is not the expected type: %v", copiedObj)
s.deliver(namespacedName, 0, true)
return
}
obj := copiedObj.(pkgruntime.Object)
meta := s.adapter.ObjectMeta(obj)
glog.V(3).Infof("Syncing secret %s in underlying clusters", baseSecret.Name) if meta.DeletionTimestamp != nil {
if err := s.delete(obj, namespacedName); err != nil {
glog.Errorf("Failed to delete %s %s: %v", kind, namespacedName, err)
s.eventRecorder.Eventf(obj, api.EventTypeNormal, "DeleteFailed",
"%s delete failed: %v", strings.ToTitle(kind), err)
s.deliver(namespacedName, 0, true)
}
return
}
clusters, err := secretcontroller.secretFederatedInformer.GetReadyClusters() glog.V(3).Infof("Ensuring delete object from underlying clusters finalizer for %s: %s",
kind, namespacedName)
// Add the required finalizers before creating the resource in underlying clusters.
obj, err = s.deletionHelper.EnsureFinalizers(obj)
if err != nil {
glog.Errorf("Failed to ensure delete object from underlying clusters finalizer in %s %s: %v",
kind, namespacedName, err)
s.deliver(namespacedName, 0, false)
return
}
glog.V(3).Infof("Syncing %s %s in underlying clusters", kind, namespacedName)
clusters, err := s.informer.GetReadyClusters()
if err != nil { if err != nil {
glog.Errorf("Failed to get cluster list: %v", err) glog.Errorf("Failed to get cluster list: %v", err)
secretcontroller.deliverSecret(secret, secretcontroller.clusterAvailableDelay, false) s.deliver(namespacedName, s.clusterAvailableDelay, false)
return return
} }
operations := make([]util.FederatedOperation, 0) operations := make([]util.FederatedOperation, 0)
for _, cluster := range clusters { for _, cluster := range clusters {
clusterSecretObj, found, err := secretcontroller.secretFederatedInformer.GetTargetStore().GetByKey(cluster.Name, key) clusterObj, found, err := s.informer.GetTargetStore().GetByKey(cluster.Name, key)
if err != nil { if err != nil {
glog.Errorf("Failed to get %s from %s: %v", key, cluster.Name, err) glog.Errorf("Failed to get %s from %s: %v", key, cluster.Name, err)
secretcontroller.deliverSecret(secret, 0, true) s.deliver(namespacedName, 0, true)
return return
} }
// The data should not be modified. // The data should not be modified.
desiredSecret := &apiv1.Secret{ desiredObj := s.adapter.Copy(obj)
ObjectMeta: util.DeepCopyRelevantObjectMeta(baseSecret.ObjectMeta),
Data: baseSecret.Data,
Type: baseSecret.Type,
}
if !found { if !found {
secretcontroller.eventRecorder.Eventf(baseSecret, api.EventTypeNormal, "CreateInCluster", s.eventRecorder.Eventf(obj, api.EventTypeNormal, "CreateInCluster",
"Creating secret in cluster %s", cluster.Name) "Creating %s in cluster %s", kind, cluster.Name)
operations = append(operations, util.FederatedOperation{ operations = append(operations, util.FederatedOperation{
Type: util.OperationTypeAdd, Type: util.OperationTypeAdd,
Obj: desiredSecret, Obj: desiredObj,
ClusterName: cluster.Name, ClusterName: cluster.Name,
}) })
} else { } else {
clusterSecret := clusterSecretObj.(*apiv1.Secret) clusterObj := clusterObj.(pkgruntime.Object)
// Update existing secret, if needed. // Update existing resource, if needed.
if !util.SecretEquivalent(*desiredSecret, *clusterSecret) { if !s.adapter.Equivalent(desiredObj, clusterObj) {
s.eventRecorder.Eventf(obj, api.EventTypeNormal, "UpdateInCluster",
secretcontroller.eventRecorder.Eventf(baseSecret, api.EventTypeNormal, "UpdateInCluster", "Updating %s in cluster %s", kind, cluster.Name)
"Updating secret in cluster %s", cluster.Name)
operations = append(operations, util.FederatedOperation{ operations = append(operations, util.FederatedOperation{
Type: util.OperationTypeUpdate, Type: util.OperationTypeUpdate,
Obj: desiredSecret, Obj: desiredObj,
ClusterName: cluster.Name, ClusterName: cluster.Name,
}) })
} }
@ -428,37 +440,38 @@ func (secretcontroller *SecretController) reconcileSecret(secret types.Namespace
// Everything is in order // Everything is in order
return return
} }
err = secretcontroller.federatedUpdater.UpdateWithOnError(operations, secretcontroller.updateTimeout, err = s.updater.UpdateWithOnError(operations, s.updateTimeout,
func(op util.FederatedOperation, operror error) { func(op util.FederatedOperation, operror error) {
secretcontroller.eventRecorder.Eventf(baseSecret, api.EventTypeNormal, "UpdateInClusterFailed", s.eventRecorder.Eventf(obj, api.EventTypeNormal, "UpdateInClusterFailed",
"Secret update in cluster %s failed: %v", op.ClusterName, operror) "%s update in cluster %s failed: %v", strings.ToTitle(kind), op.ClusterName, operror)
}) })
if err != nil { if err != nil {
glog.Errorf("Failed to execute updates for %s: %v", key, err) glog.Errorf("Failed to execute updates for %s: %v", key, err)
secretcontroller.deliverSecret(secret, 0, true) s.deliver(namespacedName, 0, true)
return return
} }
// Evertyhing is in order but lets be double sure // Evertyhing is in order but lets be double sure
secretcontroller.deliverSecret(secret, secretcontroller.secretReviewDelay, false) s.deliver(namespacedName, s.reviewDelay, false)
} }
// delete deletes the given secret or returns error if the deletion was not complete. // delete deletes the given resource or returns error if the deletion was not complete.
func (secretcontroller *SecretController) delete(secret *apiv1.Secret) error { func (s *FederationSyncController) delete(obj pkgruntime.Object, namespacedName types.NamespacedName) error {
glog.V(3).Infof("Handling deletion of secret: %v", *secret) kind := s.adapter.Kind()
_, err := secretcontroller.deletionHelper.HandleObjectInUnderlyingClusters(secret) glog.V(3).Infof("Handling deletion of %s: %v", kind, namespacedName)
_, err := s.deletionHelper.HandleObjectInUnderlyingClusters(obj)
if err != nil { if err != nil {
return err return err
} }
err = secretcontroller.federatedApiClient.Core().Secrets(secret.Namespace).Delete(secret.Name, nil) err = s.adapter.FedDelete(namespacedName, nil)
if err != nil { if err != nil {
// Its all good if the error is not found error. That means it is deleted already and we do not have to do anything. // Its all good if the error is not found error. That means it is deleted already and we do not have to do anything.
// This is expected when we are processing an update as a result of secret finalizer deletion. // This is expected when we are processing an update as a result of finalizer deletion.
// The process that deleted the last finalizer is also going to delete the secret and we do not have to do anything. // The process that deleted the last finalizer is also going to delete the resource and we do not have to do anything.
if !errors.IsNotFound(err) { if !errors.IsNotFound(err) {
return fmt.Errorf("failed to delete secret: %v", err) return fmt.Errorf("failed to delete %s: %v", kind, err)
} }
} }
return nil return nil

View File

@ -77,7 +77,7 @@ func TestSecretController(t *testing.T) {
return nil, fmt.Errorf("Unknown cluster") return nil, fmt.Errorf("Unknown cluster")
} }
} }
setClientFactory(secretController.secretFederatedInformer, informerClientFactory) setClientFactory(secretController.informer, informerClientFactory)
secretController.minimizeLatency() secretController.minimizeLatency()
@ -115,7 +115,7 @@ func TestSecretController(t *testing.T) {
// Wait for the secret to appear in the informer store // Wait for the secret to appear in the informer store
err := WaitForStoreUpdate( err := WaitForStoreUpdate(
secretController.secretFederatedInformer.GetTargetStore(), secretController.informer.GetTargetStore(),
cluster1.Name, types.NamespacedName{Namespace: secret1.Namespace, Name: secret1.Name}.String(), wait.ForeverTestTimeout) cluster1.Name, types.NamespacedName{Namespace: secret1.Namespace, Name: secret1.Name}.String(), wait.ForeverTestTimeout)
assert.Nil(t, err, "secret should have appeared in the informer store") assert.Nil(t, err, "secret should have appeared in the informer store")
@ -146,7 +146,7 @@ func TestSecretController(t *testing.T) {
// Wait for the secret to be updated in the informer store. // Wait for the secret to be updated in the informer store.
err = WaitForSecretStoreUpdate( err = WaitForSecretStoreUpdate(
secretController.secretFederatedInformer.GetTargetStore(), secretController.informer.GetTargetStore(),
cluster1.Name, types.NamespacedName{Namespace: secret1.Namespace, Name: secret1.Name}.String(), cluster1.Name, types.NamespacedName{Namespace: secret1.Namespace, Name: secret1.Name}.String(),
&secret1, wait.ForeverTestTimeout) &secret1, wait.ForeverTestTimeout)
assert.NoError(t, err, "secret should have been updated in the informer store") assert.NoError(t, err, "secret should have been updated in the informer store")

View File

@ -22,6 +22,7 @@ go_library(
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/runtime", "//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/types", "//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apimachinery/pkg/watch",
], ],
) )

View File

@ -20,8 +20,9 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
pkgruntime "k8s.io/apimachinery/pkg/runtime" pkgruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset" kubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
) )
// FederatedTypeAdapter defines operations for interacting with a // FederatedTypeAdapter defines operations for interacting with a
@ -31,18 +32,28 @@ type FederatedTypeAdapter interface {
SetClient(client federationclientset.Interface) SetClient(client federationclientset.Interface)
Kind() string Kind() string
ObjectType() pkgruntime.Object
IsExpectedType(obj interface{}) bool
Copy(obj pkgruntime.Object) pkgruntime.Object
Equivalent(obj1, obj2 pkgruntime.Object) bool Equivalent(obj1, obj2 pkgruntime.Object) bool
ObjectMeta(obj pkgruntime.Object) *metav1.ObjectMeta
NamespacedName(obj pkgruntime.Object) types.NamespacedName NamespacedName(obj pkgruntime.Object) types.NamespacedName
ObjectMeta(obj pkgruntime.Object) *metav1.ObjectMeta
// Fed* operations target the federation control plane // Fed* operations target the federation control plane
FedCreate(obj pkgruntime.Object) (pkgruntime.Object, error) FedCreate(obj pkgruntime.Object) (pkgruntime.Object, error)
FedGet(namespacedName types.NamespacedName) (pkgruntime.Object, error)
FedUpdate(obj pkgruntime.Object) (pkgruntime.Object, error)
FedDelete(namespacedName types.NamespacedName, options *metav1.DeleteOptions) error FedDelete(namespacedName types.NamespacedName, options *metav1.DeleteOptions) error
FedGet(namespacedName types.NamespacedName) (pkgruntime.Object, error)
FedList(namespace string, options metav1.ListOptions) (pkgruntime.Object, error)
FedUpdate(obj pkgruntime.Object) (pkgruntime.Object, error)
FedWatch(namespace string, options metav1.ListOptions) (watch.Interface, error)
// The following operations are intended to target a cluster that is a member of a federation // The following operations are intended to target a cluster that is a member of a federation
ClusterGet(client clientset.Interface, namespacedName types.NamespacedName) (pkgruntime.Object, error) ClusterCreate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error)
ClusterDelete(client kubeclientset.Interface, nsName types.NamespacedName, options *metav1.DeleteOptions) error
ClusterGet(client kubeclientset.Interface, namespacedName types.NamespacedName) (pkgruntime.Object, error)
ClusterList(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (pkgruntime.Object, error)
ClusterUpdate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error)
ClusterWatch(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (watch.Interface, error)
NewTestObject(namespace string) pkgruntime.Object NewTestObject(namespace string) pkgruntime.Object
} }

View File

@ -20,10 +20,11 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
pkgruntime "k8s.io/apimachinery/pkg/runtime" pkgruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset" federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset"
"k8s.io/kubernetes/federation/pkg/federation-controller/util" "k8s.io/kubernetes/federation/pkg/federation-controller/util"
apiv1 "k8s.io/kubernetes/pkg/api/v1" apiv1 "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset" kubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
) )
type SecretAdapter struct { type SecretAdapter struct {
@ -42,43 +43,91 @@ func (a *SecretAdapter) Kind() string {
return "secret" return "secret"
} }
func (a *SecretAdapter) ObjectType() pkgruntime.Object {
return &apiv1.Secret{}
}
func (a *SecretAdapter) IsExpectedType(obj interface{}) bool {
_, ok := obj.(*apiv1.Secret)
return ok
}
func (a *SecretAdapter) Copy(obj pkgruntime.Object) pkgruntime.Object {
secret := obj.(*apiv1.Secret)
return &apiv1.Secret{
ObjectMeta: util.DeepCopyRelevantObjectMeta(secret.ObjectMeta),
Data: secret.Data,
Type: secret.Type,
}
}
func (a *SecretAdapter) Equivalent(obj1, obj2 pkgruntime.Object) bool { func (a *SecretAdapter) Equivalent(obj1, obj2 pkgruntime.Object) bool {
secret1 := obj1.(*apiv1.Secret) secret1 := obj1.(*apiv1.Secret)
secret2 := obj2.(*apiv1.Secret) secret2 := obj2.(*apiv1.Secret)
return util.SecretEquivalent(*secret1, *secret2) return util.SecretEquivalent(*secret1, *secret2)
} }
func (a *SecretAdapter) ObjectMeta(obj pkgruntime.Object) *metav1.ObjectMeta {
return &obj.(*apiv1.Secret).ObjectMeta
}
func (a *SecretAdapter) NamespacedName(obj pkgruntime.Object) types.NamespacedName { func (a *SecretAdapter) NamespacedName(obj pkgruntime.Object) types.NamespacedName {
secret := obj.(*apiv1.Secret) secret := obj.(*apiv1.Secret)
return types.NamespacedName{Namespace: secret.Namespace, Name: secret.Name} return types.NamespacedName{Namespace: secret.Namespace, Name: secret.Name}
} }
func (a *SecretAdapter) ObjectMeta(obj pkgruntime.Object) *metav1.ObjectMeta {
return &obj.(*apiv1.Secret).ObjectMeta
}
func (a *SecretAdapter) FedCreate(obj pkgruntime.Object) (pkgruntime.Object, error) { func (a *SecretAdapter) FedCreate(obj pkgruntime.Object) (pkgruntime.Object, error) {
secret := obj.(*apiv1.Secret) secret := obj.(*apiv1.Secret)
return a.client.CoreV1().Secrets(secret.Namespace).Create(secret) return a.client.CoreV1().Secrets(secret.Namespace).Create(secret)
} }
func (a *SecretAdapter) FedDelete(namespacedName types.NamespacedName, options *metav1.DeleteOptions) error {
return a.client.CoreV1().Secrets(namespacedName.Namespace).Delete(namespacedName.Name, options)
}
func (a *SecretAdapter) FedGet(namespacedName types.NamespacedName) (pkgruntime.Object, error) { func (a *SecretAdapter) FedGet(namespacedName types.NamespacedName) (pkgruntime.Object, error) {
return a.client.CoreV1().Secrets(namespacedName.Namespace).Get(namespacedName.Name, metav1.GetOptions{}) return a.client.CoreV1().Secrets(namespacedName.Namespace).Get(namespacedName.Name, metav1.GetOptions{})
} }
func (a *SecretAdapter) FedList(namespace string, options metav1.ListOptions) (pkgruntime.Object, error) {
return a.client.CoreV1().Secrets(namespace).List(options)
}
func (a *SecretAdapter) FedUpdate(obj pkgruntime.Object) (pkgruntime.Object, error) { func (a *SecretAdapter) FedUpdate(obj pkgruntime.Object) (pkgruntime.Object, error) {
secret := obj.(*apiv1.Secret) secret := obj.(*apiv1.Secret)
return a.client.CoreV1().Secrets(secret.Namespace).Update(secret) return a.client.CoreV1().Secrets(secret.Namespace).Update(secret)
} }
func (a *SecretAdapter) FedDelete(namespacedName types.NamespacedName, options *metav1.DeleteOptions) error { func (a *SecretAdapter) FedWatch(namespace string, options metav1.ListOptions) (watch.Interface, error) {
return a.client.CoreV1().Secrets(namespacedName.Namespace).Delete(namespacedName.Name, options) return a.client.CoreV1().Secrets(namespace).Watch(options)
} }
func (a *SecretAdapter) ClusterGet(client clientset.Interface, namespacedName types.NamespacedName) (pkgruntime.Object, error) { func (a *SecretAdapter) ClusterCreate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) {
secret := obj.(*apiv1.Secret)
return client.CoreV1().Secrets(secret.Namespace).Create(secret)
}
func (a *SecretAdapter) ClusterDelete(client kubeclientset.Interface, nsName types.NamespacedName, options *metav1.DeleteOptions) error {
return client.CoreV1().Secrets(nsName.Namespace).Delete(nsName.Name, options)
}
func (a *SecretAdapter) ClusterGet(client kubeclientset.Interface, namespacedName types.NamespacedName) (pkgruntime.Object, error) {
return client.CoreV1().Secrets(namespacedName.Namespace).Get(namespacedName.Name, metav1.GetOptions{}) return client.CoreV1().Secrets(namespacedName.Namespace).Get(namespacedName.Name, metav1.GetOptions{})
} }
func (a *SecretAdapter) ClusterList(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (pkgruntime.Object, error) {
return client.CoreV1().Secrets(namespace).List(options)
}
func (a *SecretAdapter) ClusterUpdate(client kubeclientset.Interface, obj pkgruntime.Object) (pkgruntime.Object, error) {
secret := obj.(*apiv1.Secret)
return client.CoreV1().Secrets(secret.Namespace).Update(secret)
}
func (a *SecretAdapter) ClusterWatch(client kubeclientset.Interface, namespace string, options metav1.ListOptions) (watch.Interface, error) {
return client.CoreV1().Secrets(namespace).Watch(options)
}
func (a *SecretAdapter) NewTestObject(namespace string) pkgruntime.Object { func (a *SecretAdapter) NewTestObject(namespace string) pkgruntime.Object {
return &apiv1.Secret{ return &apiv1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{