diff --git a/pkg/backup/item_backupper.go b/pkg/backup/item_backupper.go index fd2596985..a75573d18 100644 --- a/pkg/backup/item_backupper.go +++ b/pkg/backup/item_backupper.go @@ -36,7 +36,6 @@ import ( "github.com/heptio/ark/pkg/cloudprovider" "github.com/heptio/ark/pkg/discovery" "github.com/heptio/ark/pkg/util/collections" - kubeutil "github.com/heptio/ark/pkg/util/kube" "github.com/heptio/ark/pkg/util/logging" ) @@ -289,12 +288,10 @@ func (ib *defaultItemBackupper) takePVSnapshot(pv runtime.Unstructured, backup * log.Infof("label %q is not present on PersistentVolume", zoneLabel) } - volumeID, err := kubeutil.GetVolumeID(pv.UnstructuredContent()) - // non-nil error means it's a supported PV source but volume ID can't be found + volumeID, err := ib.snapshotService.GetVolumeID(pv) if err != nil { return errors.Wrapf(err, "error getting volume ID for PersistentVolume") } - // no volumeID / nil error means unsupported PV source if volumeID == "" { log.Info("PersistentVolume is not a supported volume type for snapshots, skipping.") return nil diff --git a/pkg/backup/item_backupper_test.go b/pkg/backup/item_backupper_test.go index 633448dea..45f8f5626 100644 --- a/pkg/backup/item_backupper_test.go +++ b/pkg/backup/item_backupper_test.go @@ -324,7 +324,10 @@ func TestBackupItemNoSkips(t *testing.T) { var snapshotService *arktest.FakeSnapshotService if test.snapshottableVolumes != nil { - snapshotService = &arktest.FakeSnapshotService{SnapshottableVolumes: test.snapshottableVolumes} + snapshotService = &arktest.FakeSnapshotService{ + SnapshottableVolumes: test.snapshottableVolumes, + VolumeID: "vol-abc123", + } b.snapshotService = snapshotService } @@ -356,7 +359,7 @@ func TestBackupItemNoSkips(t *testing.T) { err = b.backupItem(arktest.NewLogger(), obj, groupResource) gotError := err != nil if e, a := test.expectError, gotError; e != a { - t.Fatalf("error: expected %t, got %t", e, a) + t.Fatalf("error: expected %t, got %t: %v", e, a, err) } if test.expectError { return @@ -445,12 +448,6 @@ func TestTakePVSnapshot(t *testing.T) { pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv"}}`, snapshotEnabled: false, }, - { - name: "can't find volume id - missing spec", - snapshotEnabled: true, - pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv"}}`, - expectError: true, - }, { name: "unsupported PV source type", snapshotEnabled: true, @@ -458,19 +455,7 @@ func TestTakePVSnapshot(t *testing.T) { expectError: false, }, { - name: "can't find volume id - aws but no volume id", - snapshotEnabled: true, - pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv"}, "spec": {"awsElasticBlockStore": {}}}`, - expectError: true, - }, - { - name: "can't find volume id - gce but no volume id", - snapshotEnabled: true, - pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv"}, "spec": {"gcePersistentDisk": {}}}`, - expectError: true, - }, - { - name: "aws - simple volume id", + name: "without iops", snapshotEnabled: true, 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, @@ -482,7 +467,7 @@ func TestTakePVSnapshot(t *testing.T) { }, }, { - name: "aws - simple volume id with provisioned IOPS", + name: "with iops", snapshotEnabled: true, 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, @@ -493,42 +478,6 @@ func TestTakePVSnapshot(t *testing.T) { "vol-abc123": {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", "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": {Type: "gp", SnapshotID: "snap-1", AvailabilityZone: "us-west-2a"}, - }, - }, - { - name: "gce", - snapshotEnabled: true, - 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": {Type: "gp", SnapshotID: "snap-1", AvailabilityZone: "gcp-zone2"}, - }, - }, - { - name: "azure", - snapshotEnabled: true, - pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv"}, "spec": {"azureDisk": {"diskName": "foo-disk"}}}`, - expectError: false, - expectedSnapshotsTaken: 1, - expectedVolumeID: "foo-disk", - ttl: 5 * time.Minute, - volumeInfo: map[string]v1.VolumeBackupInfo{ - "foo-disk": {Type: "gp", SnapshotID: "snap-1"}, - }, - }, { name: "preexisting volume backup info in backup status", snapshotEnabled: true, @@ -545,10 +494,11 @@ func TestTakePVSnapshot(t *testing.T) { }, }, { - name: "create snapshot error", - snapshotEnabled: true, - pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv"}, "spec": {"gcePersistentDisk": {"pdName": "pd-abc123"}}}`, - expectError: true, + name: "create snapshot error", + snapshotEnabled: true, + pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv"}, "spec": {"gcePersistentDisk": {"pdName": "pd-abc123"}}}`, + expectedVolumeID: "pd-abc123", + expectError: true, }, { name: "PV with label metadata but no failureDomainZone", @@ -580,7 +530,10 @@ func TestTakePVSnapshot(t *testing.T) { }, } - snapshotService := &arktest.FakeSnapshotService{SnapshottableVolumes: test.volumeInfo} + snapshotService := &arktest.FakeSnapshotService{ + SnapshottableVolumes: test.volumeInfo, + VolumeID: test.expectedVolumeID, + } ib := &defaultItemBackupper{snapshotService: snapshotService} diff --git a/pkg/cloudprovider/aws/block_store.go b/pkg/cloudprovider/aws/block_store.go index 5c41ea6f3..d01a62e37 100644 --- a/pkg/cloudprovider/aws/block_store.go +++ b/pkg/cloudprovider/aws/block_store.go @@ -17,14 +17,18 @@ limitations under the License. package aws import ( + "regexp" + "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/pkg/errors" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" "github.com/heptio/ark/pkg/cloudprovider" + "github.com/heptio/ark/pkg/util/collections" ) const regionKey = "region" @@ -205,3 +209,29 @@ func (b *blockStore) DeleteSnapshot(snapshotID string) error { return errors.WithStack(err) } + +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 +} diff --git a/pkg/cloudprovider/aws/block_store_test.go b/pkg/cloudprovider/aws/block_store_test.go new file mode 100644 index 000000000..e74e07cc3 --- /dev/null +++ b/pkg/cloudprovider/aws/block_store_test.go @@ -0,0 +1,86 @@ +/* +Copyright 2017 the Heptio Ark contributors. + +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 ( + "testing" + + "github.com/heptio/ark/pkg/util/collections" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func TestGetVolumeID(t *testing.T) { + b := &blockStore{} + + pv := &unstructured.Unstructured{} + + // missing spec.awsElasticBlockStore -> no error + volumeID, err := b.GetVolumeID(pv) + require.NoError(t, err) + assert.Equal(t, "", volumeID) + + // missing spec.awsElasticBlockStore.volumeID -> error + aws := map[string]interface{}{} + pv.Object["spec"] = map[string]interface{}{ + "awsElasticBlockStore": aws, + } + volumeID, err = b.GetVolumeID(pv) + assert.Error(t, err) + assert.Equal(t, "", volumeID) + + // regex miss + aws["volumeID"] = "foo" + volumeID, err = b.GetVolumeID(pv) + assert.NoError(t, err) + assert.Equal(t, "", volumeID) + + // regex match 1 + aws["volumeID"] = "aws://us-east-1c/vol-abc123" + volumeID, err = b.GetVolumeID(pv) + assert.NoError(t, err) + assert.Equal(t, "vol-abc123", volumeID) + + // regex match 2 + aws["volumeID"] = "vol-abc123" + volumeID, err = b.GetVolumeID(pv) + assert.NoError(t, err) + assert.Equal(t, "vol-abc123", volumeID) +} + +func TestSetVolumeID(t *testing.T) { + b := &blockStore{} + + pv := &unstructured.Unstructured{} + + // missing spec.awsElasticBlockStore -> error + updatedPV, err := b.SetVolumeID(pv, "vol-updated") + require.Error(t, err) + + // happy path + aws := map[string]interface{}{} + pv.Object["spec"] = map[string]interface{}{ + "awsElasticBlockStore": aws, + } + updatedPV, err = b.SetVolumeID(pv, "vol-updated") + require.NoError(t, err) + actual, err := collections.GetString(updatedPV.UnstructuredContent(), "spec.awsElasticBlockStore.volumeID") + require.NoError(t, err) + assert.Equal(t, "vol-updated", actual) +} diff --git a/pkg/cloudprovider/azure/block_store.go b/pkg/cloudprovider/azure/block_store.go index 8824dedc6..9349306b9 100644 --- a/pkg/cloudprovider/azure/block_store.go +++ b/pkg/cloudprovider/azure/block_store.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "os" + "strings" "time" "github.com/Azure/azure-sdk-for-go/arm/disk" @@ -29,8 +30,10 @@ import ( "github.com/Azure/go-autorest/autorest/azure" "github.com/pkg/errors" "github.com/satori/uuid" + "k8s.io/apimachinery/pkg/runtime" "github.com/heptio/ark/pkg/cloudprovider" + "github.com/heptio/ark/pkg/util/collections" ) const ( @@ -292,3 +295,36 @@ func getFullDiskName(subscription string, resourceGroup string, diskName string) func getFullSnapshotName(subscription string, resourceGroup string, snapshotName string) string { return fmt.Sprintf("/subscriptions/%v/resourceGroups/%v/providers/Microsoft.Compute/snapshots/%v", subscription, resourceGroup, snapshotName) } + +func (b *blockStore) GetVolumeID(pv runtime.Unstructured) (string, error) { + if !collections.Exists(pv.UnstructuredContent(), "spec.azureDisk") { + return "", nil + } + + volumeID, err := collections.GetString(pv.UnstructuredContent(), "spec.azureDisk.diskName") + if err != nil { + return "", err + } + + return volumeID, nil +} + +func (b *blockStore) SetVolumeID(pv runtime.Unstructured, volumeID string) (runtime.Unstructured, error) { + azure, err := collections.GetMap(pv.UnstructuredContent(), "spec.azureDisk") + if err != nil { + return nil, err + } + + if uri, err := collections.GetString(azure, "diskURI"); err == nil { + previousVolumeID, err := collections.GetString(azure, "diskName") + if err != nil { + return nil, err + } + + azure["diskURI"] = strings.Replace(uri, previousVolumeID, volumeID, -1) + } + + azure["diskName"] = volumeID + + return pv, nil +} diff --git a/pkg/cloudprovider/azure/block_store_test.go b/pkg/cloudprovider/azure/block_store_test.go new file mode 100644 index 000000000..2c845d737 --- /dev/null +++ b/pkg/cloudprovider/azure/block_store_test.go @@ -0,0 +1,86 @@ +/* +Copyright 2017 the Heptio Ark contributors. + +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 azure + +import ( + "testing" + + "github.com/heptio/ark/pkg/util/collections" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func TestGetVolumeID(t *testing.T) { + b := &blockStore{} + + pv := &unstructured.Unstructured{} + + // missing spec.azureDisk -> no error + volumeID, err := b.GetVolumeID(pv) + require.NoError(t, err) + assert.Equal(t, "", volumeID) + + // missing spec.azureDisk.diskName -> error + azure := map[string]interface{}{} + pv.Object["spec"] = map[string]interface{}{ + "azureDisk": azure, + } + volumeID, err = b.GetVolumeID(pv) + assert.Error(t, err) + assert.Equal(t, "", volumeID) + + // valid + azure["diskName"] = "foo" + volumeID, err = b.GetVolumeID(pv) + assert.NoError(t, err) + assert.Equal(t, "foo", volumeID) +} + +func TestSetVolumeID(t *testing.T) { + b := &blockStore{} + + pv := &unstructured.Unstructured{} + + // missing spec.azureDisk -> error + updatedPV, err := b.SetVolumeID(pv, "updated") + require.Error(t, err) + + // happy path, no diskURI + azure := map[string]interface{}{} + pv.Object["spec"] = map[string]interface{}{ + "azureDisk": azure, + } + updatedPV, err = b.SetVolumeID(pv, "updated") + require.NoError(t, err) + actual, err := collections.GetString(updatedPV.UnstructuredContent(), "spec.azureDisk.diskName") + require.NoError(t, err) + assert.Equal(t, "updated", actual) + assert.NotContains(t, azure, "diskURI") + + // with diskURI + azure["diskURI"] = "/foo/bar/updated/blarg" + updatedPV, err = b.SetVolumeID(pv, "revised") + require.NoError(t, err) + actual, err = collections.GetString(updatedPV.UnstructuredContent(), "spec.azureDisk.diskName") + require.NoError(t, err) + assert.Equal(t, "revised", actual) + actual, err = collections.GetString(updatedPV.UnstructuredContent(), "spec.azureDisk.diskURI") + require.NoError(t, err) + assert.Equal(t, "/foo/bar/revised/blarg", actual) +} diff --git a/pkg/cloudprovider/gcp/block_store.go b/pkg/cloudprovider/gcp/block_store.go index f012a82f0..1feac1d0f 100644 --- a/pkg/cloudprovider/gcp/block_store.go +++ b/pkg/cloudprovider/gcp/block_store.go @@ -26,9 +26,11 @@ import ( "golang.org/x/oauth2/google" "google.golang.org/api/compute/v0.beta" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/wait" "github.com/heptio/ark/pkg/cloudprovider" + "github.com/heptio/ark/pkg/util/collections" ) const projectKey = "project" @@ -191,3 +193,27 @@ func (b *blockStore) DeleteSnapshot(snapshotID string) error { return errors.WithStack(err) } + +func (b *blockStore) GetVolumeID(pv runtime.Unstructured) (string, error) { + if !collections.Exists(pv.UnstructuredContent(), "spec.gcePersistentDisk") { + return "", nil + } + + volumeID, err := collections.GetString(pv.UnstructuredContent(), "spec.gcePersistentDisk.pdName") + if err != nil { + return "", err + } + + return volumeID, nil +} + +func (b *blockStore) SetVolumeID(pv runtime.Unstructured, volumeID string) (runtime.Unstructured, error) { + gce, err := collections.GetMap(pv.UnstructuredContent(), "spec.gcePersistentDisk") + if err != nil { + return nil, err + } + + gce["pdName"] = volumeID + + return pv, nil +} diff --git a/pkg/cloudprovider/gcp/block_store_test.go b/pkg/cloudprovider/gcp/block_store_test.go new file mode 100644 index 000000000..7b19cb8ef --- /dev/null +++ b/pkg/cloudprovider/gcp/block_store_test.go @@ -0,0 +1,74 @@ +/* +Copyright 2017 the Heptio Ark contributors. + +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 gcp + +import ( + "testing" + + "github.com/heptio/ark/pkg/util/collections" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func TestGetVolumeID(t *testing.T) { + b := &blockStore{} + + pv := &unstructured.Unstructured{} + + // missing spec.gcePersistentDisk -> no error + volumeID, err := b.GetVolumeID(pv) + require.NoError(t, err) + assert.Equal(t, "", volumeID) + + // missing spec.gcePersistentDisk.pdName -> error + gce := map[string]interface{}{} + pv.Object["spec"] = map[string]interface{}{ + "gcePersistentDisk": gce, + } + volumeID, err = b.GetVolumeID(pv) + assert.Error(t, err) + assert.Equal(t, "", volumeID) + + // valid + gce["pdName"] = "abc123" + volumeID, err = b.GetVolumeID(pv) + assert.NoError(t, err) + assert.Equal(t, "abc123", volumeID) +} + +func TestSetVolumeID(t *testing.T) { + b := &blockStore{} + + pv := &unstructured.Unstructured{} + + // missing spec.gcePersistentDisk -> error + updatedPV, err := b.SetVolumeID(pv, "abc123") + require.Error(t, err) + + // happy path + gce := map[string]interface{}{} + pv.Object["spec"] = map[string]interface{}{ + "gcePersistentDisk": gce, + } + updatedPV, err = b.SetVolumeID(pv, "123abc") + require.NoError(t, err) + actual, err := collections.GetString(updatedPV.UnstructuredContent(), "spec.gcePersistentDisk.pdName") + require.NoError(t, err) + assert.Equal(t, "123abc", actual) +} diff --git a/pkg/cloudprovider/snapshot_service.go b/pkg/cloudprovider/snapshot_service.go index 8d1ba9a1d..4bccda0a5 100644 --- a/pkg/cloudprovider/snapshot_service.go +++ b/pkg/cloudprovider/snapshot_service.go @@ -20,6 +20,7 @@ import ( "time" "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/runtime" ) // SnapshotService exposes Ark-specific operations for snapshotting and restoring block @@ -46,6 +47,12 @@ type SnapshotService interface { // GetVolumeInfo gets the type and IOPS (if applicable) from the cloud API. GetVolumeInfo(volumeID, volumeAZ string) (string, *int64, error) + + // GetVolumeID returns the cloud provider specific identifier for the PersistentVolume. + GetVolumeID(pv runtime.Unstructured) (string, error) + + // SetVolumeID sets the cloud provider specific identifier for the PersistentVolume. + SetVolumeID(pv runtime.Unstructured, volumeID string) (runtime.Unstructured, error) } const ( @@ -120,3 +127,11 @@ func (sr *snapshotService) DeleteSnapshot(snapshotID string) error { func (sr *snapshotService) GetVolumeInfo(volumeID, volumeAZ string) (string, *int64, error) { return sr.blockStore.GetVolumeInfo(volumeID, volumeAZ) } + +func (sr *snapshotService) GetVolumeID(pv runtime.Unstructured) (string, error) { + return sr.blockStore.GetVolumeID(pv) +} + +func (sr *snapshotService) SetVolumeID(pv runtime.Unstructured, volumeID string) (runtime.Unstructured, error) { + return sr.blockStore.SetVolumeID(pv, volumeID) +} diff --git a/pkg/cloudprovider/storage_interfaces.go b/pkg/cloudprovider/storage_interfaces.go index 7f4a7bb38..987102d10 100644 --- a/pkg/cloudprovider/storage_interfaces.go +++ b/pkg/cloudprovider/storage_interfaces.go @@ -19,6 +19,8 @@ package cloudprovider import ( "io" "time" + + "k8s.io/apimachinery/pkg/runtime" ) // ObjectStore exposes basic object-storage operations required @@ -65,6 +67,12 @@ type BlockStore interface { // and with the specified type and IOPS (if using provisioned IOPS). CreateVolumeFromSnapshot(snapshotID, volumeType, volumeAZ string, iops *int64) (volumeID string, err error) + // GetVolumeID returns the cloud provider specific identifier for the PersistentVolume. + GetVolumeID(pv runtime.Unstructured) (string, error) + + // SetVolumeID sets the cloud provider specific identifier for the PersistentVolume. + SetVolumeID(pv runtime.Unstructured, volumeID string) (runtime.Unstructured, error) + // GetVolumeInfo returns the type and IOPS (if using provisioned IOPS) for a specified block // volume. GetVolumeInfo(volumeID, volumeAZ string) (string, *int64, error) diff --git a/pkg/plugin/block_store.go b/pkg/plugin/block_store.go index 401f8c264..ce52d1df6 100644 --- a/pkg/plugin/block_store.go +++ b/pkg/plugin/block_store.go @@ -17,9 +17,13 @@ limitations under the License. package plugin import ( + "encoding/json" + "github.com/hashicorp/go-plugin" "golang.org/x/net/context" "google.golang.org/grpc" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" "github.com/heptio/ark/pkg/cloudprovider" proto "github.com/heptio/ark/pkg/plugin/generated" @@ -150,6 +154,49 @@ func (c *BlockStoreGRPCClient) DeleteSnapshot(snapshotID string) error { return err } +func (c *BlockStoreGRPCClient) GetVolumeID(pv runtime.Unstructured) (string, error) { + encodedPV, err := json.Marshal(pv.UnstructuredContent()) + if err != nil { + return "", err + } + + req := &proto.GetVolumeIDRequest{ + PersistentVolume: encodedPV, + } + + resp, err := c.grpcClient.GetVolumeID(context.Background(), req) + if err != nil { + return "", err + } + + return resp.VolumeID, nil +} + +func (c *BlockStoreGRPCClient) SetVolumeID(pv runtime.Unstructured, volumeID string) (runtime.Unstructured, error) { + encodedPV, err := json.Marshal(pv.UnstructuredContent()) + if err != nil { + return nil, err + } + + req := &proto.SetVolumeIDRequest{ + PersistentVolume: encodedPV, + VolumeID: volumeID, + } + + resp, err := c.grpcClient.SetVolumeID(context.Background(), req) + if err != nil { + return nil, err + } + + var updatedPV unstructured.Unstructured + if err := json.Unmarshal(resp.PersistentVolume, &updatedPV); err != nil { + return nil, err + + } + + return &updatedPV, nil +} + // BlockStoreGRPCServer implements the proto-generated BlockStoreServer interface, and accepts // gRPC calls and forwards them to an implementation of the pluggable interface. type BlockStoreGRPCServer struct { @@ -245,3 +292,38 @@ func (s *BlockStoreGRPCServer) DeleteSnapshot(ctx context.Context, req *proto.De return &proto.Empty{}, nil } + +func (s *BlockStoreGRPCServer) GetVolumeID(ctx context.Context, req *proto.GetVolumeIDRequest) (*proto.GetVolumeIDResponse, error) { + var pv unstructured.Unstructured + + if err := json.Unmarshal(req.PersistentVolume, &pv); err != nil { + return nil, err + } + + volumeID, err := s.impl.GetVolumeID(&pv) + if err != nil { + return nil, err + } + + return &proto.GetVolumeIDResponse{VolumeID: volumeID}, nil +} + +func (s *BlockStoreGRPCServer) SetVolumeID(ctx context.Context, req *proto.SetVolumeIDRequest) (*proto.SetVolumeIDResponse, error) { + var pv unstructured.Unstructured + + if err := json.Unmarshal(req.PersistentVolume, &pv); err != nil { + return nil, err + } + + updatedPV, err := s.impl.SetVolumeID(&pv, req.VolumeID) + if err != nil { + return nil, err + } + + updatedPVBytes, err := json.Marshal(updatedPV.UnstructuredContent()) + if err != nil { + return nil, err + } + + return &proto.SetVolumeIDResponse{PersistentVolume: updatedPVBytes}, nil +} diff --git a/pkg/plugin/generated/BackupItemAction.pb.go b/pkg/plugin/generated/BackupItemAction.pb.go index 03cd4b644..abd8792dc 100644 --- a/pkg/plugin/generated/BackupItemAction.pb.go +++ b/pkg/plugin/generated/BackupItemAction.pb.go @@ -26,6 +26,10 @@ It has these top-level messages: CreateSnapshotRequest CreateSnapshotResponse DeleteSnapshotRequest + GetVolumeIDRequest + GetVolumeIDResponse + SetVolumeIDRequest + SetVolumeIDResponse PutObjectRequest GetObjectRequest Bytes diff --git a/pkg/plugin/generated/BlockStore.pb.go b/pkg/plugin/generated/BlockStore.pb.go index c4c35ca4e..13298060d 100644 --- a/pkg/plugin/generated/BlockStore.pb.go +++ b/pkg/plugin/generated/BlockStore.pb.go @@ -257,6 +257,78 @@ func (m *DeleteSnapshotRequest) GetSnapshotID() string { return "" } +type GetVolumeIDRequest struct { + PersistentVolume []byte `protobuf:"bytes,1,opt,name=persistentVolume,proto3" json:"persistentVolume,omitempty"` +} + +func (m *GetVolumeIDRequest) Reset() { *m = GetVolumeIDRequest{} } +func (m *GetVolumeIDRequest) String() string { return proto.CompactTextString(m) } +func (*GetVolumeIDRequest) ProtoMessage() {} +func (*GetVolumeIDRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{11} } + +func (m *GetVolumeIDRequest) GetPersistentVolume() []byte { + if m != nil { + return m.PersistentVolume + } + return nil +} + +type GetVolumeIDResponse struct { + VolumeID string `protobuf:"bytes,1,opt,name=volumeID" json:"volumeID,omitempty"` +} + +func (m *GetVolumeIDResponse) Reset() { *m = GetVolumeIDResponse{} } +func (m *GetVolumeIDResponse) String() string { return proto.CompactTextString(m) } +func (*GetVolumeIDResponse) ProtoMessage() {} +func (*GetVolumeIDResponse) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{12} } + +func (m *GetVolumeIDResponse) GetVolumeID() string { + if m != nil { + return m.VolumeID + } + return "" +} + +type SetVolumeIDRequest struct { + PersistentVolume []byte `protobuf:"bytes,1,opt,name=persistentVolume,proto3" json:"persistentVolume,omitempty"` + VolumeID string `protobuf:"bytes,2,opt,name=volumeID" json:"volumeID,omitempty"` +} + +func (m *SetVolumeIDRequest) Reset() { *m = SetVolumeIDRequest{} } +func (m *SetVolumeIDRequest) String() string { return proto.CompactTextString(m) } +func (*SetVolumeIDRequest) ProtoMessage() {} +func (*SetVolumeIDRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{13} } + +func (m *SetVolumeIDRequest) GetPersistentVolume() []byte { + if m != nil { + return m.PersistentVolume + } + return nil +} + +func (m *SetVolumeIDRequest) GetVolumeID() string { + if m != nil { + return m.VolumeID + } + return "" +} + +type SetVolumeIDResponse struct { + PersistentVolume []byte `protobuf:"bytes,1,opt,name=persistentVolume,proto3" json:"persistentVolume,omitempty"` +} + +func (m *SetVolumeIDResponse) Reset() { *m = SetVolumeIDResponse{} } +func (m *SetVolumeIDResponse) String() string { return proto.CompactTextString(m) } +func (*SetVolumeIDResponse) ProtoMessage() {} +func (*SetVolumeIDResponse) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{14} } + +func (m *SetVolumeIDResponse) GetPersistentVolume() []byte { + if m != nil { + return m.PersistentVolume + } + return nil +} + func init() { proto.RegisterType((*CreateVolumeRequest)(nil), "generated.CreateVolumeRequest") proto.RegisterType((*CreateVolumeResponse)(nil), "generated.CreateVolumeResponse") @@ -269,6 +341,10 @@ func init() { proto.RegisterType((*CreateSnapshotRequest)(nil), "generated.CreateSnapshotRequest") proto.RegisterType((*CreateSnapshotResponse)(nil), "generated.CreateSnapshotResponse") proto.RegisterType((*DeleteSnapshotRequest)(nil), "generated.DeleteSnapshotRequest") + proto.RegisterType((*GetVolumeIDRequest)(nil), "generated.GetVolumeIDRequest") + proto.RegisterType((*GetVolumeIDResponse)(nil), "generated.GetVolumeIDResponse") + proto.RegisterType((*SetVolumeIDRequest)(nil), "generated.SetVolumeIDRequest") + proto.RegisterType((*SetVolumeIDResponse)(nil), "generated.SetVolumeIDResponse") } // Reference imports to suppress errors if they are not otherwise used. @@ -289,6 +365,8 @@ type BlockStoreClient interface { ListSnapshots(ctx context.Context, in *ListSnapshotsRequest, opts ...grpc.CallOption) (*ListSnapshotsResponse, error) CreateSnapshot(ctx context.Context, in *CreateSnapshotRequest, opts ...grpc.CallOption) (*CreateSnapshotResponse, error) DeleteSnapshot(ctx context.Context, in *DeleteSnapshotRequest, opts ...grpc.CallOption) (*Empty, error) + GetVolumeID(ctx context.Context, in *GetVolumeIDRequest, opts ...grpc.CallOption) (*GetVolumeIDResponse, error) + SetVolumeID(ctx context.Context, in *SetVolumeIDRequest, opts ...grpc.CallOption) (*SetVolumeIDResponse, error) } type blockStoreClient struct { @@ -362,6 +440,24 @@ func (c *blockStoreClient) DeleteSnapshot(ctx context.Context, in *DeleteSnapsho return out, nil } +func (c *blockStoreClient) GetVolumeID(ctx context.Context, in *GetVolumeIDRequest, opts ...grpc.CallOption) (*GetVolumeIDResponse, error) { + out := new(GetVolumeIDResponse) + err := grpc.Invoke(ctx, "/generated.BlockStore/GetVolumeID", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *blockStoreClient) SetVolumeID(ctx context.Context, in *SetVolumeIDRequest, opts ...grpc.CallOption) (*SetVolumeIDResponse, error) { + out := new(SetVolumeIDResponse) + err := grpc.Invoke(ctx, "/generated.BlockStore/SetVolumeID", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // Server API for BlockStore service type BlockStoreServer interface { @@ -372,6 +468,8 @@ type BlockStoreServer interface { ListSnapshots(context.Context, *ListSnapshotsRequest) (*ListSnapshotsResponse, error) CreateSnapshot(context.Context, *CreateSnapshotRequest) (*CreateSnapshotResponse, error) DeleteSnapshot(context.Context, *DeleteSnapshotRequest) (*Empty, error) + GetVolumeID(context.Context, *GetVolumeIDRequest) (*GetVolumeIDResponse, error) + SetVolumeID(context.Context, *SetVolumeIDRequest) (*SetVolumeIDResponse, error) } func RegisterBlockStoreServer(s *grpc.Server, srv BlockStoreServer) { @@ -504,6 +602,42 @@ func _BlockStore_DeleteSnapshot_Handler(srv interface{}, ctx context.Context, de return interceptor(ctx, in, info, handler) } +func _BlockStore_GetVolumeID_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetVolumeIDRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BlockStoreServer).GetVolumeID(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/generated.BlockStore/GetVolumeID", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BlockStoreServer).GetVolumeID(ctx, req.(*GetVolumeIDRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _BlockStore_SetVolumeID_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetVolumeIDRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BlockStoreServer).SetVolumeID(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/generated.BlockStore/SetVolumeID", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BlockStoreServer).SetVolumeID(ctx, req.(*SetVolumeIDRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _BlockStore_serviceDesc = grpc.ServiceDesc{ ServiceName: "generated.BlockStore", HandlerType: (*BlockStoreServer)(nil), @@ -536,6 +670,14 @@ var _BlockStore_serviceDesc = grpc.ServiceDesc{ MethodName: "DeleteSnapshot", Handler: _BlockStore_DeleteSnapshot_Handler, }, + { + MethodName: "GetVolumeID", + Handler: _BlockStore_GetVolumeID_Handler, + }, + { + MethodName: "SetVolumeID", + Handler: _BlockStore_SetVolumeID_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "BlockStore.proto", @@ -544,39 +686,44 @@ var _BlockStore_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("BlockStore.proto", fileDescriptor1) } var fileDescriptor1 = []byte{ - // 539 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0xc1, 0x6e, 0xd3, 0x40, - 0x10, 0xd5, 0xc6, 0x06, 0x35, 0x53, 0x5a, 0xa2, 0xc5, 0xae, 0x2c, 0x1f, 0x8a, 0xf1, 0x29, 0x42, - 0x22, 0xa0, 0x70, 0x68, 0x41, 0x02, 0x09, 0x48, 0x8b, 0x22, 0x50, 0x91, 0x9c, 0xc2, 0x01, 0x4e, - 0x86, 0x2c, 0x69, 0x54, 0xc7, 0x6b, 0x76, 0x37, 0x95, 0xfc, 0x01, 0xfc, 0x0a, 0x5f, 0xc0, 0x47, - 0xf0, 0x59, 0xc8, 0xf6, 0xda, 0xde, 0xb5, 0xdd, 0x54, 0x55, 0x6e, 0x9e, 0x19, 0xcf, 0xdb, 0x37, - 0xcf, 0x6f, 0xd6, 0x30, 0x78, 0x1b, 0xd1, 0x1f, 0x97, 0x33, 0x41, 0x19, 0x19, 0x25, 0x8c, 0x0a, - 0x8a, 0xfb, 0x0b, 0x12, 0x13, 0x16, 0x0a, 0x32, 0x77, 0xef, 0xcd, 0x2e, 0x42, 0x46, 0xe6, 0x45, - 0xc1, 0xff, 0x8d, 0xe0, 0xc1, 0x3b, 0x46, 0x42, 0x41, 0xbe, 0xd0, 0x68, 0xbd, 0x22, 0x01, 0xf9, - 0xb5, 0x26, 0x5c, 0xe0, 0x43, 0x00, 0x1e, 0x87, 0x09, 0xbf, 0xa0, 0x62, 0x3a, 0x71, 0x90, 0x87, - 0x86, 0xfd, 0x40, 0xc9, 0x64, 0xf5, 0xab, 0xbc, 0xe1, 0x3c, 0x4d, 0x88, 0xd3, 0x2b, 0xea, 0x75, - 0x06, 0xbb, 0xb0, 0x53, 0x44, 0x6f, 0xbe, 0x3a, 0x46, 0x5e, 0xad, 0x62, 0x8c, 0xc1, 0x5c, 0xd2, - 0x84, 0x3b, 0xa6, 0x87, 0x86, 0x46, 0x90, 0x3f, 0xfb, 0x63, 0xb0, 0x74, 0x1a, 0x3c, 0xa1, 0x31, - 0x57, 0x70, 0x2a, 0x16, 0x55, 0xec, 0x9f, 0x81, 0xf5, 0x9e, 0x88, 0xa2, 0x61, 0x1a, 0xff, 0xa4, - 0x25, 0xf7, 0x0d, 0x3d, 0x1a, 0xaf, 0x9e, 0xce, 0xcb, 0xff, 0x00, 0x76, 0x03, 0x4f, 0x92, 0xd0, - 0x87, 0x45, 0xad, 0x61, 0xcb, 0x81, 0x7a, 0xca, 0x40, 0x67, 0x60, 0x4d, 0x79, 0x39, 0x4c, 0x38, - 0x4f, 0xb7, 0x25, 0xf7, 0x04, 0xec, 0x06, 0x9e, 0x24, 0x67, 0xc1, 0x1d, 0x96, 0x25, 0x72, 0xb4, - 0x9d, 0xa0, 0x08, 0xfc, 0x3f, 0x08, 0xac, 0x8f, 0x4b, 0x2e, 0x66, 0xf2, 0x93, 0xf1, 0xf2, 0xfc, - 0x4f, 0x00, 0x22, 0x5c, 0x9c, 0x2e, 0x23, 0x41, 0x18, 0x77, 0x90, 0x67, 0x0c, 0x77, 0xc7, 0x4f, - 0x47, 0x95, 0x3d, 0x46, 0x5d, 0x4d, 0xa3, 0xf3, 0xaa, 0xe3, 0x24, 0x16, 0x2c, 0x0d, 0x14, 0x08, - 0xf7, 0x15, 0xdc, 0x6f, 0x94, 0xf1, 0x00, 0x8c, 0x4b, 0x92, 0xca, 0xf1, 0xb2, 0xc7, 0x8c, 0xe4, - 0x55, 0x18, 0xad, 0x4b, 0xa7, 0x14, 0xc1, 0xcb, 0xde, 0x31, 0xf2, 0x5f, 0x80, 0xdd, 0x38, 0x52, - 0xce, 0xe5, 0xc1, 0x6e, 0xed, 0xb7, 0x4c, 0x5b, 0x63, 0xd8, 0x0f, 0xd4, 0x94, 0xff, 0x0f, 0x81, - 0x5d, 0x98, 0xa6, 0xec, 0xde, 0x52, 0x64, 0xfc, 0x1a, 0x4c, 0x11, 0x2e, 0xb8, 0x63, 0xe4, 0xb2, - 0x3c, 0x56, 0x64, 0xe9, 0x3c, 0x27, 0xd3, 0x45, 0x2a, 0x92, 0xf7, 0xb9, 0x47, 0xd0, 0xaf, 0x52, - 0xb7, 0x52, 0xe1, 0x18, 0x0e, 0x9a, 0x27, 0xd4, 0xde, 0xdb, 0xb4, 0x88, 0xfe, 0x11, 0xd8, 0x13, - 0x12, 0x91, 0xb6, 0x06, 0x37, 0x34, 0x8e, 0xff, 0x9a, 0x00, 0xf5, 0x3d, 0x81, 0x9f, 0x81, 0x39, - 0x8d, 0x97, 0x02, 0x1f, 0x28, 0x43, 0x67, 0x09, 0x09, 0xe7, 0x0e, 0x94, 0xfc, 0xc9, 0x2a, 0x11, - 0x29, 0xfe, 0x06, 0x8e, 0xba, 0xb2, 0xa7, 0x8c, 0xae, 0x4a, 0x0e, 0xf8, 0xb0, 0x25, 0x9d, 0x76, - 0xbd, 0xb8, 0x0f, 0xaf, 0xad, 0xcb, 0xb1, 0x03, 0xd8, 0xd3, 0x76, 0x11, 0xab, 0x1d, 0x5d, 0x5b, - 0xef, 0x7a, 0xd7, 0xbf, 0x50, 0x63, 0x6a, 0x2b, 0xa4, 0x61, 0x76, 0x2d, 0xab, 0x86, 0xd9, 0xbd, - 0x7d, 0x01, 0xec, 0x69, 0xf6, 0xd5, 0x30, 0xbb, 0x76, 0x49, 0xc3, 0xec, 0x76, 0xfe, 0x67, 0xd8, - 0xd7, 0xcd, 0x80, 0xbd, 0x9b, 0x9c, 0xe8, 0x3e, 0xda, 0xf0, 0x86, 0x84, 0x9d, 0xc0, 0xbe, 0xee, - 0x14, 0x0d, 0xb6, 0xd3, 0x44, 0xed, 0xaf, 0xfe, 0xfd, 0x6e, 0xfe, 0xdf, 0x78, 0xfe, 0x3f, 0x00, - 0x00, 0xff, 0xff, 0xd7, 0x15, 0x7f, 0x32, 0x64, 0x06, 0x00, 0x00, + // 620 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0xd1, 0x6e, 0xd3, 0x3c, + 0x14, 0x56, 0x9a, 0xee, 0xd7, 0x7a, 0xba, 0xed, 0xaf, 0xdc, 0x76, 0x8a, 0x22, 0x51, 0x42, 0xae, + 0xaa, 0x49, 0x14, 0x28, 0x17, 0x1b, 0x48, 0x20, 0x06, 0xdd, 0x50, 0x45, 0x35, 0xa4, 0x64, 0x70, + 0x01, 0xdc, 0x04, 0x6a, 0xba, 0x6a, 0x6d, 0x1c, 0x6c, 0x77, 0x52, 0x1f, 0x80, 0x57, 0xe1, 0x59, + 0xb8, 0xe4, 0x91, 0x50, 0x12, 0x27, 0xb1, 0x53, 0xb7, 0x63, 0xea, 0x5d, 0x7d, 0x4e, 0xbe, 0xcf, + 0xdf, 0x39, 0x3e, 0xe7, 0x2b, 0x34, 0x5e, 0xcf, 0xc8, 0xb7, 0x6b, 0x9f, 0x13, 0x8a, 0x7b, 0x11, + 0x25, 0x9c, 0xa0, 0xda, 0x04, 0x87, 0x98, 0x06, 0x1c, 0x8f, 0xed, 0x3d, 0xff, 0x2a, 0xa0, 0x78, + 0x9c, 0x26, 0xdc, 0x9f, 0x06, 0x34, 0xdf, 0x50, 0x1c, 0x70, 0xfc, 0x91, 0xcc, 0x16, 0x73, 0xec, + 0xe1, 0x1f, 0x0b, 0xcc, 0x38, 0xea, 0x00, 0xb0, 0x30, 0x88, 0xd8, 0x15, 0xe1, 0xc3, 0x81, 0x65, + 0x38, 0x46, 0xb7, 0xe6, 0x49, 0x91, 0x38, 0x7f, 0x93, 0x00, 0x2e, 0x97, 0x11, 0xb6, 0x2a, 0x69, + 0xbe, 0x88, 0x20, 0x1b, 0x76, 0xd3, 0xd3, 0xe9, 0x27, 0xcb, 0x4c, 0xb2, 0xf9, 0x19, 0x21, 0xa8, + 0x4e, 0x49, 0xc4, 0xac, 0xaa, 0x63, 0x74, 0x4d, 0x2f, 0xf9, 0xed, 0xf6, 0xa1, 0xa5, 0xca, 0x60, + 0x11, 0x09, 0x99, 0xc4, 0x93, 0xab, 0xc8, 0xcf, 0xee, 0x05, 0xb4, 0xde, 0x62, 0x9e, 0x02, 0x86, + 0xe1, 0x77, 0x92, 0x69, 0xdf, 0x80, 0x51, 0x74, 0x55, 0x54, 0x5d, 0xee, 0x3b, 0x68, 0x97, 0xf8, + 0x84, 0x08, 0xb5, 0x58, 0x63, 0xa5, 0xd8, 0xac, 0xa0, 0x8a, 0x54, 0xd0, 0x05, 0xb4, 0x86, 0x2c, + 0x2b, 0x26, 0x18, 0x2f, 0xb7, 0x15, 0xf7, 0x10, 0xda, 0x25, 0x3e, 0x21, 0xae, 0x05, 0x3b, 0x34, + 0x0e, 0x24, 0x6c, 0xbb, 0x5e, 0x7a, 0x70, 0x7f, 0x19, 0xd0, 0x1a, 0x4d, 0x19, 0xf7, 0xc5, 0x93, + 0xb1, 0xec, 0xfe, 0xf7, 0x00, 0x3c, 0x98, 0x9c, 0x4f, 0x67, 0x1c, 0x53, 0x66, 0x19, 0x8e, 0xd9, + 0xad, 0xf7, 0x1f, 0xf5, 0xf2, 0xf1, 0xe8, 0xe9, 0x40, 0xbd, 0xcb, 0x1c, 0x71, 0x16, 0x72, 0xba, + 0xf4, 0x24, 0x0a, 0xfb, 0x05, 0xfc, 0x5f, 0x4a, 0xa3, 0x06, 0x98, 0xd7, 0x78, 0x29, 0xca, 0x8b, + 0x7f, 0xc6, 0x22, 0x6f, 0x82, 0xd9, 0x22, 0x9b, 0x94, 0xf4, 0xf0, 0xbc, 0x72, 0x62, 0xb8, 0xcf, + 0xa0, 0x5d, 0xba, 0x52, 0xd4, 0xe5, 0x40, 0xbd, 0x98, 0xb7, 0xb8, 0xb7, 0x66, 0xb7, 0xe6, 0xc9, + 0x21, 0xf7, 0xb7, 0x01, 0xed, 0x74, 0x68, 0x32, 0xf4, 0x96, 0x4d, 0x46, 0x2f, 0xa1, 0xca, 0x83, + 0x09, 0xb3, 0xcc, 0xa4, 0x2d, 0x47, 0x52, 0x5b, 0xb4, 0xf7, 0xc4, 0x7d, 0x11, 0x1d, 0x49, 0x70, + 0xf6, 0x31, 0xd4, 0xf2, 0xd0, 0x9d, 0xba, 0x70, 0x02, 0x87, 0xe5, 0x1b, 0x8a, 0xd9, 0xdb, 0xb4, + 0x88, 0xee, 0x31, 0xb4, 0x07, 0x78, 0x86, 0x57, 0x7b, 0x70, 0x1b, 0xf0, 0x15, 0xa0, 0x62, 0xda, + 0x07, 0x19, 0xea, 0x08, 0x1a, 0x11, 0xa6, 0x6c, 0xca, 0x38, 0x0e, 0x45, 0x32, 0xc1, 0xee, 0x79, + 0x2b, 0x71, 0xf7, 0x09, 0x34, 0x15, 0x86, 0x7f, 0x58, 0xd9, 0x2f, 0x80, 0xfc, 0xad, 0x2e, 0x55, + 0xd8, 0x2b, 0x25, 0xf6, 0x53, 0x68, 0xfa, 0x1a, 0x41, 0x77, 0xa0, 0xef, 0xff, 0xd9, 0x01, 0x28, + 0xdc, 0x13, 0x3d, 0x86, 0xea, 0x30, 0x9c, 0x72, 0x74, 0x28, 0x8d, 0x42, 0x1c, 0x10, 0xca, 0xed, + 0x86, 0x14, 0x3f, 0x9b, 0x47, 0x7c, 0x89, 0x3e, 0x83, 0x25, 0x1b, 0xd9, 0x39, 0x25, 0xf3, 0xec, + 0x65, 0x50, 0x67, 0x65, 0xa0, 0x14, 0xd3, 0xb5, 0xef, 0xaf, 0xcd, 0x8b, 0x4a, 0x3c, 0xd8, 0x57, + 0x1c, 0x0a, 0xc9, 0x08, 0x9d, 0x17, 0xda, 0xce, 0xfa, 0x0f, 0x0a, 0x4e, 0xc5, 0x58, 0x14, 0x4e, + 0x9d, 0x85, 0x29, 0x9c, 0x7a, 0x4f, 0xf2, 0x60, 0x5f, 0x59, 0x6a, 0x85, 0x53, 0xe7, 0x30, 0x0a, + 0xa7, 0xde, 0x0f, 0x3e, 0xc0, 0x81, 0xba, 0x22, 0xc8, 0xb9, 0x6d, 0x3f, 0xed, 0x07, 0x1b, 0xbe, + 0x10, 0xb4, 0x03, 0x38, 0x50, 0xf7, 0x47, 0xa1, 0xd5, 0xae, 0x96, 0xe6, 0xd5, 0x47, 0x50, 0x97, + 0x56, 0x01, 0xdd, 0xd3, 0x76, 0x3d, 0x9b, 0x77, 0xbb, 0xb3, 0x2e, 0x2d, 0x34, 0x8d, 0xa0, 0xee, + 0xaf, 0x61, 0xf3, 0x37, 0xb3, 0x69, 0xc6, 0xff, 0xeb, 0x7f, 0xc9, 0x3f, 0xfd, 0xd3, 0xbf, 0x01, + 0x00, 0x00, 0xff, 0xff, 0xe7, 0x50, 0x0a, 0x1d, 0x16, 0x08, 0x00, 0x00, } diff --git a/pkg/plugin/proto/BlockStore.proto b/pkg/plugin/proto/BlockStore.proto index c126ad51c..237bc5bca 100644 --- a/pkg/plugin/proto/BlockStore.proto +++ b/pkg/plugin/proto/BlockStore.proto @@ -55,6 +55,23 @@ message DeleteSnapshotRequest { string snapshotID = 1; } +message GetVolumeIDRequest { + bytes persistentVolume = 1; +} + +message GetVolumeIDResponse { + string volumeID = 1; +} + +message SetVolumeIDRequest { + bytes persistentVolume = 1; + string volumeID = 2; +} + +message SetVolumeIDResponse { + bytes persistentVolume = 1; +} + service BlockStore { rpc Init(InitRequest) returns (Empty); rpc CreateVolumeFromSnapshot(CreateVolumeRequest) returns (CreateVolumeResponse); @@ -63,4 +80,6 @@ service BlockStore { rpc ListSnapshots(ListSnapshotsRequest) returns (ListSnapshotsResponse); rpc CreateSnapshot(CreateSnapshotRequest) returns (CreateSnapshotResponse); rpc DeleteSnapshot(DeleteSnapshotRequest) returns (Empty); + rpc GetVolumeID(GetVolumeIDRequest) returns (GetVolumeIDResponse); + rpc SetVolumeID(SetVolumeIDRequest) returns (SetVolumeIDResponse); } diff --git a/pkg/restore/restore.go b/pkg/restore/restore.go index fe8e99892..994c5b0ca 100644 --- a/pkg/restore/restore.go +++ b/pkg/restore/restore.go @@ -45,6 +45,7 @@ import ( "github.com/heptio/ark/pkg/cloudprovider" "github.com/heptio/ark/pkg/discovery" arkv1client "github.com/heptio/ark/pkg/generated/clientset/versioned/typed/ark/v1" + "github.com/heptio/ark/pkg/util/boolptr" "github.com/heptio/ark/pkg/util/collections" "github.com/heptio/ark/pkg/util/kube" "github.com/heptio/ark/pkg/util/logging" @@ -571,10 +572,7 @@ func (ctx *context) restoreResource(resource, namespace, resourcePath string) (a if groupResource.Group == "" && groupResource.Resource == "persistentvolumes" { // restore the PV from snapshot (if applicable) - updatedObj, warning, err := ctx.executePVAction(obj) - if warning != nil { - addToResult(&warnings, namespace, fmt.Errorf("warning executing PVAction for %s: %v", fullPath, warning)) - } + updatedObj, err := ctx.executePVAction(obj) if err != nil { addToResult(&errs, namespace, fmt.Errorf("error executing PVAction for %s: %v", fullPath, err)) continue @@ -661,95 +659,70 @@ func (ctx *context) restoreResource(resource, namespace, resourcePath string) (a return warnings, errs } -func (ctx *context) executePVAction(obj *unstructured.Unstructured) (*unstructured.Unstructured, error, error) { - // we need to remove annotations from PVs since they potentially contain - // information about dynamic provisioners which will confuse the controllers. - metadata, err := collections.GetMap(obj.UnstructuredContent(), "metadata") - if err != nil { - return nil, nil, err +func (ctx *context) executePVAction(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { + pvName := obj.GetName() + if pvName == "" { + return nil, errors.New("PersistentVolume is missing its name") } - delete(metadata, "annotations") spec, err := collections.GetMap(obj.UnstructuredContent(), "spec") if err != nil { - return nil, nil, err + return nil, err } + + metadata, err := collections.GetMap(obj.UnstructuredContent(), "metadata") + if err != nil { + return nil, err + } + + // We need to remove annotations from PVs since they potentially contain + // information about dynamic provisioners which will confuse the controllers. + delete(metadata, "annotations") + delete(spec, "claimRef") delete(spec, "storageClassName") - // restore the PV from snapshot (if applicable) - return ctx.restoreVolumeFromSnapshot(obj) -} + if boolptr.IsSetToFalse(ctx.backup.Spec.SnapshotVolumes) { + // The backup had snapshots disabled, so we can return early + return obj, nil + } -func (ctx *context) restoreVolumeFromSnapshot(obj *unstructured.Unstructured) (*unstructured.Unstructured, error, error) { - spec, err := collections.GetMap(obj.UnstructuredContent(), "spec") + if boolptr.IsSetToFalse(ctx.restore.Spec.RestorePVs) { + // The restore has pv restores disabled, so we can return early + return obj, nil + } + + // If we can't find a snapshot record for this particular PV, it most likely wasn't a PV that Ark + // could snapshot, so return early instead of trying to restore from a snapshot. + backupInfo, found := ctx.backup.Status.VolumeBackups[pvName] + if !found { + return obj, nil + } + + // Past this point, we expect to be doing a restore + + if ctx.snapshotService == nil { + return nil, errors.New("you must configure a persistentVolumeProvider to restore PersistentVolumes from snapshots") + } + + ctx.infof("restoring PersistentVolume %s from SnapshotID %s", pvName, backupInfo.SnapshotID) + volumeID, err := ctx.snapshotService.CreateVolumeFromSnapshot(backupInfo.SnapshotID, backupInfo.Type, backupInfo.AvailabilityZone, backupInfo.Iops) if err != nil { - return nil, nil, err + return nil, err + } + ctx.infof("successfully restored PersistentVolume %s from snapshot", pvName) + + updated1, err := ctx.snapshotService.SetVolumeID(obj, volumeID) + if err != nil { + return nil, err } - // if it's an unsupported volume type for snapshot restores, don't try to - // do a snapshot restore - if sourceType, _ := kube.GetPVSource(spec); sourceType == "" { - return obj, nil, nil + updated2, ok := updated1.(*unstructured.Unstructured) + if !ok { + return nil, errors.Errorf("unexpected type %T", updated1) } - var ( - pvName = obj.GetName() - restoreFromSnapshot = false - restore = ctx.restore - backup = ctx.backup - ) - - if restore.Spec.RestorePVs != nil && *restore.Spec.RestorePVs { - // when RestorePVs = yes, it's an error if we don't have a snapshot service - if ctx.snapshotService == nil { - return nil, nil, errors.New("PV restorer is not configured for PV snapshot restores") - } - - // if there are no snapshots in the backup, return without error - if backup.Status.VolumeBackups == nil { - return obj, nil, nil - } - - // if there are snapshots, and this is a supported PV type, but there's no - // snapshot for this PV, it's an error - if backup.Status.VolumeBackups[pvName] == nil { - return nil, nil, errors.Errorf("no snapshot found to restore volume %s from", pvName) - } - - restoreFromSnapshot = true - } - if restore.Spec.RestorePVs == nil && ctx.snapshotService != nil { - // when RestorePVs = Auto, don't error if the backup doesn't have snapshots - if backup.Status.VolumeBackups == nil || backup.Status.VolumeBackups[pvName] == nil { - return obj, nil, nil - } - - restoreFromSnapshot = true - } - - if restoreFromSnapshot { - backupInfo := backup.Status.VolumeBackups[pvName] - - ctx.infof("restoring PersistentVolume %s from SnapshotID %s", pvName, backupInfo.SnapshotID) - volumeID, err := ctx.snapshotService.CreateVolumeFromSnapshot(backupInfo.SnapshotID, backupInfo.Type, backupInfo.AvailabilityZone, backupInfo.Iops) - if err != nil { - return nil, nil, err - } - ctx.infof("successfully restored PersistentVolume %s from snapshot", pvName) - - if err := kube.SetVolumeID(spec, volumeID); err != nil { - return nil, nil, err - } - } - - var warning error - - if ctx.snapshotService == nil && len(backup.Status.VolumeBackups) > 0 { - warning = errors.New("unable to restore PV snapshots: Ark server is not configured with a PersistentVolumeProvider") - } - - return obj, warning, nil + return updated2, nil } func isPVReady(obj runtime.Unstructured) bool { diff --git a/pkg/restore/restore_test.go b/pkg/restore/restore_test.go index bbfb0f526..188b95cfe 100644 --- a/pkg/restore/restore_test.go +++ b/pkg/restore/restore_test.go @@ -39,6 +39,7 @@ import ( api "github.com/heptio/ark/pkg/apis/ark/v1" "github.com/heptio/ark/pkg/cloudprovider" + "github.com/heptio/ark/pkg/util/boolptr" "github.com/heptio/ark/pkg/util/collections" arktest "github.com/heptio/ark/pkg/util/test" ) @@ -686,7 +687,7 @@ func TestResetMetadataAndStatus(t *testing.T) { } } -func TestRestoreVolumeFromSnapshot(t *testing.T) { +func TestExecutePVAction(t *testing.T) { iops := int64(1000) tests := []struct { @@ -696,9 +697,10 @@ func TestRestoreVolumeFromSnapshot(t *testing.T) { backup *api.Backup volumeMap map[api.VolumeBackupInfo]string noSnapshotService bool - expectedWarn bool expectedErr bool expectedRes *unstructured.Unstructured + volumeID string + expectSetVolumeID bool }{ { name: "no name should error", @@ -713,91 +715,79 @@ func TestRestoreVolumeFromSnapshot(t *testing.T) { expectedErr: true, }, { - name: "when RestorePVs=false, should not error if there is no PV->BackupInfo map", + name: "ensure annotations, spec.claimRef, spec.storageClassName are deleted", + obj: NewTestUnstructured().WithName("pv-1").WithAnnotations("a", "b").WithSpec("claimRef", "storageClassName", "someOtherField").Unstructured, + restore: arktest.NewDefaultTestRestore().WithRestorePVs(false).Restore, + backup: &api.Backup{}, + expectedRes: NewTestUnstructured().WithName("pv-1").WithSpec("someOtherField").Unstructured, + }, + { + name: "if backup.spec.snapshotVolumes is false, ignore restore.spec.restorePVs and return early", + obj: NewTestUnstructured().WithName("pv-1").WithAnnotations("a", "b").WithSpec("claimRef", "storageClassName", "someOtherField").Unstructured, + restore: arktest.NewDefaultTestRestore().WithRestorePVs(true).Restore, + backup: &api.Backup{Spec: api.BackupSpec{SnapshotVolumes: boolptr.False()}}, + expectedRes: NewTestUnstructured().WithName("pv-1").WithSpec("someOtherField").Unstructured, + }, + { + name: "not restoring, return early", obj: NewTestUnstructured().WithName("pv-1").WithSpec().Unstructured, restore: arktest.NewDefaultTestRestore().WithRestorePVs(false).Restore, - backup: &api.Backup{Status: api.BackupStatus{}}, + backup: &api.Backup{Status: api.BackupStatus{VolumeBackups: map[string]*api.VolumeBackupInfo{"pv-1": {SnapshotID: "snap-1"}}}}, expectedErr: false, expectedRes: NewTestUnstructured().WithName("pv-1").WithSpec().Unstructured, }, { - name: "when RestorePVs=true, return without error if there is no PV->BackupInfo map", - obj: NewTestUnstructured().WithName("pv-1").WithSpecField("awsElasticBlockStore", make(map[string]interface{})).Unstructured, + name: "restoring, return without error if there is no PV->BackupInfo map", + obj: NewTestUnstructured().WithName("pv-1").WithSpec("xyz").Unstructured, restore: arktest.NewDefaultTestRestore().WithRestorePVs(true).Restore, backup: &api.Backup{Status: api.BackupStatus{}}, expectedErr: false, - expectedRes: NewTestUnstructured().WithName("pv-1").WithSpecField("awsElasticBlockStore", make(map[string]interface{})).Unstructured, + expectedRes: NewTestUnstructured().WithName("pv-1").WithSpec("xyz").Unstructured, }, { - name: "when RestorePVs=true, error if there is PV->BackupInfo map but no entry for this PV", - obj: NewTestUnstructured().WithName("pv-1").WithSpecField("awsElasticBlockStore", make(map[string]interface{})).Unstructured, + name: "restoring, return early if there is PV->BackupInfo map but no entry for this PV", + obj: NewTestUnstructured().WithName("pv-1").WithSpec("xyz").Unstructured, restore: arktest.NewDefaultTestRestore().WithRestorePVs(true).Restore, backup: &api.Backup{Status: api.BackupStatus{VolumeBackups: map[string]*api.VolumeBackupInfo{"another-pv": {}}}}, - expectedErr: true, - }, - { - name: "when RestorePVs=true, AWS volume ID should be set correctly", - obj: NewTestUnstructured().WithName("pv-1").WithSpecField("awsElasticBlockStore", make(map[string]interface{})).Unstructured, - restore: arktest.NewDefaultTestRestore().WithRestorePVs(true).Restore, - backup: &api.Backup{Status: api.BackupStatus{VolumeBackups: map[string]*api.VolumeBackupInfo{"pv-1": {SnapshotID: "snap-1"}}}}, - volumeMap: map[api.VolumeBackupInfo]string{{SnapshotID: "snap-1"}: "volume-1"}, expectedErr: false, - expectedRes: NewTestUnstructured().WithName("pv-1").WithSpecField("awsElasticBlockStore", map[string]interface{}{"volumeID": "volume-1"}).Unstructured, + expectedRes: NewTestUnstructured().WithName("pv-1").WithSpec("xyz").Unstructured, }, { - name: "when RestorePVs=true, GCE pdName should be set correctly", - obj: NewTestUnstructured().WithName("pv-1").WithSpecField("gcePersistentDisk", make(map[string]interface{})).Unstructured, - restore: arktest.NewDefaultTestRestore().WithRestorePVs(true).Restore, - backup: &api.Backup{Status: api.BackupStatus{VolumeBackups: map[string]*api.VolumeBackupInfo{"pv-1": {SnapshotID: "snap-1"}}}}, - volumeMap: map[api.VolumeBackupInfo]string{{SnapshotID: "snap-1"}: "volume-1"}, - expectedErr: false, - expectedRes: NewTestUnstructured().WithName("pv-1").WithSpecField("gcePersistentDisk", map[string]interface{}{"pdName": "volume-1"}).Unstructured, + name: "volume type and IOPS are correctly passed to CreateVolume", + obj: NewTestUnstructured().WithName("pv-1").WithSpec("xyz").Unstructured, + restore: arktest.NewDefaultTestRestore().WithRestorePVs(true).Restore, + backup: &api.Backup{Status: api.BackupStatus{VolumeBackups: map[string]*api.VolumeBackupInfo{"pv-1": {SnapshotID: "snap-1", Type: "gp", Iops: &iops}}}}, + volumeMap: map[api.VolumeBackupInfo]string{{SnapshotID: "snap-1", Type: "gp", Iops: &iops}: "volume-1"}, + volumeID: "volume-1", + expectedErr: false, + expectSetVolumeID: true, + expectedRes: NewTestUnstructured().WithName("pv-1").WithSpec("xyz").Unstructured, }, { - name: "when RestorePVs=true, Azure pdName should be set correctly", - obj: NewTestUnstructured().WithName("pv-1").WithSpecField("azureDisk", make(map[string]interface{})).Unstructured, - restore: arktest.NewDefaultTestRestore().WithRestorePVs(true).Restore, - backup: &api.Backup{Status: api.BackupStatus{VolumeBackups: map[string]*api.VolumeBackupInfo{"pv-1": {SnapshotID: "snap-1"}}}}, - volumeMap: map[api.VolumeBackupInfo]string{{SnapshotID: "snap-1"}: "volume-1"}, - expectedErr: false, - expectedRes: NewTestUnstructured().WithName("pv-1").WithSpecField("azureDisk", map[string]interface{}{"diskName": "volume-1"}).Unstructured, - }, - { - name: "when RestorePVs=true, unsupported PV source should not get snapshot restored", - obj: NewTestUnstructured().WithName("pv-1").WithSpecField("unsupportedPVSource", make(map[string]interface{})).Unstructured, - restore: arktest.NewDefaultTestRestore().WithRestorePVs(true).Restore, - backup: &api.Backup{Status: api.BackupStatus{VolumeBackups: map[string]*api.VolumeBackupInfo{"pv-1": {SnapshotID: "snap-1"}}}}, - volumeMap: map[api.VolumeBackupInfo]string{{SnapshotID: "snap-1"}: "volume-1"}, - expectedErr: false, - expectedRes: NewTestUnstructured().WithName("pv-1").WithSpecField("unsupportedPVSource", make(map[string]interface{})).Unstructured, - }, - { - name: "volume type and IOPS are correctly passed to CreateVolume", - obj: NewTestUnstructured().WithName("pv-1").WithSpecField("awsElasticBlockStore", make(map[string]interface{})).Unstructured, - restore: arktest.NewDefaultTestRestore().WithRestorePVs(true).Restore, - backup: &api.Backup{Status: api.BackupStatus{VolumeBackups: map[string]*api.VolumeBackupInfo{"pv-1": {SnapshotID: "snap-1", Type: "gp", Iops: &iops}}}}, - volumeMap: map[api.VolumeBackupInfo]string{{SnapshotID: "snap-1", Type: "gp", Iops: &iops}: "volume-1"}, - expectedErr: false, - expectedRes: NewTestUnstructured().WithName("pv-1").WithSpecField("awsElasticBlockStore", map[string]interface{}{"volumeID": "volume-1"}).Unstructured, - }, - { - name: "When no SnapshotService, warn if backup has snapshots that will not be restored", + name: "restoring, snapshotService=nil, backup has at least 1 snapshot -> error", obj: NewTestUnstructured().WithName("pv-1").WithSpecField("awsElasticBlockStore", make(map[string]interface{})).Unstructured, restore: arktest.NewDefaultTestRestore().Restore, backup: &api.Backup{Status: api.BackupStatus{VolumeBackups: map[string]*api.VolumeBackupInfo{"pv-1": {SnapshotID: "snap-1"}}}}, volumeMap: map[api.VolumeBackupInfo]string{{SnapshotID: "snap-1"}: "volume-1"}, + volumeID: "volume-1", noSnapshotService: true, - expectedErr: false, - expectedWarn: true, + expectedErr: true, expectedRes: NewTestUnstructured().WithName("pv-1").WithSpecField("awsElasticBlockStore", make(map[string]interface{})).Unstructured, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - var snapshotService cloudprovider.SnapshotService + var ( + snapshotService cloudprovider.SnapshotService + fakeSnapshotService *arktest.FakeSnapshotService + ) if !test.noSnapshotService { - snapshotService = &arktest.FakeSnapshotService{RestorableVolumes: test.volumeMap} + fakeSnapshotService = &arktest.FakeSnapshotService{ + RestorableVolumes: test.volumeMap, + VolumeID: test.volumeID, + } + snapshotService = fakeSnapshotService } ctx := &context{ @@ -807,13 +797,20 @@ func TestRestoreVolumeFromSnapshot(t *testing.T) { logger: arktest.NewLogger(), } - res, warn, err := ctx.restoreVolumeFromSnapshot(test.obj) + res, err := ctx.executePVAction(test.obj) - assert.Equal(t, test.expectedWarn, warn != nil) - - if assert.Equal(t, test.expectedErr, err != nil) { - assert.Equal(t, test.expectedRes, res) + if test.expectedErr { + require.Error(t, err) + return } + require.NoError(t, err) + + if test.expectSetVolumeID { + assert.Equal(t, test.volumeID, fakeSnapshotService.VolumeIDSet) + } else { + assert.Equal(t, "", fakeSnapshotService.VolumeIDSet) + } + assert.Equal(t, test.expectedRes, res) }) } } diff --git a/pkg/util/collections/map_utils.go b/pkg/util/collections/map_utils.go index 0c95e8ff1..8c4f68852 100644 --- a/pkg/util/collections/map_utils.go +++ b/pkg/util/collections/map_utils.go @@ -112,3 +112,13 @@ func ForEach(root map[string]interface{}, path string, fn func(obj map[string]in return nil } + +// Exists returns true if root[path] exists, or false otherwise. +func Exists(root map[string]interface{}, path string) bool { + if root == nil { + return false + } + + _, err := GetValue(root, path) + return err == nil +} diff --git a/pkg/util/kube/utils.go b/pkg/util/kube/utils.go index ee72dbcff..f3537b742 100644 --- a/pkg/util/kube/utils.go +++ b/pkg/util/kube/utils.go @@ -18,8 +18,6 @@ package kube import ( "fmt" - "regexp" - "strings" "github.com/pkg/errors" @@ -27,8 +25,6 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - - "github.com/heptio/ark/pkg/util/collections" ) // NamespaceAndName returns a string in the format / @@ -52,80 +48,3 @@ func EnsureNamespaceExists(namespace *v1.Namespace, client corev1.NamespaceInter return false, errors.Wrapf(err, "error creating namespace %s", namespace.Name) } } - -var ebsVolumeIDRegex = regexp.MustCompile("vol-.*") - -var supportedVolumeTypes = map[string]string{ - "awsElasticBlockStore": "volumeID", - "gcePersistentDisk": "pdName", - "azureDisk": "diskName", -} - -// GetVolumeID looks for a supported PV source within the provided PV unstructured -// data. It returns the appropriate volume ID field if found. If the PV source -// is supported but a volume ID cannot be found, an error is returned; if the PV -// source is not supported, zero values are returned. -func GetVolumeID(pv map[string]interface{}) (string, error) { - spec, err := collections.GetMap(pv, "spec") - if err != nil { - return "", err - } - - for volumeType, volumeIDKey := range supportedVolumeTypes { - if pvSource, err := collections.GetMap(spec, volumeType); err == nil { - volumeID, err := collections.GetString(pvSource, volumeIDKey) - if err != nil { - return "", err - } - - if volumeType == "awsElasticBlockStore" { - return ebsVolumeIDRegex.FindString(volumeID), nil - } - - return volumeID, nil - } - } - - return "", nil -} - -// GetPVSource looks for a supported PV source within the provided PV spec data. -// It returns the name of the PV source type and the unstructured source data if -// one is found, or zero values otherwise. -func GetPVSource(spec map[string]interface{}) (string, map[string]interface{}) { - for volumeType := range supportedVolumeTypes { - if pvSource, found := spec[volumeType]; found { - return volumeType, pvSource.(map[string]interface{}) - } - } - - return "", nil -} - -// SetVolumeID looks for a supported PV source within the provided PV spec data. -// If sets the appropriate ID field(s) within the source if found, and returns an -// error if a supported PV source is not found. -func SetVolumeID(spec map[string]interface{}, volumeID string) error { - sourceType, source := GetPVSource(spec) - if sourceType == "" { - return errors.New("persistent volume source is not compatible") - } - - // for azureDisk, we need to do a find-replace within the diskURI (if it exists) - // to switch the old disk name with the new. - if sourceType == "azureDisk" { - uri, err := collections.GetString(source, "diskURI") - if err == nil { - priorVolumeID, err := collections.GetString(source, supportedVolumeTypes["azureDisk"]) - if err != nil { - return err - } - - source["diskURI"] = strings.Replace(uri, priorVolumeID, volumeID, -1) - } - } - - source[supportedVolumeTypes[sourceType]] = volumeID - - return nil -} diff --git a/pkg/util/kube/utils_test.go b/pkg/util/kube/utils_test.go index 3a06c6732..57bf3ab50 100644 --- a/pkg/util/kube/utils_test.go +++ b/pkg/util/kube/utils_test.go @@ -18,91 +18,12 @@ package kube import ( "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/heptio/ark/pkg/util/collections" ) -func TestSetVolumeID(t *testing.T) { - tests := []struct { - name string - spec map[string]interface{} - volumeID string - expectedErr error - specFieldExpectations map[string]string - }{ - { - name: "awsElasticBlockStore normal case", - spec: map[string]interface{}{ - "awsElasticBlockStore": map[string]interface{}{ - "volumeID": "vol-old", - }, - }, - volumeID: "vol-new", - expectedErr: nil, - }, - { - name: "gcePersistentDisk normal case", - spec: map[string]interface{}{ - "gcePersistentDisk": map[string]interface{}{ - "pdName": "old-pd", - }, - }, - volumeID: "new-pd", - expectedErr: nil, - }, - { - name: "azureDisk normal case", - spec: map[string]interface{}{ - "azureDisk": map[string]interface{}{ - "diskName": "old-disk", - "diskURI": "some-nonsense/old-disk", - }, - }, - volumeID: "new-disk", - expectedErr: nil, - specFieldExpectations: map[string]string{ - "azureDisk.diskURI": "some-nonsense/new-disk", - }, - }, - { - name: "azureDisk with no diskURI", - spec: map[string]interface{}{ - "azureDisk": map[string]interface{}{ - "diskName": "old-disk", - }, - }, - volumeID: "new-disk", - expectedErr: nil, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - err := SetVolumeID(test.spec, test.volumeID) - - require.Equal(t, test.expectedErr, err) - - if test.expectedErr != nil { - return - } - - pv := map[string]interface{}{ - "spec": test.spec, - } - - volumeID, err := GetVolumeID(pv) - require.Nil(t, err) - - assert.Equal(t, test.volumeID, volumeID) - - for path, expected := range test.specFieldExpectations { - actual, err := collections.GetString(test.spec, path) - assert.Nil(t, err) - assert.Equal(t, expected, actual) - } - }) - } +func TestNamespaceAndName(t *testing.T) { + //TODO +} + +func TestEnsureNamespaceExists(t *testing.T) { + //TODO } diff --git a/pkg/util/test/fake_snapshot_service.go b/pkg/util/test/fake_snapshot_service.go index 82777e87b..9476e37f4 100644 --- a/pkg/util/test/fake_snapshot_service.go +++ b/pkg/util/test/fake_snapshot_service.go @@ -19,6 +19,7 @@ package test import ( "errors" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" api "github.com/heptio/ark/pkg/apis/ark/v1" @@ -33,6 +34,9 @@ type FakeSnapshotService struct { // VolumeBackupInfo -> VolumeID RestorableVolumes map[api.VolumeBackupInfo]string + + VolumeID string + VolumeIDSet string } func (s *FakeSnapshotService) GetAllSnapshots() ([]string, error) { @@ -80,3 +84,12 @@ func (s *FakeSnapshotService) GetVolumeInfo(volumeID, volumeAZ string) (string, return volumeInfo.Type, volumeInfo.Iops, nil } } + +func (s *FakeSnapshotService) GetVolumeID(pv runtime.Unstructured) (string, error) { + return s.VolumeID, nil +} + +func (s *FakeSnapshotService) SetVolumeID(pv runtime.Unstructured, volumeID string) (runtime.Unstructured, error) { + s.VolumeIDSet = volumeID + return pv, nil +}