mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-10 21:50:05 +00:00
Namespace.Spec.Finalizer support
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
Reference in New Issue
Block a user