Cherrypicking #66535

validate deletion admission object

backward compatibility: add validation for direct storage delete calls

apply nil validation to existing tests

revert behavior changes in deleteCollection call

fixes validation on wiring graceful deletion

remove nil validation check

continue admission check on not found error
k3s-v1.15.3
yue9944882 2018-07-24 12:24:59 +08:00 committed by Chao Xu
parent 46a80259f6
commit 34c4a6e057
22 changed files with 150 additions and 103 deletions

View File

@ -125,7 +125,7 @@ func (r *REST) Export(ctx context.Context, name string, opts metav1.ExportOption
}
// Delete enforces life-cycle rules for namespace termination
func (r *REST) Delete(ctx context.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
func (r *REST) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
nsObj, err := r.Get(ctx, name, &metav1.GetOptions{})
if err != nil {
return nil, false, err
@ -162,6 +162,10 @@ func (r *REST) Delete(ctx context.Context, name string, options *metav1.DeleteOp
// upon first request to delete, we switch the phase to start namespace termination
// TODO: enhance graceful deletion's calls to DeleteStrategy to allow phase change and finalizer patterns
if namespace.DeletionTimestamp.IsZero() {
if err := deleteValidation(nsObj); err != nil {
return nil, false, err
}
key, err := r.store.KeyFunc(ctx, name)
if err != nil {
return nil, false, err
@ -238,7 +242,7 @@ func (r *REST) Delete(ctx context.Context, name string, options *metav1.DeleteOp
err = apierrors.NewConflict(api.Resource("namespaces"), namespace.Name, fmt.Errorf("The system is ensuring all content is removed from this namespace. Upon completion, this namespace will automatically be purged by the system."))
return nil, false, err
}
return r.store.Delete(ctx, name, options)
return r.store.Delete(ctx, name, deleteValidation, options)
}
// ShouldDeleteNamespaceDuringUpdate adds namespace-specific spec.finalizer checks on top of the default generic ShouldDeleteDuringUpdate behavior

View File

@ -162,7 +162,7 @@ func TestDeleteNamespaceWithIncompleteFinalizers(t *testing.T) {
if err := storage.store.Storage.Create(ctx, key, namespace, nil, 0, false); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, _, err := storage.Delete(ctx, "foo", nil); err == nil {
if _, _, err := storage.Delete(ctx, "foo", rest.ValidateAllObjectFunc, nil); err == nil {
t.Errorf("unexpected error: %v", err)
}
// should still exist
@ -375,7 +375,7 @@ func TestDeleteNamespaceWithCompleteFinalizers(t *testing.T) {
if err := storage.store.Storage.Create(ctx, key, namespace, nil, 0, false); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, _, err := storage.Delete(ctx, "foo", nil); err != nil {
if _, _, err := storage.Delete(ctx, "foo", rest.ValidateAllObjectFunc, nil); err != nil {
t.Errorf("unexpected error: %v", err)
}
// should not exist

View File

@ -124,7 +124,7 @@ func (r *EvictionREST) Create(ctx context.Context, obj runtime.Object, createVal
// Evicting a terminal pod should result in direct deletion of pod as it already caused disruption by the time we are evicting.
// There is no need to check for pdb.
if pod.Status.Phase == api.PodSucceeded || pod.Status.Phase == api.PodFailed {
_, _, err = r.store.Delete(ctx, eviction.Name, deletionOptions)
_, _, err = r.store.Delete(ctx, eviction.Name, rest.ValidateAllObjectFunc, deletionOptions)
if err != nil {
return nil, err
}
@ -173,7 +173,7 @@ func (r *EvictionREST) Create(ctx context.Context, obj runtime.Object, createVal
// At this point there was either no PDB or we succeeded in decrementing
// Try the delete
_, _, err = r.store.Delete(ctx, eviction.Name, deletionOptions)
_, _, err = r.store.Delete(ctx, eviction.Name, rest.ValidateAllObjectFunc, deletionOptions)
if err != nil {
return nil, err
}

View File

@ -150,7 +150,7 @@ type FailDeleteUpdateStorage struct {
storage.Interface
}
func (f FailDeleteUpdateStorage) Delete(ctx context.Context, key string, out runtime.Object, precondition *storage.Preconditions) error {
func (f FailDeleteUpdateStorage) Delete(ctx context.Context, key string, out runtime.Object, precondition *storage.Preconditions, validateDeletion storage.ValidateObjectFunc) error {
return storage.NewKeyNotFoundError(key, 0)
}

View File

@ -183,7 +183,7 @@ func TestIgnoreDeleteNotFound(t *testing.T) {
defer registry.Store.DestroyFunc()
// should fail if pod A is not created yet.
_, _, err := registry.Delete(testContext, pod.Name, nil)
_, _, err := registry.Delete(testContext, pod.Name, rest.ValidateAllObjectFunc, nil)
if !errors.IsNotFound(err) {
t.Errorf("Unexpected error: %v", err)
}
@ -198,7 +198,7 @@ func TestIgnoreDeleteNotFound(t *testing.T) {
// registry shouldn't get any error since we ignore the NotFound error.
zero := int64(0)
opt := &metav1.DeleteOptions{GracePeriodSeconds: &zero}
obj, _, err := registry.Delete(testContext, pod.Name, opt)
obj, _, err := registry.Delete(testContext, pod.Name, rest.ValidateAllObjectFunc, opt)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

View File

@ -220,9 +220,9 @@ func (rs *REST) Create(ctx context.Context, obj runtime.Object, createValidation
return out, err
}
func (rs *REST) Delete(ctx context.Context, id string, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
func (rs *REST) Delete(ctx context.Context, id string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
// TODO: handle graceful
obj, _, err := rs.services.Delete(ctx, id, options)
obj, _, err := rs.services.Delete(ctx, id, deleteValidation, options)
if err != nil {
return nil, false, err
}
@ -233,7 +233,7 @@ func (rs *REST) Delete(ctx context.Context, id string, options *metav1.DeleteOpt
if !dryrun.IsDryRun(options.DryRun) {
// TODO: can leave dangling endpoints, and potentially return incorrect
// endpoints if a new service is created with the same name
_, _, err = rs.endpoints.Delete(ctx, id, &metav1.DeleteOptions{})
_, _, err = rs.endpoints.Delete(ctx, id, rest.ValidateAllObjectFunc, &metav1.DeleteOptions{})
if err != nil && !errors.IsNotFound(err) {
return nil, false, err
}

View File

@ -136,14 +136,14 @@ func (s *serviceStorage) Update(ctx context.Context, name string, objInfo rest.U
return obj, s.Created, s.Err
}
func (s *serviceStorage) Delete(ctx context.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
func (s *serviceStorage) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
if !dryrun.IsDryRun(options.DryRun) {
s.DeletedID = name
}
return s.Service, s.DeletedImmediately, s.Err
}
func (s *serviceStorage) DeleteCollection(ctx context.Context, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) {
func (s *serviceStorage) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) {
panic("not implemented")
}
@ -949,7 +949,7 @@ func TestServiceRegistryDelete(t *testing.T) {
},
}
registry.Create(ctx, svc, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
storage.Delete(ctx, svc.Name, &metav1.DeleteOptions{})
storage.Delete(ctx, svc.Name, rest.ValidateAllObjectFunc, &metav1.DeleteOptions{})
if e, a := "foo", registry.DeletedID; e != a {
t.Errorf("Expected %v, but got %v", e, a)
}
@ -1038,7 +1038,7 @@ func TestServiceRegistryDeleteExternal(t *testing.T) {
},
}
registry.Create(ctx, svc, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
storage.Delete(ctx, svc.Name, &metav1.DeleteOptions{})
storage.Delete(ctx, svc.Name, rest.ValidateAllObjectFunc, &metav1.DeleteOptions{})
if e, a := "foo", registry.DeletedID; e != a {
t.Errorf("Expected %v, but got %v", e, a)
}
@ -1440,7 +1440,7 @@ func TestServiceRegistryIPReallocation(t *testing.T) {
t.Errorf("Unexpected ClusterIP: %s", created_service_1.Spec.ClusterIP)
}
_, _, err := storage.Delete(ctx, created_service_1.Name, &metav1.DeleteOptions{})
_, _, err := storage.Delete(ctx, created_service_1.Name, rest.ValidateAllObjectFunc, &metav1.DeleteOptions{})
if err != nil {
t.Errorf("Unexpected error deleting service: %v", err)
}

View File

@ -111,7 +111,7 @@ func (e *EndpointRegistry) Update(ctx context.Context, name string, objInfo rest
return endpoints, false, nil
}
func (e *EndpointRegistry) Delete(ctx context.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
func (e *EndpointRegistry) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
// TODO: support namespaces in this mock
e.lock.Lock()
defer e.lock.Unlock()
@ -130,6 +130,6 @@ func (e *EndpointRegistry) Delete(ctx context.Context, name string, options *met
return nil, true, nil
}
func (e *EndpointRegistry) DeleteCollection(ctx context.Context, _ *metav1.DeleteOptions, _ *metainternalversion.ListOptions) (runtime.Object, error) {
func (e *EndpointRegistry) DeleteCollection(ctx context.Context, _ rest.ValidateObjectFunc, _ *metav1.DeleteOptions, _ *metainternalversion.ListOptions) (runtime.Object, error) {
return nil, fmt.Errorf("unimplemented!")
}

View File

@ -20,6 +20,7 @@ go_test(
"//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/registry/generic:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/registry/generic/testing:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library",
],
)

View File

@ -68,12 +68,12 @@ func (r *REST) ShortNames() []string {
}
// Delete ensures that system priority classes are not deleted.
func (r *REST) Delete(ctx context.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
func (r *REST) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
for _, spc := range scheduling.SystemPriorityClasses() {
if name == spc.Name {
return nil, false, apierrors.NewForbidden(scheduling.Resource("priorityclasses"), spc.Name, errors.New("this is a system priority class and cannot be deleted"))
}
}
return r.Store.Delete(ctx, name, options)
return r.Store.Delete(ctx, name, deleteValidation, options)
}

View File

@ -26,6 +26,7 @@ import (
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/generic"
genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing"
"k8s.io/apiserver/pkg/registry/rest"
etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing"
"k8s.io/kubernetes/pkg/apis/scheduling"
"k8s.io/kubernetes/pkg/registry/registrytest"
@ -117,7 +118,7 @@ func TestDeleteSystemPriorityClass(t *testing.T) {
if err := storage.Store.Storage.Create(ctx, key, pc, nil, 0, false); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, _, err := storage.Delete(ctx, pc.Name, nil); err == nil {
if _, _, err := storage.Delete(ctx, pc.Name, rest.ValidateAllObjectFunc, nil); err == nil {
t.Error("expected to receive an error")
}
}

View File

@ -204,7 +204,7 @@ func (c *CRDFinalizer) deleteInstances(crd *apiextensions.CustomResourceDefiniti
// don't retry deleting the same namespace
deletedNamespaces.Insert(metadata.GetNamespace())
nsCtx := genericapirequest.WithNamespace(ctx, metadata.GetNamespace())
if _, err := crClient.DeleteCollection(nsCtx, nil, nil); err != nil {
if _, err := crClient.DeleteCollection(nsCtx, rest.ValidateAllObjectFunc, nil, nil); err != nil {
deleteErrors = append(deleteErrors, err)
continue
}

View File

@ -67,7 +67,7 @@ func (r *REST) ShortNames() []string {
}
// Delete adds the CRD finalizer to the list
func (r *REST) Delete(ctx context.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
func (r *REST) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
obj, err := r.Get(ctx, name, &metav1.GetOptions{})
if err != nil {
return nil, false, err
@ -103,6 +103,10 @@ func (r *REST) Delete(ctx context.Context, name string, options *metav1.DeleteOp
// upon first request to delete, add our finalizer and then delegate
if crd.DeletionTimestamp.IsZero() {
if err := deleteValidation(obj); err != nil {
return nil, false, err
}
key, err := r.Store.KeyFunc(ctx, name)
if err != nil {
return nil, false, err
@ -153,7 +157,7 @@ func (r *REST) Delete(ctx context.Context, name string, options *metav1.DeleteOp
return out, false, nil
}
return r.Store.Delete(ctx, name, options)
return r.Store.Delete(ctx, name, deleteValidation, options)
}
// NewStatusREST makes a RESTStorage for status that has more limited options.

View File

@ -440,13 +440,16 @@ func (storage *SimpleRESTStorage) checkContext(ctx context.Context) {
storage.actualNamespace, storage.namespacePresent = request.NamespaceFrom(ctx)
}
func (storage *SimpleRESTStorage) Delete(ctx context.Context, id string, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
func (storage *SimpleRESTStorage) Delete(ctx context.Context, id string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
storage.checkContext(ctx)
storage.deleted = id
storage.deleteOptions = options
if err := storage.errors["delete"]; err != nil {
return nil, false, err
}
if err := deleteValidation(nil); err != nil {
return nil, false, err
}
var obj runtime.Object = &metav1.Status{Status: metav1.StatusSuccess}
var err error
if storage.injectedFunction != nil {
@ -569,6 +572,18 @@ func (s *ConnecterRESTStorage) NewConnectOptions() (runtime.Object, bool, string
return s.emptyConnectOptions, false, ""
}
<<<<<<< HEAD
=======
type LegacyRESTStorage struct {
*SimpleRESTStorage
}
func (storage LegacyRESTStorage) Delete(ctx context.Context, id string) (runtime.Object, error) {
obj, _, err := storage.SimpleRESTStorage.Delete(ctx, id, nil, nil)
return obj, err
}
>>>>>>> ab07482ecb... validate deletion admission object
type MetadataRESTStorage struct {
*SimpleRESTStorage
types []string
@ -4192,7 +4207,7 @@ type SimpleRESTStorageWithDeleteCollection struct {
}
// Delete collection doesn't do much, but let us test this path.
func (storage *SimpleRESTStorageWithDeleteCollection) DeleteCollection(ctx context.Context, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) {
func (storage *SimpleRESTStorageWithDeleteCollection) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) {
storage.checkContext(ctx)
return nil, nil
}
@ -4263,7 +4278,7 @@ func (storage *SimpleXGSubresourceRESTStorage) Get(ctx context.Context, id strin
return storage.item.DeepCopyObject(), nil
}
func (storage *SimpleXGSubresourceRESTStorage) Delete(ctx context.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
func (storage *SimpleXGSubresourceRESTStorage) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
return nil, true, nil
}

View File

@ -115,28 +115,12 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope *RequestSc
}
options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("DeleteOptions"))
trace.Step("About to check admission control")
if admit != nil && admit.Handles(admission.Delete) {
userInfo, _ := request.UserFrom(ctx)
attrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, options, dryrun.IsDryRun(options.DryRun), userInfo)
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
if err := mutatingAdmission.Admit(attrs, scope); err != nil {
scope.err(err, w, req)
return
}
}
if validatingAdmission, ok := admit.(admission.ValidationInterface); ok {
if err := validatingAdmission.Validate(attrs, scope); err != nil {
scope.err(err, w, req)
return
}
}
}
trace.Step("About to delete object from database")
wasDeleted := true
userInfo, _ := request.UserFrom(ctx)
staticAdmissionAttrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, options, dryrun.IsDryRun(options.DryRun), userInfo)
result, err := finishRequest(timeout, func() (runtime.Object, error) {
obj, deleted, err := r.Delete(ctx, name, options)
obj, deleted, err := r.Delete(ctx, name, rest.AdmissionToValidateObjectDeleteFunc(admit, staticAdmissionAttrs, scope), options)
wasDeleted = deleted
return obj, err
})
@ -196,6 +180,7 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope *RequestSc
ctx := req.Context()
ctx = request.WithNamespace(ctx, namespace)
ae := request.AuditEventFrom(ctx)
admit = admission.WithAudit(admit, ae)
outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, scope)
if err != nil {
@ -267,29 +252,10 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope *RequestSc
}
options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("DeleteOptions"))
admit = admission.WithAudit(admit, ae)
if admit != nil && admit.Handles(admission.Delete) {
userInfo, _ := request.UserFrom(ctx)
attrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, options, dryrun.IsDryRun(options.DryRun), userInfo)
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
err = mutatingAdmission.Admit(attrs, scope)
if err != nil {
scope.err(err, w, req)
return
}
}
if validatingAdmission, ok := admit.(admission.ValidationInterface); ok {
err = validatingAdmission.Validate(attrs, scope)
if err != nil {
scope.err(err, w, req)
return
}
}
}
userInfo, _ := request.UserFrom(ctx)
staticAdmissionAttrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, options, dryrun.IsDryRun(options.DryRun), userInfo)
result, err := finishRequest(timeout, func() (runtime.Object, error) {
return r.DeleteCollection(ctx, options, &listOptions)
return r.DeleteCollection(ctx, rest.AdmissionToValidateObjectDeleteFunc(admit, staticAdmissionAttrs, scope), options, &listOptions)
})
if err != nil {
scope.err(err, w, req)

View File

@ -24,6 +24,7 @@ import (
"sync"
"time"
"k8s.io/apimachinery/pkg/api/errors"
kubeerr "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/api/validation/path"
@ -881,16 +882,28 @@ func (e *Store) updateForGracefulDeletionAndFinalizers(ctx context.Context, name
}
// Delete removes the item from storage.
func (e *Store) Delete(ctx context.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
func (e *Store) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
key, err := e.KeyFunc(ctx, name)
if err != nil {
return nil, false, err
}
obj := e.NewFunc()
qualifiedResource := e.qualifiedResourceFromContext(ctx)
if err := e.Storage.Get(ctx, key, "", obj, false); err != nil {
return nil, false, storeerr.InterpretDeleteError(err, qualifiedResource, name)
err = e.Storage.Get(ctx, key, "", obj, false)
if err != nil {
// Note that we continue the admission check on "Not Found" error is for the compatibility
// with the NodeRestriction admission controller.
if interpretedErr := storeerr.InterpretDeleteError(err, qualifiedResource, name); errors.IsNotFound(interpretedErr) {
if err := deleteValidation(obj); err != nil {
return nil, false, err
}
return nil, false, interpretedErr
}
}
if err := deleteValidation(obj.DeepCopyObject()); err != nil {
return nil, false, err
}
// support older consumers of delete by treating "nil" as delete immediately
if options == nil {
options = metav1.NewDeleteOptions(0)
@ -972,13 +985,19 @@ func (e *Store) Delete(ctx context.Context, name string, options *metav1.DeleteO
// are removing all objects of a given type) with the current API (it's technically
// possibly with storage API, but watch is not delivered correctly then).
// It will be possible to fix it with v3 etcd API.
func (e *Store) DeleteCollection(ctx context.Context, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) {
func (e *Store) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) {
if listOptions == nil {
listOptions = &metainternalversion.ListOptions{}
} else {
listOptions = listOptions.DeepCopy()
}
if deleteValidation != nil {
if err := deleteValidation(nil); err != nil {
return nil, err
}
}
listObj, err := e.List(ctx, listOptions)
if err != nil {
return nil, err
@ -1025,7 +1044,7 @@ func (e *Store) DeleteCollection(ctx context.Context, options *metav1.DeleteOpti
errs <- err
return
}
if _, _, err := e.Delete(ctx, accessor.GetName(), options); err != nil && !kubeerr.IsNotFound(err) {
if _, _, err := e.Delete(ctx, accessor.GetName(), rest.ValidateAllObjectFunc, options); err != nil && !kubeerr.IsNotFound(err) {
klog.V(4).Infof("Delete %s in DeleteCollection failed: %v", accessor.GetName(), err)
errs <- err
return

View File

@ -357,7 +357,7 @@ func TestStoreCreate(t *testing.T) {
// now delete pod with graceful period set
delOpts := &metav1.DeleteOptions{GracePeriodSeconds: &gracefulPeriod}
_, _, err = registry.Delete(testContext, podA.Name, delOpts)
_, _, err = registry.Delete(testContext, podA.Name, rest.ValidateAllObjectFunc, delOpts)
if err != nil {
t.Fatalf("Failed to delete pod gracefully. Unexpected error: %v", err)
}
@ -660,7 +660,7 @@ func TestStoreDelete(t *testing.T) {
defer destroyFunc()
// test failure condition
_, _, err := registry.Delete(testContext, podA.Name, nil)
_, _, err := registry.Delete(testContext, podA.Name, rest.ValidateAllObjectFunc, nil)
if !errors.IsNotFound(err) {
t.Errorf("Unexpected error: %v", err)
}
@ -672,7 +672,7 @@ func TestStoreDelete(t *testing.T) {
}
// delete object
_, wasDeleted, err := registry.Delete(testContext, podA.Name, nil)
_, wasDeleted, err := registry.Delete(testContext, podA.Name, rest.ValidateAllObjectFunc, nil)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
@ -749,7 +749,7 @@ func TestGracefulStoreHandleFinalizers(t *testing.T) {
}
// delete the pod with grace period=0, the pod should still exist because it has a finalizer
_, wasDeleted, err := registry.Delete(testContext, podWithFinalizer.Name, metav1.NewDeleteOptions(0))
_, wasDeleted, err := registry.Delete(testContext, podWithFinalizer.Name, rest.ValidateAllObjectFunc, metav1.NewDeleteOptions(0))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
@ -815,7 +815,7 @@ func TestNonGracefulStoreHandleFinalizers(t *testing.T) {
}
// delete object with nil delete options doesn't delete the object
_, wasDeleted, err := registry.Delete(testContext, podWithFinalizer.Name, nil)
_, wasDeleted, err := registry.Delete(testContext, podWithFinalizer.Name, rest.ValidateAllObjectFunc, nil)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
@ -1115,7 +1115,7 @@ func TestStoreDeleteWithOrphanDependents(t *testing.T) {
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, _, err = registry.Delete(testContext, tc.pod.Name, tc.options)
_, _, err = registry.Delete(testContext, tc.pod.Name, rest.ValidateAllObjectFunc, tc.options)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
@ -1334,7 +1334,7 @@ func TestStoreDeletionPropagation(t *testing.T) {
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, _, err = registry.Delete(testContext, pod.Name, tc.options)
_, _, err = registry.Delete(testContext, pod.Name, rest.ValidateAllObjectFunc, tc.options)
obj, err := registry.Get(testContext, pod.Name, &metav1.GetOptions{})
if tc.expectedNotFound {
if err == nil || !errors.IsNotFound(err) {
@ -1382,7 +1382,7 @@ func TestStoreDeleteCollection(t *testing.T) {
}
// Delete all pods.
deleted, err := registry.DeleteCollection(testContext, nil, &metainternalversion.ListOptions{})
deleted, err := registry.DeleteCollection(testContext, rest.ValidateAllObjectFunc, nil, &metainternalversion.ListOptions{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
@ -1423,7 +1423,7 @@ func TestStoreDeleteCollectionNotFound(t *testing.T) {
wg.Add(1)
go func() {
defer wg.Done()
_, err := registry.DeleteCollection(testContext, nil, &metainternalversion.ListOptions{})
_, err := registry.DeleteCollection(testContext, rest.ValidateAllObjectFunc, nil, &metainternalversion.ListOptions{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
@ -1461,7 +1461,7 @@ func TestStoreDeleteCollectionWithWatch(t *testing.T) {
}
defer watcher.Stop()
if _, err := registry.DeleteCollection(testContext, nil, &metainternalversion.ListOptions{}); err != nil {
if _, err := registry.DeleteCollection(testContext, rest.ValidateAllObjectFunc, nil, &metainternalversion.ListOptions{}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
@ -1683,7 +1683,7 @@ func TestQualifiedResource(t *testing.T) {
}
// delete a non-exist object
_, _, err = registry.Delete(testContext, podA.Name, nil)
_, _, err = registry.Delete(testContext, podA.Name, rest.ValidateAllObjectFunc, nil)
if !errors.IsNotFound(err) {
t.Fatalf("Unexpected error: %v", err)

View File

@ -14,6 +14,7 @@ go_library(
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/registry/rest/resttest:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/storage/testing:go_default_library",
],

View File

@ -28,6 +28,7 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/registry/rest/resttest"
storagetesting "k8s.io/apiserver/pkg/storage/testing"
)
@ -168,7 +169,7 @@ func (t *Tester) createObject(ctx context.Context, obj runtime.Object) error {
func (t *Tester) setObjectsForList(objects []runtime.Object) []runtime.Object {
key := t.storage.KeyRootFunc(t.tester.TestContext())
if _, err := t.storage.DeleteCollection(t.tester.TestContext(), nil, nil); err != nil {
if _, err := t.storage.DeleteCollection(t.tester.TestContext(), rest.ValidateAllObjectFunc, nil, nil); err != nil {
t.tester.Errorf("unable to clear collection: %v", err)
return nil
}
@ -192,7 +193,7 @@ func (t *Tester) emitObject(obj runtime.Object, action string) error {
if err != nil {
return err
}
_, _, err = t.storage.Delete(ctx, accessor.GetName(), nil)
_, _, err = t.storage.Delete(ctx, accessor.GetName(), rest.ValidateAllObjectFunc, nil)
default:
err = fmt.Errorf("unexpected action: %v", action)
}

View File

@ -26,6 +26,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
)
// RESTDeleteStrategy defines deletion behavior on an object that follows Kubernetes
@ -140,3 +141,37 @@ func BeforeDelete(strategy RESTDeleteStrategy, ctx context.Context, obj runtime.
}
return true, false, nil
}
// AdmissionToValidateObjectDeleteFunc returns a admission validate func for object deletion
func AdmissionToValidateObjectDeleteFunc(admit admission.Interface, staticAttributes admission.Attributes, objInterfaces admission.ObjectInterfaces) ValidateObjectFunc {
mutatingAdmission, isMutatingAdmission := admit.(admission.MutationInterface)
validatingAdmission, isValidatingAdmission := admit.(admission.ValidationInterface)
return func(old runtime.Object) error {
if !isMutatingAdmission && !isValidatingAdmission {
return nil
}
finalAttributes := admission.NewAttributesRecord(
nil,
old,
staticAttributes.GetKind(),
staticAttributes.GetNamespace(),
staticAttributes.GetName(),
staticAttributes.GetResource(),
staticAttributes.GetSubresource(),
staticAttributes.GetOperation(),
staticAttributes.GetUserInfo(),
)
if isMutatingAdmission && mutatingAdmission.Handles(finalAttributes.GetOperation()) {
if err := mutatingAdmission.Admit(finalAttributes, objInterfaces); err != nil {
return err
}
}
if isValidatingAdmission && validatingAdmission.Handles(finalAttributes.GetOperation()) {
if err := validatingAdmission.Validate(finalAttributes, objInterfaces); err != nil {
return err
}
}
return nil
}
}

View File

@ -156,7 +156,7 @@ type GracefulDeleter interface {
// information about deletion.
// It also returns a boolean which is set to true if the resource was instantly
// deleted or false if it will be deleted asynchronously.
Delete(ctx context.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error)
Delete(ctx context.Context, name string, deleteValidation ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error)
}
// CollectionDeleter is an object that can delete a collection
@ -167,7 +167,7 @@ type CollectionDeleter interface {
// them or return an invalid request error.
// DeleteCollection may not be atomic - i.e. it may delete some objects and still
// return an error after it. On success, returns a list of deleted objects.
DeleteCollection(ctx context.Context, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error)
DeleteCollection(ctx context.Context, deleteValidation ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error)
}
// Creater is an object that can create an instance of a RESTful object.

View File

@ -257,7 +257,7 @@ func (t *Tester) delete(ctx context.Context, obj runtime.Object) error {
if !ok {
return fmt.Errorf("Expected deleting storage, got %v", t.storage)
}
_, _, err = deleter.Delete(ctx, objectMeta.GetName(), nil)
_, _, err = deleter.Delete(ctx, objectMeta.GetName(), rest.ValidateAllObjectFunc, nil)
return err
}
@ -840,7 +840,7 @@ func (t *Tester) testDeleteNoGraceful(obj runtime.Object, createFn CreateFunc, g
if dryRun {
opts.DryRun = []string{metav1.DryRunAll}
}
obj, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), opts)
obj, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), rest.ValidateAllObjectFunc, opts)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@ -866,7 +866,7 @@ func (t *Tester) testDeleteNoGraceful(obj runtime.Object, createFn CreateFunc, g
func (t *Tester) testDeleteNonExist(obj runtime.Object, opts metav1.DeleteOptions) {
objectMeta := t.getObjectMetaOrFail(obj)
_, _, err := t.storage.(rest.GracefulDeleter).Delete(t.TestContext(), objectMeta.GetName(), &opts)
_, _, err := t.storage.(rest.GracefulDeleter).Delete(t.TestContext(), objectMeta.GetName(), rest.ValidateAllObjectFunc, &opts)
if err == nil || !errors.IsNotFound(err) {
t.Errorf("unexpected error: %v", err)
}
@ -886,12 +886,12 @@ func (t *Tester) testDeleteWithUID(obj runtime.Object, createFn CreateFunc, getF
t.Errorf("unexpected error: %v", err)
}
opts.Preconditions = metav1.NewPreconditionDeleteOptions("UID1111").Preconditions
obj, _, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), &opts)
obj, _, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), rest.ValidateAllObjectFunc, &opts)
if err == nil || !errors.IsConflict(err) {
t.Errorf("unexpected error: %v", err)
}
obj, _, err = t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), metav1.NewPreconditionDeleteOptions("UID0000"))
obj, _, err = t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), rest.ValidateAllObjectFunc, metav1.NewPreconditionDeleteOptions("UID0000"))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@ -989,7 +989,7 @@ func (t *Tester) testDeleteGracefulHasDefault(obj runtime.Object, createFn Creat
}
objectMeta := t.getObjectMetaOrFail(foo)
generation := objectMeta.GetGeneration()
_, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), &metav1.DeleteOptions{})
_, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), rest.ValidateAllObjectFunc, &metav1.DeleteOptions{})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@ -1023,7 +1023,7 @@ func (t *Tester) testDeleteGracefulWithValue(obj runtime.Object, createFn Create
}
objectMeta := t.getObjectMetaOrFail(foo)
generation := objectMeta.GetGeneration()
_, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), metav1.NewDeleteOptions(expectedGrace+2))
_, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), rest.ValidateAllObjectFunc, metav1.NewDeleteOptions(expectedGrace+2))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@ -1057,7 +1057,7 @@ func (t *Tester) testDeleteGracefulExtend(obj runtime.Object, createFn CreateFun
}
objectMeta := t.getObjectMetaOrFail(foo)
generation := objectMeta.GetGeneration()
_, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), metav1.NewDeleteOptions(expectedGrace))
_, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), rest.ValidateAllObjectFunc, metav1.NewDeleteOptions(expectedGrace))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@ -1069,7 +1069,7 @@ func (t *Tester) testDeleteGracefulExtend(obj runtime.Object, createFn CreateFun
}
// second delete duration is ignored
_, wasDeleted, err = t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), metav1.NewDeleteOptions(expectedGrace+2))
_, wasDeleted, err = t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), rest.ValidateAllObjectFunc, metav1.NewDeleteOptions(expectedGrace+2))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@ -1099,7 +1099,7 @@ func (t *Tester) testDeleteGracefulImmediate(obj runtime.Object, createFn Create
}
objectMeta := t.getObjectMetaOrFail(foo)
generation := objectMeta.GetGeneration()
_, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), metav1.NewDeleteOptions(expectedGrace))
_, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), rest.ValidateAllObjectFunc, metav1.NewDeleteOptions(expectedGrace))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@ -1111,7 +1111,7 @@ func (t *Tester) testDeleteGracefulImmediate(obj runtime.Object, createFn Create
}
// second delete is immediate, resource is deleted
out, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), metav1.NewDeleteOptions(0))
out, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), rest.ValidateAllObjectFunc, metav1.NewDeleteOptions(0))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@ -1141,7 +1141,7 @@ func (t *Tester) testDeleteGracefulUsesZeroOnNil(obj runtime.Object, createFn Cr
t.Errorf("unexpected error: %v", err)
}
objectMeta := t.getObjectMetaOrFail(foo)
_, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), nil)
_, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), rest.ValidateAllObjectFunc, nil)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@ -1167,7 +1167,7 @@ func (t *Tester) testDeleteGracefulShorten(obj runtime.Object, createFn CreateFu
bigGrace = 2 * expectedGrace
}
objectMeta := t.getObjectMetaOrFail(foo)
_, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), metav1.NewDeleteOptions(bigGrace))
_, wasDeleted, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), rest.ValidateAllObjectFunc, metav1.NewDeleteOptions(bigGrace))
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@ -1182,7 +1182,7 @@ func (t *Tester) testDeleteGracefulShorten(obj runtime.Object, createFn CreateFu
deletionTimestamp := *objectMeta.GetDeletionTimestamp()
// second delete duration is ignored
_, wasDeleted, err = t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), metav1.NewDeleteOptions(expectedGrace))
_, wasDeleted, err = t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.GetName(), rest.ValidateAllObjectFunc, metav1.NewDeleteOptions(expectedGrace))
if err != nil {
t.Errorf("unexpected error: %v", err)
}