2017-08-02 17:27:17 +00:00
|
|
|
/*
|
2017-12-15 21:38:12 +00:00
|
|
|
Copyright 2017 the Heptio Ark Contributors.
|
2017-08-02 17:27:17 +00:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package aws
|
|
|
|
|
|
|
|
import (
|
2017-11-29 17:23:21 +00:00
|
|
|
"regexp"
|
|
|
|
|
2017-08-15 20:20:23 +00:00
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
|
|
"github.com/aws/aws-sdk-go/aws/session"
|
2017-08-02 17:27:17 +00:00
|
|
|
"github.com/aws/aws-sdk-go/service/ec2"
|
2017-09-14 21:27:31 +00:00
|
|
|
"github.com/pkg/errors"
|
2017-08-02 17:27:17 +00:00
|
|
|
|
2017-11-29 17:23:21 +00:00
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
2017-08-11 19:10:47 +00:00
|
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
|
|
|
2017-08-02 17:27:17 +00:00
|
|
|
"github.com/heptio/ark/pkg/cloudprovider"
|
2017-11-29 17:23:21 +00:00
|
|
|
"github.com/heptio/ark/pkg/util/collections"
|
2017-08-02 17:27:17 +00:00
|
|
|
)
|
|
|
|
|
2017-11-13 23:31:36 +00:00
|
|
|
const regionKey = "region"
|
|
|
|
|
|
|
|
// iopsVolumeTypes is a set of AWS EBS volume types for which IOPS should
|
|
|
|
// be captured during snapshot and provided when creating a new volume
|
|
|
|
// from snapshot.
|
|
|
|
var iopsVolumeTypes = sets.NewString("io1")
|
|
|
|
|
2017-11-02 18:36:11 +00:00
|
|
|
type blockStore struct {
|
2017-08-02 17:27:17 +00:00
|
|
|
ec2 *ec2.EC2
|
|
|
|
}
|
|
|
|
|
2017-08-15 20:20:23 +00:00
|
|
|
func getSession(config *aws.Config) (*session.Session, error) {
|
|
|
|
sess, err := session.NewSession(config)
|
|
|
|
if err != nil {
|
2017-09-14 21:27:31 +00:00
|
|
|
return nil, errors.WithStack(err)
|
2017-08-15 20:20:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := sess.Config.Credentials.Get(); err != nil {
|
2017-09-14 21:27:31 +00:00
|
|
|
return nil, errors.WithStack(err)
|
2017-08-15 20:20:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return sess, nil
|
|
|
|
}
|
|
|
|
|
2017-11-13 23:31:36 +00:00
|
|
|
func NewBlockStore() cloudprovider.BlockStore {
|
|
|
|
return &blockStore{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *blockStore) Init(config map[string]string) error {
|
|
|
|
region := config[regionKey]
|
2017-08-15 20:20:23 +00:00
|
|
|
if region == "" {
|
2017-11-13 23:31:36 +00:00
|
|
|
return errors.Errorf("missing %s in aws configuration", regionKey)
|
2017-08-15 20:20:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
awsConfig := aws.NewConfig().WithRegion(region)
|
|
|
|
|
|
|
|
sess, err := getSession(awsConfig)
|
|
|
|
if err != nil {
|
2017-11-13 23:31:36 +00:00
|
|
|
return err
|
2017-08-15 20:20:23 +00:00
|
|
|
}
|
|
|
|
|
2017-11-13 23:31:36 +00:00
|
|
|
b.ec2 = ec2.New(sess)
|
2017-08-15 20:20:23 +00:00
|
|
|
|
2017-11-13 23:31:36 +00:00
|
|
|
return nil
|
|
|
|
}
|
2017-08-11 19:10:47 +00:00
|
|
|
|
2017-11-13 23:31:36 +00:00
|
|
|
func (b *blockStore) CreateVolumeFromSnapshot(snapshotID, volumeType, volumeAZ string, iops *int64) (volumeID string, err error) {
|
2017-08-02 17:27:17 +00:00
|
|
|
req := &ec2.CreateVolumeInput{
|
|
|
|
SnapshotId: &snapshotID,
|
2017-09-25 01:45:36 +00:00
|
|
|
AvailabilityZone: &volumeAZ,
|
2017-08-02 17:27:17 +00:00
|
|
|
VolumeType: &volumeType,
|
|
|
|
}
|
|
|
|
|
2017-08-11 19:10:47 +00:00
|
|
|
if iopsVolumeTypes.Has(volumeType) && iops != nil {
|
2017-08-14 16:42:33 +00:00
|
|
|
req.Iops = iops
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
2017-11-13 23:31:36 +00:00
|
|
|
res, err := b.ec2.CreateVolume(req)
|
2017-08-02 17:27:17 +00:00
|
|
|
if err != nil {
|
2017-09-14 21:27:31 +00:00
|
|
|
return "", errors.WithStack(err)
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return *res.VolumeId, nil
|
|
|
|
}
|
|
|
|
|
2017-11-13 23:31:36 +00:00
|
|
|
func (b *blockStore) GetVolumeInfo(volumeID, volumeAZ string) (string, *int64, error) {
|
2017-08-02 17:27:17 +00:00
|
|
|
req := &ec2.DescribeVolumesInput{
|
|
|
|
VolumeIds: []*string{&volumeID},
|
|
|
|
}
|
|
|
|
|
2017-11-13 23:31:36 +00:00
|
|
|
res, err := b.ec2.DescribeVolumes(req)
|
2017-08-02 17:27:17 +00:00
|
|
|
if err != nil {
|
2017-09-14 21:27:31 +00:00
|
|
|
return "", nil, errors.WithStack(err)
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(res.Volumes) != 1 {
|
2017-09-14 21:27:31 +00:00
|
|
|
return "", nil, errors.Errorf("Expected one volume from DescribeVolumes for volume ID %v, got %v", volumeID, len(res.Volumes))
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
vol := res.Volumes[0]
|
|
|
|
|
|
|
|
var (
|
|
|
|
volumeType string
|
2017-08-14 16:42:33 +00:00
|
|
|
iops *int64
|
2017-08-02 17:27:17 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
if vol.VolumeType != nil {
|
|
|
|
volumeType = *vol.VolumeType
|
|
|
|
}
|
|
|
|
|
2017-08-11 19:10:47 +00:00
|
|
|
if iopsVolumeTypes.Has(volumeType) && vol.Iops != nil {
|
2017-08-14 16:42:33 +00:00
|
|
|
iops = vol.Iops
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
2017-08-11 19:10:47 +00:00
|
|
|
return volumeType, iops, nil
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
2017-11-13 23:31:36 +00:00
|
|
|
func (b *blockStore) IsVolumeReady(volumeID, volumeAZ string) (ready bool, err error) {
|
2017-08-02 17:27:17 +00:00
|
|
|
req := &ec2.DescribeVolumesInput{
|
|
|
|
VolumeIds: []*string{&volumeID},
|
|
|
|
}
|
|
|
|
|
2017-11-13 23:31:36 +00:00
|
|
|
res, err := b.ec2.DescribeVolumes(req)
|
2017-08-02 17:27:17 +00:00
|
|
|
if err != nil {
|
2017-09-14 21:27:31 +00:00
|
|
|
return false, errors.WithStack(err)
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
if len(res.Volumes) != 1 {
|
2017-09-14 21:27:31 +00:00
|
|
|
return false, errors.Errorf("Expected one volume from DescribeVolumes for volume ID %v, got %v", volumeID, len(res.Volumes))
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return *res.Volumes[0].State == ec2.VolumeStateAvailable, nil
|
|
|
|
}
|
|
|
|
|
2017-11-13 23:31:36 +00:00
|
|
|
func (b *blockStore) CreateSnapshot(volumeID, volumeAZ string, tags map[string]string) (string, error) {
|
2017-08-02 17:27:17 +00:00
|
|
|
req := &ec2.CreateSnapshotInput{
|
|
|
|
VolumeId: &volumeID,
|
|
|
|
}
|
|
|
|
|
2017-11-13 23:31:36 +00:00
|
|
|
res, err := b.ec2.CreateSnapshot(req)
|
2017-08-02 17:27:17 +00:00
|
|
|
if err != nil {
|
2017-09-14 21:27:31 +00:00
|
|
|
return "", errors.WithStack(err)
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
tagsReq := &ec2.CreateTagsInput{}
|
|
|
|
tagsReq.SetResources([]*string{res.SnapshotId})
|
|
|
|
|
|
|
|
ec2Tags := make([]*ec2.Tag, 0, len(tags))
|
|
|
|
|
|
|
|
for k, v := range tags {
|
|
|
|
key := k
|
|
|
|
val := v
|
|
|
|
|
|
|
|
tag := &ec2.Tag{Key: &key, Value: &val}
|
|
|
|
ec2Tags = append(ec2Tags, tag)
|
|
|
|
}
|
|
|
|
|
|
|
|
tagsReq.SetTags(ec2Tags)
|
|
|
|
|
2017-11-13 23:31:36 +00:00
|
|
|
_, err = b.ec2.CreateTags(tagsReq)
|
2017-08-02 17:27:17 +00:00
|
|
|
|
2017-09-14 21:27:31 +00:00
|
|
|
return *res.SnapshotId, errors.WithStack(err)
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
|
|
|
|
2017-11-13 23:31:36 +00:00
|
|
|
func (b *blockStore) DeleteSnapshot(snapshotID string) error {
|
2017-08-02 17:27:17 +00:00
|
|
|
req := &ec2.DeleteSnapshotInput{
|
|
|
|
SnapshotId: &snapshotID,
|
|
|
|
}
|
|
|
|
|
2017-11-13 23:31:36 +00:00
|
|
|
_, err := b.ec2.DeleteSnapshot(req)
|
2017-08-02 17:27:17 +00:00
|
|
|
|
2017-09-14 21:27:31 +00:00
|
|
|
return errors.WithStack(err)
|
2017-08-02 17:27:17 +00:00
|
|
|
}
|
2017-11-29 17:23:21 +00:00
|
|
|
|
|
|
|
var ebsVolumeIDRegex = regexp.MustCompile("vol-.*")
|
|
|
|
|
|
|
|
func (b *blockStore) GetVolumeID(pv runtime.Unstructured) (string, error) {
|
|
|
|
if !collections.Exists(pv.UnstructuredContent(), "spec.awsElasticBlockStore") {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
volumeID, err := collections.GetString(pv.UnstructuredContent(), "spec.awsElasticBlockStore.volumeID")
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return ebsVolumeIDRegex.FindString(volumeID), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *blockStore) SetVolumeID(pv runtime.Unstructured, volumeID string) (runtime.Unstructured, error) {
|
|
|
|
aws, err := collections.GetMap(pv.UnstructuredContent(), "spec.awsElasticBlockStore")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
aws["volumeID"] = volumeID
|
|
|
|
|
|
|
|
return pv, nil
|
|
|
|
}
|