mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-05 10:19:50 +00:00
Merge pull request #116469 from RomanBednar/pv-phase-transition-time
PersistentVolume last phase transition time
This commit is contained in:
commit
f3a070f9c6
4
api/openapi-spec/swagger.json
generated
4
api/openapi-spec/swagger.json
generated
@ -7783,6 +7783,10 @@
|
|||||||
"io.k8s.api.core.v1.PersistentVolumeStatus": {
|
"io.k8s.api.core.v1.PersistentVolumeStatus": {
|
||||||
"description": "PersistentVolumeStatus is the current status of a persistent volume.",
|
"description": "PersistentVolumeStatus is the current status of a persistent volume.",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"lastPhaseTransitionTime": {
|
||||||
|
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time",
|
||||||
|
"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."
|
||||||
|
},
|
||||||
"message": {
|
"message": {
|
||||||
"description": "message is a human-readable message indicating details about why the volume is in this state.",
|
"description": "message is a human-readable message indicating details about why the volume is in this state.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
@ -4621,6 +4621,14 @@
|
|||||||
"io.k8s.api.core.v1.PersistentVolumeStatus": {
|
"io.k8s.api.core.v1.PersistentVolumeStatus": {
|
||||||
"description": "PersistentVolumeStatus is the current status of a persistent volume.",
|
"description": "PersistentVolumeStatus is the current status of a persistent volume.",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"lastPhaseTransitionTime": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Time"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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."
|
||||||
|
},
|
||||||
"message": {
|
"message": {
|
||||||
"description": "message is a human-readable message indicating details about why the volume is in this state.",
|
"description": "message is a human-readable message indicating details about why the volume is in this state.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
@ -31,9 +31,9 @@ const (
|
|||||||
deprecatedStorageClassAnnotationsMsg = `deprecated since v1.8; use "storageClassName" attribute instead`
|
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.
|
// 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 !utilfeature.DefaultFeatureGate.Enabled(features.CSINodeExpandSecret) && !hasNodeExpansionSecrets(oldPVSpec) {
|
||||||
if pvSpec.CSI != nil {
|
if pvSpec.CSI != nil {
|
||||||
pvSpec.CSI.NodeExpandSecretRef = 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 {
|
func hasNodeExpansionSecrets(oldPVSpec *api.PersistentVolumeSpec) bool {
|
||||||
if oldPVSpec == nil || oldPVSpec.CSI == nil {
|
if oldPVSpec == nil || oldPVSpec.CSI == nil {
|
||||||
return false
|
return false
|
||||||
|
@ -91,7 +91,7 @@ func TestDropDisabledFields(t *testing.T) {
|
|||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSINodeExpandSecret, tc.csiExpansionEnabled)()
|
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) {
|
if !reflect.DeepEqual(tc.newSpec, tc.expectNewSpec) {
|
||||||
t.Error(cmp.Diff(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
|
// Reason is a brief CamelCase string that describes any failure and is meant for machine parsing and tidy display in the CLI
|
||||||
// +optional
|
// +optional
|
||||||
Reason string
|
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
|
// +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.Phase = core.PersistentVolumePhase(in.Phase)
|
||||||
out.Message = in.Message
|
out.Message = in.Message
|
||||||
out.Reason = in.Reason
|
out.Reason = in.Reason
|
||||||
|
out.LastPhaseTransitionTime = (*metav1.Time)(unsafe.Pointer(in.LastPhaseTransitionTime))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5586,6 +5587,7 @@ func autoConvert_core_PersistentVolumeStatus_To_v1_PersistentVolumeStatus(in *co
|
|||||||
out.Phase = v1.PersistentVolumePhase(in.Phase)
|
out.Phase = v1.PersistentVolumePhase(in.Phase)
|
||||||
out.Message = in.Message
|
out.Message = in.Message
|
||||||
out.Reason = in.Reason
|
out.Reason = in.Reason
|
||||||
|
out.LastPhaseTransitionTime = (*metav1.Time)(unsafe.Pointer(in.LastPhaseTransitionTime))
|
||||||
return nil
|
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
|
out.TypeMeta = in.TypeMeta
|
||||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||||
in.Spec.DeepCopyInto(&out.Spec)
|
in.Spec.DeepCopyInto(&out.Spec)
|
||||||
out.Status = in.Status
|
in.Status.DeepCopyInto(&out.Status)
|
||||||
return
|
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.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *PersistentVolumeStatus) DeepCopyInto(out *PersistentVolumeStatus) {
|
func (in *PersistentVolumeStatus) DeepCopyInto(out *PersistentVolumeStatus) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
if in.LastPhaseTransitionTime != nil {
|
||||||
|
in, out := &in.LastPhaseTransitionTime, &out.LastPhaseTransitionTime
|
||||||
|
*out = (*in).DeepCopy()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -626,6 +626,13 @@ const (
|
|||||||
// Enables PDBUnhealthyPodEvictionPolicy for PodDisruptionBudgets
|
// Enables PDBUnhealthyPodEvictionPolicy for PodDisruptionBudgets
|
||||||
PDBUnhealthyPodEvictionPolicy featuregate.Feature = "PDBUnhealthyPodEvictionPolicy"
|
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
|
// owner: @haircommander
|
||||||
// kep: https://kep.k8s.io/2364
|
// kep: https://kep.k8s.io/2364
|
||||||
// alpha: v1.23
|
// alpha: v1.23
|
||||||
@ -1104,6 +1111,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
|||||||
|
|
||||||
PDBUnhealthyPodEvictionPolicy: {Default: true, PreRelease: featuregate.Beta},
|
PDBUnhealthyPodEvictionPolicy: {Default: true, PreRelease: featuregate.Beta},
|
||||||
|
|
||||||
|
PersistentVolumeLastPhaseTransitionTime: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
|
||||||
PodAndContainerStatsFromCRI: {Default: false, PreRelease: featuregate.Alpha},
|
PodAndContainerStatsFromCRI: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
|
||||||
PodDeletionCost: {Default: true, PreRelease: featuregate.Beta},
|
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: "",
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"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/fields"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
@ -66,7 +69,13 @@ func (persistentvolumeStrategy) PrepareForCreate(ctx context.Context, obj runtim
|
|||||||
pv := obj.(*api.PersistentVolume)
|
pv := obj.(*api.PersistentVolume)
|
||||||
pv.Status = api.PersistentVolumeStatus{}
|
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 {
|
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)
|
oldPv := old.(*api.PersistentVolume)
|
||||||
newPv.Status = oldPv.Status
|
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 {
|
func (persistentvolumeStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||||
@ -134,11 +143,28 @@ func (persistentvolumeStatusStrategy) GetResetFields() map[fieldpath.APIVersion]
|
|||||||
return fields
|
return fields
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var nowFunc = metav1.Now
|
||||||
|
|
||||||
// PrepareForUpdate sets the Spec field which is not allowed to be changed when updating a PV's Status
|
// 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) {
|
func (persistentvolumeStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
|
||||||
newPv := obj.(*api.PersistentVolume)
|
newPv := obj.(*api.PersistentVolume)
|
||||||
oldPv := old.(*api.PersistentVolume)
|
oldPv := old.(*api.PersistentVolume)
|
||||||
newPv.Spec = oldPv.Spec
|
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 {
|
func (persistentvolumeStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||||
|
@ -17,10 +17,17 @@ limitations under the License.
|
|||||||
package persistentvolume
|
package persistentvolume
|
||||||
|
|
||||||
import (
|
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"
|
apitesting "k8s.io/kubernetes/pkg/api/testing"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
// ensure types are installed
|
// ensure types are installed
|
||||||
_ "k8s.io/kubernetes/pkg/apis/core/install"
|
_ "k8s.io/kubernetes/pkg/apis/core/install"
|
||||||
@ -34,3 +41,394 @@ func TestSelectableFieldLabelConversions(t *testing.T) {
|
|||||||
map[string]string{"name": "metadata.name"},
|
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))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1795
staging/src/k8s.io/api/core/v1/generated.pb.go
generated
1795
staging/src/k8s.io/api/core/v1/generated.pb.go
generated
File diff suppressed because it is too large
Load Diff
@ -3187,6 +3187,13 @@ message PersistentVolumeStatus {
|
|||||||
// for machine parsing and tidy display in the CLI.
|
// for machine parsing and tidy display in the CLI.
|
||||||
// +optional
|
// +optional
|
||||||
optional string reason = 3;
|
optional string reason = 3;
|
||||||
|
|
||||||
|
// 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
|
||||||
|
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastPhaseTransitionTime = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Represents a Photon Controller persistent disk resource.
|
// Represents a Photon Controller persistent disk resource.
|
||||||
|
@ -411,6 +411,12 @@ type PersistentVolumeStatus struct {
|
|||||||
// for machine parsing and tidy display in the CLI.
|
// for machine parsing and tidy display in the CLI.
|
||||||
// +optional
|
// +optional
|
||||||
Reason string `json:"reason,omitempty" protobuf:"bytes,3,opt,name=reason"`
|
Reason string `json:"reason,omitempty" protobuf:"bytes,3,opt,name=reason"`
|
||||||
|
// 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 `json:"lastPhaseTransitionTime,omitempty" protobuf:"bytes,4,opt,name=lastPhaseTransitionTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
@ -1450,6 +1450,7 @@ var map_PersistentVolumeStatus = map[string]string{
|
|||||||
"phase": "phase indicates if a volume is available, bound to a claim, or released by a claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#phase",
|
"phase": "phase indicates if a volume is available, bound to a claim, or released by a claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#phase",
|
||||||
"message": "message is a human-readable message indicating details about why the volume is in this state.",
|
"message": "message is a human-readable message indicating details about why the volume is in this state.",
|
||||||
"reason": "reason is a brief CamelCase string that describes any failure and is meant for machine parsing and tidy display in the CLI.",
|
"reason": "reason is a brief CamelCase string that describes any failure and is meant for machine parsing and tidy display in the CLI.",
|
||||||
|
"lastPhaseTransitionTime": "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.",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (PersistentVolumeStatus) SwaggerDoc() map[string]string {
|
func (PersistentVolumeStatus) SwaggerDoc() map[string]string {
|
||||||
|
@ -2926,7 +2926,7 @@ func (in *PersistentVolume) DeepCopyInto(out *PersistentVolume) {
|
|||||||
out.TypeMeta = in.TypeMeta
|
out.TypeMeta = in.TypeMeta
|
||||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||||
in.Spec.DeepCopyInto(&out.Spec)
|
in.Spec.DeepCopyInto(&out.Spec)
|
||||||
out.Status = in.Status
|
in.Status.DeepCopyInto(&out.Status)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3368,6 +3368,10 @@ func (in *PersistentVolumeSpec) DeepCopy() *PersistentVolumeSpec {
|
|||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *PersistentVolumeStatus) DeepCopyInto(out *PersistentVolumeStatus) {
|
func (in *PersistentVolumeStatus) DeepCopyInto(out *PersistentVolumeStatus) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
if in.LastPhaseTransitionTime != nil {
|
||||||
|
in, out := &in.LastPhaseTransitionTime, &out.LastPhaseTransitionTime
|
||||||
|
*out = (*in).DeepCopy()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,6 +304,7 @@
|
|||||||
"status": {
|
"status": {
|
||||||
"phase": "phaseValue",
|
"phase": "phaseValue",
|
||||||
"message": "messageValue",
|
"message": "messageValue",
|
||||||
"reason": "reasonValue"
|
"reason": "reasonValue",
|
||||||
|
"lastPhaseTransitionTime": "2004-01-01T01:01:01Z"
|
||||||
}
|
}
|
||||||
}
|
}
|
Binary file not shown.
@ -232,6 +232,7 @@ spec:
|
|||||||
storagePolicyName: storagePolicyNameValue
|
storagePolicyName: storagePolicyNameValue
|
||||||
volumePath: volumePathValue
|
volumePath: volumePathValue
|
||||||
status:
|
status:
|
||||||
|
lastPhaseTransitionTime: "2004-01-01T01:01:01Z"
|
||||||
message: messageValue
|
message: messageValue
|
||||||
phase: phaseValue
|
phase: phaseValue
|
||||||
reason: reasonValue
|
reason: reasonValue
|
||||||
|
@ -20,6 +20,7 @@ package v1
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PersistentVolumeStatusApplyConfiguration represents an declarative configuration of the PersistentVolumeStatus type for use
|
// PersistentVolumeStatusApplyConfiguration represents an declarative configuration of the PersistentVolumeStatus type for use
|
||||||
@ -28,6 +29,7 @@ type PersistentVolumeStatusApplyConfiguration struct {
|
|||||||
Phase *v1.PersistentVolumePhase `json:"phase,omitempty"`
|
Phase *v1.PersistentVolumePhase `json:"phase,omitempty"`
|
||||||
Message *string `json:"message,omitempty"`
|
Message *string `json:"message,omitempty"`
|
||||||
Reason *string `json:"reason,omitempty"`
|
Reason *string `json:"reason,omitempty"`
|
||||||
|
LastPhaseTransitionTime *metav1.Time `json:"lastPhaseTransitionTime,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PersistentVolumeStatusApplyConfiguration constructs an declarative configuration of the PersistentVolumeStatus type for use with
|
// PersistentVolumeStatusApplyConfiguration constructs an declarative configuration of the PersistentVolumeStatus type for use with
|
||||||
@ -59,3 +61,11 @@ func (b *PersistentVolumeStatusApplyConfiguration) WithReason(value string) *Per
|
|||||||
b.Reason = &value
|
b.Reason = &value
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithLastPhaseTransitionTime sets the LastPhaseTransitionTime field in the declarative configuration to the given value
|
||||||
|
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||||
|
// If called multiple times, the LastPhaseTransitionTime field is set to the value of the last call.
|
||||||
|
func (b *PersistentVolumeStatusApplyConfiguration) WithLastPhaseTransitionTime(value metav1.Time) *PersistentVolumeStatusApplyConfiguration {
|
||||||
|
b.LastPhaseTransitionTime = &value
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
@ -5991,6 +5991,9 @@ var schemaYAML = typed.YAMLObject(`types:
|
|||||||
- name: io.k8s.api.core.v1.PersistentVolumeStatus
|
- name: io.k8s.api.core.v1.PersistentVolumeStatus
|
||||||
map:
|
map:
|
||||||
fields:
|
fields:
|
||||||
|
- name: lastPhaseTransitionTime
|
||||||
|
type:
|
||||||
|
namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time
|
||||||
- name: message
|
- name: message
|
||||||
type:
|
type:
|
||||||
scalar: string
|
scalar: string
|
||||||
|
@ -201,6 +201,57 @@ var _ = utils.SIGDescribe("PersistentVolumes", func() {
|
|||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
completeTest(ctx, f, c, ns, pv, pvc)
|
completeTest(ctx, f, c, ns, pv, pvc)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Create PV and pre-bound PVC that matches the PV, verify that when PV and PVC bind
|
||||||
|
// the LastPhaseTransitionTime filed of the PV is updated.
|
||||||
|
ginkgo.It("create a PV and a pre-bound PVC: test phase transition timestamp is set [Feature: PersistentVolumeLastPhaseTransitionTime]", func(ctx context.Context) {
|
||||||
|
pv, pvc, err = e2epv.CreatePVPVC(ctx, c, f.Timeouts, pvConfig, pvcConfig, ns, true)
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
|
// The claim should transition phase to: Bound
|
||||||
|
err = e2epv.WaitForPersistentVolumeClaimPhase(ctx, v1.ClaimBound, c, ns, pvc.Name, 2*time.Second, framework.ClaimProvisionShortTimeout)
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
pvc, err = c.CoreV1().PersistentVolumeClaims(ns).Get(ctx, pvc.Name, metav1.GetOptions{})
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
pv, err = c.CoreV1().PersistentVolumes().Get(ctx, pvc.Spec.VolumeName, metav1.GetOptions{})
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
if pv.Status.LastPhaseTransitionTime == nil {
|
||||||
|
framework.Failf("Persistent volume %v should have LastPhaseTransitionTime value set after transitioning phase, but it's nil.", pv.GetName())
|
||||||
|
}
|
||||||
|
completeTest(ctx, f, c, ns, pv, pvc)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create PV and pre-bound PVC that matches the PV, verify that when PV and PVC bind
|
||||||
|
// the LastPhaseTransitionTime field of the PV is set, then delete the PVC to change PV phase to
|
||||||
|
// released and validate PV LastPhaseTransitionTime correctly updated timestamp.
|
||||||
|
ginkgo.It("create a PV and a pre-bound PVC: test phase transition timestamp multiple updates [Feature: PersistentVolumeLastPhaseTransitionTime]", func(ctx context.Context) {
|
||||||
|
pv, pvc, err = e2epv.CreatePVPVC(ctx, c, f.Timeouts, pvConfig, pvcConfig, ns, true)
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
|
// The claim should transition phase to: Bound.
|
||||||
|
err = e2epv.WaitForPersistentVolumeClaimPhase(ctx, v1.ClaimBound, c, ns, pvc.Name, 2*time.Second, framework.ClaimProvisionShortTimeout)
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
pvc, err = c.CoreV1().PersistentVolumeClaims(ns).Get(ctx, pvc.Name, metav1.GetOptions{})
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
pv, err = c.CoreV1().PersistentVolumes().Get(ctx, pvc.Spec.VolumeName, metav1.GetOptions{})
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
|
// Save first phase transition time.
|
||||||
|
firstPhaseTransition := pv.Status.LastPhaseTransitionTime
|
||||||
|
|
||||||
|
// Let test finish and delete PVC.
|
||||||
|
completeTest(ctx, f, c, ns, pv, pvc)
|
||||||
|
|
||||||
|
// The claim should transition phase to: Released.
|
||||||
|
err = e2epv.WaitForPersistentVolumePhase(ctx, v1.VolumeReleased, c, pv.Name, 2*time.Second, framework.ClaimProvisionShortTimeout)
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
|
// Verify the phase transition timestamp got updated chronologically *after* first phase transition.
|
||||||
|
pv, err = c.CoreV1().PersistentVolumes().Get(ctx, pvc.Spec.VolumeName, metav1.GetOptions{})
|
||||||
|
if !firstPhaseTransition.Before(pv.Status.LastPhaseTransitionTime) {
|
||||||
|
framework.Failf("Persistent volume %v should have LastPhaseTransitionTime value updated to be chronologically after previous phase change: %v, but it's %v.", pv.GetName(), firstPhaseTransition, pv.Status.LastPhaseTransitionTime)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Create multiple pvs and pvcs, all in the same namespace. The PVs-PVCs are
|
// Create multiple pvs and pvcs, all in the same namespace. The PVs-PVCs are
|
||||||
|
Loading…
Reference in New Issue
Block a user