diff --git a/federation/pkg/federation-controller/namespace/namespace_controller.go b/federation/pkg/federation-controller/namespace/namespace_controller.go index d46911c47fd..b8296bc449f 100644 --- a/federation/pkg/federation-controller/namespace/namespace_controller.go +++ b/federation/pkg/federation-controller/namespace/namespace_controller.go @@ -251,7 +251,12 @@ func (nc *NamespaceController) reconcileNamespace(namespace string) { } baseNamespace := baseNamespaceObj.(*api_v1.Namespace) if baseNamespace.DeletionTimestamp != nil { - nc.delete(baseNamespace) + if err := nc.delete(baseNamespace); err != nil { + glog.Errorf("Failed to delete %s: %v", namespace, err) + nc.eventRecorder.Eventf(baseNamespace, api.EventTypeNormal, "DeleteFailed", + "Namespace delete failed: %v", err) + nc.deliverNamespace(namespace, 0, true) + } return } @@ -320,7 +325,8 @@ func (nc *NamespaceController) reconcileNamespace(namespace string) { nc.deliverNamespace(namespace, nc.namespaceReviewDelay, false) } -func (nc *NamespaceController) delete(namespace *api_v1.Namespace) { +// delete deletes the given namespace or returns error if the deletion was not complete. +func (nc *NamespaceController) delete(namespace *api_v1.Namespace) error { // Set Terminating status. updatedNamespace := &api_v1.Namespace{ ObjectMeta: namespace.ObjectMeta, @@ -333,13 +339,33 @@ func (nc *NamespaceController) delete(namespace *api_v1.Namespace) { nc.eventRecorder.Event(namespace, api.EventTypeNormal, "DeleteNamespace", fmt.Sprintf("Marking for deletion")) _, err := nc.federatedApiClient.Core().Namespaces().Update(updatedNamespace) if err != nil { - glog.Errorf("Failed to update namespace %s: %v", updatedNamespace.Name, err) - nc.deliverNamespace(namespace.Name, 0, true) - return + return fmt.Errorf("failed to update namespace: %v", err) } } - // TODO: delete all namespace content. + // 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.DeleteOptions{}, api.ListOptions{}) + if err != nil { + return fmt.Errorf("failed to delete service list: %v", err) + } + err = nc.federatedApiClient.Extensions().ReplicaSets(namespace.Name).DeleteCollection(&api.DeleteOptions{}, api.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.DeleteOptions{}, api.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.DeleteOptions{}, api.ListOptions{}) + if err != nil { + return fmt.Errorf("failed to delete ingresses list from namespace: %v", err) + } + err = nc.federatedApiClient.Core().Events(namespace.Name).DeleteCollection(&api.DeleteOptions{}, api.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 { @@ -355,21 +381,19 @@ func (nc *NamespaceController) delete(namespace *api_v1.Namespace) { } _, err := nc.federatedApiClient.Core().Namespaces().Finalize(updatedNamespace) if err != nil { - glog.Errorf("Failed to update namespace %s: %v", updatedNamespace.Name, err) - nc.deliverNamespace(namespace.Name, 0, true) - return + return fmt.Errorf("failed to finalize namespace: %v", err) } } // TODO: What about namespaces in subclusters ??? - err := nc.federatedApiClient.Core().Namespaces().Delete(updatedNamespace.Name, &api.DeleteOptions{}) + err = nc.federatedApiClient.Core().Namespaces().Delete(updatedNamespace.Name, &api.DeleteOptions{}) 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. // The process that deleted the last finalizer is also going to delete the namespace and we do not have to do anything. if !errors.IsNotFound(err) { - glog.Errorf("Failed to delete namespace %s: %v", namespace.Name, err) - nc.deliverNamespace(namespace.Name, 0, true) + return fmt.Errorf("failed to delete namespace: %v", err) } } + return nil } diff --git a/federation/pkg/federation-controller/namespace/namespace_controller_test.go b/federation/pkg/federation-controller/namespace/namespace_controller_test.go index 3bd38906a9f..08608b3ecf4 100644 --- a/federation/pkg/federation-controller/namespace/namespace_controller_test.go +++ b/federation/pkg/federation-controller/namespace/namespace_controller_test.go @@ -24,9 +24,12 @@ import ( federation_api "k8s.io/kubernetes/federation/apis/federation/v1beta1" fake_federation_release_1_4 "k8s.io/kubernetes/federation/client/clientset_generated/federation_release_1_4/fake" . "k8s.io/kubernetes/federation/pkg/federation-controller/util/test" + "k8s.io/kubernetes/pkg/api/unversioned" api_v1 "k8s.io/kubernetes/pkg/api/v1" + extensionsv1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" kube_release_1_4 "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_4" fake_kube_release_1_4 "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_4/fake" + "k8s.io/kubernetes/pkg/client/testing/core" "k8s.io/kubernetes/pkg/runtime" "github.com/stretchr/testify/assert" @@ -35,6 +38,12 @@ import ( func TestNamespaceController(t *testing.T) { cluster1 := NewCluster("cluster1", api_v1.ConditionTrue) cluster2 := NewCluster("cluster2", api_v1.ConditionTrue) + ns1 := api_v1.Namespace{ + ObjectMeta: api_v1.ObjectMeta{ + Name: "test-namespace", + SelfLink: "/api/v1/namespaces/test-namespace", + }, + } fakeClient := &fake_federation_release_1_4.Clientset{} RegisterFakeList("clusters", &fakeClient.Fake, &federation_api.ClusterList{Items: []federation_api.Cluster{*cluster1}}) @@ -53,6 +62,29 @@ func TestNamespaceController(t *testing.T) { RegisterFakeList("namespaces", &cluster2Client.Fake, &api_v1.NamespaceList{Items: []api_v1.Namespace{}}) cluster2CreateChan := RegisterFakeCopyOnCreate("namespaces", &cluster2Client.Fake, cluster2Watch) + RegisterFakeList("replicasets", &fakeClient.Fake, &extensionsv1.ReplicaSetList{Items: []extensionsv1.ReplicaSet{ + { + ObjectMeta: api_v1.ObjectMeta{ + Name: "test-rs", + Namespace: ns1.Namespace, + }}}}) + RegisterFakeList("secrets", &fakeClient.Fake, &api_v1.SecretList{Items: []api_v1.Secret{ + { + ObjectMeta: api_v1.ObjectMeta{ + Name: "test-secret", + Namespace: ns1.Namespace, + }}}}) + RegisterFakeList("services", &fakeClient.Fake, &api_v1.ServiceList{Items: []api_v1.Service{ + { + ObjectMeta: api_v1.ObjectMeta{ + Name: "test-service", + Namespace: ns1.Namespace, + }}}}) + nsDeleteChan := RegisterDelete(&fakeClient.Fake, "namespaces") + rsDeleteChan := RegisterDeleteCollection(&fakeClient.Fake, "replicasets") + serviceDeleteChan := RegisterDeleteCollection(&fakeClient.Fake, "services") + secretDeleteChan := RegisterDeleteCollection(&fakeClient.Fake, "secrets") + namespaceController := NewNamespaceController(fakeClient) informer := ToFederatedInformerForTestOnly(namespaceController.namespaceFederatedInformer) informer.SetClientFactory(func(cluster *federation_api.Cluster) (kube_release_1_4.Interface, error) { @@ -73,13 +105,6 @@ func TestNamespaceController(t *testing.T) { stop := make(chan struct{}) namespaceController.Run(stop) - ns1 := api_v1.Namespace{ - ObjectMeta: api_v1.ObjectMeta{ - Name: "test-namespace", - SelfLink: "/api/v1/namespaces/test-namespace", - }, - } - // Test add federated namespace. namespaceWatch.Add(&ns1) createdNamespace := GetNamespaceFromChan(cluster1CreateChan) @@ -103,9 +128,44 @@ func TestNamespaceController(t *testing.T) { assert.Equal(t, ns1.Name, createdNamespace2.Name) // assert.Contains(t, createdNamespace2.Annotations, "A") + ns1.DeletionTimestamp = &unversioned.Time{Time: time.Now()} + namespaceWatch.Modify(&ns1) + assert.Equal(t, ns1.Name, GetStringFromChan(nsDeleteChan)) + assert.Equal(t, "all", GetStringFromChan(rsDeleteChan)) + assert.Equal(t, "all", GetStringFromChan(serviceDeleteChan)) + assert.Equal(t, "all", GetStringFromChan(secretDeleteChan)) + close(stop) } +func RegisterDeleteCollection(client *core.Fake, resource string) chan string { + deleteChan := make(chan string, 100) + client.AddReactor("delete-collection", resource, func(action core.Action) (bool, runtime.Object, error) { + deleteChan <- "all" + return true, nil, nil + }) + return deleteChan +} + +func RegisterDelete(client *core.Fake, resource string) chan string { + deleteChan := make(chan string, 100) + client.AddReactor("delete", resource, func(action core.Action) (bool, runtime.Object, error) { + deleteAction := action.(core.DeleteAction) + deleteChan <- deleteAction.GetName() + return true, nil, nil + }) + return deleteChan +} + +func GetStringFromChan(c chan string) string { + select { + case str := <-c: + return str + case <-time.After(5 * time.Second): + return "" + } +} + func GetNamespaceFromChan(c chan runtime.Object) *api_v1.Namespace { namespace := GetObjectFromChan(c).(*api_v1.Namespace) return namespace