Namespace.Spec.Finalizer support

This commit is contained in:
derekwaynecarr
2015-03-20 12:48:12 -04:00
parent cb7c781da5
commit 29c491ef2e
21 changed files with 381 additions and 24 deletions

View File

@@ -17,6 +17,8 @@ limitations under the License.
package etcd
import (
"fmt"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
@@ -25,15 +27,27 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/namespace"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
)
// rest implements a RESTStorage for namespaces against etcd
type REST struct {
*etcdgeneric.Etcd
status *etcdgeneric.Etcd
}
// StatusREST implements the REST endpoint for changing the status of a namespace.
type StatusREST struct {
store *etcdgeneric.Etcd
}
// FinalizeREST implements the REST endpoint for finalizing a namespace.
type FinalizeREST struct {
store *etcdgeneric.Etcd
}
// NewStorage returns a RESTStorage object that will work against namespaces
func NewStorage(h tools.EtcdHelper) *REST {
func NewStorage(h tools.EtcdHelper) (*REST, *StatusREST, *FinalizeREST) {
store := &etcdgeneric.Etcd{
NewFunc: func() runtime.Object { return &api.Namespace{} },
NewListFunc: func() runtime.Object { return &api.NamespaceList{} },
@@ -56,5 +70,54 @@ func NewStorage(h tools.EtcdHelper) *REST {
store.UpdateStrategy = namespace.Strategy
store.ReturnDeletedObject = true
return &REST{Etcd: store}
statusStore := *store
statusStore.UpdateStrategy = namespace.StatusStrategy
finalizeStore := *store
finalizeStore.UpdateStrategy = namespace.FinalizeStrategy
return &REST{Etcd: store, status: &statusStore}, &StatusREST{store: &statusStore}, &FinalizeREST{store: &finalizeStore}
}
// Delete enforces life-cycle rules for namespace termination
func (r *REST) Delete(ctx api.Context, name string, options *api.DeleteOptions) (runtime.Object, error) {
nsObj, err := r.Get(ctx, name)
if err != nil {
return nil, err
}
namespace := nsObj.(*api.Namespace)
// upon first request to delete, we switch the phase to start namespace termination
if namespace.DeletionTimestamp == nil {
now := util.Now()
namespace.DeletionTimestamp = &now
namespace.Status.Phase = api.NamespaceTerminating
result, _, err := r.status.Update(ctx, namespace)
return result, err
}
// prior to final deletion, we must ensure that finalizers is empty
if len(namespace.Spec.Finalizers) != 0 {
err = fmt.Errorf("Unable to delete namespace %v because finalizers is not empty %v", namespace.Name, namespace.Spec.Finalizers)
}
return r.Etcd.Delete(ctx, name, nil)
}
func (r *StatusREST) New() runtime.Object {
return &api.Namespace{}
}
// Update alters the status subset of an object.
func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
return r.store.Update(ctx, obj)
}
func (r *FinalizeREST) New() runtime.Object {
return &api.Namespace{}
}
// Update alters the status finalizers subset of an object.
func (r *FinalizeREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
return r.store.Update(ctx, obj)
}

View File

@@ -41,7 +41,7 @@ func newHelper(t *testing.T) (*tools.FakeEtcdClient, tools.EtcdHelper) {
func newStorage(t *testing.T) (*REST, *tools.FakeEtcdClient, tools.EtcdHelper) {
fakeEtcdClient, h := newHelper(t)
storage := NewStorage(h)
storage, _, _ := NewStorage(h)
return storage, fakeEtcdClient, h
}
@@ -69,7 +69,7 @@ func TestStorage(t *testing.T) {
func TestCreate(t *testing.T) {
fakeEtcdClient, helper := newHelper(t)
storage := NewStorage(helper)
storage, _, _ := NewStorage(helper)
test := resttest.New(t, storage, fakeEtcdClient.SetError)
namespace := validNewNamespace()
namespace.ObjectMeta = api.ObjectMeta{}
@@ -94,7 +94,7 @@ func expectNamespace(t *testing.T, out runtime.Object) (*api.Namespace, bool) {
func TestCreateSetsFields(t *testing.T) {
fakeEtcdClient, helper := newHelper(t)
storage := NewStorage(helper)
storage, _, _ := NewStorage(helper)
namespace := validNewNamespace()
_, err := storage.Create(api.NewDefaultContext(), namespace)
if err != fakeEtcdClient.Err {
@@ -124,7 +124,7 @@ func TestListEmptyNamespaceList(t *testing.T) {
E: fakeEtcdClient.NewError(tools.EtcdErrorCodeNotFound),
}
storage := NewStorage(helper)
storage, _, _ := NewStorage(helper)
namespaces, err := storage.List(api.NewContext(), labels.Everything(), fields.Everything())
if err != nil {
t.Errorf("Unexpected error: %v", err)
@@ -157,7 +157,7 @@ func TestListNamespaceList(t *testing.T) {
},
},
}
storage := NewStorage(helper)
storage, _, _ := NewStorage(helper)
namespacesObj, err := storage.List(api.NewDefaultContext(), labels.Everything(), fields.Everything())
namespaces := namespacesObj.(*api.NamespaceList)
if err != nil {
@@ -204,7 +204,7 @@ func TestListNamespaceListSelection(t *testing.T) {
},
},
}
storage := NewStorage(helper)
storage, _, _ := NewStorage(helper)
ctx := api.NewDefaultContext()
table := []struct {
label, field string
@@ -252,9 +252,11 @@ func TestListNamespaceListSelection(t *testing.T) {
}
func TestNamespaceDecode(t *testing.T) {
storage := NewStorage(tools.EtcdHelper{})
_, helper := newHelper(t)
storage, _, _ := NewStorage(helper)
expected := validNewNamespace()
expected.Status.Phase = api.NamespaceActive
expected.Spec.Finalizers = []api.FinalizerName{api.FinalizerKubernetes}
body, err := latest.Codec.Encode(expected)
if err != nil {
t.Fatalf("unexpected error: %v", err)
@@ -281,7 +283,7 @@ func TestGet(t *testing.T) {
},
},
}
storage := NewStorage(helper)
storage, _, _ := NewStorage(helper)
obj, err := storage.Get(api.NewContext(), "foo")
namespace := obj.(*api.Namespace)
if err != nil {
@@ -311,8 +313,9 @@ func TestDeleteNamespace(t *testing.T) {
},
},
}
storage := NewStorage(helper)
storage, _, _ := NewStorage(helper)
_, err := storage.Delete(api.NewDefaultContext(), "foo", nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

View File

@@ -45,10 +45,27 @@ func (namespaceStrategy) NamespaceScoped() bool {
// ResetBeforeCreate clears fields that are not allowed to be set by end users on creation.
func (namespaceStrategy) ResetBeforeCreate(obj runtime.Object) {
// on create, status is active
namespace := obj.(*api.Namespace)
namespace.Status = api.NamespaceStatus{
Phase: api.NamespaceActive,
}
// on create, we require the kubernetes value
// we cannot use this in defaults conversion because we let it get removed over life of object
hasKubeFinalizer := false
for i := range namespace.Spec.Finalizers {
if namespace.Spec.Finalizers[i] == api.FinalizerKubernetes {
hasKubeFinalizer = true
break
}
}
if !hasKubeFinalizer {
if len(namespace.Spec.Finalizers) == 0 {
namespace.Spec.Finalizers = []api.FinalizerName{api.FinalizerKubernetes}
} else {
namespace.Spec.Finalizers = append(namespace.Spec.Finalizers, api.FinalizerKubernetes)
}
}
}
// Validate validates a new namespace.
@@ -74,10 +91,19 @@ type namespaceStatusStrategy struct {
var StatusStrategy = namespaceStatusStrategy{Strategy}
func (namespaceStatusStrategy) ValidateUpdate(obj, old runtime.Object) fielderrors.ValidationErrorList {
// TODO: merge valid fields after update
return validation.ValidateNamespaceStatusUpdate(obj.(*api.Namespace), old.(*api.Namespace))
}
type namespaceFinalizeStrategy struct {
namespaceStrategy
}
var FinalizeStrategy = namespaceFinalizeStrategy{Strategy}
func (namespaceFinalizeStrategy) ValidateUpdate(obj, old runtime.Object) fielderrors.ValidationErrorList {
return validation.ValidateNamespaceFinalizeUpdate(obj.(*api.Namespace), old.(*api.Namespace))
}
// MatchNamespace returns a generic matcher for a given label and field selector.
func MatchNamespace(label labels.Selector, field fields.Selector) generic.Matcher {
return generic.MatcherFunc(func(obj runtime.Object) (bool, error) {