diff --git a/.gitignore b/.gitignore index 66a8103d0b94d3e43915db4b3727591383cf7ebf..5938db21682f16190d09eba230b38544c6a6fc26 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,5 @@ e2e/tls mocks ui/.npm/ + +.DS_Store diff --git a/charts/postgres-operator/templates/clusterrole.yaml b/charts/postgres-operator/templates/clusterrole.yaml index 1fd066fa5a8a576dd2f157d2994679a551a1ef18..ad3b460649cd9b911ece7aa758ba4f9612fe777d 100644 --- a/charts/postgres-operator/templates/clusterrole.yaml +++ b/charts/postgres-operator/templates/clusterrole.yaml @@ -141,7 +141,7 @@ rules: - get - list - patch -{{- if toString .Values.configKubernetes.storage_resize_mode | eq "pvc" }} +{{- if or (toString .Values.configKubernetes.storage_resize_mode | eq "pvc") (toString .Values.configKubernetes.storage_resize_mode | eq "mixed") }} - update {{- end }} # to read existing PVs. Creation should be done via dynamic provisioning diff --git a/pkg/cluster/volumes.go b/pkg/cluster/volumes.go index 165c6c7a39dbede9c865e2e4c790ff0fc88d4c91..240220ccf714c826b1c9fbd9086ad8890b539574 100644 --- a/pkg/cluster/volumes.go +++ b/pkg/cluster/volumes.go @@ -151,7 +151,7 @@ func (c *Cluster) populateVolumeMetaData() error { volumeIds := []string{} var volumeID string for _, pv := range pvs { - volumeID, err = c.VolumeResizer.ExtractVolumeID(pv.Spec.AWSElasticBlockStore.VolumeID) + volumeID, err = c.VolumeResizer.GetProviderVolumeID(pv) if err != nil { continue } diff --git a/pkg/cluster/volumes_test.go b/pkg/cluster/volumes_test.go index 99780982f3d6c1ad78ff0be38da128ef4551398b..95ecc762413de21f723b55c1a4dbbc74159aca55 100644 --- a/pkg/cluster/volumes_test.go +++ b/pkg/cluster/volumes_test.go @@ -216,6 +216,12 @@ func TestMigrateEBS(t *testing.T) { resizer.EXPECT().ExtractVolumeID(gomock.Eq("aws://eu-central-1b/ebs-volume-1")).Return("ebs-volume-1", nil) resizer.EXPECT().ExtractVolumeID(gomock.Eq("aws://eu-central-1b/ebs-volume-2")).Return("ebs-volume-2", nil) + resizer.EXPECT().GetProviderVolumeID(gomock.Any()). + DoAndReturn(func(pv *v1.PersistentVolume) (string, error) { + return resizer.ExtractVolumeID(pv.Spec.AWSElasticBlockStore.VolumeID) + }). + Times(2) + resizer.EXPECT().DescribeVolumes(gomock.Eq([]string{"ebs-volume-1", "ebs-volume-2"})).Return( []volumes.VolumeProperties{ {VolumeID: "ebs-volume-1", VolumeType: "gp2", Size: 100}, @@ -322,6 +328,12 @@ func TestMigrateGp3Support(t *testing.T) { resizer.EXPECT().ExtractVolumeID(gomock.Eq("aws://eu-central-1b/ebs-volume-2")).Return("ebs-volume-2", nil) resizer.EXPECT().ExtractVolumeID(gomock.Eq("aws://eu-central-1b/ebs-volume-3")).Return("ebs-volume-3", nil) + resizer.EXPECT().GetProviderVolumeID(gomock.Any()). + DoAndReturn(func(pv *v1.PersistentVolume) (string, error) { + return resizer.ExtractVolumeID(pv.Spec.AWSElasticBlockStore.VolumeID) + }). + Times(3) + resizer.EXPECT().DescribeVolumes(gomock.Eq([]string{"ebs-volume-1", "ebs-volume-2", "ebs-volume-3"})).Return( []volumes.VolumeProperties{ {VolumeID: "ebs-volume-1", VolumeType: "gp3", Size: 100, Iops: 3000}, @@ -377,6 +389,12 @@ func TestManualGp2Gp3Support(t *testing.T) { resizer.EXPECT().ExtractVolumeID(gomock.Eq("aws://eu-central-1b/ebs-volume-1")).Return("ebs-volume-1", nil) resizer.EXPECT().ExtractVolumeID(gomock.Eq("aws://eu-central-1b/ebs-volume-2")).Return("ebs-volume-2", nil) + resizer.EXPECT().GetProviderVolumeID(gomock.Any()). + DoAndReturn(func(pv *v1.PersistentVolume) (string, error) { + return resizer.ExtractVolumeID(pv.Spec.AWSElasticBlockStore.VolumeID) + }). + Times(2) + resizer.EXPECT().DescribeVolumes(gomock.Eq([]string{"ebs-volume-1", "ebs-volume-2"})).Return( []volumes.VolumeProperties{ {VolumeID: "ebs-volume-1", VolumeType: "gp2", Size: 150, Iops: 3000}, @@ -436,6 +454,12 @@ func TestDontTouchType(t *testing.T) { resizer.EXPECT().ExtractVolumeID(gomock.Eq("aws://eu-central-1b/ebs-volume-1")).Return("ebs-volume-1", nil) resizer.EXPECT().ExtractVolumeID(gomock.Eq("aws://eu-central-1b/ebs-volume-2")).Return("ebs-volume-2", nil) + resizer.EXPECT().GetProviderVolumeID(gomock.Any()). + DoAndReturn(func(pv *v1.PersistentVolume) (string, error) { + return resizer.ExtractVolumeID(pv.Spec.AWSElasticBlockStore.VolumeID) + }). + Times(2) + resizer.EXPECT().DescribeVolumes(gomock.Eq([]string{"ebs-volume-1", "ebs-volume-2"})).Return( []volumes.VolumeProperties{ {VolumeID: "ebs-volume-1", VolumeType: "gp2", Size: 150, Iops: 3000}, diff --git a/pkg/util/constants/aws.go b/pkg/util/constants/aws.go index f1cfd59755da66b19803d10d12222354823f795b..147e588894a5127931721bd8ba0038e9bf7fd568 100644 --- a/pkg/util/constants/aws.go +++ b/pkg/util/constants/aws.go @@ -7,6 +7,7 @@ const ( // EBS related constants EBSVolumeIDStart = "/vol-" EBSProvisioner = "kubernetes.io/aws-ebs" + EBSDriver = "ebs.csi.aws.com" //https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_VolumeModification.html EBSVolumeStateModifying = "modifying" EBSVolumeStateOptimizing = "optimizing" diff --git a/pkg/util/volumes/ebs.go b/pkg/util/volumes/ebs.go index f625dab2f785758db90c6aeb11d30a7018f3b5e0..cb8f8e97f43164d76135b515d0be259dd4aba682 100644 --- a/pkg/util/volumes/ebs.go +++ b/pkg/util/volumes/ebs.go @@ -36,7 +36,8 @@ func (r *EBSVolumeResizer) IsConnectedToProvider() bool { // VolumeBelongsToProvider checks if the given persistent volume is backed by EBS. func (r *EBSVolumeResizer) VolumeBelongsToProvider(pv *v1.PersistentVolume) bool { - return pv.Spec.AWSElasticBlockStore != nil && pv.Annotations[constants.VolumeStorateProvisionerAnnotation] == constants.EBSProvisioner + return (pv.Spec.AWSElasticBlockStore != nil && pv.Annotations[constants.VolumeStorateProvisionerAnnotation] == constants.EBSProvisioner) || + (pv.Spec.CSI != nil && pv.Spec.CSI.Driver == constants.EBSDriver) } // ExtractVolumeID extracts volumeID from "aws://eu-central-1a/vol-075ddfc4a127d0bd4" @@ -54,7 +55,12 @@ func (r *EBSVolumeResizer) ExtractVolumeID(volumeID string) (string, error) { // GetProviderVolumeID converts aws://eu-central-1b/vol-00f93d4827217c629 to vol-00f93d4827217c629 for EBS volumes func (r *EBSVolumeResizer) GetProviderVolumeID(pv *v1.PersistentVolume) (string, error) { - volumeID := pv.Spec.AWSElasticBlockStore.VolumeID + var volumeID string = "" + if pv.Spec.CSI != nil { + volumeID = pv.Spec.CSI.VolumeHandle + } else if pv.Spec.AWSElasticBlockStore != nil { + volumeID = pv.Spec.AWSElasticBlockStore.VolumeID + } if volumeID == "" { return "", fmt.Errorf("got empty volume id for volume %v", pv) } diff --git a/pkg/util/volumes/ebs_test.go b/pkg/util/volumes/ebs_test.go new file mode 100644 index 0000000000000000000000000000000000000000..6f722ff7b014728bb1e23849cb2ace94ba87c76b --- /dev/null +++ b/pkg/util/volumes/ebs_test.go @@ -0,0 +1,123 @@ +package volumes + +import ( + "fmt" + "testing" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestGetProviderVolumeID(t *testing.T) { + tests := []struct { + name string + pv *v1.PersistentVolume + expected string + err error + }{ + { + name: "CSI volume handle", + pv: &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + VolumeHandle: "vol-075ddfc4a127d0bd5", + }, + }, + }, + }, + expected: "vol-075ddfc4a127d0bd5", + err: nil, + }, + { + name: "AWS EBS volume handle", + pv: &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{ + VolumeID: "aws://eu-central-1a/vol-075ddfc4a127d0bd4", + }, + }, + }, + }, + expected: "vol-075ddfc4a127d0bd4", + err: nil, + }, + { + name: "Empty volume handle", + pv: &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{}, + }, + expected: "", + err: fmt.Errorf("got empty volume id for volume %v", &v1.PersistentVolume{}), + }, + } + + resizer := EBSVolumeResizer{} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + volumeID, err := resizer.GetProviderVolumeID(tt.pv) + if volumeID != tt.expected || (err != nil && err.Error() != tt.err.Error()) { + t.Errorf("expected %v, got %v, expected err %v, got %v", tt.expected, volumeID, tt.err, err) + } + }) + } +} + +func TestVolumeBelongsToProvider(t *testing.T) { + tests := []struct { + name string + pv *v1.PersistentVolume + expected bool + }{ + { + name: "CSI volume handle", + pv: &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + Driver: "ebs.csi.aws.com", + VolumeHandle: "vol-075ddfc4a127d0bd5", + }, + }, + }, + }, + expected: true, + }, + { + name: "AWS EBS volume handle", + pv: &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string { + "pv.kubernetes.io/provisioned-by": "kubernetes.io/aws-ebs", + }, + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{ + VolumeID: "aws://eu-central-1a/vol-075ddfc4a127d0bd4", + }, + }, + }, + }, + expected: true, + }, + { + name: "Empty volume source", + pv: &v1.PersistentVolume{ + Spec: v1.PersistentVolumeSpec{}, + }, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resizer := EBSVolumeResizer{} + isProvider := resizer.VolumeBelongsToProvider(tt.pv) + if isProvider != tt.expected { + t.Errorf("expected %v, got %v", tt.expected, isProvider) + } + }) + } +}