mirror of
https://github.com/optim-enterprises-bv/kubernetes.git
synced 2025-12-03 06:43:53 +00:00
Merge pull request #116469 from RomanBednar/pv-phase-transition-time
PersistentVolume last phase transition time
This commit is contained in:
@@ -31,9 +31,9 @@ const (
|
||||
deprecatedStorageClassAnnotationsMsg = `deprecated since v1.8; use "storageClassName" attribute instead`
|
||||
)
|
||||
|
||||
// DropDisabledFields removes disabled fields from the pv spec.
|
||||
// DropDisabledSpecFields removes disabled fields from the pv spec.
|
||||
// This should be called from PrepareForCreate/PrepareForUpdate for all resources containing a pv spec.
|
||||
func DropDisabledFields(pvSpec *api.PersistentVolumeSpec, oldPVSpec *api.PersistentVolumeSpec) {
|
||||
func DropDisabledSpecFields(pvSpec *api.PersistentVolumeSpec, oldPVSpec *api.PersistentVolumeSpec) {
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.CSINodeExpandSecret) && !hasNodeExpansionSecrets(oldPVSpec) {
|
||||
if pvSpec.CSI != nil {
|
||||
pvSpec.CSI.NodeExpandSecretRef = nil
|
||||
@@ -41,6 +41,14 @@ func DropDisabledFields(pvSpec *api.PersistentVolumeSpec, oldPVSpec *api.Persist
|
||||
}
|
||||
}
|
||||
|
||||
// DropDisabledStatusFields removes disabled fields from the pv status.
|
||||
// This should be called from PrepareForUpdate for all resources containing a pv status.
|
||||
func DropDisabledStatusFields(oldStatus, newStatus *api.PersistentVolumeStatus) {
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.PersistentVolumeLastPhaseTransitionTime) && oldStatus.LastPhaseTransitionTime.IsZero() {
|
||||
newStatus.LastPhaseTransitionTime = nil
|
||||
}
|
||||
}
|
||||
|
||||
func hasNodeExpansionSecrets(oldPVSpec *api.PersistentVolumeSpec) bool {
|
||||
if oldPVSpec == nil || oldPVSpec.CSI == nil {
|
||||
return false
|
||||
|
||||
@@ -91,7 +91,7 @@ func TestDropDisabledFields(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSINodeExpandSecret, tc.csiExpansionEnabled)()
|
||||
|
||||
DropDisabledFields(tc.newSpec, tc.oldSpec)
|
||||
DropDisabledSpecFields(tc.newSpec, tc.oldSpec)
|
||||
if !reflect.DeepEqual(tc.newSpec, tc.expectNewSpec) {
|
||||
t.Error(cmp.Diff(tc.newSpec, tc.expectNewSpec))
|
||||
}
|
||||
|
||||
@@ -380,6 +380,12 @@ type PersistentVolumeStatus struct {
|
||||
// Reason is a brief CamelCase string that describes any failure and is meant for machine parsing and tidy display in the CLI
|
||||
// +optional
|
||||
Reason string
|
||||
// LastPhaseTransitionTime is the time the phase transitioned from one to another
|
||||
// and automatically resets to current time everytime a volume phase transitions.
|
||||
// This is an alpha field and requires enabling PersistentVolumeLastPhaseTransitionTime feature.
|
||||
// +featureGate=PersistentVolumeLastPhaseTransitionTime
|
||||
// +optional
|
||||
LastPhaseTransitionTime *metav1.Time
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
2
pkg/apis/core/v1/zz_generated.conversion.go
generated
2
pkg/apis/core/v1/zz_generated.conversion.go
generated
@@ -5574,6 +5574,7 @@ func autoConvert_v1_PersistentVolumeStatus_To_core_PersistentVolumeStatus(in *v1
|
||||
out.Phase = core.PersistentVolumePhase(in.Phase)
|
||||
out.Message = in.Message
|
||||
out.Reason = in.Reason
|
||||
out.LastPhaseTransitionTime = (*metav1.Time)(unsafe.Pointer(in.LastPhaseTransitionTime))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5586,6 +5587,7 @@ func autoConvert_core_PersistentVolumeStatus_To_v1_PersistentVolumeStatus(in *co
|
||||
out.Phase = v1.PersistentVolumePhase(in.Phase)
|
||||
out.Message = in.Message
|
||||
out.Reason = in.Reason
|
||||
out.LastPhaseTransitionTime = (*metav1.Time)(unsafe.Pointer(in.LastPhaseTransitionTime))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
6
pkg/apis/core/zz_generated.deepcopy.go
generated
6
pkg/apis/core/zz_generated.deepcopy.go
generated
@@ -2928,7 +2928,7 @@ func (in *PersistentVolume) DeepCopyInto(out *PersistentVolume) {
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
out.Status = in.Status
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3370,6 +3370,10 @@ func (in *PersistentVolumeSpec) DeepCopy() *PersistentVolumeSpec {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PersistentVolumeStatus) DeepCopyInto(out *PersistentVolumeStatus) {
|
||||
*out = *in
|
||||
if in.LastPhaseTransitionTime != nil {
|
||||
in, out := &in.LastPhaseTransitionTime, &out.LastPhaseTransitionTime
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -626,6 +626,13 @@ const (
|
||||
// Enables PDBUnhealthyPodEvictionPolicy for PodDisruptionBudgets
|
||||
PDBUnhealthyPodEvictionPolicy featuregate.Feature = "PDBUnhealthyPodEvictionPolicy"
|
||||
|
||||
// owner: @RomanBednar
|
||||
// kep: https://kep.k8s.io/3762
|
||||
// alpha: v1.28
|
||||
//
|
||||
// Adds a new field to persistent volumes which holds a timestamp of when the volume last transitioned its phase.
|
||||
PersistentVolumeLastPhaseTransitionTime featuregate.Feature = "PersistentVolumeLastPhaseTransitionTime"
|
||||
|
||||
// owner: @haircommander
|
||||
// kep: https://kep.k8s.io/2364
|
||||
// alpha: v1.23
|
||||
@@ -1104,6 +1111,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
||||
|
||||
PDBUnhealthyPodEvictionPolicy: {Default: true, PreRelease: featuregate.Beta},
|
||||
|
||||
PersistentVolumeLastPhaseTransitionTime: {Default: false, PreRelease: featuregate.Alpha},
|
||||
|
||||
PodAndContainerStatsFromCRI: {Default: false, PreRelease: featuregate.Alpha},
|
||||
|
||||
PodDeletionCost: {Default: true, PreRelease: featuregate.Beta},
|
||||
|
||||
8
pkg/generated/openapi/zz_generated.openapi.go
generated
8
pkg/generated/openapi/zz_generated.openapi.go
generated
@@ -23110,9 +23110,17 @@ func schema_k8sio_api_core_v1_PersistentVolumeStatus(ref common.ReferenceCallbac
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"lastPhaseTransitionTime": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "lastPhaseTransitionTime is the time the phase transitioned from one to another and automatically resets to current time everytime a volume phase transitions. This is an alpha field and requires enabling PersistentVolumeLastPhaseTransitionTime feature.",
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@ package persistentvolume
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
@@ -66,7 +69,13 @@ func (persistentvolumeStrategy) PrepareForCreate(ctx context.Context, obj runtim
|
||||
pv := obj.(*api.PersistentVolume)
|
||||
pv.Status = api.PersistentVolumeStatus{}
|
||||
|
||||
pvutil.DropDisabledFields(&pv.Spec, nil)
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.PersistentVolumeLastPhaseTransitionTime) {
|
||||
pv.Status.Phase = api.VolumePending
|
||||
now := nowFunc()
|
||||
pv.Status.LastPhaseTransitionTime = &now
|
||||
}
|
||||
|
||||
pvutil.DropDisabledSpecFields(&pv.Spec, nil)
|
||||
}
|
||||
|
||||
func (persistentvolumeStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||
@@ -95,7 +104,7 @@ func (persistentvolumeStrategy) PrepareForUpdate(ctx context.Context, obj, old r
|
||||
oldPv := old.(*api.PersistentVolume)
|
||||
newPv.Status = oldPv.Status
|
||||
|
||||
pvutil.DropDisabledFields(&newPv.Spec, &oldPv.Spec)
|
||||
pvutil.DropDisabledSpecFields(&newPv.Spec, &oldPv.Spec)
|
||||
}
|
||||
|
||||
func (persistentvolumeStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||
@@ -134,11 +143,28 @@ func (persistentvolumeStatusStrategy) GetResetFields() map[fieldpath.APIVersion]
|
||||
return fields
|
||||
}
|
||||
|
||||
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) {
|
||||
newPv := obj.(*api.PersistentVolume)
|
||||
oldPv := old.(*api.PersistentVolume)
|
||||
newPv.Spec = oldPv.Spec
|
||||
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.PersistentVolumeLastPhaseTransitionTime) {
|
||||
switch {
|
||||
case oldPv.Status.Phase == newPv.Status.Phase && newPv.Status.LastPhaseTransitionTime == nil:
|
||||
// phase didn't change, preserve the existing transition time if set
|
||||
newPv.Status.LastPhaseTransitionTime = oldPv.Status.LastPhaseTransitionTime
|
||||
|
||||
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()
|
||||
newPv.Status.LastPhaseTransitionTime = &now
|
||||
}
|
||||
}
|
||||
|
||||
pvutil.DropDisabledStatusFields(&oldPv.Status, &newPv.Status)
|
||||
}
|
||||
|
||||
func (persistentvolumeStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user