Merge pull request #66466 from apelisse/dry-run-struct

Automatic merge from submit-queue (batch tested with PRs 64815, 66823, 66473, 66466). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

dry-run: Use dry-runnable structure

Creates a structures that decides to either by-pass persistence and tries to reproduce the same behavior (without persistence), or just pass along to storage.

This is obviously not finished, I'm would like to get feedback on the direction, is this the direction we'd like to go?

**What this PR does / why we need it**:

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Fixes #

**Special notes for your reviewer**:

**Release note**:
```release-note
NONE
```
This commit is contained in:
Kubernetes Submit Queue 2018-08-03 18:03:07 -07:00 committed by GitHub
commit 929c8459c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 594 additions and 89 deletions

View File

@ -59,6 +59,7 @@ go_library(
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/storage:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/storage/errors:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/dryrun:go_default_library",
],
)

View File

@ -31,6 +31,7 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/storage"
storeerr "k8s.io/apiserver/pkg/storage/errors"
"k8s.io/apiserver/pkg/util/dryrun"
appsv1beta1 "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
appsv1beta2 "k8s.io/kubernetes/pkg/apis/apps/v1beta2"
"k8s.io/kubernetes/pkg/apis/autoscaling"
@ -171,7 +172,7 @@ func (r *RollbackREST) Create(ctx context.Context, obj runtime.Object, createVal
}
// Update the Deployment with information in DeploymentRollback to trigger rollback
err := r.rollbackDeployment(ctx, rollback.Name, &rollback.RollbackTo, rollback.UpdatedAnnotations)
err := r.rollbackDeployment(ctx, rollback.Name, &rollback.RollbackTo, rollback.UpdatedAnnotations, dryrun.IsDryRun(options.DryRun))
if err != nil {
return nil, err
}
@ -182,8 +183,8 @@ func (r *RollbackREST) Create(ctx context.Context, obj runtime.Object, createVal
}, nil
}
func (r *RollbackREST) rollbackDeployment(ctx context.Context, deploymentID string, config *extensions.RollbackConfig, annotations map[string]string) error {
if _, err := r.setDeploymentRollback(ctx, deploymentID, config, annotations); err != nil {
func (r *RollbackREST) rollbackDeployment(ctx context.Context, deploymentID string, config *extensions.RollbackConfig, annotations map[string]string, dryRun bool) error {
if _, err := r.setDeploymentRollback(ctx, deploymentID, config, annotations, dryRun); err != nil {
err = storeerr.InterpretGetError(err, extensions.Resource("deployments"), deploymentID)
err = storeerr.InterpretUpdateError(err, extensions.Resource("deployments"), deploymentID)
if _, ok := err.(*errors.StatusError); !ok {
@ -194,7 +195,7 @@ func (r *RollbackREST) rollbackDeployment(ctx context.Context, deploymentID stri
return nil
}
func (r *RollbackREST) setDeploymentRollback(ctx context.Context, deploymentID string, config *extensions.RollbackConfig, annotations map[string]string) (*extensions.Deployment, error) {
func (r *RollbackREST) setDeploymentRollback(ctx context.Context, deploymentID string, config *extensions.RollbackConfig, annotations map[string]string, dryRun bool) (*extensions.Deployment, error) {
dKey, err := r.store.KeyFunc(ctx, deploymentID)
if err != nil {
return nil, err
@ -214,7 +215,7 @@ func (r *RollbackREST) setDeploymentRollback(ctx context.Context, deploymentID s
d.Spec.RollbackTo = config
finalDeployment = d
return d, nil
}))
}), dryRun)
return finalDeployment, err
}

View File

@ -205,7 +205,7 @@ func TestScaleGet(t *testing.T) {
var deployment extensions.Deployment
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace)
key := "/deployments/" + namespace + "/" + name
if err := storage.Deployment.Storage.Create(ctx, key, &validDeployment, &deployment, 0); err != nil {
if err := storage.Deployment.Storage.Create(ctx, key, &validDeployment, &deployment, 0, false); err != nil {
t.Fatalf("error setting new deployment (key: %s) %v: %v", key, validDeployment, err)
}
@ -246,7 +246,7 @@ func TestScaleUpdate(t *testing.T) {
var deployment extensions.Deployment
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace)
key := "/deployments/" + namespace + "/" + name
if err := storage.Deployment.Storage.Create(ctx, key, &validDeployment, &deployment, 0); err != nil {
if err := storage.Deployment.Storage.Create(ctx, key, &validDeployment, &deployment, 0, false); err != nil {
t.Fatalf("error setting new deployment (key: %s) %v: %v", key, validDeployment, err)
}
replicas := int32(12)
@ -283,7 +283,7 @@ func TestStatusUpdate(t *testing.T) {
defer storage.Deployment.Store.DestroyFunc()
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace)
key := "/deployments/" + namespace + "/" + name
if err := storage.Deployment.Storage.Create(ctx, key, &validDeployment, nil, 0); err != nil {
if err := storage.Deployment.Storage.Create(ctx, key, &validDeployment, nil, 0, false); err != nil {
t.Fatalf("unexpected error: %v", err)
}
update := extensions.Deployment{

View File

@ -260,7 +260,7 @@ func TestScaleGet(t *testing.T) {
var rs extensions.ReplicaSet
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
key := "/replicasets/" + metav1.NamespaceDefault + "/" + name
if err := storage.ReplicaSet.Storage.Create(ctx, key, &validReplicaSet, &rs, 0); err != nil {
if err := storage.ReplicaSet.Storage.Create(ctx, key, &validReplicaSet, &rs, 0, false); err != nil {
t.Fatalf("error setting new replica set (key: %s) %v: %v", key, validReplicaSet, err)
}
@ -305,7 +305,7 @@ func TestScaleUpdate(t *testing.T) {
var rs extensions.ReplicaSet
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
key := "/replicasets/" + metav1.NamespaceDefault + "/" + name
if err := storage.ReplicaSet.Storage.Create(ctx, key, &validReplicaSet, &rs, 0); err != nil {
if err := storage.ReplicaSet.Storage.Create(ctx, key, &validReplicaSet, &rs, 0, false); err != nil {
t.Fatalf("error setting new replica set (key: %s) %v: %v", key, validReplicaSet, err)
}
replicas := 12
@ -347,7 +347,7 @@ func TestStatusUpdate(t *testing.T) {
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
key := "/replicasets/" + metav1.NamespaceDefault + "/foo"
if err := storage.ReplicaSet.Storage.Create(ctx, key, &validReplicaSet, nil, 0); err != nil {
if err := storage.ReplicaSet.Storage.Create(ctx, key, &validReplicaSet, nil, 0, false); err != nil {
t.Fatalf("unexpected error: %v", err)
}
update := extensions.ReplicaSet{

View File

@ -102,7 +102,7 @@ func TestStatusUpdate(t *testing.T) {
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
key := "/statefulsets/" + metav1.NamespaceDefault + "/foo"
validStatefulSet := validNewStatefulSet()
if err := storage.StatefulSet.Storage.Create(ctx, key, validStatefulSet, nil, 0); err != nil {
if err := storage.StatefulSet.Storage.Create(ctx, key, validStatefulSet, nil, 0, false); err != nil {
t.Fatalf("unexpected error: %v", err)
}
update := apps.StatefulSet{
@ -210,7 +210,7 @@ func TestScaleGet(t *testing.T) {
var sts apps.StatefulSet
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
key := "/statefulsets/" + metav1.NamespaceDefault + "/" + name
if err := storage.StatefulSet.Storage.Create(ctx, key, &validStatefulSet, &sts, 0); err != nil {
if err := storage.StatefulSet.Storage.Create(ctx, key, &validStatefulSet, &sts, 0, false); err != nil {
t.Fatalf("error setting new statefulset (key: %s) %v: %v", key, validStatefulSet, err)
}
@ -254,7 +254,7 @@ func TestScaleUpdate(t *testing.T) {
var sts apps.StatefulSet
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
key := "/statefulsets/" + metav1.NamespaceDefault + "/" + name
if err := storage.StatefulSet.Storage.Create(ctx, key, &validStatefulSet, &sts, 0); err != nil {
if err := storage.StatefulSet.Storage.Create(ctx, key, &validStatefulSet, &sts, 0, false); err != nil {
t.Fatalf("error setting new statefulset (key: %s) %v: %v", key, validStatefulSet, err)
}
replicas := 12

View File

@ -177,7 +177,7 @@ func TestUpdateStatus(t *testing.T) {
ctx := genericapirequest.NewDefaultContext()
key, _ := storage.KeyFunc(ctx, "foo")
autoscalerStart := validNewHorizontalPodAutoscaler("foo")
err := storage.Storage.Create(ctx, key, autoscalerStart, nil, 0)
err := storage.Storage.Create(ctx, key, autoscalerStart, nil, 0, false)
if err != nil {
t.Errorf("unexpected error: %v", err)
}

View File

@ -45,6 +45,7 @@ go_library(
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/storage:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/storage/errors:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/dryrun:go_default_library",
],
)

View File

@ -31,6 +31,7 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/storage"
storageerr "k8s.io/apiserver/pkg/storage/errors"
"k8s.io/apiserver/pkg/util/dryrun"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/printers"
@ -190,6 +191,7 @@ func (r *REST) Delete(ctx context.Context, name string, options *metav1.DeleteOp
}
return existingNamespace, nil
}),
dryrun.IsDryRun(options.DryRun),
)
if err != nil {

View File

@ -156,7 +156,7 @@ func TestDeleteNamespaceWithIncompleteFinalizers(t *testing.T) {
},
Status: api.NamespaceStatus{Phase: api.NamespaceActive},
}
if err := storage.store.Storage.Create(ctx, key, namespace, nil, 0); err != nil {
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 {
@ -181,7 +181,7 @@ func TestDeleteNamespaceWithCompleteFinalizers(t *testing.T) {
},
Status: api.NamespaceStatus{Phase: api.NamespaceActive},
}
if err := storage.store.Storage.Create(ctx, key, namespace, nil, 0); err != nil {
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 {

View File

@ -169,7 +169,7 @@ func TestUpdateStatus(t *testing.T) {
ctx := genericapirequest.NewContext()
key, _ := storage.KeyFunc(ctx, "foo")
pvStart := validNewPersistentVolume("foo")
err := storage.Storage.Create(ctx, key, pvStart, nil, 0)
err := storage.Storage.Create(ctx, key, pvStart, nil, 0, false)
if err != nil {
t.Errorf("unexpected error: %v", err)
}

View File

@ -159,7 +159,7 @@ func TestUpdateStatus(t *testing.T) {
key, _ := storage.KeyFunc(ctx, "foo")
pvcStart := validNewPersistentVolumeClaim("foo", metav1.NamespaceDefault)
err := storage.Storage.Create(ctx, key, pvcStart, nil, 0)
err := storage.Storage.Create(ctx, key, pvcStart, nil, 0, false)
pvc := &api.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{

View File

@ -33,7 +33,7 @@ func TestPodLogValidates(t *testing.T) {
s, destroyFunc := generic.NewRawStorage(config)
defer destroyFunc()
store := &genericregistry.Store{
Storage: s,
Storage: genericregistry.DryRunnableStorage{Storage: s},
}
logRest := &LogREST{Store: store, KubeletConn: nil}

View File

@ -26,6 +26,7 @@ go_test(
"//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/features:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/registry/generic:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/registry/generic/registry: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:go_default_library",
@ -66,6 +67,7 @@ go_library(
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/storage:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/storage/errors:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/dryrun:go_default_library",
"//staging/src/k8s.io/client-go/util/retry:go_default_library",
],
)

View File

@ -30,6 +30,7 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/storage"
storeerr "k8s.io/apiserver/pkg/storage/errors"
"k8s.io/apiserver/pkg/util/dryrun"
podutil "k8s.io/kubernetes/pkg/api/pod"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/validation"
@ -148,7 +149,7 @@ func (r *BindingREST) Create(ctx context.Context, obj runtime.Object, createVali
return nil, errs.ToAggregate()
}
err = r.assignPod(ctx, binding.Name, binding.Target.Name, binding.Annotations)
err = r.assignPod(ctx, binding.Name, binding.Target.Name, binding.Annotations, dryrun.IsDryRun(options.DryRun))
out = &metav1.Status{Status: metav1.StatusSuccess}
return
}
@ -156,7 +157,7 @@ func (r *BindingREST) Create(ctx context.Context, obj runtime.Object, createVali
// setPodHostAndAnnotations sets the given pod's host to 'machine' if and only if it was
// previously 'oldMachine' and merges the provided annotations with those of the pod.
// Returns the current state of the pod, or an error.
func (r *BindingREST) setPodHostAndAnnotations(ctx context.Context, podID, oldMachine, machine string, annotations map[string]string) (finalPod *api.Pod, err error) {
func (r *BindingREST) setPodHostAndAnnotations(ctx context.Context, podID, oldMachine, machine string, annotations map[string]string, dryRun bool) (finalPod *api.Pod, err error) {
podKey, err := r.store.KeyFunc(ctx, podID)
if err != nil {
return nil, err
@ -185,13 +186,13 @@ func (r *BindingREST) setPodHostAndAnnotations(ctx context.Context, podID, oldMa
})
finalPod = pod
return pod, nil
}))
}), dryRun)
return finalPod, err
}
// assignPod assigns the given pod to the given machine.
func (r *BindingREST) assignPod(ctx context.Context, podID string, machine string, annotations map[string]string) (err error) {
if _, err = r.setPodHostAndAnnotations(ctx, podID, "", machine, annotations); err != nil {
func (r *BindingREST) assignPod(ctx context.Context, podID string, machine string, annotations map[string]string, dryRun bool) (err error) {
if _, err = r.setPodHostAndAnnotations(ctx, podID, "", machine, annotations, dryRun); err != nil {
err = storeerr.InterpretGetError(err, api.Resource("pods"), podID)
err = storeerr.InterpretUpdateError(err, api.Resource("pods"), podID)
if _, ok := err.(*errors.StatusError); !ok {

View File

@ -34,6 +34,7 @@ import (
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/storage"
@ -170,7 +171,7 @@ func newFailDeleteStorage(t *testing.T, called *bool) (*REST, *etcdtesting.EtcdT
ResourcePrefix: "pods",
}
storage := NewStorage(restOptions, nil, nil, nil)
storage.Pod.Store.Storage = FailDeletionStorage{storage.Pod.Store.Storage, called}
storage.Pod.Store.Storage = genericregistry.DryRunnableStorage{Storage: FailDeletionStorage{storage.Pod.Store.Storage.Storage, called}}
return storage.Pod, server
}
@ -340,7 +341,7 @@ func TestResourceLocation(t *testing.T) {
for _, tc := range testCases {
storage, _, _, server := newStorage(t)
key, _ := storage.KeyFunc(ctx, tc.pod.Name)
if err := storage.Storage.Create(ctx, key, &tc.pod, nil, 0); err != nil {
if err := storage.Storage.Create(ctx, key, &tc.pod, nil, 0, false); err != nil {
t.Fatalf("unexpected error: %v", err)
}
@ -825,7 +826,7 @@ func TestEtcdUpdateScheduled(t *testing.T) {
SecurityContext: &api.PodSecurityContext{},
SchedulerName: api.DefaultSchedulerName,
},
}, nil, 1)
}, nil, 1, false)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
@ -897,7 +898,7 @@ func TestEtcdUpdateStatus(t *testing.T) {
SchedulerName: api.DefaultSchedulerName,
},
}
err := storage.Storage.Create(ctx, key, &podStart, nil, 0)
err := storage.Storage.Create(ctx, key, &podStart, nil, 0, false)
if err != nil {
t.Errorf("unexpected error: %v", err)
}

View File

@ -162,7 +162,7 @@ func TestUpdateStatus(t *testing.T) {
key, _ := storage.KeyFunc(ctx, "foo")
resourcequotaStart := validNewResourceQuota()
err := storage.Storage.Create(ctx, key, resourcequotaStart, nil, 0)
err := storage.Storage.Create(ctx, key, resourcequotaStart, nil, 0, false)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

View File

@ -174,7 +174,7 @@ func NewTestRESTWithPods(t *testing.T, endpoints *api.EndpointsList, pods *api.P
ctx := genericapirequest.NewDefaultContext()
for ix := range pods.Items {
key, _ := podStorage.Pod.KeyFunc(ctx, pods.Items[ix].Name)
if err := podStorage.Pod.Storage.Create(ctx, key, &pods.Items[ix], nil, 0); err != nil {
if err := podStorage.Pod.Storage.Create(ctx, key, &pods.Items[ix], nil, 0, false); err != nil {
t.Fatalf("Couldn't create pod: %v", err)
}
}
@ -188,7 +188,7 @@ func NewTestRESTWithPods(t *testing.T, endpoints *api.EndpointsList, pods *api.P
ctx := genericapirequest.NewDefaultContext()
for ix := range endpoints.Items {
key, _ := endpointStorage.KeyFunc(ctx, endpoints.Items[ix].Name)
if err := endpointStorage.Store.Storage.Create(ctx, key, &endpoints.Items[ix], nil, 0); err != nil {
if err := endpointStorage.Store.Storage.Create(ctx, key, &endpoints.Items[ix], nil, 0, false); err != nil {
t.Fatalf("Couldn't create endpoint: %v", err)
}
}

View File

@ -78,7 +78,7 @@ func TestStatusUpdate(t *testing.T) {
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
key := "/poddisruptionbudgets/" + metav1.NamespaceDefault + "/foo"
validPodDisruptionBudget := validNewPodDisruptionBudget()
if err := storage.Storage.Create(ctx, key, validPodDisruptionBudget, nil, 0); err != nil {
if err := storage.Storage.Create(ctx, key, validPodDisruptionBudget, nil, 0, false); err != nil {
t.Fatalf("unexpected error: %v", err)
}

View File

@ -114,7 +114,7 @@ func TestDeleteSystemPriorityClass(t *testing.T) {
key := "test/system-node-critical"
ctx := genericapirequest.NewContext()
pc := scheduling.SystemPriorityClasses()[0]
if err := storage.Store.Storage.Create(ctx, key, pc, nil, 0); err != nil {
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 {

View File

@ -93,6 +93,7 @@ filegroup(
"//staging/src/k8s.io/apiserver/pkg/registry:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/server:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/storage:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/util/dryrun:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/util/feature:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/util/flag:all-srcs",
"//staging/src/k8s.io/apiserver/pkg/util/flushwriter:all-srcs",

View File

@ -1462,6 +1462,10 @@
"ImportPath": "k8s.io/apiserver/pkg/storage/value",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apiserver/pkg/util/dryrun",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apiserver/pkg/util/feature",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
@ -2282,6 +2286,10 @@
"ImportPath": "k8s.io/apiserver/pkg/storage/storagebackend",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apiserver/pkg/util/dryrun",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apiserver/pkg/util/feature",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

View File

@ -254,7 +254,7 @@ func TestColumns(t *testing.T) {
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
key := "/noxus/" + metav1.NamespaceDefault + "/foo"
validCustomResource := validNewCustomResource()
if err := storage.CustomResource.Storage.Create(ctx, key, validCustomResource, nil, 0); err != nil {
if err := storage.CustomResource.Storage.Create(ctx, key, validCustomResource, nil, 0, false); err != nil {
t.Fatalf("unexpected error: %v", err)
}
@ -324,7 +324,7 @@ func TestStatusUpdate(t *testing.T) {
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
key := "/noxus/" + metav1.NamespaceDefault + "/foo"
validCustomResource := validNewCustomResource()
if err := storage.CustomResource.Storage.Create(ctx, key, validCustomResource, nil, 0); err != nil {
if err := storage.CustomResource.Storage.Create(ctx, key, validCustomResource, nil, 0, false); err != nil {
t.Fatalf("unexpected error: %v", err)
}
@ -375,7 +375,7 @@ func TestScaleGet(t *testing.T) {
var cr unstructured.Unstructured
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
key := "/noxus/" + metav1.NamespaceDefault + "/" + name
if err := storage.CustomResource.Storage.Create(ctx, key, &validCustomResource, &cr, 0); err != nil {
if err := storage.CustomResource.Storage.Create(ctx, key, &validCustomResource, &cr, 0, false); err != nil {
t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, validCustomResource, err)
}
@ -415,7 +415,7 @@ func TestScaleGetWithoutSpecReplicas(t *testing.T) {
key := "/noxus/" + metav1.NamespaceDefault + "/" + name
withoutSpecReplicas := validCustomResource.DeepCopy()
unstructured.RemoveNestedField(withoutSpecReplicas.Object, "spec", "replicas")
if err := storage.CustomResource.Storage.Create(ctx, key, withoutSpecReplicas, &cr, 0); err != nil {
if err := storage.CustomResource.Storage.Create(ctx, key, withoutSpecReplicas, &cr, 0, false); err != nil {
t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, withoutSpecReplicas, err)
}
@ -438,7 +438,7 @@ func TestScaleUpdate(t *testing.T) {
var cr unstructured.Unstructured
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
key := "/noxus/" + metav1.NamespaceDefault + "/" + name
if err := storage.CustomResource.Storage.Create(ctx, key, &validCustomResource, &cr, 0); err != nil {
if err := storage.CustomResource.Storage.Create(ctx, key, &validCustomResource, &cr, 0, false); err != nil {
t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, validCustomResource, err)
}
@ -492,7 +492,7 @@ func TestScaleUpdateWithoutSpecReplicas(t *testing.T) {
key := "/noxus/" + metav1.NamespaceDefault + "/" + name
withoutSpecReplicas := validCustomResource.DeepCopy()
unstructured.RemoveNestedField(withoutSpecReplicas.Object, "spec", "replicas")
if err := storage.CustomResource.Storage.Create(ctx, key, withoutSpecReplicas, &cr, 0); err != nil {
if err := storage.CustomResource.Storage.Create(ctx, key, withoutSpecReplicas, &cr, 0, false); err != nil {
t.Fatalf("error setting new custom resource (key: %s) %v: %v", key, withoutSpecReplicas, err)
}

View File

@ -30,6 +30,7 @@ go_library(
"//staging/src/k8s.io/apiserver/pkg/storage:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/storage/errors:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/storage/names:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/dryrun:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
],
)

View File

@ -29,6 +29,7 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/storage"
storageerr "k8s.io/apiserver/pkg/storage/errors"
"k8s.io/apiserver/pkg/util/dryrun"
)
// rest implements a RESTStorage for API services against etcd
@ -129,6 +130,7 @@ func (r *REST) Delete(ctx context.Context, name string, options *metav1.DeleteOp
})
return existingCRD, nil
}),
dryrun.IsDryRun(options.DryRun),
)
if err != nil {

View File

@ -10,6 +10,7 @@ go_test(
name = "go_default_test",
srcs = [
"decorated_watcher_test.go",
"dryrun_test.go",
"store_test.go",
],
embed = [":go_default_library"],
@ -20,12 +21,14 @@ go_test(
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/fields:go_default_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/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/selection:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
@ -53,6 +56,7 @@ go_library(
srcs = [
"decorated_watcher.go",
"doc.go",
"dryrun.go",
"storage_factory.go",
"store.go",
],
@ -84,6 +88,7 @@ go_library(
"//staging/src/k8s.io/apiserver/pkg/storage/etcd/metrics:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/storage/storagebackend/factory:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/dryrun:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
],
)

View File

@ -0,0 +1,117 @@
/*
Copyright 2018 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"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/apiserver/pkg/storage"
)
type DryRunnableStorage struct {
Storage storage.Interface
Codec runtime.Codec
}
func (s *DryRunnableStorage) Versioner() storage.Versioner {
return s.Storage.Versioner()
}
func (s *DryRunnableStorage) Create(ctx context.Context, key string, obj, out runtime.Object, ttl uint64, dryRun bool) error {
if dryRun {
if err := s.Storage.Get(ctx, key, "", out, false); err == nil {
return storage.NewKeyExistsError(key, 0)
}
s.copyInto(obj, out)
return nil
}
return s.Storage.Create(ctx, key, obj, out, ttl)
}
func (s *DryRunnableStorage) Delete(ctx context.Context, key string, out runtime.Object, preconditions *storage.Preconditions, dryRun bool) error {
if dryRun {
if err := s.Storage.Get(ctx, key, "", out, false); err != nil {
return err
}
return preconditions.Check(key, out)
}
return s.Storage.Delete(ctx, key, out, preconditions)
}
func (s *DryRunnableStorage) Watch(ctx context.Context, key string, resourceVersion string, p storage.SelectionPredicate) (watch.Interface, error) {
return s.Storage.Watch(ctx, key, resourceVersion, p)
}
func (s *DryRunnableStorage) WatchList(ctx context.Context, key string, resourceVersion string, p storage.SelectionPredicate) (watch.Interface, error) {
return s.Storage.WatchList(ctx, key, resourceVersion, p)
}
func (s *DryRunnableStorage) Get(ctx context.Context, key string, resourceVersion string, objPtr runtime.Object, ignoreNotFound bool) error {
return s.Storage.Get(ctx, key, resourceVersion, objPtr, ignoreNotFound)
}
func (s *DryRunnableStorage) GetToList(ctx context.Context, key string, resourceVersion string, p storage.SelectionPredicate, listObj runtime.Object) error {
return s.Storage.GetToList(ctx, key, resourceVersion, p, listObj)
}
func (s *DryRunnableStorage) List(ctx context.Context, key string, resourceVersion string, p storage.SelectionPredicate, listObj runtime.Object) error {
return s.Storage.List(ctx, key, resourceVersion, p, listObj)
}
func (s *DryRunnableStorage) GuaranteedUpdate(
ctx context.Context, key string, ptrToType runtime.Object, ignoreNotFound bool,
preconditions *storage.Preconditions, tryUpdate storage.UpdateFunc, dryRun bool, suggestion ...runtime.Object) error {
if dryRun {
err := s.Storage.Get(ctx, key, "", ptrToType, ignoreNotFound)
if err != nil {
return err
}
err = preconditions.Check(key, ptrToType)
if err != nil {
return err
}
rev, err := s.Versioner().ObjectResourceVersion(ptrToType)
out, _, err := tryUpdate(ptrToType, storage.ResponseMeta{ResourceVersion: rev})
if err != nil {
return err
}
s.copyInto(out, ptrToType)
return nil
}
return s.Storage.GuaranteedUpdate(ctx, key, ptrToType, ignoreNotFound, preconditions, tryUpdate, suggestion...)
}
func (s *DryRunnableStorage) Count(key string) (int64, error) {
return s.Storage.Count(key)
}
func (s *DryRunnableStorage) copyInto(in, out runtime.Object) error {
var data []byte
data, err := runtime.Encode(s.Codec, in)
if err != nil {
return err
}
_, _, err = s.Codec.Decode(data, nil, out)
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,304 @@
/*
Copyright 2018 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"
"encoding/json"
"errors"
"fmt"
"reflect"
"testing"
"k8s.io/apimachinery/pkg/api/apitesting"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
examplev1 "k8s.io/apiserver/pkg/apis/example/v1"
"k8s.io/apiserver/pkg/storage"
etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing"
"k8s.io/apiserver/pkg/storage/storagebackend/factory"
)
func NewDryRunnableTestStorage(t *testing.T) (DryRunnableStorage, func()) {
server, sc := etcdtesting.NewUnsecuredEtcd3TestClientServer(t)
sc.Codec = apitesting.TestStorageCodec(codecs, examplev1.SchemeGroupVersion)
s, destroy, err := factory.Create(*sc)
if err != nil {
t.Fatalf("Error creating storage: %v", err)
}
return DryRunnableStorage{Storage: s, Codec: sc.Codec}, func() {
destroy()
server.Terminate(t)
}
}
func UnstructuredOrDie(j string) *unstructured.Unstructured {
m := map[string]interface{}{}
err := json.Unmarshal([]byte(j), &m)
if err != nil {
panic(fmt.Errorf("Failed to unmarshal into Unstructured: %v", err))
}
return &unstructured.Unstructured{Object: m}
}
func TestDryRunCreateDoesntCreate(t *testing.T) {
s, destroy := NewDryRunnableTestStorage(t)
defer destroy()
obj := UnstructuredOrDie(`{"kind": "Pod"}`)
out := UnstructuredOrDie(`{}`)
err := s.Create(context.Background(), "key", obj, out, 0, true)
if err != nil {
t.Fatalf("Failed to create new dry-run object: %v", err)
}
err = s.Get(context.Background(), "key", "", out, false)
if e, ok := err.(*storage.StorageError); !ok || e.Code != storage.ErrCodeKeyNotFound {
t.Errorf("Expected key to be not found, error: %v", err)
}
}
func TestDryRunCreateReturnsObject(t *testing.T) {
s, destroy := NewDryRunnableTestStorage(t)
defer destroy()
obj := UnstructuredOrDie(`{"kind": "Pod"}`)
out := UnstructuredOrDie(`{}`)
err := s.Create(context.Background(), "key", obj, out, 0, true)
if err != nil {
t.Fatalf("Failed to create new dry-run object: %v", err)
}
if !reflect.DeepEqual(obj, out) {
t.Errorf("Returned object different from input object:\nExpected: %v\nGot: %v", obj, out)
}
}
func TestDryRunCreateExistingObjectFails(t *testing.T) {
s, destroy := NewDryRunnableTestStorage(t)
defer destroy()
obj := UnstructuredOrDie(`{"kind": "Pod"}`)
out := UnstructuredOrDie(`{}`)
err := s.Create(context.Background(), "key", obj, out, 0, false)
if err != nil {
t.Fatalf("Failed to create new object: %v", err)
}
err = s.Create(context.Background(), "key", obj, out, 0, true)
if e, ok := err.(*storage.StorageError); !ok || e.Code != storage.ErrCodeKeyExists {
t.Errorf("Expected KeyExists error: %v", err)
}
}
func TestDryRunUpdateMissingObjectFails(t *testing.T) {
s, destroy := NewDryRunnableTestStorage(t)
defer destroy()
obj := UnstructuredOrDie(`{"kind": "Pod"}`)
updateFunc := func(input runtime.Object, res storage.ResponseMeta) (output runtime.Object, ttl *uint64, err error) {
return input, nil, errors.New("UpdateFunction shouldn't be called")
}
err := s.GuaranteedUpdate(context.Background(), "key", obj, false, nil, updateFunc, true)
if e, ok := err.(*storage.StorageError); !ok || e.Code != storage.ErrCodeKeyNotFound {
t.Errorf("Expected key to be not found, error: %v", err)
}
}
func TestDryRunUpdatePreconditions(t *testing.T) {
s, destroy := NewDryRunnableTestStorage(t)
defer destroy()
obj := UnstructuredOrDie(`{"kind": "Pod", "metadata": {"uid": "my-uid"}}`)
out := UnstructuredOrDie(`{}`)
err := s.Create(context.Background(), "key", obj, out, 0, false)
if err != nil {
t.Fatalf("Failed to create new object: %v", err)
}
updateFunc := func(input runtime.Object, res storage.ResponseMeta) (output runtime.Object, ttl *uint64, err error) {
u, ok := input.(*unstructured.Unstructured)
if !ok {
return input, nil, errors.New("Input object is not unstructured")
}
unstructured.SetNestedField(u.Object, "value", "field")
return u, nil, nil
}
wrongID := types.UID("wrong-uid")
myID := types.UID("my-uid")
err = s.GuaranteedUpdate(context.Background(), "key", obj, false, &storage.Preconditions{UID: &wrongID}, updateFunc, true)
if e, ok := err.(*storage.StorageError); !ok || e.Code != storage.ErrCodeInvalidObj {
t.Errorf("Expected invalid object, error: %v", err)
}
err = s.GuaranteedUpdate(context.Background(), "key", obj, false, &storage.Preconditions{UID: &myID}, updateFunc, true)
if err != nil {
t.Fatalf("Failed to update with valid precondition: %v", err)
}
}
func TestDryRunUpdateDoesntUpdate(t *testing.T) {
s, destroy := NewDryRunnableTestStorage(t)
defer destroy()
obj := UnstructuredOrDie(`{"kind": "Pod"}`)
created := UnstructuredOrDie(`{}`)
err := s.Create(context.Background(), "key", obj, created, 0, false)
if err != nil {
t.Fatalf("Failed to create new object: %v", err)
}
updateFunc := func(input runtime.Object, res storage.ResponseMeta) (output runtime.Object, ttl *uint64, err error) {
u, ok := input.(*unstructured.Unstructured)
if !ok {
return input, nil, errors.New("Input object is not unstructured")
}
unstructured.SetNestedField(u.Object, "value", "field")
return u, nil, nil
}
err = s.GuaranteedUpdate(context.Background(), "key", obj, false, nil, updateFunc, true)
if err != nil {
t.Fatalf("Failed to dry-run update: %v", err)
}
out := UnstructuredOrDie(`{}`)
err = s.Get(context.Background(), "key", "", out, false)
if !reflect.DeepEqual(created, out) {
t.Fatalf("Returned object %q different from expected %q", created, out)
}
}
func TestDryRunUpdateReturnsObject(t *testing.T) {
s, destroy := NewDryRunnableTestStorage(t)
defer destroy()
obj := UnstructuredOrDie(`{"kind": "Pod"}`)
out := UnstructuredOrDie(`{}`)
err := s.Create(context.Background(), "key", obj, out, 0, false)
if err != nil {
t.Fatalf("Failed to create new object: %v", err)
}
updateFunc := func(input runtime.Object, res storage.ResponseMeta) (output runtime.Object, ttl *uint64, err error) {
u, ok := input.(*unstructured.Unstructured)
if !ok {
return input, nil, errors.New("Input object is not unstructured")
}
unstructured.SetNestedField(u.Object, "value", "field")
return u, nil, nil
}
err = s.GuaranteedUpdate(context.Background(), "key", obj, false, nil, updateFunc, true)
if err != nil {
t.Fatalf("Failed to dry-run update: %v", err)
}
out = UnstructuredOrDie(`{"field": "value", "kind": "Pod", "metadata": {"resourceVersion": "2", "selfLink": ""}}`)
if !reflect.DeepEqual(obj, out) {
t.Fatalf("Returned object %#v different from expected %#v", obj, out)
}
}
func TestDryRunDeleteDoesntDelete(t *testing.T) {
s, destroy := NewDryRunnableTestStorage(t)
defer destroy()
obj := UnstructuredOrDie(`{"kind": "Pod"}`)
out := UnstructuredOrDie(`{}`)
err := s.Create(context.Background(), "key", obj, out, 0, false)
if err != nil {
t.Fatalf("Failed to create new object: %v", err)
}
err = s.Delete(context.Background(), "key", out, nil, true)
if err != nil {
t.Fatalf("Failed to dry-run delete the object: %v", err)
}
err = s.Get(context.Background(), "key", "", out, false)
if err != nil {
t.Fatalf("Failed to retrieve dry-run deleted object: %v", err)
}
}
func TestDryRunDeleteMissingObjectFails(t *testing.T) {
s, destroy := NewDryRunnableTestStorage(t)
defer destroy()
out := UnstructuredOrDie(`{}`)
err := s.Delete(context.Background(), "key", out, nil, true)
if e, ok := err.(*storage.StorageError); !ok || e.Code != storage.ErrCodeKeyNotFound {
t.Errorf("Expected key to be not found, error: %v", err)
}
}
func TestDryRunDeleteReturnsObject(t *testing.T) {
s, destroy := NewDryRunnableTestStorage(t)
defer destroy()
obj := UnstructuredOrDie(`{"kind": "Pod"}`)
out := UnstructuredOrDie(`{}`)
err := s.Create(context.Background(), "key", obj, out, 0, false)
if err != nil {
t.Fatalf("Failed to create new object: %v", err)
}
out = UnstructuredOrDie(`{}`)
expected := UnstructuredOrDie(`{"kind": "Pod", "metadata": {"resourceVersion": "2", "selfLink": ""}}`)
err = s.Delete(context.Background(), "key", out, nil, true)
if err != nil {
t.Fatalf("Failed to delete with valid precondition: %v", err)
}
if !reflect.DeepEqual(expected, out) {
t.Fatalf("Returned object %q doesn't match expected: %q", out, expected)
}
}
func TestDryRunDeletePreconditions(t *testing.T) {
s, destroy := NewDryRunnableTestStorage(t)
defer destroy()
obj := UnstructuredOrDie(`{"kind": "Pod", "metadata": {"uid": "my-uid"}}`)
out := UnstructuredOrDie(`{}`)
err := s.Create(context.Background(), "key", obj, out, 0, false)
if err != nil {
t.Fatalf("Failed to create new object: %v", err)
}
wrongID := types.UID("wrong-uid")
myID := types.UID("my-uid")
err = s.Delete(context.Background(), "key", out, &storage.Preconditions{UID: &wrongID}, true)
if e, ok := err.(*storage.StorageError); !ok || e.Code != storage.ErrCodeInvalidObj {
t.Errorf("Expected invalid object, error: %v", err)
}
err = s.Delete(context.Background(), "key", out, &storage.Preconditions{UID: &myID}, true)
if err != nil {
t.Fatalf("Failed to delete with valid precondition: %v", err)
}
}

View File

@ -45,6 +45,7 @@ import (
"k8s.io/apiserver/pkg/storage"
storeerr "k8s.io/apiserver/pkg/storage/errors"
"k8s.io/apiserver/pkg/storage/etcd/metrics"
"k8s.io/apiserver/pkg/util/dryrun"
"github.com/golang/glog"
)
@ -172,8 +173,10 @@ type Store struct {
// of items into tabular output. If unset, the default will be used.
TableConvertor rest.TableConvertor
// Storage is the interface for the underlying storage for the resource.
Storage storage.Interface
// Storage is the interface for the underlying storage for the
// resource. It is wrapped into a "DryRunnableStorage" that will
// either pass-through or simply dry-run.
Storage DryRunnableStorage
// Called to cleanup clients used by the underlying Storage; optional.
DestroyFunc func()
}
@ -348,7 +351,7 @@ func (e *Store) Create(ctx context.Context, obj runtime.Object, createValidation
return nil, err
}
out := e.NewFunc()
if err := e.Storage.Create(ctx, key, obj, out, ttl); err != nil {
if err := e.Storage.Create(ctx, key, obj, out, ttl, dryrun.IsDryRun(options.DryRun)); err != nil {
err = storeerr.InterpretCreateError(err, qualifiedResource, name)
err = rest.CheckGeneratedNameError(e.CreateStrategy, err, obj)
if !kubeerr.IsAlreadyExists(err) {
@ -496,10 +499,10 @@ func (e *Store) shouldDeleteForFailedInitialization(ctx context.Context, obj run
// deleteWithoutFinalizers handles deleting an object ignoring its finalizer list.
// Used for objects that are either been finalized or have never initialized.
func (e *Store) deleteWithoutFinalizers(ctx context.Context, name, key string, obj runtime.Object, preconditions *storage.Preconditions) (runtime.Object, bool, error) {
func (e *Store) deleteWithoutFinalizers(ctx context.Context, name, key string, obj runtime.Object, preconditions *storage.Preconditions, dryRun bool) (runtime.Object, bool, error) {
out := e.NewFunc()
glog.V(6).Infof("going to delete %s from registry, triggered by update", name)
if err := e.Storage.Delete(ctx, key, out, preconditions); err != nil {
if err := e.Storage.Delete(ctx, key, out, preconditions, dryRun); err != nil {
// Deletion is racy, i.e., there could be multiple update
// requests to remove all finalizers from the object, so we
// ignore the NotFound error.
@ -633,12 +636,12 @@ func (e *Store) Update(ctx context.Context, name string, objInfo rest.UpdatedObj
return obj, &ttl, nil
}
return obj, nil, nil
})
}, dryrun.IsDryRun(options.DryRun))
if err != nil {
// delete the object
if err == errEmptiedFinalizers {
return e.deleteWithoutFinalizers(ctx, name, key, deleteObj, storagePreconditions)
return e.deleteWithoutFinalizers(ctx, name, key, deleteObj, storagePreconditions, dryrun.IsDryRun(options.DryRun))
}
if creating {
err = storeerr.InterpretCreateError(err, qualifiedResource, name)
@ -650,7 +653,7 @@ func (e *Store) Update(ctx context.Context, name string, objInfo rest.UpdatedObj
}
if e.shouldDeleteForFailedInitialization(ctx, out) {
return e.deleteWithoutFinalizers(ctx, name, key, out, storagePreconditions)
return e.deleteWithoutFinalizers(ctx, name, key, out, storagePreconditions, dryrun.IsDryRun(options.DryRun))
}
if creating {
@ -919,6 +922,7 @@ func (e *Store) updateForGracefulDeletionAndFinalizers(ctx context.Context, name
lastExisting = existing
return existing, nil
}),
dryrun.IsDryRun(options.DryRun),
)
switch err {
case nil:
@ -1000,10 +1004,15 @@ func (e *Store) Delete(ctx context.Context, name string, options *metav1.DeleteO
return out, false, err
}
// If dry-run, then just return the object as just saved.
if dryrun.IsDryRun(options.DryRun) {
return out, true, nil
}
// delete immediately, or no graceful deletion supported
glog.V(6).Infof("going to delete %s from registry: ", name)
out = e.NewFunc()
if err := e.Storage.Delete(ctx, key, out, &preconditions); err != nil {
if err := e.Storage.Delete(ctx, key, out, &preconditions, dryrun.IsDryRun(options.DryRun)); err != nil {
// Please refer to the place where we set ignoreNotFound for the reason
// why we ignore the NotFound error .
if storage.IsNotFound(err) && ignoreNotFound && lastExisting != nil {
@ -1364,8 +1373,9 @@ func (e *Store) CompleteWithOptions(options *generic.StoreOptions) error {
}
}
if e.Storage == nil {
e.Storage, e.DestroyFunc = opts.Decorator(
if e.Storage.Storage == nil {
e.Storage.Codec = opts.StorageConfig.Codec
e.Storage.Storage, e.DestroyFunc = opts.Decorator(
opts.StorageConfig,
e.NewFunc(),
prefix,

View File

@ -213,7 +213,7 @@ func TestStoreList(t *testing.T) {
destroyFunc, registry := NewTestGenericStoreRegistry(t)
if item.in != nil {
if err := storagetesting.CreateList("/pods", registry.Storage, item.in); err != nil {
if err := storagetesting.CreateList("/pods", registry.Storage.Storage, item.in); err != nil {
t.Errorf("Unexpected error %v", err)
}
}
@ -1901,7 +1901,7 @@ func newTestGenericStoreRegistry(t *testing.T, scheme *runtime.Scheme, hasCacheE
},
}
},
Storage: s,
Storage: DryRunnableStorage{Storage: s},
}
}

View File

@ -164,7 +164,7 @@ func (t *Tester) createObject(ctx context.Context, obj runtime.Object) error {
if err != nil {
return err
}
return t.storage.Storage.Create(ctx, key, obj, nil, 0)
return t.storage.Storage.Create(ctx, key, obj, nil, 0, false)
}
func (t *Tester) setObjectsForList(objects []runtime.Object) []runtime.Object {
@ -173,7 +173,7 @@ func (t *Tester) setObjectsForList(objects []runtime.Object) []runtime.Object {
t.tester.Errorf("unable to clear collection: %v", err)
return nil
}
if err := storagetesting.CreateObjList(key, t.storage.Storage, objects); err != nil {
if err := storagetesting.CreateObjList(key, t.storage.Storage.Storage, objects); err != nil {
t.tester.Errorf("unexpected error: %v", err)
return nil
}

View File

@ -153,21 +153,6 @@ func (h *etcdHelper) Create(ctx context.Context, key string, obj, out runtime.Ob
return err
}
func checkPreconditions(key string, preconditions *storage.Preconditions, out runtime.Object) error {
if preconditions == nil {
return nil
}
objMeta, err := meta.Accessor(out)
if err != nil {
return storage.NewInternalErrorf("can't enforce preconditions %v on un-introspectable object %v, got error: %v", *preconditions, out, err)
}
if preconditions.UID != nil && *preconditions.UID != objMeta.GetUID() {
errMsg := fmt.Sprintf("Precondition failed: UID in precondition: %v, UID in object meta: %v", preconditions.UID, objMeta.GetUID())
return storage.NewInvalidObjError(key, errMsg)
}
return nil
}
// Implements storage.Interface.
func (h *etcdHelper) Delete(ctx context.Context, key string, out runtime.Object, preconditions *storage.Preconditions) error {
if ctx == nil {
@ -199,7 +184,7 @@ func (h *etcdHelper) Delete(ctx context.Context, key string, out runtime.Object,
if err != nil {
return toStorageErr(err, key, 0)
}
if err := checkPreconditions(key, preconditions, obj); err != nil {
if err := preconditions.Check(key, obj); err != nil {
return toStorageErr(err, key, 0)
}
index := uint64(0)
@ -493,7 +478,7 @@ func (h *etcdHelper) GuaranteedUpdate(
if err != nil {
return toStorageErr(err, key, 0)
}
if err := checkPreconditions(key, preconditions, obj); err != nil {
if err := preconditions.Check(key, obj); err != nil {
return toStorageErr(err, key, 0)
}
meta := storage.ResponseMeta{}

View File

@ -233,7 +233,7 @@ func (s *store) conditionalDelete(ctx context.Context, key string, out runtime.O
if err != nil {
return err
}
if err := checkPreconditions(key, preconditions, origState.obj); err != nil {
if err := preconditions.Check(key, origState.obj); err != nil {
return err
}
txnResp, err := s.client.KV.Txn(ctx).If(
@ -294,7 +294,7 @@ func (s *store) GuaranteedUpdate(
transformContext := authenticatedDataString(key)
for {
if err := checkPreconditions(key, preconditions, origState.obj); err != nil {
if err := preconditions.Check(key, origState.obj); err != nil {
return err
}
@ -791,21 +791,6 @@ func appendListItem(v reflect.Value, data []byte, rev uint64, pred storage.Selec
return nil
}
func checkPreconditions(key string, preconditions *storage.Preconditions, out runtime.Object) error {
if preconditions == nil {
return nil
}
objMeta, err := meta.Accessor(out)
if err != nil {
return storage.NewInternalErrorf("can't enforce preconditions %v on un-introspectable object %v, got error: %v", *preconditions, out, err)
}
if preconditions.UID != nil && *preconditions.UID != objMeta.GetUID() {
errMsg := fmt.Sprintf("Precondition failed: UID in precondition: %v, UID in object meta: %v", *preconditions.UID, objMeta.GetUID())
return storage.NewInvalidObjError(key, errMsg)
}
return nil
}
func notFound(key string) clientv3.Cmp {
return clientv3.Compare(clientv3.ModRevision(key), "=", 0)
}

View File

@ -18,7 +18,9 @@ package storage
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
@ -106,6 +108,29 @@ func NewUIDPreconditions(uid string) *Preconditions {
return &Preconditions{UID: &u}
}
func (p *Preconditions) Check(key string, obj runtime.Object) error {
if p == nil {
return nil
}
objMeta, err := meta.Accessor(obj)
if err != nil {
return NewInternalErrorf(
"can't enforce preconditions %v on un-introspectable object %v, got error: %v",
*p,
obj,
err)
}
if p.UID != nil && *p.UID != objMeta.GetUID() {
err := fmt.Sprintf(
"Precondition failed: UID in precondition: %v, UID in object meta: %v",
*p.UID,
objMeta.GetUID())
return NewInvalidObjError(key, err)
}
return nil
}
// Interface offers a common interface for object marshaling/unmarshaling operations and
// hides all the storage-related operations behind it.
type Interface interface {

View File

@ -0,0 +1,23 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["dryrun.go"],
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/util/dryrun",
importpath = "k8s.io/apiserver/pkg/util/dryrun",
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,22 @@
/*
Copyright 2018 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 dryrun
// IsDryRun returns true if the DryRun flag is an actual dry-run.
func IsDryRun(flag []string) bool {
return len(flag) > 0
}

View File

@ -1134,6 +1134,10 @@
"ImportPath": "k8s.io/apiserver/pkg/storage/value",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apiserver/pkg/util/dryrun",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apiserver/pkg/util/feature",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

View File

@ -1106,6 +1106,10 @@
"ImportPath": "k8s.io/apiserver/pkg/storage/value",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apiserver/pkg/util/dryrun",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"ImportPath": "k8s.io/apiserver/pkg/util/feature",
"Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"