blob: bdc06164d3d907eb5e8562a0c9cb89433ea1217b [file] [log] [blame]
/*
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 registry
import (
"context"
"fmt"
"path"
"reflect"
"strconv"
"strings"
"sync"
"testing"
"time"
apitesting "k8s.io/apimachinery/pkg/api/apitesting"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/selection"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/apiserver/pkg/apis/example"
examplev1 "k8s.io/apiserver/pkg/apis/example/v1"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/registry/generic"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/storage"
cacherstorage "k8s.io/apiserver/pkg/storage/cacher"
etcdstorage "k8s.io/apiserver/pkg/storage/etcd"
etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing"
"k8s.io/apiserver/pkg/storage/names"
"k8s.io/apiserver/pkg/storage/storagebackend/factory"
storagetesting "k8s.io/apiserver/pkg/storage/testing"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing"
)
var scheme = runtime.NewScheme()
var codecs = serializer.NewCodecFactory(scheme)
const validInitializerName = "test.k8s.io"
func init() {
metav1.AddToGroupVersion(scheme, metav1.SchemeGroupVersion)
utilruntime.Must(example.AddToScheme(scheme))
utilruntime.Must(examplev1.AddToScheme(scheme))
}
type testGracefulStrategy struct {
testRESTStrategy
}
func (t testGracefulStrategy) CheckGracefulDelete(ctx context.Context, obj runtime.Object, options *metav1.DeleteOptions) bool {
return true
}
var _ rest.RESTGracefulDeleteStrategy = testGracefulStrategy{}
type testOrphanDeleteStrategy struct {
*testRESTStrategy
}
func (t *testOrphanDeleteStrategy) DefaultGarbageCollectionPolicy(ctx context.Context) rest.GarbageCollectionPolicy {
return rest.OrphanDependents
}
type testRESTStrategy struct {
runtime.ObjectTyper
names.NameGenerator
namespaceScoped bool
allowCreateOnUpdate bool
allowUnconditionalUpdate bool
}
func (t *testRESTStrategy) NamespaceScoped() bool { return t.namespaceScoped }
func (t *testRESTStrategy) AllowCreateOnUpdate() bool { return t.allowCreateOnUpdate }
func (t *testRESTStrategy) AllowUnconditionalUpdate() bool { return t.allowUnconditionalUpdate }
func (t *testRESTStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
metaObj, err := meta.Accessor(obj)
if err != nil {
panic(err.Error())
}
labels := metaObj.GetLabels()
if labels == nil {
labels = map[string]string{}
}
labels["prepare_create"] = "true"
metaObj.SetLabels(labels)
}
func (t *testRESTStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {}
func (t *testRESTStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
return nil
}
func (t *testRESTStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
return nil
}
func (t *testRESTStrategy) Canonicalize(obj runtime.Object) {}
func NewTestGenericStoreRegistry(t *testing.T) (factory.DestroyFunc, *Store) {
return newTestGenericStoreRegistry(t, scheme, false)
}
func getPodAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) {
pod := obj.(*example.Pod)
return labels.Set{"name": pod.ObjectMeta.Name}, nil, pod.Initializers != nil, nil
}
// matchPodName returns selection predicate that matches any pod with name in the set.
// Makes testing simpler.
func matchPodName(names ...string) storage.SelectionPredicate {
// Note: even if pod name is a field, we have to use labels,
// because field selector doesn't support "IN" operator.
l, err := labels.NewRequirement("name", selection.In, names)
if err != nil {
panic("Labels requirement must validate successfully")
}
return storage.SelectionPredicate{
Label: labels.Everything().Add(*l),
Field: fields.Everything(),
GetAttrs: getPodAttrs,
}
}
func matchEverything() storage.SelectionPredicate {
return storage.SelectionPredicate{
Label: labels.Everything(),
Field: fields.Everything(),
GetAttrs: func(obj runtime.Object) (label labels.Set, field fields.Set, uninitialized bool, err error) {
return nil, nil, false, nil
},
}
}
func TestStoreList(t *testing.T) {
podA := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "bar"},
Spec: example.PodSpec{NodeName: "machine"},
}
podB := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "foo"},
Spec: example.PodSpec{NodeName: "machine"},
}
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
noNamespaceContext := genericapirequest.NewContext()
table := map[string]struct {
in *example.PodList
m storage.SelectionPredicate
out runtime.Object
context context.Context
}{
"notFound": {
in: nil,
m: matchEverything(),
out: &example.PodList{Items: []example.Pod{}},
},
"normal": {
in: &example.PodList{Items: []example.Pod{*podA, *podB}},
m: matchEverything(),
out: &example.PodList{Items: []example.Pod{*podA, *podB}},
},
"normalFiltered": {
in: &example.PodList{Items: []example.Pod{*podA, *podB}},
m: matchPodName("foo"),
out: &example.PodList{Items: []example.Pod{*podB}},
},
"normalFilteredNoNamespace": {
in: &example.PodList{Items: []example.Pod{*podA, *podB}},
m: matchPodName("foo"),
out: &example.PodList{Items: []example.Pod{*podB}},
context: noNamespaceContext,
},
"normalFilteredMatchMultiple": {
in: &example.PodList{Items: []example.Pod{*podA, *podB}},
m: matchPodName("foo", "makeMatchSingleReturnFalse"),
out: &example.PodList{Items: []example.Pod{*podB}},
},
}
for name, item := range table {
ctx := testContext
if item.context != nil {
ctx = item.context
}
destroyFunc, registry := NewTestGenericStoreRegistry(t)
if item.in != nil {
if err := storagetesting.CreateList("/pods", registry.Storage.Storage, item.in); err != nil {
t.Errorf("Unexpected error %v", err)
}
}
list, err := registry.ListPredicate(ctx, item.m, nil)
if err != nil {
t.Errorf("Unexpected error %v", err)
continue
}
// DeepDerivative e,a is needed here b/c the storage layer sets ResourceVersion
if e, a := item.out, list; !apiequality.Semantic.DeepDerivative(e, a) {
t.Errorf("%v: Expected %#v, got %#v", name, e, a)
}
destroyFunc()
}
}
// TestStoreListResourceVersion tests that if List with ResourceVersion > 0, it will wait until
// the results are as fresh as given version.
func TestStoreListResourceVersion(t *testing.T) {
fooPod := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "foo"},
Spec: example.PodSpec{NodeName: "machine"},
}
barPod := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "bar"},
Spec: example.PodSpec{NodeName: "machine"},
}
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
destroyFunc, registry := newTestGenericStoreRegistry(t, scheme, true)
defer destroyFunc()
obj, err := registry.Create(ctx, fooPod, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
if err != nil {
t.Fatal(err)
}
versioner := etcdstorage.APIObjectVersioner{}
rev, err := versioner.ObjectResourceVersion(obj)
if err != nil {
t.Fatal(err)
}
waitListCh := make(chan runtime.Object, 1)
go func(listRev uint64) {
option := &metainternalversion.ListOptions{ResourceVersion: strconv.FormatUint(listRev, 10)}
// It will wait until we create the second pod.
l, err := registry.List(ctx, option)
if err != nil {
close(waitListCh)
t.Fatal(err)
return
}
waitListCh <- l
}(rev + 1)
select {
case <-time.After(500 * time.Millisecond):
case l := <-waitListCh:
t.Fatalf("expected waiting, but get %#v", l)
}
if _, err := registry.Create(ctx, barPod, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil {
t.Fatal(err)
}
select {
case <-time.After(wait.ForeverTestTimeout):
t.Fatalf("timeout after %v", wait.ForeverTestTimeout)
case l, ok := <-waitListCh:
if !ok {
return
}
pl := l.(*example.PodList).Items
if len(pl) != 2 {
t.Errorf("Expected get 2 items, but got %d", len(pl))
}
}
}
func TestStoreCreate(t *testing.T) {
gracefulPeriod := int64(50)
podA := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test"},
Spec: example.PodSpec{NodeName: "machine"},
}
podB := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test"},
Spec: example.PodSpec{NodeName: "machine2"},
}
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defer destroyFunc()
// re-define delete strategy to have graceful delete capability
defaultDeleteStrategy := testRESTStrategy{scheme, names.SimpleNameGenerator, true, false, true}
registry.DeleteStrategy = testGracefulStrategy{defaultDeleteStrategy}
registry.Decorator = func(obj runtime.Object) error {
pod := obj.(*example.Pod)
pod.Status.Phase = example.PodPhase("Testing")
return nil
}
// create the object with denying admission
objA, err := registry.Create(testContext, podA, denyCreateValidation, &metav1.CreateOptions{})
if err == nil {
t.Errorf("Expected admission error: %v", err)
}
// create the object
objA, err = registry.Create(testContext, podA, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// verify the decorator was called
if objA.(*example.Pod).Status.Phase != example.PodPhase("Testing") {
t.Errorf("Decorator was not called: %#v", objA)
}
// get the object
checkobj, err := registry.Get(testContext, podA.Name, &metav1.GetOptions{})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// verify objects are equal
if e, a := objA, checkobj; !reflect.DeepEqual(e, a) {
t.Errorf("Expected %#v, got %#v", e, a)
}
// now try to create the second pod
_, err = registry.Create(testContext, podB, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
if !errors.IsAlreadyExists(err) {
t.Errorf("Unexpected error: %v", err)
}
// verify graceful delete capability is defined
_, ok := registry.DeleteStrategy.(rest.RESTGracefulDeleteStrategy)
if !ok {
t.Fatalf("No graceful capability set.")
}
// now delete pod with graceful period set
delOpts := &metav1.DeleteOptions{GracePeriodSeconds: &gracefulPeriod}
_, _, err = registry.Delete(testContext, podA.Name, delOpts)
if err != nil {
t.Fatalf("Failed to delete pod gracefully. Unexpected error: %v", err)
}
// try to create before graceful deletion period is over
_, err = registry.Create(testContext, podA, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
if err == nil || !errors.IsAlreadyExists(err) {
t.Fatalf("Expected 'already exists' error from storage, but got %v", err)
}
// check the 'alredy exists' msg was edited
msg := &err.(*errors.StatusError).ErrStatus.Message
if !strings.Contains(*msg, "object is being deleted:") {
t.Errorf("Unexpected error without the 'object is being deleted:' in message: %v", err)
}
}
func isPendingInitialization(obj metav1.Object) bool {
return obj.GetInitializers() != nil && obj.GetInitializers().Result == nil && len(obj.GetInitializers().Pending) > 0
}
func hasInitializers(obj metav1.Object, expected ...string) bool {
if !isPendingInitialization(obj) {
return false
}
if len(expected) != len(obj.GetInitializers().Pending) {
return false
}
for i, init := range obj.GetInitializers().Pending {
if init.Name != expected[i] {
return false
}
}
return true
}
func isFailedInitialization(obj metav1.Object) bool {
return obj.GetInitializers() != nil && obj.GetInitializers().Result != nil && obj.GetInitializers().Result.Status == metav1.StatusFailure
}
func isInitialized(obj metav1.Object) bool {
return obj.GetInitializers() == nil
}
func isQualifiedResource(err error, kind, group string) bool {
if err.(errors.APIStatus).Status().Details.Kind != kind || err.(errors.APIStatus).Status().Details.Group != group {
return false
}
return true
}
func TestStoreCreateInitialized(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.Initializers, true)()
podA := &example.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo", Namespace: "test",
Initializers: &metav1.Initializers{
Pending: []metav1.Initializer{{Name: validInitializerName}},
},
},
Spec: example.PodSpec{NodeName: "machine"},
}
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defer destroyFunc()
ch := make(chan struct{})
chObserver := make(chan struct{})
// simulate a background initializer that initializes the object
early := make(chan struct{}, 1)
go func() {
defer close(ch)
w, err := registry.Watch(ctx, &metainternalversion.ListOptions{
IncludeUninitialized: true,
Watch: true,
FieldSelector: fields.OneTermEqualSelector("metadata.name", "foo"),
})
if err != nil {
t.Fatal(err)
}
defer w.Stop()
event := <-w.ResultChan()
pod := event.Object.(*example.Pod)
if event.Type != watch.Added || !hasInitializers(pod, validInitializerName) {
t.Fatalf("unexpected event: %s %#v", event.Type, event.Object)
}
select {
case <-early:
t.Fatalf("CreateInitialized should not have returned")
default:
}
pod.Initializers = nil
updated, _, err := registry.Update(ctx, podA.Name, rest.DefaultUpdatedObjectInfo(pod), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
if err != nil {
t.Fatal(err)
}
pod = updated.(*example.Pod)
if !isInitialized(pod) {
t.Fatalf("unexpected update: %#v", pod.Initializers)
}
event = <-w.ResultChan()
if event.Type != watch.Modified || !isInitialized(event.Object.(*example.Pod)) {
t.Fatalf("unexpected event: %s %#v", event.Type, event.Object)
}
}()
// create a background worker that should only observe the final creation
go func() {
defer close(chObserver)
w, err := registry.Watch(ctx, &metainternalversion.ListOptions{
IncludeUninitialized: false,
Watch: true,
FieldSelector: fields.OneTermEqualSelector("metadata.name", "foo"),
})
if err != nil {
t.Fatal(err)
}
defer w.Stop()
event := <-w.ResultChan()
pod := event.Object.(*example.Pod)
if event.Type != watch.Added || !isInitialized(pod) {
t.Fatalf("unexpected event: %s %#v", event.Type, event.Object)
}
}()
// create the object
objA, err := registry.Create(ctx, podA, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// signal that we're now waiting, then wait for both observers to see
// the result of the create.
early <- struct{}{}
<-ch
<-chObserver
// get the object
checkobj, err := registry.Get(ctx, podA.Name, &metav1.GetOptions{})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// verify objects are equal
if e, a := objA, checkobj; !reflect.DeepEqual(e, a) {
t.Errorf("Expected %#v, got %#v", e, a)
}
}
func TestStoreCreateInitializedFailed(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.Initializers, true)()
podA := &example.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo", Namespace: "test",
Initializers: &metav1.Initializers{
Pending: []metav1.Initializer{{Name: validInitializerName}},
},
},
Spec: example.PodSpec{NodeName: "machine"},
}
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defer destroyFunc()
ch := make(chan struct{})
go func() {
w, err := registry.Watch(ctx, &metainternalversion.ListOptions{
IncludeUninitialized: true,
Watch: true,
FieldSelector: fields.OneTermEqualSelector("metadata.name", "foo"),
})
if err != nil {
t.Fatal(err)
}
event := <-w.ResultChan()
pod := event.Object.(*example.Pod)
if event.Type != watch.Added || !hasInitializers(pod, validInitializerName) {
t.Fatalf("unexpected event: %s %#v", event.Type, event.Object)
}
pod.Initializers.Pending = nil
pod.Initializers.Result = &metav1.Status{Status: metav1.StatusFailure, Code: 403, Reason: metav1.StatusReasonForbidden, Message: "induced failure"}
updated, _, err := registry.Update(ctx, podA.Name, rest.DefaultUpdatedObjectInfo(pod), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
if err != nil {
t.Fatal(err)
}
pod = updated.(*example.Pod)
if !isFailedInitialization(pod) {
t.Fatalf("unexpected update: %#v", pod.Initializers)
}
event = <-w.ResultChan()
if event.Type != watch.Modified || !isFailedInitialization(event.Object.(*example.Pod)) {
t.Fatalf("unexpected event: %s %#v", event.Type, event.Object)
}
event = <-w.ResultChan()
if event.Type != watch.Deleted || !isFailedInitialization(event.Object.(*example.Pod)) {
t.Fatalf("unexpected event: %s %#v", event.Type, event.Object)
}
w.Stop()
close(ch)
}()
// create the object
_, err := registry.Create(ctx, podA, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
if !errors.IsForbidden(err) {
t.Fatalf("unexpected error: %#v", err.(errors.APIStatus).Status())
}
if err.(errors.APIStatus).Status().Message != "induced failure" {
t.Fatalf("unexpected error: %#v", err)
}
<-ch
// get the object
_, err = registry.Get(ctx, podA.Name, &metav1.GetOptions{})
if !errors.IsNotFound(err) {
t.Fatalf("Unexpected error: %v", err)
}
}
func updateAndVerify(t *testing.T, ctx context.Context, registry *Store, pod *example.Pod) bool {
obj, _, err := registry.Update(ctx, pod.Name, rest.DefaultUpdatedObjectInfo(pod), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
if err != nil {
t.Errorf("Unexpected error: %v", err)
return false
}
checkObj, err := registry.Get(ctx, pod.Name, &metav1.GetOptions{})
if err != nil {
t.Errorf("Unexpected error: %v", err)
return false
}
if e, a := obj, checkObj; !reflect.DeepEqual(e, a) {
t.Errorf("Expected %#v, got %#v", e, a)
return false
}
return true
}
func TestStoreUpdate(t *testing.T) {
podA := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test"},
Spec: example.PodSpec{NodeName: "machine"},
}
podB := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test"},
Spec: example.PodSpec{NodeName: "machine2"},
}
podAWithResourceVersion := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "7"},
Spec: example.PodSpec{NodeName: "machine"},
}
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defer destroyFunc()
// try to update a non-existing node with denying admission, should still return NotFound
_, _, err := registry.Update(testContext, podA.Name, rest.DefaultUpdatedObjectInfo(podA), denyCreateValidation, denyUpdateValidation, false, &metav1.UpdateOptions{})
if !errors.IsNotFound(err) {
t.Errorf("Unexpected error: %v", err)
}
// try to update a non-existing node
_, _, err = registry.Update(testContext, podA.Name, rest.DefaultUpdatedObjectInfo(podA), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
if !errors.IsNotFound(err) {
t.Errorf("Unexpected error: %v", err)
}
// allow creation
registry.UpdateStrategy.(*testRESTStrategy).allowCreateOnUpdate = true
// createIfNotFound with denying create admission
_, _, err = registry.Update(testContext, podA.Name, rest.DefaultUpdatedObjectInfo(podA), denyCreateValidation, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
if err == nil {
t.Errorf("expected admission error on create")
}
// createIfNotFound and verify
if !updateAndVerify(t, testContext, registry, podA) {
t.Errorf("Unexpected error updating podA")
}
// forbid creation again
registry.UpdateStrategy.(*testRESTStrategy).allowCreateOnUpdate = false
// outofDate
_, _, err = registry.Update(testContext, podAWithResourceVersion.Name, rest.DefaultUpdatedObjectInfo(podAWithResourceVersion), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
if !errors.IsConflict(err) {
t.Errorf("Unexpected error updating podAWithResourceVersion: %v", err)
}
// try to update with denying admission
_, _, err = registry.Update(testContext, podA.Name, rest.DefaultUpdatedObjectInfo(podA), rest.ValidateAllObjectFunc, denyUpdateValidation, false, &metav1.UpdateOptions{})
if err == nil {
t.Errorf("expected admission error on update")
}
// normal update and verify
if !updateAndVerify(t, testContext, registry, podB) {
t.Errorf("Unexpected error updating podB")
}
// unconditional update
// NOTE: The logic for unconditional updates doesn't make sense to me, and imho should be removed.
// doUnconditionalUpdate := resourceVersion == 0 && e.UpdateStrategy.AllowUnconditionalUpdate()
// ^^ That condition can *never be true due to the creation of root objects.
//
// registry.UpdateStrategy.(*testRESTStrategy).allowUnconditionalUpdate = true
// updateAndVerify(t, testContext, registry, podAWithResourceVersion)
}
func TestNoOpUpdates(t *testing.T) {
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defer destroyFunc()
newPod := func() *example.Pod {
return &example.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: metav1.NamespaceDefault,
Name: "foo",
Labels: map[string]string{"prepare_create": "true"},
},
Spec: example.PodSpec{NodeName: "machine"},
}
}
var err error
var createResult runtime.Object
if createResult, err = registry.Create(genericapirequest.NewDefaultContext(), newPod(), rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
createdPod, err := registry.Get(genericapirequest.NewDefaultContext(), "foo", &metav1.GetOptions{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
var updateResult runtime.Object
p := newPod()
if updateResult, _, err = registry.Update(genericapirequest.NewDefaultContext(), p.Name, rest.DefaultUpdatedObjectInfo(p), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Check whether we do not return empty result on no-op update.
if !reflect.DeepEqual(createResult, updateResult) {
t.Errorf("no-op update should return a correct value, got: %#v", updateResult)
}
updatedPod, err := registry.Get(genericapirequest.NewDefaultContext(), "foo", &metav1.GetOptions{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
createdMeta, err := meta.Accessor(createdPod)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
updatedMeta, err := meta.Accessor(updatedPod)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if createdMeta.GetResourceVersion() != updatedMeta.GetResourceVersion() {
t.Errorf("no-op update should be ignored and not written to etcd")
}
}
// TODO: Add a test to check no-op update if we have object with ResourceVersion
// already stored in etcd. Currently there is no easy way to store object with
// ResourceVersion in etcd.
type testPodExport struct{}
func (t testPodExport) Export(ctx context.Context, obj runtime.Object, exact bool) error {
pod := obj.(*example.Pod)
if pod.Labels == nil {
pod.Labels = map[string]string{}
}
pod.Labels["exported"] = "true"
pod.Labels["exact"] = strconv.FormatBool(exact)
return nil
}
func TestStoreCustomExport(t *testing.T) {
podA := example.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "foo",
Labels: map[string]string{},
},
Spec: example.PodSpec{NodeName: "machine"},
}
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defer destroyFunc()
registry.ExportStrategy = testPodExport{}
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
registry.UpdateStrategy.(*testRESTStrategy).allowCreateOnUpdate = true
if !updateAndVerify(t, testContext, registry, &podA) {
t.Errorf("Unexpected error updating podA")
}
obj, err := registry.Export(testContext, podA.Name, metav1.ExportOptions{})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
exportedPod := obj.(*example.Pod)
if exportedPod.Labels["exported"] != "true" {
t.Errorf("expected: exported->true, found: %s", exportedPod.Labels["exported"])
}
if exportedPod.Labels["exact"] != "false" {
t.Errorf("expected: exact->false, found: %s", exportedPod.Labels["exact"])
}
if exportedPod.Labels["prepare_create"] != "true" {
t.Errorf("expected: prepare_create->true, found: %s", exportedPod.Labels["prepare_create"])
}
delete(exportedPod.Labels, "exported")
delete(exportedPod.Labels, "exact")
delete(exportedPod.Labels, "prepare_create")
exportObjectMeta(&podA.ObjectMeta, false)
podA.Spec = exportedPod.Spec
if !reflect.DeepEqual(&podA, exportedPod) {
t.Errorf("expected:\n%v\nsaw:\n%v\n", &podA, exportedPod)
}
}
func TestStoreBasicExport(t *testing.T) {
podA := example.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test",
Name: "foo",
Labels: map[string]string{},
},
Spec: example.PodSpec{NodeName: "machine"},
Status: example.PodStatus{HostIP: "1.2.3.4"},
}
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defer destroyFunc()
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
registry.UpdateStrategy.(*testRESTStrategy).allowCreateOnUpdate = true
if !updateAndVerify(t, testContext, registry, &podA) {
t.Errorf("Unexpected error updating podA")
}
obj, err := registry.Export(testContext, podA.Name, metav1.ExportOptions{})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
exportedPod := obj.(*example.Pod)
if exportedPod.Labels["prepare_create"] != "true" {
t.Errorf("expected: prepare_create->true, found: %s", exportedPod.Labels["prepare_create"])
}
delete(exportedPod.Labels, "prepare_create")
exportObjectMeta(&podA.ObjectMeta, false)
podA.Spec = exportedPod.Spec
if !reflect.DeepEqual(&podA, exportedPod) {
t.Errorf("expected:\n%v\nsaw:\n%v\n", &podA, exportedPod)
}
}
func TestStoreGet(t *testing.T) {
podA := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "foo"},
Spec: example.PodSpec{NodeName: "machine"},
}
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defer destroyFunc()
_, err := registry.Get(testContext, podA.Name, &metav1.GetOptions{})
if !errors.IsNotFound(err) {
t.Errorf("Unexpected error: %v", err)
}
registry.UpdateStrategy.(*testRESTStrategy).allowCreateOnUpdate = true
if !updateAndVerify(t, testContext, registry, podA) {
t.Errorf("Unexpected error updating podA")
}
}
func TestStoreDelete(t *testing.T) {
podA := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: example.PodSpec{NodeName: "machine"},
}
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defer destroyFunc()
// test failure condition
_, _, err := registry.Delete(testContext, podA.Name, nil)
if !errors.IsNotFound(err) {
t.Errorf("Unexpected error: %v", err)
}
// create pod
_, err = registry.Create(testContext, podA, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// delete object
_, wasDeleted, err := registry.Delete(testContext, podA.Name, nil)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if !wasDeleted {
t.Errorf("unexpected, pod %s should have been deleted immediately", podA.Name)
}
// try to get a item which should be deleted
_, err = registry.Get(testContext, podA.Name, &metav1.GetOptions{})
if !errors.IsNotFound(err) {
t.Errorf("Unexpected error: %v", err)
}
}
func TestStoreDeleteUninitialized(t *testing.T) {
podA := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Initializers: &metav1.Initializers{Pending: []metav1.Initializer{{Name: validInitializerName}}}},
Spec: example.PodSpec{NodeName: "machine"},
}
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defer destroyFunc()
// test failure condition
_, _, err := registry.Delete(testContext, podA.Name, nil)
if !errors.IsNotFound(err) {
t.Errorf("Unexpected error: %v", err)
}
// create pod
_, err = registry.Create(testContext, podA, rest.ValidateAllObjectFunc, &metav1.CreateOptions{IncludeUninitialized: true})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// delete object
_, wasDeleted, err := registry.Delete(testContext, podA.Name, nil)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if !wasDeleted {
t.Errorf("unexpected, pod %s should have been deleted immediately", podA.Name)
}
// try to get a item which should be deleted
_, err = registry.Get(testContext, podA.Name, &metav1.GetOptions{})
if !errors.IsNotFound(err) {
t.Errorf("Unexpected error: %v", err)
}
}
// TestGracefulStoreCanDeleteIfExistingGracePeriodZero tests recovery from
// race condition where the graceful delete is unable to complete
// in prior operation, but the pod remains with deletion timestamp
// and grace period set to 0.
func TestGracefulStoreCanDeleteIfExistingGracePeriodZero(t *testing.T) {
deletionTimestamp := metav1.NewTime(time.Now())
deletionGracePeriodSeconds := int64(0)
initialGeneration := int64(1)
pod := &example.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Generation: initialGeneration,
DeletionGracePeriodSeconds: &deletionGracePeriodSeconds,
DeletionTimestamp: &deletionTimestamp,
},
Spec: example.PodSpec{NodeName: "machine"},
}
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
destroyFunc, registry := NewTestGenericStoreRegistry(t)
registry.EnableGarbageCollection = false
defaultDeleteStrategy := testRESTStrategy{scheme, names.SimpleNameGenerator, true, false, true}
registry.DeleteStrategy = testGracefulStrategy{defaultDeleteStrategy}
defer destroyFunc()
graceful, gracefulPending, err := rest.BeforeDelete(registry.DeleteStrategy, testContext, pod, metav1.NewDeleteOptions(0))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if graceful {
t.Fatalf("graceful should be false if object has DeletionTimestamp and DeletionGracePeriodSeconds is 0")
}
if gracefulPending {
t.Fatalf("gracefulPending should be false if object has DeletionTimestamp and DeletionGracePeriodSeconds is 0")
}
}
func TestGracefulStoreHandleFinalizers(t *testing.T) {
initialGeneration := int64(1)
podWithFinalizer := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Finalizers: []string{"foo.com/x"}, Generation: initialGeneration},
Spec: example.PodSpec{NodeName: "machine"},
}
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defaultDeleteStrategy := testRESTStrategy{scheme, names.SimpleNameGenerator, true, false, true}
registry.DeleteStrategy = testGracefulStrategy{defaultDeleteStrategy}
defer destroyFunc()
gcStates := []bool{true, false}
for _, gcEnabled := range gcStates {
t.Logf("garbage collection enabled: %t", gcEnabled)
registry.EnableGarbageCollection = gcEnabled
// create pod
_, err := registry.Create(testContext, podWithFinalizer, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// 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))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if wasDeleted {
t.Errorf("unexpected, pod %s should not have been deleted immediately", podWithFinalizer.Name)
}
_, err = registry.Get(testContext, podWithFinalizer.Name, &metav1.GetOptions{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
updatedPodWithFinalizer := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Finalizers: []string{"foo.com/x"}, ResourceVersion: podWithFinalizer.ObjectMeta.ResourceVersion},
Spec: example.PodSpec{NodeName: "machine"},
}
_, _, err = registry.Update(testContext, updatedPodWithFinalizer.ObjectMeta.Name, rest.DefaultUpdatedObjectInfo(updatedPodWithFinalizer), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// the object should still exist, because it still has a finalizer
_, err = registry.Get(testContext, podWithFinalizer.Name, &metav1.GetOptions{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
podWithNoFinalizer := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: podWithFinalizer.ObjectMeta.ResourceVersion},
Spec: example.PodSpec{NodeName: "anothermachine"},
}
_, _, err = registry.Update(testContext, podWithFinalizer.ObjectMeta.Name, rest.DefaultUpdatedObjectInfo(podWithNoFinalizer), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// the pod should be removed, because its finalizer is removed
_, err = registry.Get(testContext, podWithFinalizer.Name, &metav1.GetOptions{})
if !errors.IsNotFound(err) {
t.Fatalf("Unexpected error: %v", err)
}
}
}
func TestFailedInitializationStoreUpdate(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.Initializers, true)()
initialGeneration := int64(1)
podInitializing := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Initializers: &metav1.Initializers{Pending: []metav1.Initializer{{Name: validInitializerName}}}, Generation: initialGeneration},
Spec: example.PodSpec{NodeName: "machine"},
}
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
destroyFunc, registry := NewTestGenericStoreRegistry(t)
registry.EnableGarbageCollection = true
defaultDeleteStrategy := testRESTStrategy{scheme, names.SimpleNameGenerator, true, false, true}
registry.DeleteStrategy = testGracefulStrategy{defaultDeleteStrategy}
defer destroyFunc()
// create pod, view initializing
obj, err := registry.Create(testContext, podInitializing, rest.ValidateAllObjectFunc, &metav1.CreateOptions{IncludeUninitialized: true})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
pod := obj.(*example.Pod)
// update the pod with initialization failure, the pod should be deleted
pod.Initializers.Result = &metav1.Status{Status: metav1.StatusFailure}
result, _, err := registry.Update(testContext, podInitializing.Name, rest.DefaultUpdatedObjectInfo(pod), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, err = registry.Get(testContext, podInitializing.Name, &metav1.GetOptions{})
if err == nil || !errors.IsNotFound(err) {
t.Fatalf("Unexpected error: %v", err)
}
pod = result.(*example.Pod)
if pod.Initializers == nil || pod.Initializers.Result == nil || pod.Initializers.Result.Status != metav1.StatusFailure {
t.Fatalf("Pod returned from update was not correct: %#v", pod)
}
}
func TestNonGracefulStoreHandleFinalizers(t *testing.T) {
initialGeneration := int64(1)
podWithFinalizer := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Finalizers: []string{"foo.com/x"}, Generation: initialGeneration},
Spec: example.PodSpec{NodeName: "machine"},
}
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defer destroyFunc()
gcStates := []bool{true, false}
for _, gcEnabled := range gcStates {
t.Logf("garbage collection enabled: %t", gcEnabled)
registry.EnableGarbageCollection = gcEnabled
// create pod
_, err := registry.Create(testContext, podWithFinalizer, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// delete object with nil delete options doesn't delete the object
_, wasDeleted, err := registry.Delete(testContext, podWithFinalizer.Name, nil)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if wasDeleted {
t.Errorf("unexpected, pod %s should not have been deleted immediately", podWithFinalizer.Name)
}
// the object should still exist
obj, err := registry.Get(testContext, podWithFinalizer.Name, &metav1.GetOptions{})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
podWithFinalizer, ok := obj.(*example.Pod)
if !ok {
t.Errorf("Unexpected object: %#v", obj)
}
if podWithFinalizer.ObjectMeta.DeletionTimestamp == nil {
t.Errorf("Expect the object to have DeletionTimestamp set, but got %#v", podWithFinalizer.ObjectMeta)
}
if podWithFinalizer.ObjectMeta.DeletionGracePeriodSeconds == nil || *podWithFinalizer.ObjectMeta.DeletionGracePeriodSeconds != 0 {
t.Errorf("Expect the object to have 0 DeletionGracePeriodSecond, but got %#v", podWithFinalizer.ObjectMeta)
}
if podWithFinalizer.Generation <= initialGeneration {
t.Errorf("Deletion didn't increase Generation.")
}
updatedPodWithFinalizer := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Finalizers: []string{"foo.com/x"}, ResourceVersion: podWithFinalizer.ObjectMeta.ResourceVersion},
Spec: example.PodSpec{NodeName: "machine"},
}
_, _, err = registry.Update(testContext, updatedPodWithFinalizer.ObjectMeta.Name, rest.DefaultUpdatedObjectInfo(updatedPodWithFinalizer), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// the object should still exist, because it still has a finalizer
obj, err = registry.Get(testContext, podWithFinalizer.Name, &metav1.GetOptions{})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
podWithFinalizer, ok = obj.(*example.Pod)
if !ok {
t.Errorf("Unexpected object: %#v", obj)
}
podWithNoFinalizer := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: podWithFinalizer.ObjectMeta.ResourceVersion},
Spec: example.PodSpec{NodeName: "anothermachine"},
}
_, _, err = registry.Update(testContext, podWithFinalizer.ObjectMeta.Name, rest.DefaultUpdatedObjectInfo(podWithNoFinalizer), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// the pod should be removed, because its finalizer is removed
_, err = registry.Get(testContext, podWithFinalizer.Name, &metav1.GetOptions{})
if !errors.IsNotFound(err) {
t.Errorf("Unexpected error: %v", err)
}
}
}
func TestStoreDeleteWithOrphanDependents(t *testing.T) {
initialGeneration := int64(1)
podWithOrphanFinalizer := func(name string) *example.Pod {
return &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: name, Finalizers: []string{"foo.com/x", metav1.FinalizerOrphanDependents, "bar.com/y"}, Generation: initialGeneration},
Spec: example.PodSpec{NodeName: "machine"},
}
}
podWithOtherFinalizers := func(name string) *example.Pod {
return &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: name, Finalizers: []string{"foo.com/x", "bar.com/y"}, Generation: initialGeneration},
Spec: example.PodSpec{NodeName: "machine"},
}
}
podWithNoFinalizer := func(name string) *example.Pod {
return &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: name, Generation: initialGeneration},
Spec: example.PodSpec{NodeName: "machine"},
}
}
podWithOnlyOrphanFinalizer := func(name string) *example.Pod {
return &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: name, Finalizers: []string{metav1.FinalizerOrphanDependents}, Generation: initialGeneration},
Spec: example.PodSpec{NodeName: "machine"},
}
}
trueVar, falseVar := true, false
orphanOptions := &metav1.DeleteOptions{OrphanDependents: &trueVar}
nonOrphanOptions := &metav1.DeleteOptions{OrphanDependents: &falseVar}
nilOrphanOptions := &metav1.DeleteOptions{}
// defaultDeleteStrategy doesn't implement rest.GarbageCollectionDeleteStrategy.
defaultDeleteStrategy := &testRESTStrategy{scheme, names.SimpleNameGenerator, true, false, true}
// orphanDeleteStrategy indicates the default garbage collection policy is
// to orphan dependentes.
orphanDeleteStrategy := &testOrphanDeleteStrategy{defaultDeleteStrategy}
testcases := []struct {
pod *example.Pod
options *metav1.DeleteOptions
strategy rest.RESTDeleteStrategy
expectNotFound bool
updatedFinalizers []string
}{
// cases run with DeleteOptions.OrphanDedependents=true
{
podWithOrphanFinalizer("pod1"),
orphanOptions,
defaultDeleteStrategy,
false,
[]string{"foo.com/x", metav1.FinalizerOrphanDependents, "bar.com/y"},
},
{
podWithOtherFinalizers("pod2"),
orphanOptions,
defaultDeleteStrategy,
false,
[]string{"foo.com/x", "bar.com/y", metav1.FinalizerOrphanDependents},
},
{
podWithNoFinalizer("pod3"),
orphanOptions,
defaultDeleteStrategy,
false,
[]string{metav1.FinalizerOrphanDependents},
},
{
podWithOnlyOrphanFinalizer("pod4"),
orphanOptions,
defaultDeleteStrategy,
false,
[]string{metav1.FinalizerOrphanDependents},
},
// cases run with DeleteOptions.OrphanDedependents=false
// these cases all have oprhanDeleteStrategy, which should be ignored
// because DeleteOptions has the highest priority.
{
podWithOrphanFinalizer("pod5"),
nonOrphanOptions,
orphanDeleteStrategy,
false,
[]string{"foo.com/x", "bar.com/y"},
},
{
podWithOtherFinalizers("pod6"),
nonOrphanOptions,
orphanDeleteStrategy,
false,
[]string{"foo.com/x", "bar.com/y"},
},
{
podWithNoFinalizer("pod7"),
nonOrphanOptions,
orphanDeleteStrategy,
true,
[]string{},
},
{
podWithOnlyOrphanFinalizer("pod8"),
nonOrphanOptions,
orphanDeleteStrategy,
true,
[]string{},
},
// cases run with nil DeleteOptions.OrphanDependents. If the object
// already has the orphan finalizer, then the DeleteStrategy should be
// ignored. Otherwise the DeleteStrategy decides whether to add the
// orphan finalizer.
{
podWithOrphanFinalizer("pod9"),
nilOrphanOptions,
defaultDeleteStrategy,
false,
[]string{"foo.com/x", metav1.FinalizerOrphanDependents, "bar.com/y"},
},
{
podWithOrphanFinalizer("pod10"),
nilOrphanOptions,
orphanDeleteStrategy,
false,
[]string{"foo.com/x", metav1.FinalizerOrphanDependents, "bar.com/y"},
},
{
podWithOtherFinalizers("pod11"),
nilOrphanOptions,
defaultDeleteStrategy,
false,
[]string{"foo.com/x", "bar.com/y"},
},
{
podWithOtherFinalizers("pod12"),
nilOrphanOptions,
orphanDeleteStrategy,
false,
[]string{"foo.com/x", "bar.com/y", metav1.FinalizerOrphanDependents},
},
{
podWithNoFinalizer("pod13"),
nilOrphanOptions,
defaultDeleteStrategy,
true,
[]string{},
},
{
podWithNoFinalizer("pod14"),
nilOrphanOptions,
orphanDeleteStrategy,
false,
[]string{metav1.FinalizerOrphanDependents},
},
{
podWithOnlyOrphanFinalizer("pod15"),
nilOrphanOptions,
defaultDeleteStrategy,
false,
[]string{metav1.FinalizerOrphanDependents},
},
{
podWithOnlyOrphanFinalizer("pod16"),
nilOrphanOptions,
orphanDeleteStrategy,
false,
[]string{metav1.FinalizerOrphanDependents},
},
// cases run with nil DeleteOptions should have exact same behavior.
// They should be exactly the same as above cases where
// DeleteOptions.OrphanDependents is nil.
{
podWithOrphanFinalizer("pod17"),
nil,
defaultDeleteStrategy,
false,
[]string{"foo.com/x", metav1.FinalizerOrphanDependents, "bar.com/y"},
},
{
podWithOrphanFinalizer("pod18"),
nil,
orphanDeleteStrategy,
false,
[]string{"foo.com/x", metav1.FinalizerOrphanDependents, "bar.com/y"},
},
{
podWithOtherFinalizers("pod19"),
nil,
defaultDeleteStrategy,
false,
[]string{"foo.com/x", "bar.com/y"},
},
{
podWithOtherFinalizers("pod20"),
nil,
orphanDeleteStrategy,
false,
[]string{"foo.com/x", "bar.com/y", metav1.FinalizerOrphanDependents},
},
{
podWithNoFinalizer("pod21"),
nil,
defaultDeleteStrategy,
true,
[]string{},
},
{
podWithNoFinalizer("pod22"),
nil,
orphanDeleteStrategy,
false,
[]string{metav1.FinalizerOrphanDependents},
},
{
podWithOnlyOrphanFinalizer("pod23"),
nil,
defaultDeleteStrategy,
false,
[]string{metav1.FinalizerOrphanDependents},
},
{
podWithOnlyOrphanFinalizer("pod24"),
nil,
orphanDeleteStrategy,
false,
[]string{metav1.FinalizerOrphanDependents},
},
}
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
destroyFunc, registry := NewTestGenericStoreRegistry(t)
registry.EnableGarbageCollection = true
defer destroyFunc()
for _, tc := range testcases {
registry.DeleteStrategy = tc.strategy
// create pod
_, err := registry.Create(testContext, tc.pod, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, _, err = registry.Delete(testContext, tc.pod.Name, tc.options)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
obj, err := registry.Get(testContext, tc.pod.Name, &metav1.GetOptions{})
if tc.expectNotFound && (err == nil || !errors.IsNotFound(err)) {
t.Fatalf("Unexpected error: %v", err)
}
if !tc.expectNotFound && err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !tc.expectNotFound {
pod, ok := obj.(*example.Pod)
if !ok {
t.Fatalf("Expect the object to be a pod, but got %#v", obj)
}
if pod.ObjectMeta.DeletionTimestamp == nil {
t.Errorf("%v: Expect the object to have DeletionTimestamp set, but got %#v", pod.Name, pod.ObjectMeta)
}
if pod.ObjectMeta.DeletionGracePeriodSeconds == nil || *pod.ObjectMeta.DeletionGracePeriodSeconds != 0 {
t.Errorf("%v: Expect the object to have 0 DeletionGracePeriodSecond, but got %#v", pod.Name, pod.ObjectMeta)
}
if pod.Generation <= initialGeneration {
t.Errorf("%v: Deletion didn't increase Generation.", pod.Name)
}
if e, a := tc.updatedFinalizers, pod.ObjectMeta.Finalizers; !reflect.DeepEqual(e, a) {
t.Errorf("%v: Expect object %s to have finalizers %v, got %v", pod.Name, pod.ObjectMeta.Name, e, a)
}
}
}
}
// Test the DeleteOptions.PropagationPolicy is handled correctly
func TestStoreDeletionPropagation(t *testing.T) {
initialGeneration := int64(1)
// defaultDeleteStrategy doesn't implement rest.GarbageCollectionDeleteStrategy.
defaultDeleteStrategy := &testRESTStrategy{scheme, names.SimpleNameGenerator, true, false, true}
// orphanDeleteStrategy indicates the default garbage collection policy is
// to orphan dependentes.
orphanDeleteStrategy := &testOrphanDeleteStrategy{defaultDeleteStrategy}
foregroundPolicy := metav1.DeletePropagationForeground
backgroundPolicy := metav1.DeletePropagationBackground
orphanPolicy := metav1.DeletePropagationOrphan
testcases := map[string]struct {
options *metav1.DeleteOptions
strategy rest.RESTDeleteStrategy
// finalizers that are already set in the object
existingFinalizers []string
expectedNotFound bool
expectedFinalizers []string
}{
"no existing finalizers, PropagationPolicy=Foreground, defaultDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: &foregroundPolicy},
strategy: defaultDeleteStrategy,
expectedFinalizers: []string{metav1.FinalizerDeleteDependents},
},
"no existing finalizers, PropagationPolicy=Foreground, orphanDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: &foregroundPolicy},
strategy: orphanDeleteStrategy,
expectedFinalizers: []string{metav1.FinalizerDeleteDependents},
},
"no existing finalizers, PropagationPolicy=Background, defaultDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: &backgroundPolicy},
strategy: defaultDeleteStrategy,
expectedNotFound: true,
},
"no existing finalizers, PropagationPolicy=Background, orphanDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: &backgroundPolicy},
strategy: orphanDeleteStrategy,
expectedNotFound: true,
},
"no existing finalizers, PropagationPolicy=OrphanDependents, defaultDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: &orphanPolicy},
strategy: defaultDeleteStrategy,
expectedFinalizers: []string{metav1.FinalizerOrphanDependents},
},
"no existing finalizers, PropagationPolicy=OrphanDependents, orphanDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: &orphanPolicy},
strategy: orphanDeleteStrategy,
expectedFinalizers: []string{metav1.FinalizerOrphanDependents},
},
"no existing finalizers, PropagationPolicy=Default, defaultDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: nil},
strategy: defaultDeleteStrategy,
expectedNotFound: true,
},
"no existing finalizers, PropagationPolicy=Default, orphanDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: nil},
strategy: orphanDeleteStrategy,
expectedFinalizers: []string{metav1.FinalizerOrphanDependents},
},
// all cases in the following block have "existing orphan finalizer"
"existing orphan finalizer, PropagationPolicy=Foreground, defaultDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: &foregroundPolicy},
strategy: defaultDeleteStrategy,
existingFinalizers: []string{metav1.FinalizerOrphanDependents},
expectedFinalizers: []string{metav1.FinalizerDeleteDependents},
},
"existing orphan finalizer, PropagationPolicy=Foreground, orphanDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: &foregroundPolicy},
strategy: orphanDeleteStrategy,
existingFinalizers: []string{metav1.FinalizerOrphanDependents},
expectedFinalizers: []string{metav1.FinalizerDeleteDependents},
},
"existing orphan finalizer, PropagationPolicy=Background, defaultDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: &backgroundPolicy},
strategy: defaultDeleteStrategy,
existingFinalizers: []string{metav1.FinalizerOrphanDependents},
expectedNotFound: true,
},
"existing orphan finalizer, PropagationPolicy=Background, orphanDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: &backgroundPolicy},
strategy: orphanDeleteStrategy,
existingFinalizers: []string{metav1.FinalizerOrphanDependents},
expectedNotFound: true,
},
"existing orphan finalizer, PropagationPolicy=OrphanDependents, defaultDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: &orphanPolicy},
strategy: defaultDeleteStrategy,
existingFinalizers: []string{metav1.FinalizerOrphanDependents},
expectedFinalizers: []string{metav1.FinalizerOrphanDependents},
},
"existing orphan finalizer, PropagationPolicy=OrphanDependents, orphanDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: &orphanPolicy},
strategy: orphanDeleteStrategy,
existingFinalizers: []string{metav1.FinalizerOrphanDependents},
expectedFinalizers: []string{metav1.FinalizerOrphanDependents},
},
"existing orphan finalizer, PropagationPolicy=Default, defaultDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: nil},
strategy: defaultDeleteStrategy,
existingFinalizers: []string{metav1.FinalizerOrphanDependents},
expectedFinalizers: []string{metav1.FinalizerOrphanDependents},
},
"existing orphan finalizer, PropagationPolicy=Default, orphanDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: nil},
strategy: orphanDeleteStrategy,
existingFinalizers: []string{metav1.FinalizerOrphanDependents},
expectedFinalizers: []string{metav1.FinalizerOrphanDependents},
},
// all cases in the following block have "existing deleteDependents finalizer"
"existing deleteDependents finalizer, PropagationPolicy=Foreground, defaultDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: &foregroundPolicy},
strategy: defaultDeleteStrategy,
existingFinalizers: []string{metav1.FinalizerDeleteDependents},
expectedFinalizers: []string{metav1.FinalizerDeleteDependents},
},
"existing deleteDependents finalizer, PropagationPolicy=Foreground, orphanDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: &foregroundPolicy},
strategy: orphanDeleteStrategy,
existingFinalizers: []string{metav1.FinalizerDeleteDependents},
expectedFinalizers: []string{metav1.FinalizerDeleteDependents},
},
"existing deleteDependents finalizer, PropagationPolicy=Background, defaultDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: &backgroundPolicy},
strategy: defaultDeleteStrategy,
existingFinalizers: []string{metav1.FinalizerDeleteDependents},
expectedNotFound: true,
},
"existing deleteDependents finalizer, PropagationPolicy=Background, orphanDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: &backgroundPolicy},
strategy: orphanDeleteStrategy,
existingFinalizers: []string{metav1.FinalizerDeleteDependents},
expectedNotFound: true,
},
"existing deleteDependents finalizer, PropagationPolicy=OrphanDependents, defaultDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: &orphanPolicy},
strategy: defaultDeleteStrategy,
existingFinalizers: []string{metav1.FinalizerDeleteDependents},
expectedFinalizers: []string{metav1.FinalizerOrphanDependents},
},
"existing deleteDependents finalizer, PropagationPolicy=OrphanDependents, orphanDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: &orphanPolicy},
strategy: orphanDeleteStrategy,
existingFinalizers: []string{metav1.FinalizerDeleteDependents},
expectedFinalizers: []string{metav1.FinalizerOrphanDependents},
},
"existing deleteDependents finalizer, PropagationPolicy=Default, defaultDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: nil},
strategy: defaultDeleteStrategy,
existingFinalizers: []string{metav1.FinalizerDeleteDependents},
expectedFinalizers: []string{metav1.FinalizerDeleteDependents},
},
"existing deleteDependents finalizer, PropagationPolicy=Default, orphanDeleteStrategy": {
options: &metav1.DeleteOptions{PropagationPolicy: nil},
strategy: orphanDeleteStrategy,
existingFinalizers: []string{metav1.FinalizerDeleteDependents},
expectedFinalizers: []string{metav1.FinalizerDeleteDependents},
},
}
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
destroyFunc, registry := NewTestGenericStoreRegistry(t)
registry.EnableGarbageCollection = true
defer destroyFunc()
createPod := func(i int, finalizers []string) *example.Pod {
return &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i), Finalizers: finalizers, Generation: initialGeneration},
Spec: example.PodSpec{NodeName: "machine"},
}
}
i := 0
for title, tc := range testcases {
t.Logf("case title: %s", title)
registry.DeleteStrategy = tc.strategy
i++
pod := createPod(i, tc.existingFinalizers)
// create pod
_, err := registry.Create(testContext, pod, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
_, _, err = registry.Delete(testContext, pod.Name, tc.options)
obj, err := registry.Get(testContext, pod.Name, &metav1.GetOptions{})
if tc.expectedNotFound {
if err == nil || !errors.IsNotFound(err) {
t.Fatalf("Unexpected error: %v", err)
}
continue
}
if !tc.expectedNotFound && err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !tc.expectedNotFound {
pod, ok := obj.(*example.Pod)
if !ok {
t.Fatalf("Expect the object to be a pod, but got %#v", obj)
}
if e, a := tc.expectedFinalizers, pod.ObjectMeta.Finalizers; !reflect.DeepEqual(e, a) {
t.Errorf("%v: Expect object %s to have finalizers %v, got %v", pod.Name, pod.ObjectMeta.Name, e, a)
}
if pod.ObjectMeta.DeletionTimestamp == nil {
t.Errorf("%v: Expect the object to have DeletionTimestamp set, but got %#v", pod.Name, pod.ObjectMeta)
}
if pod.ObjectMeta.DeletionGracePeriodSeconds == nil || *pod.ObjectMeta.DeletionGracePeriodSeconds != 0 {
t.Errorf("%v: Expect the object to have 0 DeletionGracePeriodSecond, but got %#v", pod.Name, pod.ObjectMeta)
}
if pod.Generation <= initialGeneration {
t.Errorf("%v: Deletion didn't increase Generation.", pod.Name)
}
}
}
}
func TestStoreDeleteCollection(t *testing.T) {
podA := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
podB := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}}
podC := &example.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "baz",
Initializers: &metav1.Initializers{
Pending: []metav1.Initializer{{Name: validInitializerName}},
},
},
}
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defer destroyFunc()
if _, err := registry.Create(testContext, podA, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil {
t.Errorf("Unexpected error: %v", err)
}
if _, err := registry.Create(testContext, podB, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil {
t.Errorf("Unexpected error: %v", err)
}
if _, err := registry.Create(testContext, podC, rest.ValidateAllObjectFunc, &metav1.CreateOptions{IncludeUninitialized: true}); err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Delete all pods.
deleted, err := registry.DeleteCollection(testContext, nil, &metainternalversion.ListOptions{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
deletedPods := deleted.(*example.PodList)
if len(deletedPods.Items) != 3 {
t.Errorf("Unexpected number of pods deleted: %d, expected: 3", len(deletedPods.Items))
}
if _, err := registry.Get(testContext, podA.Name, &metav1.GetOptions{}); !errors.IsNotFound(err) {
t.Errorf("Unexpected error: %v", err)
}
if _, err := registry.Get(testContext, podB.Name, &metav1.GetOptions{}); !errors.IsNotFound(err) {
t.Errorf("Unexpected error: %v", err)
}
if _, err := registry.Get(testContext, podC.Name, &metav1.GetOptions{}); !errors.IsNotFound(err) {
t.Errorf("Unexpected error: %v", err)
}
}
func TestStoreDeleteCollectionNotFound(t *testing.T) {
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defer destroyFunc()
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
podA := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
podB := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}}
for i := 0; i < 10; i++ {
// Setup
if _, err := registry.Create(testContext, podA, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil {
t.Errorf("Unexpected error: %v", err)
}
if _, err := registry.Create(testContext, podB, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Kick off multiple delete collection calls to test notfound behavior
wg := &sync.WaitGroup{}
for j := 0; j < 2; j++ {
wg.Add(1)
go func() {
defer wg.Done()
_, err := registry.DeleteCollection(testContext, nil, &metainternalversion.ListOptions{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}()
}
wg.Wait()
if _, err := registry.Get(testContext, podA.Name, &metav1.GetOptions{}); !errors.IsNotFound(err) {
t.Errorf("Unexpected error: %v", err)
}
if _, err := registry.Get(testContext, podB.Name, &metav1.GetOptions{}); !errors.IsNotFound(err) {
t.Errorf("Unexpected error: %v", err)
}
}
}
// Test whether objects deleted with DeleteCollection are correctly delivered
// to watchers.
func TestStoreDeleteCollectionWithWatch(t *testing.T) {
podA := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defer destroyFunc()
objCreated, err := registry.Create(testContext, podA, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
podCreated := objCreated.(*example.Pod)
watcher, err := registry.WatchPredicate(testContext, matchPodName("foo"), podCreated.ResourceVersion)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
defer watcher.Stop()
if _, err := registry.DeleteCollection(testContext, nil, &metainternalversion.ListOptions{}); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
got, open := <-watcher.ResultChan()
if !open {
t.Errorf("Unexpected channel close")
} else {
if got.Type != "DELETED" {
t.Errorf("Unexpected event type: %s", got.Type)
}
gotObject := got.Object.(*example.Pod)
gotObject.ResourceVersion = podCreated.ResourceVersion
if e, a := podCreated, gotObject; !reflect.DeepEqual(e, a) {
t.Errorf("Expected: %#v, got: %#v", e, a)
}
}
}
func TestStoreWatch(t *testing.T) {
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
noNamespaceContext := genericapirequest.NewContext()
table := map[string]struct {
selectPred storage.SelectionPredicate
context context.Context
}{
"single": {
selectPred: matchPodName("foo"),
},
"multi": {
selectPred: matchPodName("foo", "bar"),
},
"singleNoNamespace": {
selectPred: matchPodName("foo"),
context: noNamespaceContext,
},
}
for name, m := range table {
ctx := testContext
if m.context != nil {
ctx = m.context
}
podA := &example.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "test",
},
Spec: example.PodSpec{NodeName: "machine"},
}
destroyFunc, registry := NewTestGenericStoreRegistry(t)
wi, err := registry.WatchPredicate(ctx, m.selectPred, "0")
if err != nil {
t.Errorf("%v: unexpected error: %v", name, err)
} else {
obj, err := registry.Create(testContext, podA, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
if err != nil {
got, open := <-wi.ResultChan()
if !open {
t.Errorf("%v: unexpected channel close", name)
} else {
if e, a := obj, got.Object; !reflect.DeepEqual(e, a) {
t.Errorf("Expected %#v, got %#v", e, a)
}
}
}
wi.Stop()
}
destroyFunc()
}
}
func newTestGenericStoreRegistry(t *testing.T, scheme *runtime.Scheme, hasCacheEnabled bool) (factory.DestroyFunc, *Store) {
podPrefix := "/pods"
server, sc := etcdtesting.NewUnsecuredEtcd3TestClientServer(t)
strategy := &testRESTStrategy{scheme, names.SimpleNameGenerator, true, false, true}
sc.Codec = apitesting.TestStorageCodec(codecs, examplev1.SchemeGroupVersion)
s, dFunc, err := factory.Create(*sc)
if err != nil {
t.Fatalf("Error creating storage: %v", err)
}
destroyFunc := func() {
dFunc()
server.Terminate(t)
}
if hasCacheEnabled {
config := cacherstorage.Config{
CacheCapacity: 10,
Storage: s,
Versioner: etcdstorage.APIObjectVersioner{},
Type: &example.Pod{},
ResourcePrefix: podPrefix,
KeyFunc: func(obj runtime.Object) (string, error) { return storage.NoNamespaceKeyFunc(podPrefix, obj) },
GetAttrsFunc: getPodAttrs,
NewListFunc: func() runtime.Object { return &example.PodList{} },
Codec: sc.Codec,
}
cacher := cacherstorage.NewCacherFromConfig(config)
d := destroyFunc
s = cacher
destroyFunc = func() {
cacher.Stop()
d()
}
}
return destroyFunc, &Store{
NewFunc: func() runtime.Object { return &example.Pod{} },
NewListFunc: func() runtime.Object { return &example.PodList{} },
DefaultQualifiedResource: example.Resource("pods"),
CreateStrategy: strategy,
UpdateStrategy: strategy,
DeleteStrategy: strategy,
KeyRootFunc: func(ctx context.Context) string {
return podPrefix
},
KeyFunc: func(ctx context.Context, id string) (string, error) {
if _, ok := genericapirequest.NamespaceFrom(ctx); !ok {
return "", fmt.Errorf("namespace is required")
}
return path.Join(podPrefix, id), nil
},
ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*example.Pod).Name, nil },
PredicateFunc: func(label labels.Selector, field fields.Selector) storage.SelectionPredicate {
return storage.SelectionPredicate{
Label: label,
Field: field,
GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, bool, error) {
pod, ok := obj.(*example.Pod)
if !ok {
return nil, nil, false, fmt.Errorf("not a pod")
}
return labels.Set(pod.ObjectMeta.Labels), generic.ObjectMetaFieldsSet(&pod.ObjectMeta, true), pod.Initializers != nil, nil
},
}
},
Storage: DryRunnableStorage{Storage: s},
}
}
func TestFinalizeDelete(t *testing.T) {
// Verify that it returns the expected Status.
destroyFunc, s := NewTestGenericStoreRegistry(t)
defer destroyFunc()
obj := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "random-uid"},
}
result, err := s.finalizeDelete(genericapirequest.NewContext(), obj, false)
if err != nil {
t.Fatalf("unexpected err: %s", err)
}
returnedObj := result.(*metav1.Status)
expectedObj := &metav1.Status{
Status: metav1.StatusSuccess,
Details: &metav1.StatusDetails{
Name: "foo",
Group: s.DefaultQualifiedResource.Group,
Kind: s.DefaultQualifiedResource.Resource,
UID: "random-uid",
},
}
if !apiequality.Semantic.DeepEqual(expectedObj, returnedObj) {
t.Errorf("unexpected obj. expected %#v, got %#v", expectedObj, returnedObj)
}
}
func fakeRequestInfo(resource, apiGroup string) *genericapirequest.RequestInfo {
return &genericapirequest.RequestInfo{
IsResourceRequest: true,
Path: "/api/v1/test",
Verb: "test",
APIPrefix: "api",
APIGroup: apiGroup,
APIVersion: "v1",
Namespace: "",
Resource: resource,
Subresource: "",
Name: "",
Parts: []string{"test"},
}
}
func TestQualifiedResource(t *testing.T) {
podA := &example.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test"},
Spec: example.PodSpec{NodeName: "machine"},
}
qualifiedKind := "pod"
qualifiedGroup := "test"
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), "test")
testContext = genericapirequest.WithRequestInfo(testContext, fakeRequestInfo(qualifiedKind, qualifiedGroup))
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defer destroyFunc()
// update a non-exist object
_, _, err := registry.Update(testContext, podA.Name, rest.DefaultUpdatedObjectInfo(podA), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
if !errors.IsNotFound(err) {
t.Fatalf("Unexpected error: %v", err)
}
if !isQualifiedResource(err, qualifiedKind, qualifiedGroup) {
t.Fatalf("Unexpected error: %#v", err)
}
// get a non-exist object
_, err = registry.Get(testContext, podA.Name, &metav1.GetOptions{})
if !errors.IsNotFound(err) {
t.Fatalf("Unexpected error: %v", err)
}
if !isQualifiedResource(err, qualifiedKind, qualifiedGroup) {
t.Fatalf("Unexpected error: %#v", err)
}
// delete a non-exist object
_, _, err = registry.Delete(testContext, podA.Name, nil)
if !errors.IsNotFound(err) {
t.Fatalf("Unexpected error: %v", err)
}
if !isQualifiedResource(err, qualifiedKind, qualifiedGroup) {
t.Fatalf("Unexpected error: %#v", err)
}
// create a non-exist object
_, err = registry.Create(testContext, podA, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
if err != nil {
t.Fatal(err)
}
// create a exist object will fail
_, err = registry.Create(testContext, podA, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
if !errors.IsAlreadyExists(err) {
t.Fatalf("Unexpected error: %v", err)
}
if !isQualifiedResource(err, qualifiedKind, qualifiedGroup) {
t.Fatalf("Unexpected error: %#v", err)
}
}
func denyCreateValidation(obj runtime.Object) error {
return fmt.Errorf("admission denied")
}
func denyUpdateValidation(obj, old runtime.Object) error {
return fmt.Errorf("admission denied")
}
type fakeStrategy struct {
runtime.ObjectTyper
names.NameGenerator
}
func (fakeStrategy) DefaultGarbageCollectionPolicy(ctx context.Context) rest.GarbageCollectionPolicy {
appsv1beta1 := schema.GroupVersion{Group: "apps", Version: "v1beta1"}
appsv1beta2 := schema.GroupVersion{Group: "apps", Version: "v1beta2"}
extensionsv1beta1 := schema.GroupVersion{Group: "extensions", Version: "v1beta1"}
if requestInfo, found := genericapirequest.RequestInfoFrom(ctx); found {
groupVersion := schema.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion}
switch groupVersion {
case appsv1beta1, appsv1beta2, extensionsv1beta1:
// for back compatibility
return rest.OrphanDependents
default:
return rest.DeleteDependents
}
}
return rest.OrphanDependents
}
func TestDeletionFinalizersForGarbageCollection(t *testing.T) {
destroyFunc, registry := NewTestGenericStoreRegistry(t)
defer destroyFunc()
registry.DeleteStrategy = fakeStrategy{}
registry.EnableGarbageCollection = true
tests := []struct {
requestInfo genericapirequest.RequestInfo
desiredFinalizers []string
isNilRequestInfo bool
changed bool
}{
{
genericapirequest.RequestInfo{
APIGroup: "extensions",
APIVersion: "v1beta1",
},
[]string{metav1.FinalizerOrphanDependents},
false,
true,
},
{
genericapirequest.RequestInfo{
APIGroup: "apps",
APIVersion: "v1beta1",
},
[]string{metav1.FinalizerOrphanDependents},
false,
true,
},
{
genericapirequest.RequestInfo{
APIGroup: "apps",
APIVersion: "v1beta2",
},
[]string{metav1.FinalizerOrphanDependents},
false,
true,
},
{
genericapirequest.RequestInfo{
APIGroup: "apps",
APIVersion: "v1",
},
[]string{},
false,
false,
},
}
for _, test := range tests {
context := genericapirequest.NewContext()
if !test.isNilRequestInfo {
context = genericapirequest.WithRequestInfo(context, &test.requestInfo)
}
changed, finalizers := deletionFinalizersForGarbageCollection(context, registry, &example.ReplicaSet{}, &metav1.DeleteOptions{})
if !changed {
if test.changed {
t.Errorf("%s/%s: no new finalizers are added", test.requestInfo.APIGroup, test.requestInfo.APIVersion)
}
} else if !reflect.DeepEqual(finalizers, test.desiredFinalizers) {
t.Errorf("%s/%s: want %#v, got %#v", test.requestInfo.APIGroup, test.requestInfo.APIVersion,
test.desiredFinalizers, finalizers)
}
}
}