2019-01-12 04:58:27 +00:00
/ *
Copyright 2014 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package rest
import (
"context"
"fmt"
"time"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
2019-08-30 18:33:25 +00:00
"k8s.io/apiserver/pkg/admission"
2019-01-12 04:58:27 +00:00
)
// RESTDeleteStrategy defines deletion behavior on an object that follows Kubernetes
// API conventions.
type RESTDeleteStrategy interface {
runtime . ObjectTyper
}
type GarbageCollectionPolicy string
const (
DeleteDependents GarbageCollectionPolicy = "DeleteDependents"
OrphanDependents GarbageCollectionPolicy = "OrphanDependents"
// Unsupported means that the resource knows that it cannot be GC'd, so the finalizers
// should never be set in storage.
Unsupported GarbageCollectionPolicy = "Unsupported"
)
// GarbageCollectionDeleteStrategy must be implemented by the registry that wants to
// orphan dependents by default.
type GarbageCollectionDeleteStrategy interface {
// DefaultGarbageCollectionPolicy returns the default garbage collection behavior.
DefaultGarbageCollectionPolicy ( ctx context . Context ) GarbageCollectionPolicy
}
// RESTGracefulDeleteStrategy must be implemented by the registry that supports
// graceful deletion.
type RESTGracefulDeleteStrategy interface {
// CheckGracefulDelete should return true if the object can be gracefully deleted and set
// any default values on the DeleteOptions.
CheckGracefulDelete ( ctx context . Context , obj runtime . Object , options * metav1 . DeleteOptions ) bool
}
// BeforeDelete tests whether the object can be gracefully deleted.
// If graceful is set, the object should be gracefully deleted. If gracefulPending
// is set, the object has already been gracefully deleted (and the provided grace
// period is longer than the time to deletion). An error is returned if the
// condition cannot be checked or the gracePeriodSeconds is invalid. The options
// argument may be updated with default values if graceful is true. Second place
// where we set deletionTimestamp is pkg/registry/generic/registry/store.go.
// This function is responsible for setting deletionTimestamp during gracefulDeletion,
// other one for cascading deletions.
func BeforeDelete ( strategy RESTDeleteStrategy , ctx context . Context , obj runtime . Object , options * metav1 . DeleteOptions ) ( graceful , gracefulPending bool , err error ) {
objectMeta , gvk , kerr := objectMetaAndKind ( strategy , obj )
if kerr != nil {
return false , false , kerr
}
if errs := validation . ValidateDeleteOptions ( options ) ; len ( errs ) > 0 {
return false , false , errors . NewInvalid ( schema . GroupKind { Group : metav1 . GroupName , Kind : "DeleteOptions" } , "" , errs )
}
// Checking the Preconditions here to fail early. They'll be enforced later on when we actually do the deletion, too.
2019-04-07 17:07:55 +00:00
if options . Preconditions != nil {
if options . Preconditions . UID != nil && * options . Preconditions . UID != objectMeta . GetUID ( ) {
return false , false , errors . NewConflict ( schema . GroupResource { Group : gvk . Group , Resource : gvk . Kind } , objectMeta . GetName ( ) , fmt . Errorf ( "the UID in the precondition (%s) does not match the UID in record (%s). The object might have been deleted and then recreated" , * options . Preconditions . UID , objectMeta . GetUID ( ) ) )
}
if options . Preconditions . ResourceVersion != nil && * options . Preconditions . ResourceVersion != objectMeta . GetResourceVersion ( ) {
return false , false , errors . NewConflict ( schema . GroupResource { Group : gvk . Group , Resource : gvk . Kind } , objectMeta . GetName ( ) , fmt . Errorf ( "the ResourceVersion in the precondition (%s) does not match the ResourceVersion in record (%s). The object might have been modified" , * options . Preconditions . ResourceVersion , objectMeta . GetResourceVersion ( ) ) )
}
2019-01-12 04:58:27 +00:00
}
gracefulStrategy , ok := strategy . ( RESTGracefulDeleteStrategy )
if ! ok {
// If we're not deleting gracefully there's no point in updating Generation, as we won't update
// the obcject before deleting it.
return false , false , nil
}
// if the object is already being deleted, no need to update generation.
if objectMeta . GetDeletionTimestamp ( ) != nil {
// if we are already being deleted, we may only shorten the deletion grace period
// this means the object was gracefully deleted previously but deletionGracePeriodSeconds was not set,
// so we force deletion immediately
// IMPORTANT:
// The deletion operation happens in two phases.
// 1. Update to set DeletionGracePeriodSeconds and DeletionTimestamp
// 2. Delete the object from storage.
// If the update succeeds, but the delete fails (network error, internal storage error, etc.),
// a resource was previously left in a state that was non-recoverable. We
// check if the existing stored resource has a grace period as 0 and if so
// attempt to delete immediately in order to recover from this scenario.
if objectMeta . GetDeletionGracePeriodSeconds ( ) == nil || * objectMeta . GetDeletionGracePeriodSeconds ( ) == 0 {
return false , false , nil
}
// only a shorter grace period may be provided by a user
if options . GracePeriodSeconds != nil {
period := int64 ( * options . GracePeriodSeconds )
if period >= * objectMeta . GetDeletionGracePeriodSeconds ( ) {
return false , true , nil
}
newDeletionTimestamp := metav1 . NewTime (
objectMeta . GetDeletionTimestamp ( ) . Add ( - time . Second * time . Duration ( * objectMeta . GetDeletionGracePeriodSeconds ( ) ) ) .
Add ( time . Second * time . Duration ( * options . GracePeriodSeconds ) ) )
objectMeta . SetDeletionTimestamp ( & newDeletionTimestamp )
objectMeta . SetDeletionGracePeriodSeconds ( & period )
return true , false , nil
}
// graceful deletion is pending, do nothing
options . GracePeriodSeconds = objectMeta . GetDeletionGracePeriodSeconds ( )
return false , true , nil
}
if ! gracefulStrategy . CheckGracefulDelete ( ctx , obj , options ) {
return false , false , nil
}
now := metav1 . NewTime ( metav1 . Now ( ) . Add ( time . Second * time . Duration ( * options . GracePeriodSeconds ) ) )
objectMeta . SetDeletionTimestamp ( & now )
objectMeta . SetDeletionGracePeriodSeconds ( options . GracePeriodSeconds )
// If it's the first graceful deletion we are going to set the DeletionTimestamp to non-nil.
// Controllers of the object that's being deleted shouldn't take any nontrivial actions, hence its behavior changes.
// Thus we need to bump object's Generation (if set). This handles generation bump during graceful deletion.
// The bump for objects that don't support graceful deletion is handled in pkg/registry/generic/registry/store.go.
if objectMeta . GetGeneration ( ) > 0 {
objectMeta . SetGeneration ( objectMeta . GetGeneration ( ) + 1 )
}
return true , false , nil
}
2019-08-30 18:33:25 +00:00
// 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 )
mutating := isMutatingAdmission && mutatingAdmission . Handles ( staticAttributes . GetOperation ( ) )
validating := isValidatingAdmission && validatingAdmission . Handles ( staticAttributes . GetOperation ( ) )
return func ( old runtime . Object ) error {
if ! mutating && ! validating {
return nil
}
finalAttributes := admission . NewAttributesRecord (
nil ,
// Deep copy the object to avoid accidentally changing the object.
old . DeepCopyObject ( ) ,
staticAttributes . GetKind ( ) ,
staticAttributes . GetNamespace ( ) ,
staticAttributes . GetName ( ) ,
staticAttributes . GetResource ( ) ,
staticAttributes . GetSubresource ( ) ,
staticAttributes . GetOperation ( ) ,
staticAttributes . GetOperationOptions ( ) ,
staticAttributes . IsDryRun ( ) ,
staticAttributes . GetUserInfo ( ) ,
)
if mutating {
if err := mutatingAdmission . Admit ( finalAttributes , objInterfaces ) ; err != nil {
return err
}
}
if validating {
if err := validatingAdmission . Validate ( finalAttributes , objInterfaces ) ; err != nil {
return err
}
}
return nil
}
}