diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index 7246974fb0c..e5359d88bc5 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -1047,7 +1047,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS PDBUnhealthyPodEvictionPolicy: {Default: true, PreRelease: featuregate.Beta}, - PersistentVolumeLastPhaseTransitionTime: {Default: false, PreRelease: featuregate.Alpha}, + PersistentVolumeLastPhaseTransitionTime: {Default: true, PreRelease: featuregate.Beta}, PodAndContainerStatsFromCRI: {Default: false, PreRelease: featuregate.Alpha}, diff --git a/pkg/registry/core/persistentvolume/storage/storage_test.go b/pkg/registry/core/persistentvolume/storage/storage_test.go index 271a459d2a1..2570ebba358 100644 --- a/pkg/registry/core/persistentvolume/storage/storage_test.go +++ b/pkg/registry/core/persistentvolume/storage/storage_test.go @@ -17,8 +17,12 @@ limitations under the License. package storage import ( + utilfeature "k8s.io/apiserver/pkg/util/feature" + featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/kubernetes/pkg/features" "testing" + "context" "github.com/google/go-cmp/cmp" apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/resource" @@ -32,6 +36,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing" api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/registry/core/persistentvolume" "k8s.io/kubernetes/pkg/registry/registrytest" ) @@ -57,6 +62,7 @@ func newHostPathType(pathType string) *api.HostPathType { } func validNewPersistentVolume(name string) *api.PersistentVolume { + now := persistentvolume.NowFunc() pv := &api.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -72,9 +78,10 @@ func validNewPersistentVolume(name string) *api.PersistentVolume { PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimRetain, }, Status: api.PersistentVolumeStatus{ - Phase: api.VolumePending, - Message: "bar", - Reason: "foo", + Phase: api.VolumePending, + Message: "bar", + Reason: "foo", + LastPhaseTransitionTime: &now, }, } return pv @@ -166,6 +173,7 @@ func TestWatch(t *testing.T) { } func TestUpdateStatus(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PersistentVolumeLastPhaseTransitionTime, true)() storage, statusStorage, server := newStorage(t) defer server.Terminate(t) defer storage.Store.DestroyFunc() @@ -177,12 +185,19 @@ func TestUpdateStatus(t *testing.T) { t.Errorf("unexpected error: %v", err) } + pvStartTimestamp, err := getPhaseTranstitionTime(ctx, pvStart.Name, storage) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + pvIn := &api.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", }, Status: api.PersistentVolumeStatus{ Phase: api.VolumeBound, + // Set the same timestamp as original PV so this won't get updated on phase change breaking DeepEqual() later in test. + LastPhaseTransitionTime: pvStartTimestamp, }, } @@ -201,6 +216,14 @@ func TestUpdateStatus(t *testing.T) { } } +func getPhaseTranstitionTime(ctx context.Context, pvName string, storage *REST) (*metav1.Time, error) { + obj, err := storage.Get(ctx, pvName, &metav1.GetOptions{}) + if err != nil { + return nil, err + } + return obj.(*api.PersistentVolume).Status.LastPhaseTransitionTime, nil +} + func TestShortNames(t *testing.T) { storage, _, server := newStorage(t) defer server.Terminate(t) diff --git a/pkg/registry/core/persistentvolume/strategy.go b/pkg/registry/core/persistentvolume/strategy.go index d33a3f0a925..515a9b76869 100644 --- a/pkg/registry/core/persistentvolume/strategy.go +++ b/pkg/registry/core/persistentvolume/strategy.go @@ -71,7 +71,7 @@ func (persistentvolumeStrategy) PrepareForCreate(ctx context.Context, obj runtim if utilfeature.DefaultFeatureGate.Enabled(features.PersistentVolumeLastPhaseTransitionTime) { pv.Status.Phase = api.VolumePending - now := nowFunc() + now := NowFunc() pv.Status.LastPhaseTransitionTime = &now } @@ -143,7 +143,7 @@ func (persistentvolumeStatusStrategy) GetResetFields() map[fieldpath.APIVersion] return fields } -var nowFunc = metav1.Now +var NowFunc = metav1.Now // PrepareForUpdate sets the Spec field which is not allowed to be changed when updating a PV's Status func (persistentvolumeStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { @@ -159,7 +159,7 @@ func (persistentvolumeStatusStrategy) PrepareForUpdate(ctx context.Context, obj, case oldPv.Status.Phase != newPv.Status.Phase && (newPv.Status.LastPhaseTransitionTime == nil || newPv.Status.LastPhaseTransitionTime.Equal(oldPv.Status.LastPhaseTransitionTime)): // phase changed and client didn't set or didn't change the transition time - now := nowFunc() + now := NowFunc() newPv.Status.LastPhaseTransitionTime = &now } } diff --git a/pkg/registry/core/persistentvolume/strategy_test.go b/pkg/registry/core/persistentvolume/strategy_test.go index e7d9794b5fe..c5ca9d07110 100644 --- a/pkg/registry/core/persistentvolume/strategy_test.go +++ b/pkg/registry/core/persistentvolume/strategy_test.go @@ -46,9 +46,9 @@ 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 } + NowFunc = func() metav1.Time { return now } defer func() { - nowFunc = metav1.Now + NowFunc = metav1.Now }() tests := []struct { name string @@ -376,9 +376,9 @@ func TestStatusUpdate(t *testing.T) { func TestStatusCreate(t *testing.T) { now := metav1.Now() - nowFunc = func() metav1.Time { return now } + NowFunc = func() metav1.Time { return now } defer func() { - nowFunc = metav1.Now + NowFunc = metav1.Now }() tests := []struct { name string