Change:
In PR the specific resource types are handled explicitly. Need to delete them in more generic way, in a similar way that namespace controller does.
- switching this to 1.6 as it's too late for 1.5. ok? (please switch it right back if you disagree)
did fix this?
Please close or move to next-candidate with work remaining to be done.
Yes fixed by

Discussion:
} baseNamespace := baseNamespaceObj.(*api_v1.Namespace) if baseNamespace.DeletionTimestamp != nil { <del> nc.delete(baseNamespace) </del> <ins> 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) } </ins> return }

nc.deliverNamespace(namespace, nc.namespaceReviewDelay, false) } <del> func (nc *NamespaceController) delete(namespace *api_v1.Namespace) { </del> <ins> // delete  deletes the given namespace or returns error if the deletion was not complete. func (nc *NamespaceController) delete(namespace *api_v1.Namespace) error { </ins> // Set Terminating status. updatedNamespace := &api_v1.Namespace{ ObjectMeta: namespace.ObjectMeta,

nc.eventRecorder.Event(namespace, api.EventTypeNormal, "DeleteNamespace", fmt.Sprintf("Marking for deletion")) _, err := nc.federatedApiClient.Core().Namespaces().Update(updatedNamespace) if err != nil { <del> glog.Errorf("Failed to update namespace %s: %v", updatedNamespace.Name, err) nc.deliverNamespace(namespace.Name, 0, true) return </del> <ins> return fmt.Errorf("failed to update namespace: %v", err) </ins> } } <del> // TODO: delete all namespace content. </del> <ins> // 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) } </ins> // Remove kube_api.FinalzerKubernetes if len(updatedNamespace.Spec.Finalizers) != 0 {

} _, err := nc.federatedApiClient.Core().Namespaces().Finalize(updatedNamespace) if err != nil { <del> glog.Errorf("Failed to update namespace %s: %v", updatedNamespace.Name, err) nc.deliverNamespace(namespace.Name, 0, true) return </del> <ins> return fmt.Errorf("failed to finalize namespace: %v", err) </ins> } } // TODO: What about namespaces in subclusters ??? <del> err := nc.federatedApiClient.Core().Namespaces().Delete(updatedNamespace.Name, &api.DeleteOptions{}) </del> <ins> err = nc.federatedApiClient.Core().Namespaces().Delete(updatedNamespace.Name, &api.DeleteOptions{}) </ins> 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) { <del> glog.Errorf("Failed to delete namespace %s: %v", namespace.Name, err) nc.deliverNamespace(namespace.Name, 0, true) </del> <ins> return fmt.Errorf("failed to delete namespace: %v", err) </ins> } } <ins> return nil </ins> }

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" <ins> "k8s.io/kubernetes/pkg/api/unversioned" </ins> api_v1 "k8s.io/kubernetes/pkg/api/v1" <ins> extensionsv1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" </ins> 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" <ins> "k8s.io/kubernetes/pkg/client/testing/core" </ins> "k8s.io/kubernetes/pkg/runtime" "github.com/stretchr/testify/assert"

func TestNamespaceController(t *testing.T) { cluster1 := NewCluster("cluster1", api_v1.ConditionTrue) cluster2 := NewCluster("cluster2", api_v1.ConditionTrue) <ins> ns1 := api_v1.Namespace{ ObjectMeta: api_v1.ObjectMeta{ Name:     "test-namespace", SelfLink: "/api/v1/namespaces/test-namespace", }, } </ins> fakeClient := &fake_federation_release_1_4.Clientset{} RegisterFakeList("clusters", &fakeClient.Fake, &federation_api.ClusterList{Items: []federation_api.Cluster{*cluster1}})

RegisterFakeList("namespaces", &cluster2Client.Fake, &api_v1.NamespaceList{Items: []api_v1.Namespace{}}) cluster2CreateChan := RegisterFakeCopyOnCreate("namespaces", &cluster2Client.Fake, cluster2Watch) <ins> 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") </ins> namespaceController := NewNamespaceController(fakeClient) informer := ToFederatedInformerForTestOnly(namespaceController.namespaceFederatedInformer) informer.SetClientFactory(func(cluster *federation_api.Cluster) (kube_release_1_4.Interface, error) {

stop := make(chan struct{}) namespaceController.Run(stop) <del> ns1 := api_v1.Namespace{ ObjectMeta: api_v1.ObjectMeta{ Name:     "test-namespace", SelfLink: "/api/v1/namespaces/test-namespace", }, } </del> // Test add federated namespace. namespaceWatch.Add(&ns1) createdNamespace := GetNamespaceFromChan(cluster1CreateChan)

assert.Equal(t, ns1.Name, createdNamespace2.Name) // assert.Contains(t, createdNamespace2.Annotations, "A") <ins> 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)) </ins> close(stop) } <ins> 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 "" } } </ins> func GetNamespaceFromChan(c chan runtime.Object) *api_v1.Namespace { namespace := GetObjectFromChan(c).(*api_v1.Namespace) return namespace