mirror of
https://github.com/zalando/postgres-operator.git
synced 2026-09-30 16:12:07 +02:00
Full AWS gp3 support for iops and througput config. (#1261)
Support new AWS EBS volume type `gp3` with `iops` and `throughput` in the manifest. Co-authored-by: Felix Kunde <felix-kunde@gmx.de>
This commit is contained in:
co-authored by
Felix Kunde
parent
4ea0b5f432
commit
4a88f00a3f
+2
-70
@@ -53,8 +53,6 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error {
|
||||
return err
|
||||
}
|
||||
|
||||
c.logger.Debugf("syncing volumes using %q storage resize mode", c.OpConfig.StorageResizeMode)
|
||||
|
||||
if c.OpConfig.EnableEBSGp3Migration {
|
||||
err = c.executeEBSMigration()
|
||||
if nil != err {
|
||||
@@ -62,32 +60,8 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error {
|
||||
}
|
||||
}
|
||||
|
||||
if c.OpConfig.StorageResizeMode == "mixed" {
|
||||
// mixed op uses AWS API to adjust size,throughput,iops and calls pvc chance for file system resize
|
||||
|
||||
// resize pvc to adjust filesystem size until better K8s support
|
||||
if err = c.syncVolumeClaims(); err != nil {
|
||||
err = fmt.Errorf("could not sync persistent volume claims: %v", err)
|
||||
return err
|
||||
}
|
||||
} else if c.OpConfig.StorageResizeMode == "pvc" {
|
||||
if err = c.syncVolumeClaims(); err != nil {
|
||||
err = fmt.Errorf("could not sync persistent volume claims: %v", err)
|
||||
return err
|
||||
}
|
||||
} else if c.OpConfig.StorageResizeMode == "ebs" {
|
||||
// potentially enlarge volumes before changing the statefulset. By doing that
|
||||
// in this order we make sure the operator is not stuck waiting for a pod that
|
||||
// cannot start because it ran out of disk space.
|
||||
// TODO: handle the case of the cluster that is downsized and enlarged again
|
||||
// (there will be a volume from the old pod for which we can't act before the
|
||||
// the statefulset modification is concluded)
|
||||
if err = c.syncVolumes(); err != nil {
|
||||
err = fmt.Errorf("could not sync persistent volumes: %v", err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
c.logger.Infof("Storage resize is disabled (storage_resize_mode is off). Skipping volume sync.")
|
||||
if err = c.syncVolumes(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = c.enforceMinResourceLimits(&c.Spec); err != nil {
|
||||
@@ -590,48 +564,6 @@ func (c *Cluster) syncRoles() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// syncVolumeClaims reads all persistent volume claims and checks that their size matches the one declared in the statefulset.
|
||||
func (c *Cluster) syncVolumeClaims() error {
|
||||
c.setProcessName("syncing volume claims")
|
||||
|
||||
act, err := c.volumeClaimsNeedResizing(c.Spec.Volume)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not compare size of the volume claims: %v", err)
|
||||
}
|
||||
if !act {
|
||||
c.logger.Infof("volume claims do not require changes")
|
||||
return nil
|
||||
}
|
||||
if err := c.resizeVolumeClaims(c.Spec.Volume); err != nil {
|
||||
return fmt.Errorf("could not sync volume claims: %v", err)
|
||||
}
|
||||
|
||||
c.logger.Infof("volume claims have been synced successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// syncVolumes reads all persistent volumes and checks that their size matches the one declared in the statefulset.
|
||||
func (c *Cluster) syncVolumes() error {
|
||||
c.setProcessName("syncing volumes")
|
||||
|
||||
act, err := c.volumesNeedResizing(c.Spec.Volume)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not compare size of the volumes: %v", err)
|
||||
}
|
||||
if !act {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := c.resizeVolumes(); err != nil {
|
||||
return fmt.Errorf("could not sync volumes: %v", err)
|
||||
}
|
||||
|
||||
c.logger.Infof("volumes have been synced successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cluster) syncDatabases() error {
|
||||
c.setProcessName("syncing databases")
|
||||
|
||||
|
||||
+218
-22
@@ -10,13 +10,215 @@ import (
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
|
||||
"github.com/zalando/postgres-operator/pkg/spec"
|
||||
"github.com/zalando/postgres-operator/pkg/util"
|
||||
"github.com/zalando/postgres-operator/pkg/util/constants"
|
||||
"github.com/zalando/postgres-operator/pkg/util/filesystems"
|
||||
"github.com/zalando/postgres-operator/pkg/util/volumes"
|
||||
)
|
||||
|
||||
func (c *Cluster) syncVolumes() error {
|
||||
c.logger.Debugf("syncing volumes using %q storage resize mode", c.OpConfig.StorageResizeMode)
|
||||
var err error
|
||||
|
||||
// check quantity string once, and do not bother with it anymore anywhere else
|
||||
_, err = resource.ParseQuantity(c.Spec.Volume.Size)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse volume size from the manifest: %v", err)
|
||||
}
|
||||
|
||||
if c.OpConfig.StorageResizeMode == "mixed" {
|
||||
// mixed op uses AWS API to adjust size, throughput, iops, and calls pvc change for file system resize
|
||||
// in case of errors we proceed to let K8s do its work, favoring disk space increase of other adjustments
|
||||
|
||||
err = c.populateVolumeMetaData()
|
||||
if err != nil {
|
||||
c.logger.Errorf("populating EBS meta data failed, skipping potential adjustements: %v", err)
|
||||
} else {
|
||||
err = c.syncUnderlyingEBSVolume()
|
||||
if err != nil {
|
||||
c.logger.Errorf("errors occured during EBS volume adjustments: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// resize pvc to adjust filesystem size until better K8s support
|
||||
if err = c.syncVolumeClaims(); err != nil {
|
||||
err = fmt.Errorf("could not sync persistent volume claims: %v", err)
|
||||
return err
|
||||
}
|
||||
} else if c.OpConfig.StorageResizeMode == "pvc" {
|
||||
if err = c.syncVolumeClaims(); err != nil {
|
||||
err = fmt.Errorf("could not sync persistent volume claims: %v", err)
|
||||
return err
|
||||
}
|
||||
} else if c.OpConfig.StorageResizeMode == "ebs" {
|
||||
// potentially enlarge volumes before changing the statefulset. By doing that
|
||||
// in this order we make sure the operator is not stuck waiting for a pod that
|
||||
// cannot start because it ran out of disk space.
|
||||
// TODO: handle the case of the cluster that is downsized and enlarged again
|
||||
// (there will be a volume from the old pod for which we can't act before the
|
||||
// the statefulset modification is concluded)
|
||||
if err = c.syncEbsVolumes(); err != nil {
|
||||
err = fmt.Errorf("could not sync persistent volumes: %v", err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
c.logger.Infof("Storage resize is disabled (storage_resize_mode is off). Skipping volume sync.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cluster) syncUnderlyingEBSVolume() error {
|
||||
c.logger.Infof("starting to sync EBS volumes: type, iops, throughput, and size")
|
||||
|
||||
var err error
|
||||
|
||||
targetValue := c.Spec.Volume
|
||||
newSize, err := resource.ParseQuantity(targetValue.Size)
|
||||
targetSize := quantityToGigabyte(newSize)
|
||||
|
||||
awsGp3 := aws.String("gp3")
|
||||
awsIo2 := aws.String("io2")
|
||||
|
||||
errors := []string{}
|
||||
|
||||
for _, volume := range c.EBSVolumes {
|
||||
var modifyIops *int64
|
||||
var modifyThroughput *int64
|
||||
var modifySize *int64
|
||||
var modifyType *string
|
||||
|
||||
if targetValue.Iops != nil {
|
||||
if volume.Iops != *targetValue.Iops {
|
||||
modifyIops = targetValue.Iops
|
||||
}
|
||||
}
|
||||
|
||||
if targetValue.Throughput != nil {
|
||||
if volume.Throughput != *targetValue.Throughput {
|
||||
modifyThroughput = targetValue.Throughput
|
||||
}
|
||||
}
|
||||
|
||||
if targetSize > volume.Size {
|
||||
modifySize = &targetSize
|
||||
}
|
||||
|
||||
if modifyIops != nil || modifyThroughput != nil || modifySize != nil {
|
||||
if modifyIops != nil || modifyThroughput != nil {
|
||||
// we default to gp3 if iops and throughput are configured
|
||||
modifyType = awsGp3
|
||||
if targetValue.VolumeType == "io2" {
|
||||
modifyType = awsIo2
|
||||
}
|
||||
} else if targetValue.VolumeType == "gp3" && volume.VolumeType != "gp3" {
|
||||
modifyType = awsGp3
|
||||
} else {
|
||||
// do not touch type
|
||||
modifyType = nil
|
||||
}
|
||||
|
||||
err = c.VolumeResizer.ModifyVolume(volume.VolumeID, modifyType, modifySize, modifyIops, modifyThroughput)
|
||||
if err != nil {
|
||||
errors = append(errors, fmt.Sprintf("modify volume failed: volume=%s size=%d iops=%d throughput=%d", volume.VolumeID, volume.Size, volume.Iops, volume.Throughput))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
for _, s := range errors {
|
||||
c.logger.Warningf(s)
|
||||
}
|
||||
// c.logger.Errorf("failed to modify %d of %d volumes", len(c.EBSVolumes), len(errors))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cluster) populateVolumeMetaData() error {
|
||||
c.logger.Infof("starting reading ebs meta data")
|
||||
|
||||
pvs, err := c.listPersistentVolumes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not list persistent volumes: %v", err)
|
||||
}
|
||||
c.logger.Debugf("found %d volumes, size of known volumes %d", len(pvs), len(c.EBSVolumes))
|
||||
|
||||
volumeIds := []string{}
|
||||
var volumeID string
|
||||
for _, pv := range pvs {
|
||||
volumeID, err = c.VolumeResizer.ExtractVolumeID(pv.Spec.AWSElasticBlockStore.VolumeID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
volumeIds = append(volumeIds, volumeID)
|
||||
}
|
||||
|
||||
currentVolumes, err := c.VolumeResizer.DescribeVolumes(volumeIds)
|
||||
if nil != err {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(currentVolumes) != len(c.EBSVolumes) {
|
||||
c.logger.Debugf("number of ebs volumes (%d) discovered differs from already known volumes (%d)", len(currentVolumes), len(c.EBSVolumes))
|
||||
}
|
||||
|
||||
// reset map, operator is not responsible for dangling ebs volumes
|
||||
c.EBSVolumes = make(map[string]volumes.VolumeProperties)
|
||||
for _, volume := range currentVolumes {
|
||||
c.EBSVolumes[volume.VolumeID] = volume
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// syncVolumeClaims reads all persistent volume claims and checks that their size matches the one declared in the statefulset.
|
||||
func (c *Cluster) syncVolumeClaims() error {
|
||||
c.setProcessName("syncing volume claims")
|
||||
|
||||
needsResizing, err := c.volumeClaimsNeedResizing(c.Spec.Volume)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not compare size of the volume claims: %v", err)
|
||||
}
|
||||
|
||||
if !needsResizing {
|
||||
c.logger.Infof("volume claims do not require changes")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := c.resizeVolumeClaims(c.Spec.Volume); err != nil {
|
||||
return fmt.Errorf("could not sync volume claims: %v", err)
|
||||
}
|
||||
|
||||
c.logger.Infof("volume claims have been synced successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// syncVolumes reads all persistent volumes and checks that their size matches the one declared in the statefulset.
|
||||
func (c *Cluster) syncEbsVolumes() error {
|
||||
c.setProcessName("syncing EBS and Claims volumes")
|
||||
|
||||
act, err := c.volumesNeedResizing()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not compare size of the volumes: %v", err)
|
||||
}
|
||||
if !act {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := c.resizeVolumes(); err != nil {
|
||||
return fmt.Errorf("could not sync volumes: %v", err)
|
||||
}
|
||||
|
||||
c.logger.Infof("volumes have been synced successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cluster) listPersistentVolumeClaims() ([]v1.PersistentVolumeClaim, error) {
|
||||
ns := c.Namespace
|
||||
listOptions := metav1.ListOptions{
|
||||
@@ -125,15 +327,16 @@ func (c *Cluster) resizeVolumes() error {
|
||||
|
||||
c.setProcessName("resizing EBS volumes")
|
||||
|
||||
resizer := c.VolumeResizer
|
||||
var totalIncompatible int
|
||||
|
||||
newQuantity, err := resource.ParseQuantity(c.Spec.Volume.Size)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not parse volume size: %v", err)
|
||||
}
|
||||
|
||||
pvs, newSize, err := c.listVolumesWithManifestSize(c.Spec.Volume)
|
||||
newSize := quantityToGigabyte(newQuantity)
|
||||
resizer := c.VolumeResizer
|
||||
var totalIncompatible int
|
||||
|
||||
pvs, err := c.listPersistentVolumes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not list persistent volumes: %v", err)
|
||||
}
|
||||
@@ -214,33 +417,23 @@ func (c *Cluster) volumeClaimsNeedResizing(newVolume acidv1.Volume) (bool, error
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (c *Cluster) volumesNeedResizing(newVolume acidv1.Volume) (bool, error) {
|
||||
vols, manifestSize, err := c.listVolumesWithManifestSize(newVolume)
|
||||
func (c *Cluster) volumesNeedResizing() (bool, error) {
|
||||
newQuantity, _ := resource.ParseQuantity(c.Spec.Volume.Size)
|
||||
newSize := quantityToGigabyte(newQuantity)
|
||||
|
||||
vols, err := c.listPersistentVolumes()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, pv := range vols {
|
||||
currentSize := quantityToGigabyte(pv.Spec.Capacity[v1.ResourceStorage])
|
||||
if currentSize != manifestSize {
|
||||
if currentSize != newSize {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (c *Cluster) listVolumesWithManifestSize(newVolume acidv1.Volume) ([]*v1.PersistentVolume, int64, error) {
|
||||
newSize, err := resource.ParseQuantity(newVolume.Size)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("could not parse volume size from the manifest: %v", err)
|
||||
}
|
||||
manifestSize := quantityToGigabyte(newSize)
|
||||
vols, err := c.listPersistentVolumes()
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("could not list persistent volumes: %v", err)
|
||||
}
|
||||
return vols, manifestSize, nil
|
||||
}
|
||||
|
||||
// getPodNameFromPersistentVolume returns a pod name that it extracts from the volume claim ref.
|
||||
func getPodNameFromPersistentVolume(pv *v1.PersistentVolume) *spec.NamespacedName {
|
||||
namespace := pv.Spec.ClaimRef.Namespace
|
||||
@@ -258,7 +451,7 @@ func (c *Cluster) executeEBSMigration() error {
|
||||
}
|
||||
c.logger.Infof("starting EBS gp2 to gp3 migration")
|
||||
|
||||
pvs, _, err := c.listVolumesWithManifestSize(c.Spec.Volume)
|
||||
pvs, err := c.listPersistentVolumes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not list persistent volumes: %v", err)
|
||||
}
|
||||
@@ -294,10 +487,13 @@ func (c *Cluster) executeEBSMigration() error {
|
||||
return err
|
||||
}
|
||||
|
||||
var i3000 int64 = 3000
|
||||
var i125 int64 = 125
|
||||
|
||||
for _, volume := range awsVolumes {
|
||||
if volume.VolumeType == "gp2" && volume.Size < c.OpConfig.EnableEBSGp3MigrationMaxSize {
|
||||
c.logger.Infof("modifying EBS volume %s to type gp3 migration (%d)", volume.VolumeID, volume.Size)
|
||||
err = c.VolumeResizer.ModifyVolume(volume.VolumeID, "gp3", volume.Size, 3000, 125)
|
||||
err = c.VolumeResizer.ModifyVolume(volume.VolumeID, aws.String("gp3"), &volume.Size, &i3000, &i125)
|
||||
if nil != err {
|
||||
c.logger.Warningf("modifying volume %s failed: %v", volume.VolumeID, err)
|
||||
}
|
||||
|
||||
+253
-52
@@ -11,7 +11,9 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/golang/mock/gomock"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zalando/postgres-operator/mocks"
|
||||
acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
|
||||
@@ -187,60 +189,16 @@ func TestMigrateEBS(t *testing.T) {
|
||||
cluster.Namespace = namespace
|
||||
filterLabels := cluster.labelsSet(false)
|
||||
|
||||
pvcList := CreatePVCs(namespace, clusterName, filterLabels, 2, "1Gi")
|
||||
|
||||
ps := v1.PersistentVolumeSpec{}
|
||||
ps.AWSElasticBlockStore = &v1.AWSElasticBlockStoreVolumeSource{}
|
||||
ps.AWSElasticBlockStore.VolumeID = "aws://eu-central-1b/ebs-volume-1"
|
||||
|
||||
ps2 := v1.PersistentVolumeSpec{}
|
||||
ps2.AWSElasticBlockStore = &v1.AWSElasticBlockStoreVolumeSource{}
|
||||
ps2.AWSElasticBlockStore.VolumeID = "aws://eu-central-1b/ebs-volume-2"
|
||||
|
||||
pvList := &v1.PersistentVolumeList{
|
||||
Items: []v1.PersistentVolume{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "persistent-volume-0",
|
||||
},
|
||||
Spec: ps,
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "persistent-volume-1",
|
||||
},
|
||||
Spec: ps2,
|
||||
},
|
||||
testVolumes := []testVolume{
|
||||
{
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
size: 100,
|
||||
},
|
||||
}
|
||||
|
||||
for _, pvc := range pvcList.Items {
|
||||
cluster.KubeClient.PersistentVolumeClaims(namespace).Create(context.TODO(), &pvc, metav1.CreateOptions{})
|
||||
}
|
||||
|
||||
for _, pv := range pvList.Items {
|
||||
cluster.KubeClient.PersistentVolumes().Create(context.TODO(), &pv, metav1.CreateOptions{})
|
||||
}
|
||||
|
||||
pod := v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: clusterName + "-0",
|
||||
Labels: filterLabels,
|
||||
},
|
||||
Spec: v1.PodSpec{},
|
||||
}
|
||||
|
||||
cluster.KubeClient.Pods(namespace).Create(context.TODO(), &pod, metav1.CreateOptions{})
|
||||
|
||||
pod = v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: clusterName + "-1",
|
||||
Labels: filterLabels,
|
||||
},
|
||||
Spec: v1.PodSpec{},
|
||||
}
|
||||
|
||||
cluster.KubeClient.Pods(namespace).Create(context.TODO(), &pod, metav1.CreateOptions{})
|
||||
initTestVolumesAndPods(cluster.KubeClient, namespace, clusterName, filterLabels, testVolumes)
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
@@ -256,8 +214,251 @@ func TestMigrateEBS(t *testing.T) {
|
||||
{VolumeID: "ebs-volume-2", VolumeType: "gp3", Size: 100}}, nil)
|
||||
|
||||
// expect only gp2 volume to be modified
|
||||
resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-1"), gomock.Eq("gp3"), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
|
||||
resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-1"), gomock.Eq(aws.String("gp3")), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
|
||||
|
||||
cluster.VolumeResizer = resizer
|
||||
cluster.executeEBSMigration()
|
||||
}
|
||||
|
||||
type testVolume struct {
|
||||
iops int64
|
||||
throughtput int64
|
||||
size int64
|
||||
volType string
|
||||
}
|
||||
|
||||
func initTestVolumesAndPods(client k8sutil.KubernetesClient, namespace, clustername string, labels labels.Set, volumes []testVolume) {
|
||||
i := 0
|
||||
for _, v := range volumes {
|
||||
storage1Gi, _ := resource.ParseQuantity(fmt.Sprintf("%d", v.size))
|
||||
|
||||
ps := v1.PersistentVolumeSpec{}
|
||||
ps.AWSElasticBlockStore = &v1.AWSElasticBlockStoreVolumeSource{}
|
||||
ps.AWSElasticBlockStore.VolumeID = fmt.Sprintf("aws://eu-central-1b/ebs-volume-%d", i+1)
|
||||
|
||||
pv := v1.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("persistent-volume-%d", i),
|
||||
},
|
||||
Spec: ps,
|
||||
}
|
||||
|
||||
client.PersistentVolumes().Create(context.TODO(), &pv, metav1.CreateOptions{})
|
||||
|
||||
pvc := v1.PersistentVolumeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("%s-%s-%d", constants.DataVolumeName, clustername, i),
|
||||
Namespace: namespace,
|
||||
Labels: labels,
|
||||
},
|
||||
Spec: v1.PersistentVolumeClaimSpec{
|
||||
Resources: v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceStorage: storage1Gi,
|
||||
},
|
||||
},
|
||||
VolumeName: fmt.Sprintf("persistent-volume-%d", i),
|
||||
},
|
||||
}
|
||||
|
||||
client.PersistentVolumeClaims(namespace).Create(context.TODO(), &pvc, metav1.CreateOptions{})
|
||||
|
||||
pod := v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("%s-%d", clustername, i),
|
||||
Labels: labels,
|
||||
},
|
||||
Spec: v1.PodSpec{},
|
||||
}
|
||||
|
||||
client.Pods(namespace).Create(context.TODO(), &pod, metav1.CreateOptions{})
|
||||
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateGp3Support(t *testing.T) {
|
||||
client, _ := newFakeK8sPVCclient()
|
||||
clusterName := "acid-test-cluster"
|
||||
namespace := "default"
|
||||
|
||||
// new cluster with pvc storage resize mode and configured labels
|
||||
var cluster = New(
|
||||
Config{
|
||||
OpConfig: config.Config{
|
||||
Resources: config.Resources{
|
||||
ClusterLabels: map[string]string{"application": "spilo"},
|
||||
ClusterNameLabel: "cluster-name",
|
||||
},
|
||||
StorageResizeMode: "mixed",
|
||||
EnableEBSGp3Migration: false,
|
||||
EnableEBSGp3MigrationMaxSize: 1000,
|
||||
},
|
||||
}, client, acidv1.Postgresql{}, logger, eventRecorder)
|
||||
|
||||
cluster.Spec.Volume.Size = "150Gi"
|
||||
cluster.Spec.Volume.Iops = aws.Int64(6000)
|
||||
cluster.Spec.Volume.Throughput = aws.Int64(275)
|
||||
|
||||
// set metadata, so that labels will get correct values
|
||||
cluster.Name = clusterName
|
||||
cluster.Namespace = namespace
|
||||
filterLabels := cluster.labelsSet(false)
|
||||
|
||||
testVolumes := []testVolume{
|
||||
{
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
size: 100,
|
||||
},
|
||||
}
|
||||
|
||||
initTestVolumesAndPods(cluster.KubeClient, namespace, clusterName, filterLabels, testVolumes)
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
resizer := mocks.NewMockVolumeResizer(ctrl)
|
||||
|
||||
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().ExtractVolumeID(gomock.Eq("aws://eu-central-1b/ebs-volume-3")).Return("ebs-volume-3", nil)
|
||||
|
||||
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},
|
||||
{VolumeID: "ebs-volume-2", VolumeType: "gp3", Size: 105, Iops: 4000},
|
||||
{VolumeID: "ebs-volume-3", VolumeType: "gp3", Size: 151, Iops: 6000, Throughput: 275}}, nil)
|
||||
|
||||
// expect only gp2 volume to be modified
|
||||
resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-1"), gomock.Eq(aws.String("gp3")), gomock.Eq(aws.Int64(150)), gomock.Eq(aws.Int64(6000)), gomock.Eq(aws.Int64(275))).Return(nil)
|
||||
resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-2"), gomock.Eq(aws.String("gp3")), gomock.Eq(aws.Int64(150)), gomock.Eq(aws.Int64(6000)), gomock.Eq(aws.Int64(275))).Return(nil)
|
||||
// resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-3"), gomock.Eq(aws.String("gp3")), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
|
||||
|
||||
cluster.VolumeResizer = resizer
|
||||
cluster.syncVolumes()
|
||||
}
|
||||
|
||||
func TestManualGp2Gp3Support(t *testing.T) {
|
||||
client, _ := newFakeK8sPVCclient()
|
||||
clusterName := "acid-test-cluster"
|
||||
namespace := "default"
|
||||
|
||||
// new cluster with pvc storage resize mode and configured labels
|
||||
var cluster = New(
|
||||
Config{
|
||||
OpConfig: config.Config{
|
||||
Resources: config.Resources{
|
||||
ClusterLabels: map[string]string{"application": "spilo"},
|
||||
ClusterNameLabel: "cluster-name",
|
||||
},
|
||||
StorageResizeMode: "mixed",
|
||||
EnableEBSGp3Migration: false,
|
||||
EnableEBSGp3MigrationMaxSize: 1000,
|
||||
},
|
||||
}, client, acidv1.Postgresql{}, logger, eventRecorder)
|
||||
|
||||
cluster.Spec.Volume.Size = "150Gi"
|
||||
cluster.Spec.Volume.Iops = aws.Int64(6000)
|
||||
cluster.Spec.Volume.Throughput = aws.Int64(275)
|
||||
|
||||
// set metadata, so that labels will get correct values
|
||||
cluster.Name = clusterName
|
||||
cluster.Namespace = namespace
|
||||
filterLabels := cluster.labelsSet(false)
|
||||
|
||||
testVolumes := []testVolume{
|
||||
{
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
size: 100,
|
||||
},
|
||||
}
|
||||
|
||||
initTestVolumesAndPods(cluster.KubeClient, namespace, clusterName, filterLabels, testVolumes)
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
resizer := mocks.NewMockVolumeResizer(ctrl)
|
||||
|
||||
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().DescribeVolumes(gomock.Eq([]string{"ebs-volume-1", "ebs-volume-2"})).Return(
|
||||
[]volumes.VolumeProperties{
|
||||
{VolumeID: "ebs-volume-1", VolumeType: "gp2", Size: 150, Iops: 3000},
|
||||
{VolumeID: "ebs-volume-2", VolumeType: "gp2", Size: 150, Iops: 4000},
|
||||
}, nil)
|
||||
|
||||
// expect only gp2 volume to be modified
|
||||
resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-1"), gomock.Eq(aws.String("gp3")), gomock.Nil(), gomock.Eq(aws.Int64(6000)), gomock.Eq(aws.Int64(275))).Return(nil)
|
||||
resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-2"), gomock.Eq(aws.String("gp3")), gomock.Nil(), gomock.Eq(aws.Int64(6000)), gomock.Eq(aws.Int64(275))).Return(nil)
|
||||
|
||||
cluster.VolumeResizer = resizer
|
||||
cluster.syncVolumes()
|
||||
}
|
||||
|
||||
func TestDontTouchType(t *testing.T) {
|
||||
client, _ := newFakeK8sPVCclient()
|
||||
clusterName := "acid-test-cluster"
|
||||
namespace := "default"
|
||||
|
||||
// new cluster with pvc storage resize mode and configured labels
|
||||
var cluster = New(
|
||||
Config{
|
||||
OpConfig: config.Config{
|
||||
Resources: config.Resources{
|
||||
ClusterLabels: map[string]string{"application": "spilo"},
|
||||
ClusterNameLabel: "cluster-name",
|
||||
},
|
||||
StorageResizeMode: "mixed",
|
||||
EnableEBSGp3Migration: false,
|
||||
EnableEBSGp3MigrationMaxSize: 1000,
|
||||
},
|
||||
}, client, acidv1.Postgresql{}, logger, eventRecorder)
|
||||
|
||||
cluster.Spec.Volume.Size = "177Gi"
|
||||
|
||||
// set metadata, so that labels will get correct values
|
||||
cluster.Name = clusterName
|
||||
cluster.Namespace = namespace
|
||||
filterLabels := cluster.labelsSet(false)
|
||||
|
||||
testVolumes := []testVolume{
|
||||
{
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
size: 150,
|
||||
},
|
||||
}
|
||||
|
||||
initTestVolumesAndPods(cluster.KubeClient, namespace, clusterName, filterLabels, testVolumes)
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
resizer := mocks.NewMockVolumeResizer(ctrl)
|
||||
|
||||
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().DescribeVolumes(gomock.Eq([]string{"ebs-volume-1", "ebs-volume-2"})).Return(
|
||||
[]volumes.VolumeProperties{
|
||||
{VolumeID: "ebs-volume-1", VolumeType: "gp2", Size: 150, Iops: 3000},
|
||||
{VolumeID: "ebs-volume-2", VolumeType: "gp2", Size: 150, Iops: 4000},
|
||||
}, nil)
|
||||
|
||||
// expect only gp2 volume to be modified
|
||||
resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-1"), gomock.Nil(), gomock.Eq(aws.Int64(177)), gomock.Nil(), gomock.Nil()).Return(nil)
|
||||
resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-2"), gomock.Nil(), gomock.Eq(aws.Int64(177)), gomock.Nil(), gomock.Nil()).Return(nil)
|
||||
|
||||
cluster.VolumeResizer = resizer
|
||||
cluster.syncVolumes()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user