From 68937c4934013739a1efc1b051820667d6d6349d Mon Sep 17 00:00:00 2001 From: Antoine Pelisse Date: Thu, 21 Jun 2018 08:49:13 -0700 Subject: [PATCH] dry-run: Use dry-runnable structure --- pkg/registry/apps/deployment/storage/BUILD | 1 + .../apps/deployment/storage/storage.go | 11 +- .../apps/deployment/storage/storage_test.go | 6 +- .../apps/replicaset/storage/storage_test.go | 6 +- .../apps/statefulset/storage/storage_test.go | 6 +- .../storage/storage_test.go | 2 +- pkg/registry/core/namespace/storage/BUILD | 1 + .../core/namespace/storage/storage.go | 2 + .../core/namespace/storage/storage_test.go | 4 +- .../persistentvolume/storage/storage_test.go | 2 +- .../storage/storage_test.go | 2 +- pkg/registry/core/pod/rest/log_test.go | 2 +- pkg/registry/core/pod/storage/BUILD | 2 + pkg/registry/core/pod/storage/storage.go | 11 +- pkg/registry/core/pod/storage/storage_test.go | 9 +- .../resourcequota/storage/storage_test.go | 2 +- .../core/service/storage/rest_test.go | 4 +- .../storage/storage_test.go | 2 +- .../priorityclass/storage/storage_test.go | 2 +- staging/src/BUILD | 1 + .../Godeps/Godeps.json | 8 + .../pkg/registry/customresource/etcd_test.go | 12 +- .../registry/customresourcedefinition/BUILD | 1 + .../registry/customresourcedefinition/etcd.go | 2 + .../pkg/registry/generic/registry/BUILD | 5 + .../pkg/registry/generic/registry/dryrun.go | 134 ++++++++ .../registry/generic/registry/dryrun_test.go | 304 ++++++++++++++++++ .../pkg/registry/generic/registry/store.go | 32 +- .../registry/generic/registry/store_test.go | 4 +- .../pkg/registry/generic/testing/tester.go | 4 +- .../k8s.io/apiserver/pkg/util/dryrun/BUILD | 23 ++ .../apiserver/pkg/util/dryrun/dryrun.go | 22 ++ .../k8s.io/kube-aggregator/Godeps/Godeps.json | 4 + .../sample-apiserver/Godeps/Godeps.json | 4 + 34 files changed, 582 insertions(+), 55 deletions(-) create mode 100644 staging/src/k8s.io/apiserver/pkg/registry/generic/registry/dryrun.go create mode 100644 staging/src/k8s.io/apiserver/pkg/registry/generic/registry/dryrun_test.go create mode 100644 staging/src/k8s.io/apiserver/pkg/util/dryrun/BUILD create mode 100644 staging/src/k8s.io/apiserver/pkg/util/dryrun/dryrun.go diff --git a/pkg/registry/apps/deployment/storage/BUILD b/pkg/registry/apps/deployment/storage/BUILD index fbe7f7f15c..a61386978d 100644 --- a/pkg/registry/apps/deployment/storage/BUILD +++ b/pkg/registry/apps/deployment/storage/BUILD @@ -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", ], ) diff --git a/pkg/registry/apps/deployment/storage/storage.go b/pkg/registry/apps/deployment/storage/storage.go index 1298967bf1..902419b62f 100644 --- a/pkg/registry/apps/deployment/storage/storage.go +++ b/pkg/registry/apps/deployment/storage/storage.go @@ -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 } diff --git a/pkg/registry/apps/deployment/storage/storage_test.go b/pkg/registry/apps/deployment/storage/storage_test.go index 483e470683..b3efd531a2 100644 --- a/pkg/registry/apps/deployment/storage/storage_test.go +++ b/pkg/registry/apps/deployment/storage/storage_test.go @@ -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{ diff --git a/pkg/registry/apps/replicaset/storage/storage_test.go b/pkg/registry/apps/replicaset/storage/storage_test.go index c77703f702..e1877c773e 100644 --- a/pkg/registry/apps/replicaset/storage/storage_test.go +++ b/pkg/registry/apps/replicaset/storage/storage_test.go @@ -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{ diff --git a/pkg/registry/apps/statefulset/storage/storage_test.go b/pkg/registry/apps/statefulset/storage/storage_test.go index a23a6cecb7..1392c4cce6 100644 --- a/pkg/registry/apps/statefulset/storage/storage_test.go +++ b/pkg/registry/apps/statefulset/storage/storage_test.go @@ -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 diff --git a/pkg/registry/autoscaling/horizontalpodautoscaler/storage/storage_test.go b/pkg/registry/autoscaling/horizontalpodautoscaler/storage/storage_test.go index 6a8999893b..ab0a55defa 100644 --- a/pkg/registry/autoscaling/horizontalpodautoscaler/storage/storage_test.go +++ b/pkg/registry/autoscaling/horizontalpodautoscaler/storage/storage_test.go @@ -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) } diff --git a/pkg/registry/core/namespace/storage/BUILD b/pkg/registry/core/namespace/storage/BUILD index 1747db6e2a..257a5a7598 100644 --- a/pkg/registry/core/namespace/storage/BUILD +++ b/pkg/registry/core/namespace/storage/BUILD @@ -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", ], ) diff --git a/pkg/registry/core/namespace/storage/storage.go b/pkg/registry/core/namespace/storage/storage.go index 842edbe3cd..c8f0c278cb 100644 --- a/pkg/registry/core/namespace/storage/storage.go +++ b/pkg/registry/core/namespace/storage/storage.go @@ -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 { diff --git a/pkg/registry/core/namespace/storage/storage_test.go b/pkg/registry/core/namespace/storage/storage_test.go index 6730de112a..c1e93f2d1c 100644 --- a/pkg/registry/core/namespace/storage/storage_test.go +++ b/pkg/registry/core/namespace/storage/storage_test.go @@ -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 { diff --git a/pkg/registry/core/persistentvolume/storage/storage_test.go b/pkg/registry/core/persistentvolume/storage/storage_test.go index fa6fd9d66b..568a0d8bc8 100644 --- a/pkg/registry/core/persistentvolume/storage/storage_test.go +++ b/pkg/registry/core/persistentvolume/storage/storage_test.go @@ -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) } diff --git a/pkg/registry/core/persistentvolumeclaim/storage/storage_test.go b/pkg/registry/core/persistentvolumeclaim/storage/storage_test.go index 14f6a45ee2..6af212a079 100644 --- a/pkg/registry/core/persistentvolumeclaim/storage/storage_test.go +++ b/pkg/registry/core/persistentvolumeclaim/storage/storage_test.go @@ -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{ diff --git a/pkg/registry/core/pod/rest/log_test.go b/pkg/registry/core/pod/rest/log_test.go index 2b58ee2587..59763c56c5 100644 --- a/pkg/registry/core/pod/rest/log_test.go +++ b/pkg/registry/core/pod/rest/log_test.go @@ -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} diff --git a/pkg/registry/core/pod/storage/BUILD b/pkg/registry/core/pod/storage/BUILD index a22c0bb496..8e06b27530 100644 --- a/pkg/registry/core/pod/storage/BUILD +++ b/pkg/registry/core/pod/storage/BUILD @@ -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", ], ) diff --git a/pkg/registry/core/pod/storage/storage.go b/pkg/registry/core/pod/storage/storage.go index 1f0bcc4091..1a81995c5d 100644 --- a/pkg/registry/core/pod/storage/storage.go +++ b/pkg/registry/core/pod/storage/storage.go @@ -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 { diff --git a/pkg/registry/core/pod/storage/storage_test.go b/pkg/registry/core/pod/storage/storage_test.go index c9581980b2..9a2a8774bf 100644 --- a/pkg/registry/core/pod/storage/storage_test.go +++ b/pkg/registry/core/pod/storage/storage_test.go @@ -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) } diff --git a/pkg/registry/core/resourcequota/storage/storage_test.go b/pkg/registry/core/resourcequota/storage/storage_test.go index 744eb16a56..bc2621da7b 100644 --- a/pkg/registry/core/resourcequota/storage/storage_test.go +++ b/pkg/registry/core/resourcequota/storage/storage_test.go @@ -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) } diff --git a/pkg/registry/core/service/storage/rest_test.go b/pkg/registry/core/service/storage/rest_test.go index eb3b7e8cb0..61dd73cac8 100644 --- a/pkg/registry/core/service/storage/rest_test.go +++ b/pkg/registry/core/service/storage/rest_test.go @@ -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) } } diff --git a/pkg/registry/policy/poddisruptionbudget/storage/storage_test.go b/pkg/registry/policy/poddisruptionbudget/storage/storage_test.go index 225c9a0195..222974f93c 100644 --- a/pkg/registry/policy/poddisruptionbudget/storage/storage_test.go +++ b/pkg/registry/policy/poddisruptionbudget/storage/storage_test.go @@ -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) } diff --git a/pkg/registry/scheduling/priorityclass/storage/storage_test.go b/pkg/registry/scheduling/priorityclass/storage/storage_test.go index 01a081ea5d..a096354586 100644 --- a/pkg/registry/scheduling/priorityclass/storage/storage_test.go +++ b/pkg/registry/scheduling/priorityclass/storage/storage_test.go @@ -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 { diff --git a/staging/src/BUILD b/staging/src/BUILD index 7fd5eff446..b0af7e9e49 100644 --- a/staging/src/BUILD +++ b/staging/src/BUILD @@ -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", diff --git a/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json b/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json index 2741b75831..106a72255e 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json +++ b/staging/src/k8s.io/apiextensions-apiserver/Godeps/Godeps.json @@ -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" diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd_test.go index 6bb0e749ea..0a6870dc05 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd_test.go @@ -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) } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/BUILD b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/BUILD index 56133f8e3c..153b889eac 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/BUILD +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/BUILD @@ -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", ], ) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/etcd.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/etcd.go index e152e8a507..c6b50915f1 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/etcd.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/etcd.go @@ -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 { diff --git a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/BUILD b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/BUILD index d56a4e0a61..8281a8e5a1 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/BUILD @@ -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", ], ) diff --git a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/dryrun.go b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/dryrun.go new file mode 100644 index 0000000000..dd6d443efc --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/dryrun.go @@ -0,0 +1,134 @@ +/* +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" + "fmt" + + "k8s.io/apimachinery/pkg/api/meta" + "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 checkPreconditions(key, preconditions, 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 = checkPreconditions(key, preconditions, 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 checkPreconditions(key string, preconditions *storage.Preconditions, obj runtime.Object) error { + if preconditions == nil { + return nil + } + objMeta, err := meta.Accessor(obj) + if err != nil { + return storage.NewInternalErrorf("can't enforce preconditions %v on un-introspectable object %v, got error: %v", *preconditions, obj, 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 (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 + +} diff --git a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/dryrun_test.go b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/dryrun_test.go new file mode 100644 index 0000000000..129c925afa --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/dryrun_test.go @@ -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) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go index 358da7de5c..4be4248b15 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go @@ -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, diff --git a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store_test.go b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store_test.go index 76957349b4..bdc06164d3 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store_test.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store_test.go @@ -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}, } } diff --git a/staging/src/k8s.io/apiserver/pkg/registry/generic/testing/tester.go b/staging/src/k8s.io/apiserver/pkg/registry/generic/testing/tester.go index e1ac7c1357..1ee637f09d 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/generic/testing/tester.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/generic/testing/tester.go @@ -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 } diff --git a/staging/src/k8s.io/apiserver/pkg/util/dryrun/BUILD b/staging/src/k8s.io/apiserver/pkg/util/dryrun/BUILD new file mode 100644 index 0000000000..356e902c71 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/util/dryrun/BUILD @@ -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"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/util/dryrun/dryrun.go b/staging/src/k8s.io/apiserver/pkg/util/dryrun/dryrun.go new file mode 100644 index 0000000000..3e28c29345 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/util/dryrun/dryrun.go @@ -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 +} diff --git a/staging/src/k8s.io/kube-aggregator/Godeps/Godeps.json b/staging/src/k8s.io/kube-aggregator/Godeps/Godeps.json index a2c573111d..2a38883540 100644 --- a/staging/src/k8s.io/kube-aggregator/Godeps/Godeps.json +++ b/staging/src/k8s.io/kube-aggregator/Godeps/Godeps.json @@ -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" diff --git a/staging/src/k8s.io/sample-apiserver/Godeps/Godeps.json b/staging/src/k8s.io/sample-apiserver/Godeps/Godeps.json index 290068ff50..44d03f4cd2 100644 --- a/staging/src/k8s.io/sample-apiserver/Godeps/Godeps.json +++ b/staging/src/k8s.io/sample-apiserver/Godeps/Godeps.json @@ -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"