Adding cascading deletion support to federated namespaces
This commit is contained in:
@@ -23,6 +23,7 @@ import (
|
||||
federation_api "k8s.io/kubernetes/federation/apis/federation/v1beta1"
|
||||
federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_release_1_5"
|
||||
"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/eventsink"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/errors"
|
||||
@@ -31,9 +32,8 @@ import (
|
||||
kubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5"
|
||||
"k8s.io/kubernetes/pkg/client/record"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
pkg_runtime "k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/util/flowcontrol"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
"k8s.io/kubernetes/pkg/watch"
|
||||
|
||||
"github.com/golang/glog"
|
||||
@@ -71,6 +71,8 @@ type NamespaceController struct {
|
||||
// For events
|
||||
eventRecorder record.EventRecorder
|
||||
|
||||
deletionHelper *deletionhelper.DeletionHelper
|
||||
|
||||
namespaceReviewDelay time.Duration
|
||||
clusterAvailableDelay time.Duration
|
||||
smallDelay time.Duration
|
||||
@@ -100,7 +102,7 @@ func NewNamespaceController(client federationclientset.Interface) *NamespaceCont
|
||||
// Start informer in federated API servers on namespaces that should be federated.
|
||||
nc.namespaceInformerStore, nc.namespaceInformerController = cache.NewInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options api.ListOptions) (pkg_runtime.Object, error) {
|
||||
ListFunc: func(options api.ListOptions) (runtime.Object, error) {
|
||||
versionedOptions := util.VersionizeV1ListOptions(options)
|
||||
return client.Core().Namespaces().List(versionedOptions)
|
||||
},
|
||||
@@ -111,7 +113,7 @@ func NewNamespaceController(client federationclientset.Interface) *NamespaceCont
|
||||
},
|
||||
&api_v1.Namespace{},
|
||||
controller.NoResyncPeriodFunc(),
|
||||
util.NewTriggerOnAllChanges(func(obj pkg_runtime.Object) { nc.deliverNamespaceObj(obj, 0, false) }))
|
||||
util.NewTriggerOnAllChanges(func(obj runtime.Object) { nc.deliverNamespaceObj(obj, 0, false) }))
|
||||
|
||||
// Federated informer on namespaces in members of federation.
|
||||
nc.namespaceFederatedInformer = util.NewFederatedInformer(
|
||||
@@ -119,7 +121,7 @@ func NewNamespaceController(client federationclientset.Interface) *NamespaceCont
|
||||
func(cluster *federation_api.Cluster, targetClient kubeclientset.Interface) (cache.Store, cache.ControllerInterface) {
|
||||
return cache.NewInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options api.ListOptions) (pkg_runtime.Object, error) {
|
||||
ListFunc: func(options api.ListOptions) (runtime.Object, error) {
|
||||
versionedOptions := util.VersionizeV1ListOptions(options)
|
||||
return targetClient.Core().Namespaces().List(versionedOptions)
|
||||
},
|
||||
@@ -133,10 +135,9 @@ func NewNamespaceController(client federationclientset.Interface) *NamespaceCont
|
||||
// Trigger reconciliation whenever something in federated cluster is changed. In most cases it
|
||||
// would be just confirmation that some namespace opration succeeded.
|
||||
util.NewTriggerOnMetaAndSpecChanges(
|
||||
func(obj pkg_runtime.Object) { nc.deliverNamespaceObj(obj, nc.namespaceReviewDelay, false) },
|
||||
func(obj runtime.Object) { nc.deliverNamespaceObj(obj, nc.namespaceReviewDelay, false) },
|
||||
))
|
||||
},
|
||||
|
||||
&util.ClusterLifecycleHandlerFuncs{
|
||||
ClusterAvailable: func(cluster *federation_api.Cluster) {
|
||||
// When new cluster becomes available process all the namespaces again.
|
||||
@@ -147,24 +148,118 @@ func NewNamespaceController(client federationclientset.Interface) *NamespaceCont
|
||||
|
||||
// Federated updeater along with Create/Update/Delete operations.
|
||||
nc.federatedUpdater = util.NewFederatedUpdater(nc.namespaceFederatedInformer,
|
||||
func(client kubeclientset.Interface, obj pkg_runtime.Object) error {
|
||||
func(client kubeclientset.Interface, obj runtime.Object) error {
|
||||
namespace := obj.(*api_v1.Namespace)
|
||||
_, err := client.Core().Namespaces().Create(namespace)
|
||||
return err
|
||||
},
|
||||
func(client kubeclientset.Interface, obj pkg_runtime.Object) error {
|
||||
func(client kubeclientset.Interface, obj runtime.Object) error {
|
||||
namespace := obj.(*api_v1.Namespace)
|
||||
_, err := client.Core().Namespaces().Update(namespace)
|
||||
return err
|
||||
},
|
||||
func(client kubeclientset.Interface, obj pkg_runtime.Object) error {
|
||||
func(client kubeclientset.Interface, obj runtime.Object) error {
|
||||
namespace := obj.(*api_v1.Namespace)
|
||||
err := client.Core().Namespaces().Delete(namespace.Name, &api_v1.DeleteOptions{})
|
||||
// IsNotFound error is fine since that means the object is deleted already.
|
||||
if errors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
nc.deletionHelper = deletionhelper.NewDeletionHelper(
|
||||
nc.hasFinalizerFunc,
|
||||
nc.removeFinalizerFunc,
|
||||
nc.addFinalizerFunc,
|
||||
// objNameFunc
|
||||
func(obj runtime.Object) string {
|
||||
namespace := obj.(*api_v1.Namespace)
|
||||
return namespace.Name
|
||||
},
|
||||
nc.updateTimeout,
|
||||
nc.eventRecorder,
|
||||
nc.namespaceFederatedInformer,
|
||||
nc.federatedUpdater,
|
||||
)
|
||||
return nc
|
||||
}
|
||||
|
||||
// Returns true if the given object has the given finalizer in its ObjectMeta.
|
||||
func (nc *NamespaceController) hasFinalizerFunc(obj runtime.Object, finalizer string) bool {
|
||||
namespace := obj.(*api_v1.Namespace)
|
||||
for i := range namespace.ObjectMeta.Finalizers {
|
||||
if string(namespace.ObjectMeta.Finalizers[i]) == finalizer {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Removes the finalizer from the given objects ObjectMeta.
|
||||
// Assumes that the given object is a namespace.
|
||||
func (nc *NamespaceController) removeFinalizerFunc(obj runtime.Object, finalizer string) (runtime.Object, error) {
|
||||
namespace := obj.(*api_v1.Namespace)
|
||||
newFinalizers := []string{}
|
||||
hasFinalizer := false
|
||||
for i := range namespace.ObjectMeta.Finalizers {
|
||||
if string(namespace.ObjectMeta.Finalizers[i]) != finalizer {
|
||||
newFinalizers = append(newFinalizers, namespace.ObjectMeta.Finalizers[i])
|
||||
} else {
|
||||
hasFinalizer = true
|
||||
}
|
||||
}
|
||||
if !hasFinalizer {
|
||||
// Nothing to do.
|
||||
return obj, nil
|
||||
}
|
||||
namespace.ObjectMeta.Finalizers = newFinalizers
|
||||
namespace, err := nc.federatedApiClient.Core().Namespaces().Update(namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to remove finalizer %s from namespace %s: %v", finalizer, namespace.Name, err)
|
||||
}
|
||||
return namespace, nil
|
||||
}
|
||||
|
||||
// Adds the given finalizer to the given objects ObjectMeta.
|
||||
// Assumes that the given object is a namespace.
|
||||
func (nc *NamespaceController) addFinalizerFunc(obj runtime.Object, finalizer string) (runtime.Object, error) {
|
||||
namespace := obj.(*api_v1.Namespace)
|
||||
namespace.ObjectMeta.Finalizers = append(namespace.ObjectMeta.Finalizers, finalizer)
|
||||
namespace, err := nc.federatedApiClient.Core().Namespaces().Finalize(namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add finalizer %s to namespace %s: %v", finalizer, namespace.Name, err)
|
||||
}
|
||||
return namespace, nil
|
||||
}
|
||||
|
||||
// Returns true if the given object has the given finalizer in its NamespaceSpec.
|
||||
func (nc *NamespaceController) hasFinalizerFuncInSpec(obj runtime.Object, finalizer api_v1.FinalizerName) bool {
|
||||
namespace := obj.(*api_v1.Namespace)
|
||||
for i := range namespace.Spec.Finalizers {
|
||||
if namespace.Spec.Finalizers[i] == finalizer {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Removes the finalizer from the given objects NamespaceSpec.
|
||||
func (nc *NamespaceController) removeFinalizerFromSpec(namespace *api_v1.Namespace, finalizer api_v1.FinalizerName) (*api_v1.Namespace, error) {
|
||||
updatedFinalizers := []api_v1.FinalizerName{}
|
||||
for i := range namespace.Spec.Finalizers {
|
||||
if namespace.Spec.Finalizers[i] != finalizer {
|
||||
updatedFinalizers = append(updatedFinalizers, namespace.Spec.Finalizers[i])
|
||||
}
|
||||
}
|
||||
namespace.Spec.Finalizers = updatedFinalizers
|
||||
updatedNamespace, err := nc.federatedApiClient.Core().Namespaces().Finalize(namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to remove finalizer %s from namespace %s: %v", string(finalizer), namespace.Name, err)
|
||||
}
|
||||
return updatedNamespace, nil
|
||||
}
|
||||
|
||||
func (nc *NamespaceController) Run(stopChan <-chan struct{}) {
|
||||
go nc.namespaceInformerController.Run(stopChan)
|
||||
nc.namespaceFederatedInformer.Start()
|
||||
@@ -255,6 +350,23 @@ func (nc *NamespaceController) reconcileNamespace(namespace string) {
|
||||
return
|
||||
}
|
||||
|
||||
glog.V(3).Infof("Ensuring delete object from underlying clusters finalizer for namespace: %s",
|
||||
baseNamespace.Name)
|
||||
// Add the DeleteFromUnderlyingClusters finalizer before creating a namespace in
|
||||
// underlying clusters.
|
||||
// This ensures that the dependent namespaces are deleted in underlying
|
||||
// clusters when the federated namespace is deleted.
|
||||
updatedNamespaceObj, err := nc.deletionHelper.EnsureDeleteFromUnderlyingClustersFinalizer(baseNamespace)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to ensure delete object from underlying clusters finalizer in namespace %s: %v",
|
||||
baseNamespace.Name, err)
|
||||
nc.deliverNamespace(namespace, 0, false)
|
||||
return
|
||||
}
|
||||
baseNamespace = updatedNamespaceObj.(*api_v1.Namespace)
|
||||
|
||||
glog.V(3).Infof("Syncing namespace %s in underlying clusters", baseNamespace.Name)
|
||||
// Sync the namespace in all underlying clusters.
|
||||
clusters, err := nc.namespaceFederatedInformer.GetReadyClusters()
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to get cluster list: %v", err)
|
||||
@@ -274,6 +386,7 @@ func (nc *NamespaceController) reconcileNamespace(namespace string) {
|
||||
ObjectMeta: util.CopyObjectMeta(baseNamespace.ObjectMeta),
|
||||
Spec: baseNamespace.Spec,
|
||||
}
|
||||
glog.V(5).Infof("Desired namespace in underlying clusters: %+v", desiredNamespace)
|
||||
|
||||
if !found {
|
||||
nc.eventRecorder.Eventf(baseNamespace, api.EventTypeNormal, "CreateInCluster",
|
||||
@@ -290,7 +403,7 @@ func (nc *NamespaceController) reconcileNamespace(namespace string) {
|
||||
// Update existing namespace, if needed.
|
||||
if !util.ObjectMetaAndSpecEquivalent(desiredNamespace, clusterNamespace) {
|
||||
nc.eventRecorder.Eventf(baseNamespace, api.EventTypeNormal, "UpdateInCluster",
|
||||
"Updating namespace in cluster %s", cluster.Name)
|
||||
"Updating namespace in cluster %s. Desired: %+v\n Actual: %+v\n", cluster.Name, desiredNamespace, clusterNamespace)
|
||||
|
||||
operations = append(operations, util.FederatedOperation{
|
||||
Type: util.OperationTypeUpdate,
|
||||
@@ -305,6 +418,8 @@ func (nc *NamespaceController) reconcileNamespace(namespace string) {
|
||||
// Everything is in order
|
||||
return
|
||||
}
|
||||
glog.V(2).Infof("Updating namespace %s in underlying clusters. Operations: %d", baseNamespace.Name, len(operations))
|
||||
|
||||
err = nc.federatedUpdater.UpdateWithOnError(operations, nc.updateTimeout, func(op util.FederatedOperation, operror error) {
|
||||
nc.eventRecorder.Eventf(baseNamespace, api.EventTypeNormal, "UpdateInClusterFailed",
|
||||
"Namespace update in cluster %s failed: %v", op.ClusterName, operror)
|
||||
@@ -329,66 +444,30 @@ func (nc *NamespaceController) delete(namespace *api_v1.Namespace) error {
|
||||
Phase: api_v1.NamespaceTerminating,
|
||||
},
|
||||
}
|
||||
var err error
|
||||
if namespace.Status.Phase != api_v1.NamespaceTerminating {
|
||||
nc.eventRecorder.Event(namespace, api.EventTypeNormal, "DeleteNamespace", fmt.Sprintf("Marking for deletion"))
|
||||
_, err := nc.federatedApiClient.Core().Namespaces().Update(updatedNamespace)
|
||||
_, err = nc.federatedApiClient.Core().Namespaces().Update(updatedNamespace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update namespace: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Right now there is just 5 types of objects: ReplicaSet, Secret, Ingress, Events and Service.
|
||||
// Temporarly these items are simply deleted one by one to squeeze this code into 1.4.
|
||||
// TODO: Make it generic (like in the regular namespace controller) and parallel.
|
||||
err := nc.federatedApiClient.Core().Services(namespace.Name).DeleteCollection(&api_v1.DeleteOptions{}, api_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete service list: %v", err)
|
||||
}
|
||||
err = nc.federatedApiClient.Extensions().ReplicaSets(namespace.Name).DeleteCollection(&api_v1.DeleteOptions{}, api_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete replicaset list from namespace: %v", err)
|
||||
}
|
||||
err = nc.federatedApiClient.Core().Secrets(namespace.Name).DeleteCollection(&api_v1.DeleteOptions{}, api_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete secret list from namespace: %v", err)
|
||||
}
|
||||
err = nc.federatedApiClient.Extensions().Ingresses(namespace.Name).DeleteCollection(&api_v1.DeleteOptions{}, api_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete ingresses list from namespace: %v", err)
|
||||
}
|
||||
err = nc.federatedApiClient.Extensions().DaemonSets(namespace.Name).DeleteCollection(&api_v1.DeleteOptions{}, api_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete daemonsets list from namespace: %v", err)
|
||||
}
|
||||
err = nc.federatedApiClient.Extensions().Deployments(namespace.Name).DeleteCollection(&api_v1.DeleteOptions{}, api_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete deployments list from namespace: %v", err)
|
||||
}
|
||||
err = nc.federatedApiClient.Core().Events(namespace.Name).DeleteCollection(&api_v1.DeleteOptions{}, api_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete events list from namespace: %v", err)
|
||||
}
|
||||
|
||||
// Remove kube_api.FinalzerKubernetes
|
||||
if len(updatedNamespace.Spec.Finalizers) != 0 {
|
||||
finalizerSet := sets.NewString()
|
||||
for i := range namespace.Spec.Finalizers {
|
||||
if namespace.Spec.Finalizers[i] != api_v1.FinalizerKubernetes {
|
||||
finalizerSet.Insert(string(namespace.Spec.Finalizers[i]))
|
||||
}
|
||||
}
|
||||
updatedNamespace.Spec.Finalizers = make([]api_v1.FinalizerName, 0, len(finalizerSet))
|
||||
for _, value := range finalizerSet.List() {
|
||||
updatedNamespace.Spec.Finalizers = append(updatedNamespace.Spec.Finalizers, api_v1.FinalizerName(value))
|
||||
}
|
||||
_, err := nc.federatedApiClient.Core().Namespaces().Finalize(updatedNamespace)
|
||||
if nc.hasFinalizerFuncInSpec(updatedNamespace, api_v1.FinalizerKubernetes) {
|
||||
// Delete resources in this namespace.
|
||||
updatedNamespace, err = nc.removeKubernetesFinalizer(updatedNamespace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to finalize namespace: %v", err)
|
||||
return fmt.Errorf("error in deleting resources in namespace %s: %v", namespace.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: What about namespaces in subclusters ???
|
||||
err = nc.federatedApiClient.Core().Namespaces().Delete(updatedNamespace.Name, &api_v1.DeleteOptions{})
|
||||
// Delete the namespace from all underlying clusters.
|
||||
_, err = nc.deletionHelper.HandleObjectInUnderlyingClusters(updatedNamespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = nc.federatedApiClient.Core().Namespaces().Delete(namespace.Name, 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.
|
||||
// This is expected when we are processing an update as a result of namespace finalizer deletion.
|
||||
@@ -399,3 +478,44 @@ func (nc *NamespaceController) delete(namespace *api_v1.Namespace) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensures that all resources in this namespace are deleted and then removes the kubernetes finalizer.
|
||||
func (nc *NamespaceController) removeKubernetesFinalizer(namespace *api_v1.Namespace) (*api_v1.Namespace, error) {
|
||||
// Right now there are just 7 types of objects: Deployments, DaemonSets, ReplicaSet, Secret, Ingress, Events and Service.
|
||||
// Temporarly these items are simply deleted one by one to squeeze this code into 1.4.
|
||||
// TODO: Make it generic (like in the regular namespace controller) and parallel.
|
||||
err := nc.federatedApiClient.Core().Services(namespace.Name).DeleteCollection(&api_v1.DeleteOptions{}, api_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to delete service list: %v", err)
|
||||
}
|
||||
err = nc.federatedApiClient.Extensions().ReplicaSets(namespace.Name).DeleteCollection(&api_v1.DeleteOptions{}, api_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to delete replicaset list from namespace: %v", err)
|
||||
}
|
||||
err = nc.federatedApiClient.Core().Secrets(namespace.Name).DeleteCollection(&api_v1.DeleteOptions{}, api_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to delete secret list from namespace: %v", err)
|
||||
}
|
||||
err = nc.federatedApiClient.Extensions().Ingresses(namespace.Name).DeleteCollection(&api_v1.DeleteOptions{}, api_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to delete ingresses list from namespace: %v", err)
|
||||
}
|
||||
err = nc.federatedApiClient.Extensions().DaemonSets(namespace.Name).DeleteCollection(&api_v1.DeleteOptions{}, api_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to delete daemonsets list from namespace: %v", err)
|
||||
}
|
||||
err = nc.federatedApiClient.Extensions().Deployments(namespace.Name).DeleteCollection(&api_v1.DeleteOptions{}, api_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to delete deployments list from namespace: %v", err)
|
||||
}
|
||||
err = nc.federatedApiClient.Core().Events(namespace.Name).DeleteCollection(&api_v1.DeleteOptions{}, api_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to delete events list from namespace: %v", err)
|
||||
}
|
||||
|
||||
// Remove kube_api.FinalizerKubernetes
|
||||
if len(namespace.Spec.Finalizers) != 0 {
|
||||
return nc.removeFinalizerFromSpec(namespace, api_v1.FinalizerKubernetes)
|
||||
}
|
||||
return namespace, nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user