From 0ffaeb949d98eb1990476a8eea51c12ac2bf3395 Mon Sep 17 00:00:00 2001 From: Nolan Brubaker Date: Thu, 7 Feb 2019 14:20:41 -0500 Subject: [PATCH] Fix restoring GCP regional disks To create a regional disk, the URLs for the zones in which the disk is replicated must be provided to the GCP API. Fixes #1183 Signed-off-by: Nolan Brubaker --- changelogs/unreleased/1200-nrb | 1 + docs/gcp-config.md | 1 + pkg/cloudprovider/gcp/block_store.go | 42 +++++++++++++++++++++++----- 3 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 changelogs/unreleased/1200-nrb diff --git a/changelogs/unreleased/1200-nrb b/changelogs/unreleased/1200-nrb new file mode 100644 index 000000000..0d4c6a124 --- /dev/null +++ b/changelogs/unreleased/1200-nrb @@ -0,0 +1 @@ +Set the zones for GCP regional disks on restore. This requires the `compute.zones.get` permission on the GCP serviceaccount in order to work correctly. diff --git a/docs/gcp-config.md b/docs/gcp-config.md index f0531e775..8e357742d 100644 --- a/docs/gcp-config.md +++ b/docs/gcp-config.md @@ -67,6 +67,7 @@ To integrate Velero with GCP, create an Velero-specific [Service Account][15]: compute.snapshots.create compute.snapshots.useReadOnly compute.snapshots.delete + compute.zones.get ) gcloud iam roles create velero.server \ diff --git a/pkg/cloudprovider/gcp/block_store.go b/pkg/cloudprovider/gcp/block_store.go index 4fa9243c1..ff0567a99 100644 --- a/pkg/cloudprovider/gcp/block_store.go +++ b/pkg/cloudprovider/gcp/block_store.go @@ -36,7 +36,10 @@ import ( "github.com/heptio/velero/pkg/util/collections" ) -const projectKey = "project" +const ( + projectKey = "project" + zoneSeparator = "__" +) type blockStore struct { gce *compute.Service @@ -97,23 +100,23 @@ func extractProjectFromCreds() (string, error) { // by GKE when a storage class spans multiple availablity // zones. func isMultiZone(volumeAZ string) bool { - return strings.Contains(volumeAZ, "__") + return strings.Contains(volumeAZ, zoneSeparator) } -// parseRegion parses a failure-domain tag with multiple regions -// and returns a single region. Regions are sperated by double underscores (__). +// parseRegion parses a failure-domain tag with multiple zones +// and returns a single region. Zones are sperated by double underscores (__). // For example // input: us-central1-a__us-central1-b // return: us-central1 -// When a custom storage class spans multiple geographical regions, -// such as us-central1 and us-west1 only the region matching the cluster is used +// When a custom storage class spans multiple geographical zones, +// such as us-central1 and us-west1 only the zone matching the cluster is used // in the failure-domain tag. // For example // Cluster nodes in us-central1-c, us-central1-f // Storage class zones us-central1-a, us-central1-f, us-east1-a, us-east1-d // The failure-domain tag would be: us-central1-a__us-central1-f func parseRegion(volumeAZ string) (string, error) { - zones := strings.Split(volumeAZ, "__") + zones := strings.Split(volumeAZ, zoneSeparator) zone := zones[0] parts := strings.SplitAfterN(zone, "-", 3) if len(parts) < 2 { @@ -122,6 +125,22 @@ func parseRegion(volumeAZ string) (string, error) { return parts[0] + strings.TrimSuffix(parts[1], "-"), nil } +// Retrieve the URLs for zones via the GCP API. +func (b *blockStore) getZoneURLs(volumeAZ string) ([]string, error) { + zones := strings.Split(volumeAZ, zoneSeparator) + var zoneURLs []string + for _, z := range zones { + zone, err := b.gce.Zones.Get(b.project, z).Do() + if err != nil { + return nil, errors.WithStack(err) + } + + zoneURLs = append(zoneURLs, zone.SelfLink) + } + + return zoneURLs, nil +} + func (b *blockStore) CreateVolumeFromSnapshot(snapshotID, volumeType, volumeAZ string, iops *int64) (volumeID string, err error) { // get the snapshot so we can apply its tags to the volume res, err := b.gce.Snapshots.Get(b.project, snapshotID).Do() @@ -146,6 +165,15 @@ func (b *blockStore) CreateVolumeFromSnapshot(snapshotID, volumeType, volumeAZ s if err != nil { return "", err } + + // URLs for zones that the volume is replicated to within GCP + zoneURLs, err := b.getZoneURLs(volumeAZ) + if err != nil { + return "", err + } + + disk.ReplicaZones = zoneURLs + if _, err = b.gce.RegionDisks.Insert(b.project, volumeRegion, disk).Do(); err != nil { return "", errors.WithStack(err) }