Preserve PV's AZ info when snapshotting and restoring PVs.
- Read PV's AZ info from fault-domain label of the PV object for snapshotting. - Store PV's AZ info in the VolumeInfo. - Add tests for reading the label from the PV object. - Remove availability zone validation in AWS and GCP BlockStorageAdaptor. - Add volumeAZ as a parameter to methods in the BlockStorageAdapter interface. - Get AZ from VolumeInfo when restoring PV snapshot. - Remove references to PV availability zone in docs. Signed-off-by: Ashish Amarnath <ashish.amarnath@gmail.com>pull/102/head
parent
62ab21f849
commit
9fc9dbb413
|
@ -89,7 +89,7 @@ Now that you have your IAM user credentials stored in a Secret, you need to repl
|
|||
|
||||
* In file `examples/aws/00-ark-config.yaml`:
|
||||
|
||||
* Replace `<YOUR_BUCKET>`, `<YOUR_REGION>`, and `<YOUR_AVAILABILITY_ZONE>`. See the [Config definition][6] for details.
|
||||
* Replace `<YOUR_BUCKET>` and `<YOUR_REGION>`. See the [Config definition][6] for details.
|
||||
|
||||
|
||||
* In file `examples/common/10-deployment.yaml`:
|
||||
|
@ -167,7 +167,7 @@ Now that you have your Google Cloud credentials stored in a Secret, you need to
|
|||
|
||||
* In file `examples/gcp/00-ark-config.yaml`:
|
||||
|
||||
* Replace `<YOUR_BUCKET>`, `<YOUR_PROJECT>` and `<YOUR_ZONE>`. See the [Config definition][7] for details.
|
||||
* Replace `<YOUR_BUCKET>` and `<YOUR_PROJECT>`. See the [Config definition][7] for details.
|
||||
|
||||
|
||||
* In file `examples/common/10-deployment.yaml`:
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
# Ark Config definition
|
||||
|
||||
* [Overview][10]
|
||||
* [Example][11]
|
||||
* [Parameter Reference][8]
|
||||
* [Main config][9]
|
||||
* [Overview][8]
|
||||
* [Example][9]
|
||||
* [Parameter Reference][6]
|
||||
* [Main config][7]
|
||||
* [AWS][0]
|
||||
* [GCP][1]
|
||||
* [Azure][2]
|
||||
|
@ -26,7 +26,6 @@ metadata:
|
|||
persistentVolumeProvider:
|
||||
aws:
|
||||
region: us-west-2
|
||||
availabilityZone: us-west-2a
|
||||
backupStorageProvider:
|
||||
bucket: ark
|
||||
aws:
|
||||
|
@ -65,55 +64,51 @@ The configurable parameters are as follows:
|
|||
| `region` | string | Required Field | *Example*: "us-east-1"<br><br>See [AWS documentation][3] for the full list. |
|
||||
| `disableSSL` | bool | `false` | Set this to `true` if you are using Minio (or another local, S3-compatible storage service) and your deployment is not secured. |
|
||||
| `s3ForcePathStyle` | bool | `false` | Set this to `true` if you are using a local storage service like Minio. |
|
||||
| `s3Url` | string | Required field for non-AWS-hosted storage| *Example*: http://minio:9000<br><br>You can specify the AWS S3 URL here for explicitness, but Ark can already generate it from `region`, `availabilityZone`, and `bucket`. This field is primarily for local storage services like Minio.|
|
||||
| `kmsKeyID` | string | Empty | *Example*: "502b409c-4da1-419f-a16e-eif453b3i49f"<br><br>Specify an [AWS KMS key][12] id to enable encryption of the backups stored in S3. Only works with AWS S3 and may require explicitly granting key usage rights.|
|
||||
| `s3Url` | string | Required field for non-AWS-hosted storage| *Example*: http://minio:9000<br><br>You can specify the AWS S3 URL here for explicitness, but Ark can already generate it from `region`, and `bucket`. This field is primarily for local storage services like Minio.|
|
||||
| `kmsKeyID` | string | Empty | *Example*: "502b409c-4da1-419f-a16e-eif453b3i49f"<br><br>Specify an [AWS KMS key][10] id to enable encryption of the backups stored in S3. Only works with AWS S3 and may require explicitly granting key usage rights.|
|
||||
|
||||
#### persistentVolumeProvider (AWS Only)
|
||||
|
||||
| Key | Type | Default | Meaning |
|
||||
| --- | --- | --- | --- |
|
||||
| `region` | string | Required Field | *Example*: "us-east-1"<br><br>See [AWS documentation][3] for the full list. |
|
||||
| `availabilityZone` | string | Required Field | *Example*: "us-east-1a"<br><br>See [AWS documentation][4] for details. |
|
||||
|
||||
### GCP
|
||||
|
||||
#### backupStorageProvider
|
||||
|
||||
No parameters required; specify an empty object per [example file][13].
|
||||
No parameters required; specify an empty object per [example file][11].
|
||||
|
||||
#### persistentVolumeProvider
|
||||
|
||||
| Key | Type | Default | Meaning |
|
||||
| --- | --- | --- | --- |
|
||||
| `project` | string | Required Field | *Example*: "project-example-3jsn23"<br><br> See the [Project ID documentation][5] for details. |
|
||||
| `zone` | string | Required Field | *Example*: "us-central1-a"<br><br>See [GCP documentation][6] for the full list. |
|
||||
| `project` | string | Required Field | *Example*: "project-example-3jsn23"<br><br> See the [Project ID documentation][4] for details. |
|
||||
|
||||
### Azure
|
||||
|
||||
#### backupStorageProvider
|
||||
|
||||
No parameters required; specify an empty object per [example file][14].
|
||||
No parameters required; specify an empty object per [example file][12].
|
||||
|
||||
#### persistentVolumeProvider
|
||||
|
||||
| Key | Type | Default | Meaning |
|
||||
| --- | --- | --- | --- |
|
||||
| `location` | string | Required Field | *Example*: "Canada East"<br><br>See [the list of available locations][7] (note that this particular page refers to them as "Regions"). |
|
||||
| `location` | string | Required Field | *Example*: "Canada East"<br><br>See [the list of available locations][5] (note that this particular page refers to them as "Regions"). |
|
||||
| `apiTimeout` | metav1.Duration | 2m0s | How long to wait for an Azure API request to complete before timeout. |
|
||||
|
||||
[0]: #aws
|
||||
[1]: #gcp
|
||||
[2]: #azure
|
||||
[3]: http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-available-regions
|
||||
[4]: http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-availability-zones
|
||||
[5]: https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects
|
||||
[6]: https://cloud.google.com/compute/docs/regions-zones/regions-zones
|
||||
[7]: https://azure.microsoft.com/en-us/regions/
|
||||
[8]: #parameter-reference
|
||||
[9]: #main-config-parameters
|
||||
[10]: #overview
|
||||
[11]: #example
|
||||
[12]: http://docs.aws.amazon.com/kms/latest/developerguide/overview.html
|
||||
[13]: ../examples/gcp/00-ark-config.yaml
|
||||
[14]: ../examples/azure/10-ark-config.yaml
|
||||
[4]: https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects
|
||||
[5]: https://azure.microsoft.com/en-us/regions/
|
||||
[6]: #parameter-reference
|
||||
[7]: #main-config-parameters
|
||||
[8]: #overview
|
||||
[9]: #example
|
||||
[10]: http://docs.aws.amazon.com/kms/latest/developerguide/overview.html
|
||||
[11]: ../examples/gcp/00-ark-config.yaml
|
||||
[12]: ../examples/azure/10-ark-config.yaml
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ metadata:
|
|||
persistentVolumeProvider:
|
||||
aws:
|
||||
region: <YOUR_REGION>
|
||||
availabilityZone: <YOUR_AVAILABILITY_ZONE>
|
||||
backupStorageProvider:
|
||||
bucket: <YOUR_BUCKET>
|
||||
aws:
|
||||
|
|
|
@ -21,7 +21,6 @@ metadata:
|
|||
persistentVolumeProvider:
|
||||
gcp:
|
||||
project: <YOUR_PROJECT>
|
||||
zone: <YOUR_ZONE>
|
||||
backupStorageProvider:
|
||||
bucket: <YOUR_BUCKET>
|
||||
gcp: {}
|
||||
|
|
|
@ -109,6 +109,10 @@ type VolumeBackupInfo struct {
|
|||
// API.
|
||||
Type string `json:"type"`
|
||||
|
||||
// AvailabilityZone is the where the volume is provisioned
|
||||
// in the cloud provider.
|
||||
AvailabilityZone string `json:"availabilityZone,omitempty"`
|
||||
|
||||
// Iops is the optional value of provisioned IOPS for the
|
||||
// disk/volume in the cloud provider API.
|
||||
Iops *int64 `json:"iops,omitempty"`
|
||||
|
|
|
@ -94,7 +94,6 @@ type ObjectStorageProviderConfig struct {
|
|||
// AWSConfig is configuration information for connecting to AWS.
|
||||
type AWSConfig struct {
|
||||
Region string `json:"region"`
|
||||
AvailabilityZone string `json:"availabilityZone"`
|
||||
DisableSSL bool `json:"disableSSL"`
|
||||
S3ForcePathStyle bool `json:"s3ForcePathStyle"`
|
||||
S3Url string `json:"s3Url"`
|
||||
|
@ -104,7 +103,6 @@ type AWSConfig struct {
|
|||
// GCPConfig is configuration information for connecting to GCP.
|
||||
type GCPConfig struct {
|
||||
Project string `json:"project"`
|
||||
Zone string `json:"zone"`
|
||||
}
|
||||
|
||||
// AzureConfig is configuration information for connecting to Azure.
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/cloudprovider"
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
kubeutil "github.com/heptio/ark/pkg/util/kube"
|
||||
)
|
||||
|
||||
|
@ -59,6 +60,17 @@ func (a *volumeSnapshotAction) Execute(ctx ActionContext, volume map[string]inte
|
|||
|
||||
metadata := volume["metadata"].(map[string]interface{})
|
||||
name := metadata["name"].(string)
|
||||
var pvfailureDomainZone string
|
||||
labelsMap, err := collections.GetMap(metadata, "labels")
|
||||
if err == nil {
|
||||
if labelsMap["failure-domain.beta.kubernetes.io/zone"] != nil {
|
||||
pvfailureDomainZone = labelsMap["failure-domain.beta.kubernetes.io/zone"].(string)
|
||||
} else {
|
||||
ctx.log("error getting 'failure-domain.beta.kubernetes.io/zone' label on PersistentVolume %q for backup %q.\n", name, backupName)
|
||||
}
|
||||
} else {
|
||||
ctx.log("error getting labels on PersistentVolume %q for backup %q. ", name, backupName)
|
||||
}
|
||||
|
||||
volumeID, err := kubeutil.GetVolumeID(volume)
|
||||
// non-nil error means it's a supported PV source but volume ID can't be found
|
||||
|
@ -75,13 +87,13 @@ func (a *volumeSnapshotAction) Execute(ctx ActionContext, volume map[string]inte
|
|||
|
||||
ctx.log("Backup %q: snapshotting PersistentVolume %q, volume-id %q, expiration %v", backupName, name, volumeID, expiration)
|
||||
|
||||
snapshotID, err := a.snapshotService.CreateSnapshot(volumeID)
|
||||
snapshotID, err := a.snapshotService.CreateSnapshot(volumeID, pvfailureDomainZone)
|
||||
if err != nil {
|
||||
ctx.log("error creating snapshot for backup %q, volume %q, volume-id %q: %v", backupName, name, volumeID, err)
|
||||
return err
|
||||
}
|
||||
|
||||
volumeType, iops, err := a.snapshotService.GetVolumeInfo(volumeID)
|
||||
volumeType, iops, err := a.snapshotService.GetVolumeInfo(volumeID, pvfailureDomainZone)
|
||||
if err != nil {
|
||||
ctx.log("error getting volume info for backup %q, volume %q, volume-id %q: %v", backupName, name, volumeID, err)
|
||||
return err
|
||||
|
@ -92,9 +104,10 @@ func (a *volumeSnapshotAction) Execute(ctx ActionContext, volume map[string]inte
|
|||
}
|
||||
|
||||
backup.Status.VolumeBackups[name] = &api.VolumeBackupInfo{
|
||||
SnapshotID: snapshotID,
|
||||
Type: volumeType,
|
||||
Iops: iops,
|
||||
SnapshotID: snapshotID,
|
||||
Type: volumeType,
|
||||
Iops: iops,
|
||||
AvailabilityZone: pvfailureDomainZone,
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -77,49 +77,49 @@ func TestVolumeSnapshotAction(t *testing.T) {
|
|||
{
|
||||
name: "aws - simple volume id",
|
||||
snapshotEnabled: true,
|
||||
pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv"}, "spec": {"awsElasticBlockStore": {"volumeID": "vol-abc123"}}}`,
|
||||
pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv", "labels": {"failure-domain.beta.kubernetes.io/zone": "us-east-1c"}}, "spec": {"awsElasticBlockStore": {"volumeID": "aws://us-east-1c/vol-abc123"}}}`,
|
||||
expectError: false,
|
||||
expectedSnapshotsTaken: 1,
|
||||
expectedVolumeID: "vol-abc123",
|
||||
ttl: 5 * time.Minute,
|
||||
volumeInfo: map[string]v1.VolumeBackupInfo{
|
||||
"vol-abc123": v1.VolumeBackupInfo{Type: "gp", SnapshotID: "snap-1"},
|
||||
"vol-abc123": v1.VolumeBackupInfo{Type: "gp", SnapshotID: "snap-1", AvailabilityZone: "us-east-1c"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "aws - simple volume id with provisioned IOPS",
|
||||
snapshotEnabled: true,
|
||||
pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv"}, "spec": {"awsElasticBlockStore": {"volumeID": "vol-abc123"}}}`,
|
||||
pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv", "labels": {"failure-domain.beta.kubernetes.io/zone": "us-east-1c"}}, "spec": {"awsElasticBlockStore": {"volumeID": "aws://us-east-1c/vol-abc123"}}}`,
|
||||
expectError: false,
|
||||
expectedSnapshotsTaken: 1,
|
||||
expectedVolumeID: "vol-abc123",
|
||||
ttl: 5 * time.Minute,
|
||||
volumeInfo: map[string]v1.VolumeBackupInfo{
|
||||
"vol-abc123": v1.VolumeBackupInfo{Type: "io1", Iops: &iops, SnapshotID: "snap-1"},
|
||||
"vol-abc123": v1.VolumeBackupInfo{Type: "io1", Iops: &iops, SnapshotID: "snap-1", AvailabilityZone: "us-east-1c"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "aws - dynamically provisioned volume id",
|
||||
snapshotEnabled: true,
|
||||
pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv"}, "spec": {"awsElasticBlockStore": {"volumeID": "aws://us-west-2a/vol-abc123"}}}`,
|
||||
pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv", "labels": {"failure-domain.beta.kubernetes.io/zone": "us-west-2a"}}, "spec": {"awsElasticBlockStore": {"volumeID": "aws://us-west-2a/vol-abc123"}}}`,
|
||||
expectError: false,
|
||||
expectedSnapshotsTaken: 1,
|
||||
expectedVolumeID: "vol-abc123",
|
||||
ttl: 5 * time.Minute,
|
||||
volumeInfo: map[string]v1.VolumeBackupInfo{
|
||||
"vol-abc123": v1.VolumeBackupInfo{Type: "gp", SnapshotID: "snap-1"},
|
||||
"vol-abc123": v1.VolumeBackupInfo{Type: "gp", SnapshotID: "snap-1", AvailabilityZone: "us-west-2a"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "gce",
|
||||
snapshotEnabled: true,
|
||||
pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv"}, "spec": {"gcePersistentDisk": {"pdName": "pd-abc123"}}}`,
|
||||
pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv", "labels": {"failure-domain.beta.kubernetes.io/zone": "gcp-zone2"}}, "spec": {"gcePersistentDisk": {"pdName": "pd-abc123"}}}`,
|
||||
expectError: false,
|
||||
expectedSnapshotsTaken: 1,
|
||||
expectedVolumeID: "pd-abc123",
|
||||
ttl: 5 * time.Minute,
|
||||
volumeInfo: map[string]v1.VolumeBackupInfo{
|
||||
"pd-abc123": v1.VolumeBackupInfo{Type: "gp", SnapshotID: "snap-1"},
|
||||
"pd-abc123": v1.VolumeBackupInfo{Type: "gp", SnapshotID: "snap-1", AvailabilityZone: "gcp-zone2"},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -155,6 +155,18 @@ func TestVolumeSnapshotAction(t *testing.T) {
|
|||
pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv"}, "spec": {"gcePersistentDisk": {"pdName": "pd-abc123"}}}`,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "PV with label metadata but no failureDomainZone",
|
||||
snapshotEnabled: true,
|
||||
pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv", "labels": {"failure-domain.beta.kubernetes.io/region": "us-east-1"}}, "spec": {"awsElasticBlockStore": {"volumeID": "aws://us-east-1c/vol-abc123"}}}`,
|
||||
expectError: false,
|
||||
expectedSnapshotsTaken: 1,
|
||||
expectedVolumeID: "vol-abc123",
|
||||
ttl: 5 * time.Minute,
|
||||
volumeInfo: map[string]v1.VolumeBackupInfo{
|
||||
"vol-abc123": v1.VolumeBackupInfo{Type: "gp", SnapshotID: "snap-1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
@ -216,9 +228,10 @@ func TestVolumeSnapshotAction(t *testing.T) {
|
|||
snapshotID, _ := snapshotService.SnapshotsTaken.PopAny()
|
||||
|
||||
expectedVolumeBackups["mypv"] = &v1.VolumeBackupInfo{
|
||||
SnapshotID: snapshotID,
|
||||
Type: test.volumeInfo[test.expectedVolumeID].Type,
|
||||
Iops: test.volumeInfo[test.expectedVolumeID].Iops,
|
||||
SnapshotID: snapshotID,
|
||||
Type: test.volumeInfo[test.expectedVolumeID].Type,
|
||||
Iops: test.volumeInfo[test.expectedVolumeID].Iops,
|
||||
AvailabilityZone: test.volumeInfo[test.expectedVolumeID].AvailabilityZone,
|
||||
}
|
||||
|
||||
if e, a := expectedVolumeBackups, backup.Status.VolumeBackups; !reflect.DeepEqual(e, a) {
|
||||
|
|
|
@ -33,7 +33,6 @@ var _ cloudprovider.BlockStorageAdapter = &blockStorageAdapter{}
|
|||
|
||||
type blockStorageAdapter struct {
|
||||
ec2 *ec2.EC2
|
||||
az string
|
||||
}
|
||||
|
||||
func getSession(config *aws.Config) (*session.Session, error) {
|
||||
|
@ -49,13 +48,10 @@ func getSession(config *aws.Config) (*session.Session, error) {
|
|||
return sess, nil
|
||||
}
|
||||
|
||||
func NewBlockStorageAdapter(region, availabilityZone string) (cloudprovider.BlockStorageAdapter, error) {
|
||||
func NewBlockStorageAdapter(region string) (cloudprovider.BlockStorageAdapter, error) {
|
||||
if region == "" {
|
||||
return nil, errors.New("missing region in aws configuration in config file")
|
||||
}
|
||||
if availabilityZone == "" {
|
||||
return nil, errors.New("missing availabilityZone in aws configuration in config file")
|
||||
}
|
||||
|
||||
awsConfig := aws.NewConfig().WithRegion(region)
|
||||
|
||||
|
@ -64,22 +60,8 @@ func NewBlockStorageAdapter(region, availabilityZone string) (cloudprovider.Bloc
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// validate the availabilityZone
|
||||
var (
|
||||
ec2Client = ec2.New(sess)
|
||||
azReq = &ec2.DescribeAvailabilityZonesInput{ZoneNames: []*string{&availabilityZone}}
|
||||
)
|
||||
res, err := ec2Client.DescribeAvailabilityZones(azReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(res.AvailabilityZones) == 0 {
|
||||
return nil, fmt.Errorf("availability zone %q not found", availabilityZone)
|
||||
}
|
||||
|
||||
return &blockStorageAdapter{
|
||||
ec2: ec2Client,
|
||||
az: availabilityZone,
|
||||
ec2: ec2.New(sess),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -88,10 +70,10 @@ func NewBlockStorageAdapter(region, availabilityZone string) (cloudprovider.Bloc
|
|||
// from snapshot.
|
||||
var iopsVolumeTypes = sets.NewString("io1")
|
||||
|
||||
func (op *blockStorageAdapter) CreateVolumeFromSnapshot(snapshotID, volumeType string, iops *int64) (volumeID string, err error) {
|
||||
func (op *blockStorageAdapter) CreateVolumeFromSnapshot(snapshotID, volumeType, volumeAZ string, iops *int64) (volumeID string, err error) {
|
||||
req := &ec2.CreateVolumeInput{
|
||||
SnapshotId: &snapshotID,
|
||||
AvailabilityZone: &op.az,
|
||||
AvailabilityZone: &volumeAZ,
|
||||
VolumeType: &volumeType,
|
||||
}
|
||||
|
||||
|
@ -107,7 +89,7 @@ func (op *blockStorageAdapter) CreateVolumeFromSnapshot(snapshotID, volumeType s
|
|||
return *res.VolumeId, nil
|
||||
}
|
||||
|
||||
func (op *blockStorageAdapter) GetVolumeInfo(volumeID string) (string, *int64, error) {
|
||||
func (op *blockStorageAdapter) GetVolumeInfo(volumeID, volumeAZ string) (string, *int64, error) {
|
||||
req := &ec2.DescribeVolumesInput{
|
||||
VolumeIds: []*string{&volumeID},
|
||||
}
|
||||
|
@ -139,7 +121,7 @@ func (op *blockStorageAdapter) GetVolumeInfo(volumeID string) (string, *int64, e
|
|||
return volumeType, iops, nil
|
||||
}
|
||||
|
||||
func (op *blockStorageAdapter) IsVolumeReady(volumeID string) (ready bool, err error) {
|
||||
func (op *blockStorageAdapter) IsVolumeReady(volumeID, volumeAZ string) (ready bool, err error) {
|
||||
req := &ec2.DescribeVolumesInput{
|
||||
VolumeIds: []*string{&volumeID},
|
||||
}
|
||||
|
@ -181,7 +163,7 @@ func (op *blockStorageAdapter) ListSnapshots(tagFilters map[string]string) ([]st
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
func (op *blockStorageAdapter) CreateSnapshot(volumeID string, tags map[string]string) (string, error) {
|
||||
func (op *blockStorageAdapter) CreateSnapshot(volumeID, volumeAZ string, tags map[string]string) (string, error) {
|
||||
req := &ec2.CreateSnapshotInput{
|
||||
VolumeId: &volumeID,
|
||||
}
|
||||
|
|
|
@ -130,7 +130,7 @@ func NewBlockStorageAdapter(location string, apiTimeout time.Duration) (cloudpro
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (op *blockStorageAdapter) CreateVolumeFromSnapshot(snapshotID, volumeType string, iops *int64) (string, error) {
|
||||
func (op *blockStorageAdapter) CreateVolumeFromSnapshot(snapshotID, volumeType, volumeAZ string, iops *int64) (string, error) {
|
||||
fullSnapshotName := getFullSnapshotName(op.subscription, op.resourceGroup, snapshotID)
|
||||
diskName := "restore-" + uuid.NewV4().String()
|
||||
|
||||
|
@ -159,7 +159,7 @@ func (op *blockStorageAdapter) CreateVolumeFromSnapshot(snapshotID, volumeType s
|
|||
return diskName, nil
|
||||
}
|
||||
|
||||
func (op *blockStorageAdapter) GetVolumeInfo(volumeID string) (string, *int64, error) {
|
||||
func (op *blockStorageAdapter) GetVolumeInfo(volumeID, volumeAZ string) (string, *int64, error) {
|
||||
res, err := op.disks.Get(op.resourceGroup, volumeID)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
|
@ -168,7 +168,7 @@ func (op *blockStorageAdapter) GetVolumeInfo(volumeID string) (string, *int64, e
|
|||
return string(res.AccountType), nil, nil
|
||||
}
|
||||
|
||||
func (op *blockStorageAdapter) IsVolumeReady(volumeID string) (ready bool, err error) {
|
||||
func (op *blockStorageAdapter) IsVolumeReady(volumeID, volumeAZ string) (ready bool, err error) {
|
||||
res, err := op.disks.Get(op.resourceGroup, volumeID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
@ -215,7 +215,7 @@ Snapshot:
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
func (op *blockStorageAdapter) CreateSnapshot(volumeID string, tags map[string]string) (string, error) {
|
||||
func (op *blockStorageAdapter) CreateSnapshot(volumeID, volumeAZ string, tags map[string]string) (string, error) {
|
||||
fullDiskName := getFullDiskName(op.subscription, op.resourceGroup, volumeID)
|
||||
// snapshot names must be <= 80 characters long
|
||||
var snapshotName string
|
||||
|
|
|
@ -35,18 +35,14 @@ import (
|
|||
type blockStorageAdapter struct {
|
||||
gce *compute.Service
|
||||
project string
|
||||
zone string
|
||||
}
|
||||
|
||||
var _ cloudprovider.BlockStorageAdapter = &blockStorageAdapter{}
|
||||
|
||||
func NewBlockStorageAdapter(project, zone string) (cloudprovider.BlockStorageAdapter, error) {
|
||||
func NewBlockStorageAdapter(project string) (cloudprovider.BlockStorageAdapter, error) {
|
||||
if project == "" {
|
||||
return nil, errors.New("missing project in gcp configuration in config file")
|
||||
}
|
||||
if zone == "" {
|
||||
return nil, errors.New("missing zone in gcp configuration in config file")
|
||||
}
|
||||
|
||||
client, err := google.DefaultClient(oauth2.NoContext, compute.ComputeScope)
|
||||
if err != nil {
|
||||
|
@ -58,24 +54,23 @@ func NewBlockStorageAdapter(project, zone string) (cloudprovider.BlockStorageAda
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// validate project & zone
|
||||
res, err := gce.Zones.Get(project, zone).Do()
|
||||
// validate project
|
||||
res, err := gce.Projects.Get(project).Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res == nil {
|
||||
return nil, fmt.Errorf("zone %q not found for project %q", project, zone)
|
||||
return nil, fmt.Errorf("error getting project %q", project)
|
||||
}
|
||||
|
||||
return &blockStorageAdapter{
|
||||
gce: gce,
|
||||
project: project,
|
||||
zone: zone,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (op *blockStorageAdapter) CreateVolumeFromSnapshot(snapshotID string, volumeType string, iops *int64) (volumeID string, err error) {
|
||||
func (op *blockStorageAdapter) CreateVolumeFromSnapshot(snapshotID, volumeType, volumeAZ string, iops *int64) (volumeID string, err error) {
|
||||
res, err := op.gce.Snapshots.Get(op.project, snapshotID).Do()
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -87,15 +82,15 @@ func (op *blockStorageAdapter) CreateVolumeFromSnapshot(snapshotID string, volum
|
|||
Type: volumeType,
|
||||
}
|
||||
|
||||
if _, err = op.gce.Disks.Insert(op.project, op.zone, disk).Do(); err != nil {
|
||||
if _, err = op.gce.Disks.Insert(op.project, volumeAZ, disk).Do(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return disk.Name, nil
|
||||
}
|
||||
|
||||
func (op *blockStorageAdapter) GetVolumeInfo(volumeID string) (string, *int64, error) {
|
||||
res, err := op.gce.Disks.Get(op.project, op.zone, volumeID).Do()
|
||||
func (op *blockStorageAdapter) GetVolumeInfo(volumeID, volumeAZ string) (string, *int64, error) {
|
||||
res, err := op.gce.Disks.Get(op.project, volumeAZ, volumeID).Do()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
@ -103,8 +98,8 @@ func (op *blockStorageAdapter) GetVolumeInfo(volumeID string) (string, *int64, e
|
|||
return res.Type, nil, nil
|
||||
}
|
||||
|
||||
func (op *blockStorageAdapter) IsVolumeReady(volumeID string) (ready bool, err error) {
|
||||
disk, err := op.gce.Disks.Get(op.project, op.zone, volumeID).Do()
|
||||
func (op *blockStorageAdapter) IsVolumeReady(volumeID, volumeAZ string) (ready bool, err error) {
|
||||
disk, err := op.gce.Disks.Get(op.project, volumeAZ, volumeID).Do()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -140,7 +135,7 @@ func (op *blockStorageAdapter) ListSnapshots(tagFilters map[string]string) ([]st
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
func (op *blockStorageAdapter) CreateSnapshot(volumeID string, tags map[string]string) (string, error) {
|
||||
func (op *blockStorageAdapter) CreateSnapshot(volumeID, volumeAZ string, tags map[string]string) (string, error) {
|
||||
// snapshot names must adhere to RFC1035 and be 1-63 characters
|
||||
// long
|
||||
var snapshotName string
|
||||
|
@ -156,7 +151,7 @@ func (op *blockStorageAdapter) CreateSnapshot(volumeID string, tags map[string]s
|
|||
Name: snapshotName,
|
||||
}
|
||||
|
||||
_, err := op.gce.Disks.CreateSnapshot(op.project, op.zone, volumeID, &gceSnap).Do()
|
||||
_, err := op.gce.Disks.CreateSnapshot(op.project, volumeAZ, volumeID, &gceSnap).Do()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
@ -32,19 +32,19 @@ type SnapshotService interface {
|
|||
// CreateSnapshot triggers a snapshot for the specified cloud volume and tags it with metadata.
|
||||
// it returns the cloud snapshot ID, or an error if a problem is encountered triggering the snapshot via
|
||||
// the cloud API.
|
||||
CreateSnapshot(volumeID string) (string, error)
|
||||
CreateSnapshot(volumeID, volumeAZ string) (string, error)
|
||||
|
||||
// CreateVolumeFromSnapshot triggers a restore operation to create a new cloud volume from the specified
|
||||
// snapshot and volume characteristics. Returns the cloud volume ID, or an error if a problem is
|
||||
// encountered triggering the restore via the cloud API.
|
||||
CreateVolumeFromSnapshot(snapshotID, volumeType string, iops *int64) (string, error)
|
||||
CreateVolumeFromSnapshot(snapshotID, volumeType, volumeAZ string, iops *int64) (string, error)
|
||||
|
||||
// DeleteSnapshot triggers a deletion of the specified Ark snapshot via the cloud API. It returns an
|
||||
// error if a problem is encountered triggering the deletion via the cloud API.
|
||||
DeleteSnapshot(snapshotID string) error
|
||||
|
||||
// GetVolumeInfo gets the type and IOPS (if applicable) from the cloud API.
|
||||
GetVolumeInfo(volumeID string) (string, *int64, error)
|
||||
GetVolumeInfo(volumeID, volumeAZ string) (string, *int64, error)
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -67,8 +67,8 @@ func NewSnapshotService(blockStorage BlockStorageAdapter) SnapshotService {
|
|||
}
|
||||
}
|
||||
|
||||
func (sr *snapshotService) CreateVolumeFromSnapshot(snapshotID string, volumeType string, iops *int64) (string, error) {
|
||||
volumeID, err := sr.blockStorage.CreateVolumeFromSnapshot(snapshotID, volumeType, iops)
|
||||
func (sr *snapshotService) CreateVolumeFromSnapshot(snapshotID string, volumeType string, volumeAZ string, iops *int64) (string, error) {
|
||||
volumeID, err := sr.blockStorage.CreateVolumeFromSnapshot(snapshotID, volumeType, volumeAZ, iops)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ func (sr *snapshotService) CreateVolumeFromSnapshot(snapshotID string, volumeTyp
|
|||
case <-timeout.C:
|
||||
return "", fmt.Errorf("timeout reached waiting for volume %v to be ready", volumeID)
|
||||
case <-ticker.C:
|
||||
if ready, err := sr.blockStorage.IsVolumeReady(volumeID); err == nil && ready {
|
||||
if ready, err := sr.blockStorage.IsVolumeReady(volumeID, volumeAZ); err == nil && ready {
|
||||
return volumeID, nil
|
||||
}
|
||||
}
|
||||
|
@ -104,18 +104,18 @@ func (sr *snapshotService) GetAllSnapshots() ([]string, error) {
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func (sr *snapshotService) CreateSnapshot(volumeID string) (string, error) {
|
||||
func (sr *snapshotService) CreateSnapshot(volumeID, volumeAZ string) (string, error) {
|
||||
tags := map[string]string{
|
||||
snapshotTagKey: snapshotTagVal,
|
||||
}
|
||||
|
||||
return sr.blockStorage.CreateSnapshot(volumeID, tags)
|
||||
return sr.blockStorage.CreateSnapshot(volumeID, volumeAZ, tags)
|
||||
}
|
||||
|
||||
func (sr *snapshotService) DeleteSnapshot(snapshotID string) error {
|
||||
return sr.blockStorage.DeleteSnapshot(snapshotID)
|
||||
}
|
||||
|
||||
func (sr *snapshotService) GetVolumeInfo(volumeID string) (string, *int64, error) {
|
||||
return sr.blockStorage.GetVolumeInfo(volumeID)
|
||||
func (sr *snapshotService) GetVolumeInfo(volumeID, volumeAZ string) (string, *int64, error) {
|
||||
return sr.blockStorage.GetVolumeInfo(volumeID, volumeAZ)
|
||||
}
|
||||
|
|
|
@ -53,21 +53,21 @@ type ObjectStorageAdapter interface {
|
|||
type BlockStorageAdapter interface {
|
||||
// CreateVolumeFromSnapshot creates a new block volume, initialized from the provided snapshot,
|
||||
// and with the specified type and IOPS (if using provisioned IOPS).
|
||||
CreateVolumeFromSnapshot(snapshotID, volumeType string, iops *int64) (volumeID string, err error)
|
||||
CreateVolumeFromSnapshot(snapshotID, volumeType, volumeAZ string, iops *int64) (volumeID string, err error)
|
||||
|
||||
// GetVolumeInfo returns the type and IOPS (if using provisioned IOPS) for a specified block
|
||||
// volume.
|
||||
GetVolumeInfo(volumeID string) (string, *int64, error)
|
||||
GetVolumeInfo(volumeID, volumeAZ string) (string, *int64, error)
|
||||
|
||||
// IsVolumeReady returns whether the specified volume is ready to be used.
|
||||
IsVolumeReady(volumeID string) (ready bool, err error)
|
||||
IsVolumeReady(volumeID, volumeAZ string) (ready bool, err error)
|
||||
|
||||
// ListSnapshots returns a list of all snapshots matching the specified set of tag key/values.
|
||||
ListSnapshots(tagFilters map[string]string) ([]string, error)
|
||||
|
||||
// CreateSnapshot creates a snapshot of the specified block volume, and applies the provided
|
||||
// set of tags to the snapshot.
|
||||
CreateSnapshot(volumeID string, tags map[string]string) (snapshotID string, err error)
|
||||
CreateSnapshot(volumeID, volumeAZ string, tags map[string]string) (snapshotID string, err error)
|
||||
|
||||
// DeleteSnapshot deletes the specified volume snapshot.
|
||||
DeleteSnapshot(snapshotID string) error
|
||||
|
|
|
@ -375,9 +375,9 @@ func getBlockStorageProvider(cloudConfig api.CloudProviderConfig, field string)
|
|||
|
||||
switch {
|
||||
case cloudConfig.AWS != nil:
|
||||
blockStorage, err = arkaws.NewBlockStorageAdapter(cloudConfig.AWS.Region, cloudConfig.AWS.AvailabilityZone)
|
||||
blockStorage, err = arkaws.NewBlockStorageAdapter(cloudConfig.AWS.Region)
|
||||
case cloudConfig.GCP != nil:
|
||||
blockStorage, err = gcp.NewBlockStorageAdapter(cloudConfig.GCP.Project, cloudConfig.GCP.Zone)
|
||||
blockStorage, err = gcp.NewBlockStorageAdapter(cloudConfig.GCP.Project)
|
||||
case cloudConfig.Azure != nil:
|
||||
blockStorage, err = azure.NewBlockStorageAdapter(cloudConfig.Azure.Location, cloudConfig.Azure.APITimeout.Duration)
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ func (sr *persistentVolumeRestorer) Prepare(obj runtime.Unstructured, restore *a
|
|||
if restoreFromSnapshot {
|
||||
backupInfo := backup.Status.VolumeBackups[pvName]
|
||||
|
||||
volumeID, err := sr.snapshotService.CreateVolumeFromSnapshot(backupInfo.SnapshotID, backupInfo.Type, backupInfo.Iops)
|
||||
volumeID, err := sr.snapshotService.CreateVolumeFromSnapshot(backupInfo.SnapshotID, backupInfo.Type, backupInfo.AvailabilityZone, backupInfo.Iops)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ func (s *FakeSnapshotService) GetAllSnapshots() ([]string, error) {
|
|||
return s.SnapshotsTaken.List(), nil
|
||||
}
|
||||
|
||||
func (s *FakeSnapshotService) CreateSnapshot(volumeID string) (string, error) {
|
||||
func (s *FakeSnapshotService) CreateSnapshot(volumeID, volumeAZ string) (string, error) {
|
||||
if _, exists := s.SnapshottableVolumes[volumeID]; !exists {
|
||||
return "", errors.New("snapshottable volume not found")
|
||||
}
|
||||
|
@ -52,11 +52,12 @@ func (s *FakeSnapshotService) CreateSnapshot(volumeID string) (string, error) {
|
|||
return s.SnapshottableVolumes[volumeID].SnapshotID, nil
|
||||
}
|
||||
|
||||
func (s *FakeSnapshotService) CreateVolumeFromSnapshot(snapshotID, volumeType string, iops *int64) (string, error) {
|
||||
func (s *FakeSnapshotService) CreateVolumeFromSnapshot(snapshotID, volumeType, volumeAZ string, iops *int64) (string, error) {
|
||||
key := api.VolumeBackupInfo{
|
||||
SnapshotID: snapshotID,
|
||||
Type: volumeType,
|
||||
Iops: iops,
|
||||
SnapshotID: snapshotID,
|
||||
Type: volumeType,
|
||||
Iops: iops,
|
||||
AvailabilityZone: volumeAZ,
|
||||
}
|
||||
|
||||
return s.RestorableVolumes[key], nil
|
||||
|
@ -72,7 +73,7 @@ func (s *FakeSnapshotService) DeleteSnapshot(snapshotID string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *FakeSnapshotService) GetVolumeInfo(volumeID string) (string, *int64, error) {
|
||||
func (s *FakeSnapshotService) GetVolumeInfo(volumeID, volumeAZ string) (string, *int64, error) {
|
||||
if volumeInfo, exists := s.SnapshottableVolumes[volumeID]; !exists {
|
||||
return "", nil, errors.New("VolumeID not found")
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue