postgres-operator/pkg/util/volumes/ebs.go

102 lines
3.6 KiB
Go

package volumes
import (
"fmt"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/zalando-incubator/postgres-operator/pkg/util/constants"
"github.com/zalando-incubator/postgres-operator/pkg/util/retryutil"
"k8s.io/client-go/pkg/api/v1"
)
type EBSVolumeResizer struct {
connection *ec2.EC2
}
func (c *EBSVolumeResizer) ConnectToProvider() error {
sess, err := session.NewSession(&aws.Config{Region: aws.String(constants.AWS_REGION)})
if err != nil {
return fmt.Errorf("could not establish AWS session: %v", err)
}
c.connection = ec2.New(sess)
return nil
}
func (c *EBSVolumeResizer) IsConnectedToProvider() bool {
return c.connection != nil
}
func (c *EBSVolumeResizer) VolumeBelongsToProvider(pv *v1.PersistentVolume) bool {
return pv.Spec.AWSElasticBlockStore != nil && pv.Annotations[constants.VolumeStorateProvisionerAnnotation] == constants.EBSProvisioner
}
// GetProviderVolumeID converts aws://eu-central-1b/vol-00f93d4827217c629 to vol-00f93d4827217c629 for EBS volumes
func (c *EBSVolumeResizer) GetProviderVolumeID(pv *v1.PersistentVolume) (string, error) {
volumeID := pv.Spec.AWSElasticBlockStore.VolumeID
if volumeID == "" {
return "", fmt.Errorf("volume id is empty for volume %s", pv.Name)
}
idx := strings.LastIndex(volumeID, constants.EBSVolumeIDStart) + 1
if idx == 0 {
return "", fmt.Errorf("malfored EBS volume id %s", volumeID)
}
return volumeID[idx:], nil
}
func (c *EBSVolumeResizer) ResizeVolume(volumeId string, newSize int64) error {
/* first check if the volume is already of a requested size */
volumeOutput, err := c.connection.DescribeVolumes(&ec2.DescribeVolumesInput{VolumeIds: []*string{&volumeId}})
if err != nil {
return fmt.Errorf("could not get information about the volume: %v", err)
}
vol := volumeOutput.Volumes[0]
if *vol.VolumeId != volumeId {
return fmt.Errorf("describe volume %s returned information about a non-matching volume %s", volumeId, *vol.VolumeId)
}
if *vol.Size == newSize {
// nothing to do
return nil
}
input := ec2.ModifyVolumeInput{Size: &newSize, VolumeId: &volumeId}
output, err := c.connection.ModifyVolume(&input)
if err != nil {
return fmt.Errorf("could not modify persistent volume: %v", err)
}
state := *output.VolumeModification.ModificationState
if state == constants.EBSVolumeStateFailed {
return fmt.Errorf("could not modify persistent volume %s: modification state failed", volumeId)
}
if state == "" {
return fmt.Errorf("received empty modification status")
}
if state == constants.EBSVolumeStateOptimizing || state == constants.EBSVolumeStateCompleted {
return nil
}
// wait until the volume reaches the "optimizing" or "completed" state
in := ec2.DescribeVolumesModificationsInput{VolumeIds: []*string{&volumeId}}
return retryutil.Retry(constants.EBSVolumeResizeWaitInterval, constants.EBSVolumeResizeWaitTimeout,
func() (bool, error) {
out, err := c.connection.DescribeVolumesModifications(&in)
if err != nil {
return false, fmt.Errorf("could not describe volume modification: %v", err)
}
if len(out.VolumesModifications) != 1 {
return false, fmt.Errorf("describe volume modification didn't return one record for volume \"%s\"", volumeId)
}
if *out.VolumesModifications[0].VolumeId != volumeId {
return false, fmt.Errorf("non-matching volume id when describing modifications: \"%s\" is different from \"%s\"")
}
return *out.VolumesModifications[0].ModificationState != constants.EBSVolumeStateModifying, nil
})
}
func (c *EBSVolumeResizer) DisconnectFromProvider() error {
c.connection = nil
return nil
}