Support custom volume snapshots & restores
The main Ark code was hard-coding specific support for AWS, GCE, and Azure volume snapshots and restores, and anything else was considered unsupported. Add GetVolumeID and SetVolumeID to the BlockStore interface, to allow block store plugins to handle volume snapshots and restores. Signed-off-by: Andy Goldstein <andy.goldstein@gmail.com>pull/215/head
parent
526b604237
commit
c700455272
|
@ -36,7 +36,6 @@ import (
|
||||||
"github.com/heptio/ark/pkg/cloudprovider"
|
"github.com/heptio/ark/pkg/cloudprovider"
|
||||||
"github.com/heptio/ark/pkg/discovery"
|
"github.com/heptio/ark/pkg/discovery"
|
||||||
"github.com/heptio/ark/pkg/util/collections"
|
"github.com/heptio/ark/pkg/util/collections"
|
||||||
kubeutil "github.com/heptio/ark/pkg/util/kube"
|
|
||||||
"github.com/heptio/ark/pkg/util/logging"
|
"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)
|
log.Infof("label %q is not present on PersistentVolume", zoneLabel)
|
||||||
}
|
}
|
||||||
|
|
||||||
volumeID, err := kubeutil.GetVolumeID(pv.UnstructuredContent())
|
volumeID, err := ib.snapshotService.GetVolumeID(pv)
|
||||||
// non-nil error means it's a supported PV source but volume ID can't be found
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "error getting volume ID for PersistentVolume")
|
return errors.Wrapf(err, "error getting volume ID for PersistentVolume")
|
||||||
}
|
}
|
||||||
// no volumeID / nil error means unsupported PV source
|
|
||||||
if volumeID == "" {
|
if volumeID == "" {
|
||||||
log.Info("PersistentVolume is not a supported volume type for snapshots, skipping.")
|
log.Info("PersistentVolume is not a supported volume type for snapshots, skipping.")
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -324,7 +324,10 @@ func TestBackupItemNoSkips(t *testing.T) {
|
||||||
|
|
||||||
var snapshotService *arktest.FakeSnapshotService
|
var snapshotService *arktest.FakeSnapshotService
|
||||||
if test.snapshottableVolumes != nil {
|
if test.snapshottableVolumes != nil {
|
||||||
snapshotService = &arktest.FakeSnapshotService{SnapshottableVolumes: test.snapshottableVolumes}
|
snapshotService = &arktest.FakeSnapshotService{
|
||||||
|
SnapshottableVolumes: test.snapshottableVolumes,
|
||||||
|
VolumeID: "vol-abc123",
|
||||||
|
}
|
||||||
b.snapshotService = snapshotService
|
b.snapshotService = snapshotService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,7 +359,7 @@ func TestBackupItemNoSkips(t *testing.T) {
|
||||||
err = b.backupItem(arktest.NewLogger(), obj, groupResource)
|
err = b.backupItem(arktest.NewLogger(), obj, groupResource)
|
||||||
gotError := err != nil
|
gotError := err != nil
|
||||||
if e, a := test.expectError, gotError; e != a {
|
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 {
|
if test.expectError {
|
||||||
return
|
return
|
||||||
|
@ -445,12 +448,6 @@ func TestTakePVSnapshot(t *testing.T) {
|
||||||
pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv"}}`,
|
pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv"}}`,
|
||||||
snapshotEnabled: false,
|
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",
|
name: "unsupported PV source type",
|
||||||
snapshotEnabled: true,
|
snapshotEnabled: true,
|
||||||
|
@ -458,19 +455,7 @@ func TestTakePVSnapshot(t *testing.T) {
|
||||||
expectError: false,
|
expectError: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "can't find volume id - aws but no volume id",
|
name: "without iops",
|
||||||
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",
|
|
||||||
snapshotEnabled: true,
|
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"}}}`,
|
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,
|
expectError: false,
|
||||||
|
@ -482,7 +467,7 @@ func TestTakePVSnapshot(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "aws - simple volume id with provisioned IOPS",
|
name: "with iops",
|
||||||
snapshotEnabled: true,
|
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"}}}`,
|
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,
|
expectError: false,
|
||||||
|
@ -493,42 +478,6 @@ func TestTakePVSnapshot(t *testing.T) {
|
||||||
"vol-abc123": {Type: "io1", Iops: &iops, SnapshotID: "snap-1", AvailabilityZone: "us-east-1c"},
|
"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",
|
name: "preexisting volume backup info in backup status",
|
||||||
snapshotEnabled: true,
|
snapshotEnabled: true,
|
||||||
|
@ -545,10 +494,11 @@ func TestTakePVSnapshot(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "create snapshot error",
|
name: "create snapshot error",
|
||||||
snapshotEnabled: true,
|
snapshotEnabled: true,
|
||||||
pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv"}, "spec": {"gcePersistentDisk": {"pdName": "pd-abc123"}}}`,
|
pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv"}, "spec": {"gcePersistentDisk": {"pdName": "pd-abc123"}}}`,
|
||||||
expectError: true,
|
expectedVolumeID: "pd-abc123",
|
||||||
|
expectError: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "PV with label metadata but no failureDomainZone",
|
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}
|
ib := &defaultItemBackupper{snapshotService: snapshotService}
|
||||||
|
|
||||||
|
|
|
@ -17,14 +17,18 @@ limitations under the License.
|
||||||
package aws
|
package aws
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/aws/session"
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
|
||||||
"github.com/heptio/ark/pkg/cloudprovider"
|
"github.com/heptio/ark/pkg/cloudprovider"
|
||||||
|
"github.com/heptio/ark/pkg/util/collections"
|
||||||
)
|
)
|
||||||
|
|
||||||
const regionKey = "region"
|
const regionKey = "region"
|
||||||
|
@ -205,3 +209,29 @@ func (b *blockStore) DeleteSnapshot(snapshotID string) error {
|
||||||
|
|
||||||
return errors.WithStack(err)
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/arm/disk"
|
"github.com/Azure/azure-sdk-for-go/arm/disk"
|
||||||
|
@ -29,8 +30,10 @@ import (
|
||||||
"github.com/Azure/go-autorest/autorest/azure"
|
"github.com/Azure/go-autorest/autorest/azure"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/satori/uuid"
|
"github.com/satori/uuid"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
"github.com/heptio/ark/pkg/cloudprovider"
|
"github.com/heptio/ark/pkg/cloudprovider"
|
||||||
|
"github.com/heptio/ark/pkg/util/collections"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -292,3 +295,36 @@ func getFullDiskName(subscription string, resourceGroup string, diskName string)
|
||||||
func getFullSnapshotName(subscription string, resourceGroup string, snapshotName string) 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)
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -26,9 +26,11 @@ import (
|
||||||
"golang.org/x/oauth2/google"
|
"golang.org/x/oauth2/google"
|
||||||
"google.golang.org/api/compute/v0.beta"
|
"google.golang.org/api/compute/v0.beta"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
|
||||||
"github.com/heptio/ark/pkg/cloudprovider"
|
"github.com/heptio/ark/pkg/cloudprovider"
|
||||||
|
"github.com/heptio/ark/pkg/util/collections"
|
||||||
)
|
)
|
||||||
|
|
||||||
const projectKey = "project"
|
const projectKey = "project"
|
||||||
|
@ -191,3 +193,27 @@ func (b *blockStore) DeleteSnapshot(snapshotID string) error {
|
||||||
|
|
||||||
return errors.WithStack(err)
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SnapshotService exposes Ark-specific operations for snapshotting and restoring block
|
// 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 gets the type and IOPS (if applicable) from the cloud API.
|
||||||
GetVolumeInfo(volumeID, volumeAZ string) (string, *int64, error)
|
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 (
|
const (
|
||||||
|
@ -120,3 +127,11 @@ func (sr *snapshotService) DeleteSnapshot(snapshotID string) error {
|
||||||
func (sr *snapshotService) GetVolumeInfo(volumeID, volumeAZ string) (string, *int64, error) {
|
func (sr *snapshotService) GetVolumeInfo(volumeID, volumeAZ string) (string, *int64, error) {
|
||||||
return sr.blockStore.GetVolumeInfo(volumeID, volumeAZ)
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ package cloudprovider
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ObjectStore exposes basic object-storage operations required
|
// 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).
|
// and with the specified type and IOPS (if using provisioned IOPS).
|
||||||
CreateVolumeFromSnapshot(snapshotID, volumeType, volumeAZ string, iops *int64) (volumeID string, err error)
|
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
|
// GetVolumeInfo returns the type and IOPS (if using provisioned IOPS) for a specified block
|
||||||
// volume.
|
// volume.
|
||||||
GetVolumeInfo(volumeID, volumeAZ string) (string, *int64, error)
|
GetVolumeInfo(volumeID, volumeAZ string) (string, *int64, error)
|
||||||
|
|
|
@ -17,9 +17,13 @@ limitations under the License.
|
||||||
package plugin
|
package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/hashicorp/go-plugin"
|
"github.com/hashicorp/go-plugin"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
"github.com/heptio/ark/pkg/cloudprovider"
|
"github.com/heptio/ark/pkg/cloudprovider"
|
||||||
proto "github.com/heptio/ark/pkg/plugin/generated"
|
proto "github.com/heptio/ark/pkg/plugin/generated"
|
||||||
|
@ -150,6 +154,49 @@ func (c *BlockStoreGRPCClient) DeleteSnapshot(snapshotID string) error {
|
||||||
return err
|
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
|
// BlockStoreGRPCServer implements the proto-generated BlockStoreServer interface, and accepts
|
||||||
// gRPC calls and forwards them to an implementation of the pluggable interface.
|
// gRPC calls and forwards them to an implementation of the pluggable interface.
|
||||||
type BlockStoreGRPCServer struct {
|
type BlockStoreGRPCServer struct {
|
||||||
|
@ -245,3 +292,38 @@ func (s *BlockStoreGRPCServer) DeleteSnapshot(ctx context.Context, req *proto.De
|
||||||
|
|
||||||
return &proto.Empty{}, nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -26,6 +26,10 @@ It has these top-level messages:
|
||||||
CreateSnapshotRequest
|
CreateSnapshotRequest
|
||||||
CreateSnapshotResponse
|
CreateSnapshotResponse
|
||||||
DeleteSnapshotRequest
|
DeleteSnapshotRequest
|
||||||
|
GetVolumeIDRequest
|
||||||
|
GetVolumeIDResponse
|
||||||
|
SetVolumeIDRequest
|
||||||
|
SetVolumeIDResponse
|
||||||
PutObjectRequest
|
PutObjectRequest
|
||||||
GetObjectRequest
|
GetObjectRequest
|
||||||
Bytes
|
Bytes
|
||||||
|
|
|
@ -257,6 +257,78 @@ func (m *DeleteSnapshotRequest) GetSnapshotID() string {
|
||||||
return ""
|
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() {
|
func init() {
|
||||||
proto.RegisterType((*CreateVolumeRequest)(nil), "generated.CreateVolumeRequest")
|
proto.RegisterType((*CreateVolumeRequest)(nil), "generated.CreateVolumeRequest")
|
||||||
proto.RegisterType((*CreateVolumeResponse)(nil), "generated.CreateVolumeResponse")
|
proto.RegisterType((*CreateVolumeResponse)(nil), "generated.CreateVolumeResponse")
|
||||||
|
@ -269,6 +341,10 @@ func init() {
|
||||||
proto.RegisterType((*CreateSnapshotRequest)(nil), "generated.CreateSnapshotRequest")
|
proto.RegisterType((*CreateSnapshotRequest)(nil), "generated.CreateSnapshotRequest")
|
||||||
proto.RegisterType((*CreateSnapshotResponse)(nil), "generated.CreateSnapshotResponse")
|
proto.RegisterType((*CreateSnapshotResponse)(nil), "generated.CreateSnapshotResponse")
|
||||||
proto.RegisterType((*DeleteSnapshotRequest)(nil), "generated.DeleteSnapshotRequest")
|
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.
|
// 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)
|
ListSnapshots(ctx context.Context, in *ListSnapshotsRequest, opts ...grpc.CallOption) (*ListSnapshotsResponse, error)
|
||||||
CreateSnapshot(ctx context.Context, in *CreateSnapshotRequest, opts ...grpc.CallOption) (*CreateSnapshotResponse, error)
|
CreateSnapshot(ctx context.Context, in *CreateSnapshotRequest, opts ...grpc.CallOption) (*CreateSnapshotResponse, error)
|
||||||
DeleteSnapshot(ctx context.Context, in *DeleteSnapshotRequest, opts ...grpc.CallOption) (*Empty, 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 {
|
type blockStoreClient struct {
|
||||||
|
@ -362,6 +440,24 @@ func (c *blockStoreClient) DeleteSnapshot(ctx context.Context, in *DeleteSnapsho
|
||||||
return out, nil
|
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
|
// Server API for BlockStore service
|
||||||
|
|
||||||
type BlockStoreServer interface {
|
type BlockStoreServer interface {
|
||||||
|
@ -372,6 +468,8 @@ type BlockStoreServer interface {
|
||||||
ListSnapshots(context.Context, *ListSnapshotsRequest) (*ListSnapshotsResponse, error)
|
ListSnapshots(context.Context, *ListSnapshotsRequest) (*ListSnapshotsResponse, error)
|
||||||
CreateSnapshot(context.Context, *CreateSnapshotRequest) (*CreateSnapshotResponse, error)
|
CreateSnapshot(context.Context, *CreateSnapshotRequest) (*CreateSnapshotResponse, error)
|
||||||
DeleteSnapshot(context.Context, *DeleteSnapshotRequest) (*Empty, 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) {
|
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)
|
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{
|
var _BlockStore_serviceDesc = grpc.ServiceDesc{
|
||||||
ServiceName: "generated.BlockStore",
|
ServiceName: "generated.BlockStore",
|
||||||
HandlerType: (*BlockStoreServer)(nil),
|
HandlerType: (*BlockStoreServer)(nil),
|
||||||
|
@ -536,6 +670,14 @@ var _BlockStore_serviceDesc = grpc.ServiceDesc{
|
||||||
MethodName: "DeleteSnapshot",
|
MethodName: "DeleteSnapshot",
|
||||||
Handler: _BlockStore_DeleteSnapshot_Handler,
|
Handler: _BlockStore_DeleteSnapshot_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "GetVolumeID",
|
||||||
|
Handler: _BlockStore_GetVolumeID_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "SetVolumeID",
|
||||||
|
Handler: _BlockStore_SetVolumeID_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
Streams: []grpc.StreamDesc{},
|
||||||
Metadata: "BlockStore.proto",
|
Metadata: "BlockStore.proto",
|
||||||
|
@ -544,39 +686,44 @@ var _BlockStore_serviceDesc = grpc.ServiceDesc{
|
||||||
func init() { proto.RegisterFile("BlockStore.proto", fileDescriptor1) }
|
func init() { proto.RegisterFile("BlockStore.proto", fileDescriptor1) }
|
||||||
|
|
||||||
var fileDescriptor1 = []byte{
|
var fileDescriptor1 = []byte{
|
||||||
// 539 bytes of a gzipped FileDescriptorProto
|
// 620 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0xc1, 0x6e, 0xd3, 0x40,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0xd1, 0x6e, 0xd3, 0x3c,
|
||||||
0x10, 0xd5, 0xc6, 0x06, 0x35, 0x53, 0x5a, 0xa2, 0xc5, 0xae, 0x2c, 0x1f, 0x8a, 0xf1, 0x29, 0x42,
|
0x14, 0x56, 0x9a, 0xee, 0xd7, 0x7a, 0xba, 0xed, 0xaf, 0xdc, 0x76, 0x8a, 0x22, 0x51, 0x42, 0xae,
|
||||||
0x22, 0xa0, 0x70, 0x68, 0x41, 0x02, 0x09, 0x48, 0x8b, 0x22, 0x50, 0x91, 0x9c, 0xc2, 0x01, 0x4e,
|
0xaa, 0x49, 0x14, 0x28, 0x17, 0x1b, 0x48, 0x20, 0x06, 0xdd, 0x50, 0x45, 0x35, 0xa4, 0x64, 0x70,
|
||||||
0x86, 0x2c, 0x69, 0x54, 0xc7, 0x6b, 0x76, 0x37, 0x95, 0xfc, 0x01, 0xfc, 0x0a, 0x5f, 0xc0, 0x47,
|
0x01, 0xdc, 0x04, 0x6a, 0xba, 0x6a, 0x6d, 0x1c, 0x6c, 0x77, 0x52, 0x1f, 0x80, 0x57, 0xe1, 0x59,
|
||||||
0xf0, 0x59, 0xc8, 0xf6, 0xda, 0xde, 0xb5, 0xdd, 0x54, 0x55, 0x6e, 0x9e, 0x19, 0xcf, 0xdb, 0x37,
|
0xb8, 0xe4, 0x91, 0x50, 0x12, 0x27, 0xb1, 0x53, 0xb7, 0x63, 0xea, 0x5d, 0x7d, 0x4e, 0xbe, 0xcf,
|
||||||
0xcf, 0x6f, 0xd6, 0x30, 0x78, 0x1b, 0xd1, 0x1f, 0x97, 0x33, 0x41, 0x19, 0x19, 0x25, 0x8c, 0x0a,
|
0xdf, 0x39, 0x3e, 0xe7, 0x2b, 0x34, 0x5e, 0xcf, 0xc8, 0xb7, 0x6b, 0x9f, 0x13, 0x8a, 0x7b, 0x11,
|
||||||
0x8a, 0xfb, 0x0b, 0x12, 0x13, 0x16, 0x0a, 0x32, 0x77, 0xef, 0xcd, 0x2e, 0x42, 0x46, 0xe6, 0x45,
|
0x25, 0x9c, 0xa0, 0xda, 0x04, 0x87, 0x98, 0x06, 0x1c, 0x8f, 0xed, 0x3d, 0xff, 0x2a, 0xa0, 0x78,
|
||||||
0xc1, 0xff, 0x8d, 0xe0, 0xc1, 0x3b, 0x46, 0x42, 0x41, 0xbe, 0xd0, 0x68, 0xbd, 0x22, 0x01, 0xf9,
|
0x9c, 0x26, 0xdc, 0x9f, 0x06, 0x34, 0xdf, 0x50, 0x1c, 0x70, 0xfc, 0x91, 0xcc, 0x16, 0x73, 0xec,
|
||||||
0xb5, 0x26, 0x5c, 0xe0, 0x43, 0x00, 0x1e, 0x87, 0x09, 0xbf, 0xa0, 0x62, 0x3a, 0x71, 0x90, 0x87,
|
0xe1, 0x1f, 0x0b, 0xcc, 0x38, 0xea, 0x00, 0xb0, 0x30, 0x88, 0xd8, 0x15, 0xe1, 0xc3, 0x81, 0x65,
|
||||||
0x86, 0xfd, 0x40, 0xc9, 0x64, 0xf5, 0xab, 0xbc, 0xe1, 0x3c, 0x4d, 0x88, 0xd3, 0x2b, 0xea, 0x75,
|
0x38, 0x46, 0xb7, 0xe6, 0x49, 0x91, 0x38, 0x7f, 0x93, 0x00, 0x2e, 0x97, 0x11, 0xb6, 0x2a, 0x69,
|
||||||
0x06, 0xbb, 0xb0, 0x53, 0x44, 0x6f, 0xbe, 0x3a, 0x46, 0x5e, 0xad, 0x62, 0x8c, 0xc1, 0x5c, 0xd2,
|
0xbe, 0x88, 0x20, 0x1b, 0x76, 0xd3, 0xd3, 0xe9, 0x27, 0xcb, 0x4c, 0xb2, 0xf9, 0x19, 0x21, 0xa8,
|
||||||
0x84, 0x3b, 0xa6, 0x87, 0x86, 0x46, 0x90, 0x3f, 0xfb, 0x63, 0xb0, 0x74, 0x1a, 0x3c, 0xa1, 0x31,
|
0x4e, 0x49, 0xc4, 0xac, 0xaa, 0x63, 0x74, 0x4d, 0x2f, 0xf9, 0xed, 0xf6, 0xa1, 0xa5, 0xca, 0x60,
|
||||||
0x57, 0x70, 0x2a, 0x16, 0x55, 0xec, 0x9f, 0x81, 0xf5, 0x9e, 0x88, 0xa2, 0x61, 0x1a, 0xff, 0xa4,
|
0x11, 0x09, 0x99, 0xc4, 0x93, 0xab, 0xc8, 0xcf, 0xee, 0x05, 0xb4, 0xde, 0x62, 0x9e, 0x02, 0x86,
|
||||||
0x25, 0xf7, 0x0d, 0x3d, 0x1a, 0xaf, 0x9e, 0xce, 0xcb, 0xff, 0x00, 0x76, 0x03, 0x4f, 0x92, 0xd0,
|
0xe1, 0x77, 0x92, 0x69, 0xdf, 0x80, 0x51, 0x74, 0x55, 0x54, 0x5d, 0xee, 0x3b, 0x68, 0x97, 0xf8,
|
||||||
0x87, 0x45, 0xad, 0x61, 0xcb, 0x81, 0x7a, 0xca, 0x40, 0x67, 0x60, 0x4d, 0x79, 0x39, 0x4c, 0x38,
|
0x84, 0x08, 0xb5, 0x58, 0x63, 0xa5, 0xd8, 0xac, 0xa0, 0x8a, 0x54, 0xd0, 0x05, 0xb4, 0x86, 0x2c,
|
||||||
0x4f, 0xb7, 0x25, 0xf7, 0x04, 0xec, 0x06, 0x9e, 0x24, 0x67, 0xc1, 0x1d, 0x96, 0x25, 0x72, 0xb4,
|
0x2b, 0x26, 0x18, 0x2f, 0xb7, 0x15, 0xf7, 0x10, 0xda, 0x25, 0x3e, 0x21, 0xae, 0x05, 0x3b, 0x34,
|
||||||
0x9d, 0xa0, 0x08, 0xfc, 0x3f, 0x08, 0xac, 0x8f, 0x4b, 0x2e, 0x66, 0xf2, 0x93, 0xf1, 0xf2, 0xfc,
|
0x0e, 0x24, 0x6c, 0xbb, 0x5e, 0x7a, 0x70, 0x7f, 0x19, 0xd0, 0x1a, 0x4d, 0x19, 0xf7, 0xc5, 0x93,
|
||||||
0x4f, 0x00, 0x22, 0x5c, 0x9c, 0x2e, 0x23, 0x41, 0x18, 0x77, 0x90, 0x67, 0x0c, 0x77, 0xc7, 0x4f,
|
0xb1, 0xec, 0xfe, 0xf7, 0x00, 0x3c, 0x98, 0x9c, 0x4f, 0x67, 0x1c, 0x53, 0x66, 0x19, 0x8e, 0xd9,
|
||||||
0x47, 0x95, 0x3d, 0x46, 0x5d, 0x4d, 0xa3, 0xf3, 0xaa, 0xe3, 0x24, 0x16, 0x2c, 0x0d, 0x14, 0x08,
|
0xad, 0xf7, 0x1f, 0xf5, 0xf2, 0xf1, 0xe8, 0xe9, 0x40, 0xbd, 0xcb, 0x1c, 0x71, 0x16, 0x72, 0xba,
|
||||||
0xf7, 0x15, 0xdc, 0x6f, 0x94, 0xf1, 0x00, 0x8c, 0x4b, 0x92, 0xca, 0xf1, 0xb2, 0xc7, 0x8c, 0xe4,
|
0xf4, 0x24, 0x0a, 0xfb, 0x05, 0xfc, 0x5f, 0x4a, 0xa3, 0x06, 0x98, 0xd7, 0x78, 0x29, 0xca, 0x8b,
|
||||||
0x55, 0x18, 0xad, 0x4b, 0xa7, 0x14, 0xc1, 0xcb, 0xde, 0x31, 0xf2, 0x5f, 0x80, 0xdd, 0x38, 0x52,
|
0x7f, 0xc6, 0x22, 0x6f, 0x82, 0xd9, 0x22, 0x9b, 0x94, 0xf4, 0xf0, 0xbc, 0x72, 0x62, 0xb8, 0xcf,
|
||||||
0xce, 0xe5, 0xc1, 0x6e, 0xed, 0xb7, 0x4c, 0x5b, 0x63, 0xd8, 0x0f, 0xd4, 0x94, 0xff, 0x0f, 0x81,
|
0xa0, 0x5d, 0xba, 0x52, 0xd4, 0xe5, 0x40, 0xbd, 0x98, 0xb7, 0xb8, 0xb7, 0x66, 0xb7, 0xe6, 0xc9,
|
||||||
0x5d, 0x98, 0xa6, 0xec, 0xde, 0x52, 0x64, 0xfc, 0x1a, 0x4c, 0x11, 0x2e, 0xb8, 0x63, 0xe4, 0xb2,
|
0x21, 0xf7, 0xb7, 0x01, 0xed, 0x74, 0x68, 0x32, 0xf4, 0x96, 0x4d, 0x46, 0x2f, 0xa1, 0xca, 0x83,
|
||||||
0x3c, 0x56, 0x64, 0xe9, 0x3c, 0x27, 0xd3, 0x45, 0x2a, 0x92, 0xf7, 0xb9, 0x47, 0xd0, 0xaf, 0x52,
|
0x09, 0xb3, 0xcc, 0xa4, 0x2d, 0x47, 0x52, 0x5b, 0xb4, 0xf7, 0xc4, 0x7d, 0x11, 0x1d, 0x49, 0x70,
|
||||||
0xb7, 0x52, 0xe1, 0x18, 0x0e, 0x9a, 0x27, 0xd4, 0xde, 0xdb, 0xb4, 0x88, 0xfe, 0x11, 0xd8, 0x13,
|
0xf6, 0x31, 0xd4, 0xf2, 0xd0, 0x9d, 0xba, 0x70, 0x02, 0x87, 0xe5, 0x1b, 0x8a, 0xd9, 0xdb, 0xb4,
|
||||||
0x12, 0x91, 0xb6, 0x06, 0x37, 0x34, 0x8e, 0xff, 0x9a, 0x00, 0xf5, 0x3d, 0x81, 0x9f, 0x81, 0x39,
|
0x88, 0xee, 0x31, 0xb4, 0x07, 0x78, 0x86, 0x57, 0x7b, 0x70, 0x1b, 0xf0, 0x15, 0xa0, 0x62, 0xda,
|
||||||
0x8d, 0x97, 0x02, 0x1f, 0x28, 0x43, 0x67, 0x09, 0x09, 0xe7, 0x0e, 0x94, 0xfc, 0xc9, 0x2a, 0x11,
|
0x07, 0x19, 0xea, 0x08, 0x1a, 0x11, 0xa6, 0x6c, 0xca, 0x38, 0x0e, 0x45, 0x32, 0xc1, 0xee, 0x79,
|
||||||
0x29, 0xfe, 0x06, 0x8e, 0xba, 0xb2, 0xa7, 0x8c, 0xae, 0x4a, 0x0e, 0xf8, 0xb0, 0x25, 0x9d, 0x76,
|
0x2b, 0x71, 0xf7, 0x09, 0x34, 0x15, 0x86, 0x7f, 0x58, 0xd9, 0x2f, 0x80, 0xfc, 0xad, 0x2e, 0x55,
|
||||||
0xbd, 0xb8, 0x0f, 0xaf, 0xad, 0xcb, 0xb1, 0x03, 0xd8, 0xd3, 0x76, 0x11, 0xab, 0x1d, 0x5d, 0x5b,
|
0xd8, 0x2b, 0x25, 0xf6, 0x53, 0x68, 0xfa, 0x1a, 0x41, 0x77, 0xa0, 0xef, 0xff, 0xd9, 0x01, 0x28,
|
||||||
0xef, 0x7a, 0xd7, 0xbf, 0x50, 0x63, 0x6a, 0x2b, 0xa4, 0x61, 0x76, 0x2d, 0xab, 0x86, 0xd9, 0xbd,
|
0xdc, 0x13, 0x3d, 0x86, 0xea, 0x30, 0x9c, 0x72, 0x74, 0x28, 0x8d, 0x42, 0x1c, 0x10, 0xca, 0xed,
|
||||||
0x7d, 0x01, 0xec, 0x69, 0xf6, 0xd5, 0x30, 0xbb, 0x76, 0x49, 0xc3, 0xec, 0x76, 0xfe, 0x67, 0xd8,
|
0x86, 0x14, 0x3f, 0x9b, 0x47, 0x7c, 0x89, 0x3e, 0x83, 0x25, 0x1b, 0xd9, 0x39, 0x25, 0xf3, 0xec,
|
||||||
0xd7, 0xcd, 0x80, 0xbd, 0x9b, 0x9c, 0xe8, 0x3e, 0xda, 0xf0, 0x86, 0x84, 0x9d, 0xc0, 0xbe, 0xee,
|
0x65, 0x50, 0x67, 0x65, 0xa0, 0x14, 0xd3, 0xb5, 0xef, 0xaf, 0xcd, 0x8b, 0x4a, 0x3c, 0xd8, 0x57,
|
||||||
0x14, 0x0d, 0xb6, 0xd3, 0x44, 0xed, 0xaf, 0xfe, 0xfd, 0x6e, 0xfe, 0xdf, 0x78, 0xfe, 0x3f, 0x00,
|
0x1c, 0x0a, 0xc9, 0x08, 0x9d, 0x17, 0xda, 0xce, 0xfa, 0x0f, 0x0a, 0x4e, 0xc5, 0x58, 0x14, 0x4e,
|
||||||
0x00, 0xff, 0xff, 0xd7, 0x15, 0x7f, 0x32, 0x64, 0x06, 0x00, 0x00,
|
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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,23 @@ message DeleteSnapshotRequest {
|
||||||
string snapshotID = 1;
|
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 {
|
service BlockStore {
|
||||||
rpc Init(InitRequest) returns (Empty);
|
rpc Init(InitRequest) returns (Empty);
|
||||||
rpc CreateVolumeFromSnapshot(CreateVolumeRequest) returns (CreateVolumeResponse);
|
rpc CreateVolumeFromSnapshot(CreateVolumeRequest) returns (CreateVolumeResponse);
|
||||||
|
@ -63,4 +80,6 @@ service BlockStore {
|
||||||
rpc ListSnapshots(ListSnapshotsRequest) returns (ListSnapshotsResponse);
|
rpc ListSnapshots(ListSnapshotsRequest) returns (ListSnapshotsResponse);
|
||||||
rpc CreateSnapshot(CreateSnapshotRequest) returns (CreateSnapshotResponse);
|
rpc CreateSnapshot(CreateSnapshotRequest) returns (CreateSnapshotResponse);
|
||||||
rpc DeleteSnapshot(DeleteSnapshotRequest) returns (Empty);
|
rpc DeleteSnapshot(DeleteSnapshotRequest) returns (Empty);
|
||||||
|
rpc GetVolumeID(GetVolumeIDRequest) returns (GetVolumeIDResponse);
|
||||||
|
rpc SetVolumeID(SetVolumeIDRequest) returns (SetVolumeIDResponse);
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ import (
|
||||||
"github.com/heptio/ark/pkg/cloudprovider"
|
"github.com/heptio/ark/pkg/cloudprovider"
|
||||||
"github.com/heptio/ark/pkg/discovery"
|
"github.com/heptio/ark/pkg/discovery"
|
||||||
arkv1client "github.com/heptio/ark/pkg/generated/clientset/versioned/typed/ark/v1"
|
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/collections"
|
||||||
"github.com/heptio/ark/pkg/util/kube"
|
"github.com/heptio/ark/pkg/util/kube"
|
||||||
"github.com/heptio/ark/pkg/util/logging"
|
"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" {
|
if groupResource.Group == "" && groupResource.Resource == "persistentvolumes" {
|
||||||
// restore the PV from snapshot (if applicable)
|
// restore the PV from snapshot (if applicable)
|
||||||
updatedObj, warning, err := ctx.executePVAction(obj)
|
updatedObj, err := ctx.executePVAction(obj)
|
||||||
if warning != nil {
|
|
||||||
addToResult(&warnings, namespace, fmt.Errorf("warning executing PVAction for %s: %v", fullPath, warning))
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
addToResult(&errs, namespace, fmt.Errorf("error executing PVAction for %s: %v", fullPath, err))
|
addToResult(&errs, namespace, fmt.Errorf("error executing PVAction for %s: %v", fullPath, err))
|
||||||
continue
|
continue
|
||||||
|
@ -661,95 +659,70 @@ func (ctx *context) restoreResource(resource, namespace, resourcePath string) (a
|
||||||
return warnings, errs
|
return warnings, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *context) executePVAction(obj *unstructured.Unstructured) (*unstructured.Unstructured, error, error) {
|
func (ctx *context) executePVAction(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
|
||||||
// we need to remove annotations from PVs since they potentially contain
|
pvName := obj.GetName()
|
||||||
// information about dynamic provisioners which will confuse the controllers.
|
if pvName == "" {
|
||||||
metadata, err := collections.GetMap(obj.UnstructuredContent(), "metadata")
|
return nil, errors.New("PersistentVolume is missing its name")
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
}
|
||||||
delete(metadata, "annotations")
|
|
||||||
|
|
||||||
spec, err := collections.GetMap(obj.UnstructuredContent(), "spec")
|
spec, err := collections.GetMap(obj.UnstructuredContent(), "spec")
|
||||||
if err != nil {
|
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, "claimRef")
|
||||||
delete(spec, "storageClassName")
|
delete(spec, "storageClassName")
|
||||||
|
|
||||||
// restore the PV from snapshot (if applicable)
|
if boolptr.IsSetToFalse(ctx.backup.Spec.SnapshotVolumes) {
|
||||||
return ctx.restoreVolumeFromSnapshot(obj)
|
// The backup had snapshots disabled, so we can return early
|
||||||
}
|
return obj, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ctx *context) restoreVolumeFromSnapshot(obj *unstructured.Unstructured) (*unstructured.Unstructured, error, error) {
|
if boolptr.IsSetToFalse(ctx.restore.Spec.RestorePVs) {
|
||||||
spec, err := collections.GetMap(obj.UnstructuredContent(), "spec")
|
// 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 {
|
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
|
updated2, ok := updated1.(*unstructured.Unstructured)
|
||||||
// do a snapshot restore
|
if !ok {
|
||||||
if sourceType, _ := kube.GetPVSource(spec); sourceType == "" {
|
return nil, errors.Errorf("unexpected type %T", updated1)
|
||||||
return obj, nil, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
return updated2, nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isPVReady(obj runtime.Unstructured) bool {
|
func isPVReady(obj runtime.Unstructured) bool {
|
||||||
|
|
|
@ -39,6 +39,7 @@ import (
|
||||||
|
|
||||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||||
"github.com/heptio/ark/pkg/cloudprovider"
|
"github.com/heptio/ark/pkg/cloudprovider"
|
||||||
|
"github.com/heptio/ark/pkg/util/boolptr"
|
||||||
"github.com/heptio/ark/pkg/util/collections"
|
"github.com/heptio/ark/pkg/util/collections"
|
||||||
arktest "github.com/heptio/ark/pkg/util/test"
|
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)
|
iops := int64(1000)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
@ -696,9 +697,10 @@ func TestRestoreVolumeFromSnapshot(t *testing.T) {
|
||||||
backup *api.Backup
|
backup *api.Backup
|
||||||
volumeMap map[api.VolumeBackupInfo]string
|
volumeMap map[api.VolumeBackupInfo]string
|
||||||
noSnapshotService bool
|
noSnapshotService bool
|
||||||
expectedWarn bool
|
|
||||||
expectedErr bool
|
expectedErr bool
|
||||||
expectedRes *unstructured.Unstructured
|
expectedRes *unstructured.Unstructured
|
||||||
|
volumeID string
|
||||||
|
expectSetVolumeID bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no name should error",
|
name: "no name should error",
|
||||||
|
@ -713,91 +715,79 @@ func TestRestoreVolumeFromSnapshot(t *testing.T) {
|
||||||
expectedErr: true,
|
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,
|
obj: NewTestUnstructured().WithName("pv-1").WithSpec().Unstructured,
|
||||||
restore: arktest.NewDefaultTestRestore().WithRestorePVs(false).Restore,
|
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,
|
expectedErr: false,
|
||||||
expectedRes: NewTestUnstructured().WithName("pv-1").WithSpec().Unstructured,
|
expectedRes: NewTestUnstructured().WithName("pv-1").WithSpec().Unstructured,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "when RestorePVs=true, return without error if there is no PV->BackupInfo map",
|
name: "restoring, return without error if there is no PV->BackupInfo map",
|
||||||
obj: NewTestUnstructured().WithName("pv-1").WithSpecField("awsElasticBlockStore", make(map[string]interface{})).Unstructured,
|
obj: NewTestUnstructured().WithName("pv-1").WithSpec("xyz").Unstructured,
|
||||||
restore: arktest.NewDefaultTestRestore().WithRestorePVs(true).Restore,
|
restore: arktest.NewDefaultTestRestore().WithRestorePVs(true).Restore,
|
||||||
backup: &api.Backup{Status: api.BackupStatus{}},
|
backup: &api.Backup{Status: api.BackupStatus{}},
|
||||||
expectedErr: false,
|
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",
|
name: "restoring, return early if there is PV->BackupInfo map but no entry for this PV",
|
||||||
obj: NewTestUnstructured().WithName("pv-1").WithSpecField("awsElasticBlockStore", make(map[string]interface{})).Unstructured,
|
obj: NewTestUnstructured().WithName("pv-1").WithSpec("xyz").Unstructured,
|
||||||
restore: arktest.NewDefaultTestRestore().WithRestorePVs(true).Restore,
|
restore: arktest.NewDefaultTestRestore().WithRestorePVs(true).Restore,
|
||||||
backup: &api.Backup{Status: api.BackupStatus{VolumeBackups: map[string]*api.VolumeBackupInfo{"another-pv": {}}}},
|
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,
|
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",
|
name: "volume type and IOPS are correctly passed to CreateVolume",
|
||||||
obj: NewTestUnstructured().WithName("pv-1").WithSpecField("gcePersistentDisk", make(map[string]interface{})).Unstructured,
|
obj: NewTestUnstructured().WithName("pv-1").WithSpec("xyz").Unstructured,
|
||||||
restore: arktest.NewDefaultTestRestore().WithRestorePVs(true).Restore,
|
restore: arktest.NewDefaultTestRestore().WithRestorePVs(true).Restore,
|
||||||
backup: &api.Backup{Status: api.BackupStatus{VolumeBackups: map[string]*api.VolumeBackupInfo{"pv-1": {SnapshotID: "snap-1"}}}},
|
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"}: "volume-1"},
|
volumeMap: map[api.VolumeBackupInfo]string{{SnapshotID: "snap-1", Type: "gp", Iops: &iops}: "volume-1"},
|
||||||
expectedErr: false,
|
volumeID: "volume-1",
|
||||||
expectedRes: NewTestUnstructured().WithName("pv-1").WithSpecField("gcePersistentDisk", map[string]interface{}{"pdName": "volume-1"}).Unstructured,
|
expectedErr: false,
|
||||||
|
expectSetVolumeID: true,
|
||||||
|
expectedRes: NewTestUnstructured().WithName("pv-1").WithSpec("xyz").Unstructured,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "when RestorePVs=true, Azure pdName should be set correctly",
|
name: "restoring, snapshotService=nil, backup has at least 1 snapshot -> error",
|
||||||
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",
|
|
||||||
obj: NewTestUnstructured().WithName("pv-1").WithSpecField("awsElasticBlockStore", make(map[string]interface{})).Unstructured,
|
obj: NewTestUnstructured().WithName("pv-1").WithSpecField("awsElasticBlockStore", make(map[string]interface{})).Unstructured,
|
||||||
restore: arktest.NewDefaultTestRestore().Restore,
|
restore: arktest.NewDefaultTestRestore().Restore,
|
||||||
backup: &api.Backup{Status: api.BackupStatus{VolumeBackups: map[string]*api.VolumeBackupInfo{"pv-1": {SnapshotID: "snap-1"}}}},
|
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"},
|
volumeMap: map[api.VolumeBackupInfo]string{{SnapshotID: "snap-1"}: "volume-1"},
|
||||||
|
volumeID: "volume-1",
|
||||||
noSnapshotService: true,
|
noSnapshotService: true,
|
||||||
expectedErr: false,
|
expectedErr: true,
|
||||||
expectedWarn: true,
|
|
||||||
expectedRes: NewTestUnstructured().WithName("pv-1").WithSpecField("awsElasticBlockStore", make(map[string]interface{})).Unstructured,
|
expectedRes: NewTestUnstructured().WithName("pv-1").WithSpecField("awsElasticBlockStore", make(map[string]interface{})).Unstructured,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
var snapshotService cloudprovider.SnapshotService
|
var (
|
||||||
|
snapshotService cloudprovider.SnapshotService
|
||||||
|
fakeSnapshotService *arktest.FakeSnapshotService
|
||||||
|
)
|
||||||
if !test.noSnapshotService {
|
if !test.noSnapshotService {
|
||||||
snapshotService = &arktest.FakeSnapshotService{RestorableVolumes: test.volumeMap}
|
fakeSnapshotService = &arktest.FakeSnapshotService{
|
||||||
|
RestorableVolumes: test.volumeMap,
|
||||||
|
VolumeID: test.volumeID,
|
||||||
|
}
|
||||||
|
snapshotService = fakeSnapshotService
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := &context{
|
ctx := &context{
|
||||||
|
@ -807,13 +797,20 @@ func TestRestoreVolumeFromSnapshot(t *testing.T) {
|
||||||
logger: arktest.NewLogger(),
|
logger: arktest.NewLogger(),
|
||||||
}
|
}
|
||||||
|
|
||||||
res, warn, err := ctx.restoreVolumeFromSnapshot(test.obj)
|
res, err := ctx.executePVAction(test.obj)
|
||||||
|
|
||||||
assert.Equal(t, test.expectedWarn, warn != nil)
|
if test.expectedErr {
|
||||||
|
require.Error(t, err)
|
||||||
if assert.Equal(t, test.expectedErr, err != nil) {
|
return
|
||||||
assert.Equal(t, test.expectedRes, res)
|
|
||||||
}
|
}
|
||||||
|
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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,3 +112,13 @@ func ForEach(root map[string]interface{}, path string, fn func(obj map[string]in
|
||||||
|
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -18,8 +18,6 @@ package kube
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
@ -27,8 +25,6 @@ import (
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||||
|
|
||||||
"github.com/heptio/ark/pkg/util/collections"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NamespaceAndName returns a string in the format <namespace>/<name>
|
// NamespaceAndName returns a string in the format <namespace>/<name>
|
||||||
|
@ -52,80 +48,3 @@ func EnsureNamespaceExists(namespace *v1.Namespace, client corev1.NamespaceInter
|
||||||
return false, errors.Wrapf(err, "error creating namespace %s", namespace.Name)
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -18,91 +18,12 @@ package kube
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/heptio/ark/pkg/util/collections"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSetVolumeID(t *testing.T) {
|
func TestNamespaceAndName(t *testing.T) {
|
||||||
tests := []struct {
|
//TODO
|
||||||
name string
|
}
|
||||||
spec map[string]interface{}
|
|
||||||
volumeID string
|
func TestEnsureNamespaceExists(t *testing.T) {
|
||||||
expectedErr error
|
//TODO
|
||||||
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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ package test
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
|
||||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||||
|
@ -33,6 +34,9 @@ type FakeSnapshotService struct {
|
||||||
|
|
||||||
// VolumeBackupInfo -> VolumeID
|
// VolumeBackupInfo -> VolumeID
|
||||||
RestorableVolumes map[api.VolumeBackupInfo]string
|
RestorableVolumes map[api.VolumeBackupInfo]string
|
||||||
|
|
||||||
|
VolumeID string
|
||||||
|
VolumeIDSet string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FakeSnapshotService) GetAllSnapshots() ([]string, error) {
|
func (s *FakeSnapshotService) GetAllSnapshots() ([]string, error) {
|
||||||
|
@ -80,3 +84,12 @@ func (s *FakeSnapshotService) GetVolumeInfo(volumeID, volumeAZ string) (string,
|
||||||
return volumeInfo.Type, volumeInfo.Iops, nil
|
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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue