From a9c3444a47b07d4da5d3cbfffbc87ce1629d2744 Mon Sep 17 00:00:00 2001 From: Roman Bednar Date: Thu, 20 Jul 2023 11:09:38 +0200 Subject: [PATCH] test: add coverage for pv status update and create strategy --- .../core/persistentvolume/strategy_test.go | 402 +++++++++++++++++- 1 file changed, 400 insertions(+), 2 deletions(-) diff --git a/pkg/registry/core/persistentvolume/strategy_test.go b/pkg/registry/core/persistentvolume/strategy_test.go index 3aac0d1612a..e7d9794b5fe 100644 --- a/pkg/registry/core/persistentvolume/strategy_test.go +++ b/pkg/registry/core/persistentvolume/strategy_test.go @@ -17,10 +17,17 @@ limitations under the License. package persistentvolume import ( - "testing" - + "context" + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" apitesting "k8s.io/kubernetes/pkg/api/testing" api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/features" + "reflect" + "testing" + "time" // ensure types are installed _ "k8s.io/kubernetes/pkg/apis/core/install" @@ -34,3 +41,394 @@ func TestSelectableFieldLabelConversions(t *testing.T) { map[string]string{"name": "metadata.name"}, ) } + +func TestStatusUpdate(t *testing.T) { + now := metav1.Now() + origin := metav1.NewTime(now.Add(time.Hour)) + later := metav1.NewTime(now.Add(time.Hour * 2)) + nowFunc = func() metav1.Time { return now } + defer func() { + nowFunc = metav1.Now + }() + tests := []struct { + name string + fg bool + oldObj *api.PersistentVolume + newObj *api.PersistentVolume + expectedObj *api.PersistentVolume + }{ + { + name: "feature enabled: timestamp is updated when phase changes", + fg: true, + oldObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumePending, + }, + }, + newObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumeBound, + }, + }, + expectedObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumeBound, + LastPhaseTransitionTime: &now, + }, + }, + }, + { + name: "feature enabled: timestamp is updated when phase changes and old pv has a timestamp", + fg: true, + oldObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumePending, + LastPhaseTransitionTime: &origin, + }, + }, + newObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumeBound, + }, + }, + expectedObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumeBound, + LastPhaseTransitionTime: &now, + }, + }, + }, + { + name: "feature enabled: user timestamp change is respected on no phase change", + fg: true, + oldObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumePending, + }, + }, + newObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumePending, + LastPhaseTransitionTime: &later, + }, + }, + expectedObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumePending, + LastPhaseTransitionTime: &later, + }, + }, + }, + { + name: "feature enabled: user timestamp is respected on phase change", + fg: true, + oldObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumePending, + LastPhaseTransitionTime: &origin, + }, + }, + newObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumeBound, + LastPhaseTransitionTime: &later, + }, + }, + expectedObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumeBound, + LastPhaseTransitionTime: &later, + }, + }, + }, + { + name: "feature enabled: user timestamp change is respected on no phase change when old pv has a timestamp", + fg: true, + oldObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumeBound, + LastPhaseTransitionTime: &origin, + }, + }, + newObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumeBound, + LastPhaseTransitionTime: &later, + }, + }, + expectedObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumeBound, + LastPhaseTransitionTime: &later, + }, + }, + }, + { + name: "feature enabled: timestamp is updated when phase changes and both new and old timestamp matches", + fg: true, + oldObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumePending, + LastPhaseTransitionTime: &origin, + }, + }, + newObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumeBound, + LastPhaseTransitionTime: &origin, + }, + }, + expectedObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumeBound, + LastPhaseTransitionTime: &now, + }, + }, + }, + { + name: "feature disabled: timestamp is not updated", + fg: false, + oldObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumePending, + }, + }, + newObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumeBound, + }, + }, + expectedObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumeBound, + }, + }, + }, + { + name: "feature disabled: user timestamp is overwritten on phase change to nil", + fg: false, + oldObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumePending, + }, + }, + newObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumePending, + LastPhaseTransitionTime: &later, + }, + }, + expectedObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumePending, + LastPhaseTransitionTime: nil, + }, + }, + }, + { + name: "feature disabled: user timestamp change is respected on phase change", + fg: false, + oldObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumePending, + LastPhaseTransitionTime: &origin, + }, + }, + newObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumeBound, + LastPhaseTransitionTime: &later, + }, + }, + expectedObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumeBound, + LastPhaseTransitionTime: &later, + }, + }, + }, + { + name: "feature disabled: user timestamp change is respected on no phase change", + fg: false, + oldObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumeBound, + LastPhaseTransitionTime: &origin, + }, + }, + newObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumeBound, + LastPhaseTransitionTime: &later, + }, + }, + expectedObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumeBound, + LastPhaseTransitionTime: &later, + }, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PersistentVolumeLastPhaseTransitionTime, tc.fg)() + + obj := tc.newObj.DeepCopy() + StatusStrategy.PrepareForUpdate(context.TODO(), obj, tc.oldObj.DeepCopy()) + if !reflect.DeepEqual(obj, tc.expectedObj) { + t.Errorf("object diff: %s", cmp.Diff(obj, tc.expectedObj)) + } + }) + } +} + +func TestStatusCreate(t *testing.T) { + now := metav1.Now() + nowFunc = func() metav1.Time { return now } + defer func() { + nowFunc = metav1.Now + }() + tests := []struct { + name string + fg bool + newObj *api.PersistentVolume + expectedObj *api.PersistentVolume + }{ + { + name: "feature enabled: pv is in pending phase and has a timestamp", + fg: true, + newObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + }, + expectedObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumePending, + LastPhaseTransitionTime: &now, + }, + }, + }, + { + name: "feature disabled: pv does not have phase and timestamp", + fg: false, + newObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + }, + expectedObj: &api.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PersistentVolumeLastPhaseTransitionTime, tc.fg)() + obj := tc.newObj.DeepCopy() + StatusStrategy.PrepareForCreate(context.TODO(), obj) + if !reflect.DeepEqual(obj, tc.expectedObj) { + t.Errorf("object diff: %s", cmp.Diff(obj, tc.expectedObj)) + } + }) + } +}