diff --git a/examples/backup_at_b_restore_at_a.md b/examples/backup_at_b_restore_at_a.md index be0337b..ec5d32e 100644 --- a/examples/backup_at_b_restore_at_a.md +++ b/examples/backup_at_b_restore_at_a.md @@ -5,7 +5,7 @@ These steps are heavily inspired by the [gcp documentation](https://cloud.google Assume the following... - Project A [project-a]: GCP project we want to restore TO -- Project B [project-b]: GCP Project we want to restore FROM +- Project B [project-b]: GCP Project we want to backup FROM The steps below assume that you have not setup Velero yet. So make sure to skip any steps you've already completed. diff --git a/examples/velero_at_a_br_at_other.md b/examples/velero_at_a_br_at_other.md index cdf7541..e282f6e 100644 --- a/examples/velero_at_a_br_at_other.md +++ b/examples/velero_at_a_br_at_other.md @@ -6,7 +6,7 @@ Assume the following... - Project A [project-a]: The project where the Velero's service account is located, and the Velero service account is granted to have enough permission to do backup and restore in the other projects. - Project B [project-b]: The GCP project we want to restore TO. -- Project C [project-c]: The GCP project we want to restore FROM. +- Project C [project-c]: The GCP project we want to backup FROM. ## Set up Velero with permission in projects * In **project-a** diff --git a/velero-plugin-for-gcp/volume_snapshotter.go b/velero-plugin-for-gcp/volume_snapshotter.go index 338bbaa..85c91eb 100644 --- a/velero-plugin-for-gcp/volume_snapshotter.go +++ b/velero-plugin-for-gcp/volume_snapshotter.go @@ -416,6 +416,10 @@ func (b *VolumeSnapshotter) SetVolumeID(unstructuredPV runtime.Unstructured, vol return nil, fmt.Errorf("invalid volumeHandle for restore with CSI driver:%s, expected projects/{project}/zones/{zone}/disks/{name}, got %s", pdCSIDriver, handle) } + if b.IsVolumeCreatedCrossProjects(handle) == true { + projectRE := regexp.MustCompile(`projects\/[^\/]+\/`) + handle = projectRE.ReplaceAllString(handle, "projects/"+b.volumeProject+"/") + } pv.Spec.CSI.VolumeHandle = handle[:strings.LastIndex(handle, "/")+1] + volumeID } else { return nil, fmt.Errorf("unable to handle CSI driver: %s", driver) @@ -433,3 +437,18 @@ func (b *VolumeSnapshotter) SetVolumeID(unstructuredPV runtime.Unstructured, vol return &unstructured.Unstructured{Object: res}, nil } + +func (b *VolumeSnapshotter) IsVolumeCreatedCrossProjects(volumeHandle string) bool { + // Get project ID from volume handle + parsedStr := strings.Split(volumeHandle, "/") + if len(parsedStr) < 2 { + return false + } + projectID := parsedStr[1] + + if projectID != b.volumeProject { + return true + } + + return false +} diff --git a/velero-plugin-for-gcp/volume_snapshotter_test.go b/velero-plugin-for-gcp/volume_snapshotter_test.go index 8ff7ec9..c285d13 100644 --- a/velero-plugin-for-gcp/volume_snapshotter_test.go +++ b/velero-plugin-for-gcp/volume_snapshotter_test.go @@ -19,7 +19,6 @@ package main import ( "encoding/json" "os" - "strings" "testing" "github.com/pkg/errors" @@ -156,15 +155,13 @@ func TestSetVolumeID(t *testing.T) { } func TestSetVolumeIDForCSI(t *testing.T) { - b := &VolumeSnapshotter{ - log: logrus.New(), - } - cases := []struct { - name string - csiJSON string - volumeID string - wantErr bool + name string + csiJSON string + volumeID string + wantErr bool + volumeProject string + wantedVolumeID string }{ { name: "set ID to CSI with GKE pd CSI driver", @@ -173,8 +170,10 @@ func TestSetVolumeIDForCSI(t *testing.T) { "fsType": "ext4", "volumeHandle": "projects/velero-gcp/zones/us-central1-f/disks/pvc-a970184f-6cc1-4769-85ad-61dcaf8bf51d" }`, - volumeID: "restore-fd9729b5-868b-4544-9568-1c5d9121dabc", - wantErr: false, + volumeID: "restore-fd9729b5-868b-4544-9568-1c5d9121dabc", + wantErr: false, + volumeProject: "velero-gcp", + wantedVolumeID: "projects/velero-gcp/zones/us-central1-f/disks/restore-fd9729b5-868b-4544-9568-1c5d9121dabc", }, { name: "set ID to CSI with GKE pd CSI driver, but the volumeHandle is invalid", @@ -183,22 +182,41 @@ func TestSetVolumeIDForCSI(t *testing.T) { "fsType": "ext4", "volumeHandle": "pvc-a970184f-6cc1-4769-85ad-61dcaf8bf51d" }`, - volumeID: "restore-fd9729b5-868b-4544-9568-1c5d9121dabc", - wantErr: true, + volumeID: "restore-fd9729b5-868b-4544-9568-1c5d9121dabc", + wantErr: true, + volumeProject: "velero-gcp", }, { name: "set ID to CSI with unknown driver", - csiJSON: `"{ + csiJSON: `{ "driver": "xxx.csi.storage.gke.io", "fsType": "ext4", "volumeHandle": "projects/velero-gcp/zones/us-central1-f/disks/pvc-a970184f-6cc1-4769-85ad-61dcaf8bf51d" }`, - volumeID: "restore-fd9729b5-868b-4544-9568-1c5d9121dabc", - wantErr: true, + volumeID: "restore-fd9729b5-868b-4544-9568-1c5d9121dabc", + wantErr: true, + volumeProject: "velero-gcp", + }, + { + name: "volume project is different from original handle project", + csiJSON: `{ + "driver": "pd.csi.storage.gke.io", + "fsType": "ext4", + "volumeHandle": "projects/velero-gcp/zones/us-central1-f/disks/pvc-a970184f-6cc1-4769-85ad-61dcaf8bf51d" + }`, + volumeID: "restore-fd9729b5-868b-4544-9568-1c5d9121dabc", + wantErr: false, + volumeProject: "velero-gcp-2", + wantedVolumeID: "projects/velero-gcp-2/zones/us-central1-f/disks/restore-fd9729b5-868b-4544-9568-1c5d9121dabc", }, } for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { + b := &VolumeSnapshotter{ + log: logrus.New(), + volumeProject: tt.volumeProject, + } + res := &unstructured.Unstructured{ Object: map[string]interface{}{}, } @@ -207,17 +225,16 @@ func TestSetVolumeIDForCSI(t *testing.T) { res.Object["spec"] = map[string]interface{}{ "csi": csi, } - originalVolHanle, _ := csi["volumeHandle"].(string) newRes, err := b.SetVolumeID(res, tt.volumeID) if tt.wantErr { - assert.Error(t, err) + require.Error(t, err) } else { - assert.NoError(t, err) + require.NoError(t, err) newPV := new(v1.PersistentVolume) require.NoError(t, runtime.DefaultUnstructuredConverter.FromUnstructured(newRes.UnstructuredContent(), newPV)) - ind := strings.LastIndex(newPV.Spec.CSI.VolumeHandle, "/") - assert.Equal(t, tt.volumeID, newPV.Spec.CSI.VolumeHandle[ind+1:]) - assert.Equal(t, originalVolHanle[:ind], newPV.Spec.CSI.VolumeHandle[:ind]) + if tt.wantedVolumeID != "" { + require.Equal(t, tt.wantedVolumeID, newPV.Spec.CSI.VolumeHandle) + } } }) } @@ -421,3 +438,45 @@ func TestInit(t *testing.T) { err = os.Remove(default_credential_file_name) require.NoError(t, err) } + +func TestIsVolumeCreatedCrossProjects(t *testing.T) { + tests := []struct { + name string + volumeSnapshotter VolumeSnapshotter + volumeHandle string + expectedResult bool + }{ + { + name: "Invalid Volume handle", + volumeSnapshotter: VolumeSnapshotter{ + log: logrus.New(), + }, + volumeHandle: "InvalidHandle", + expectedResult: false, + }, + { + name: "Volume is created cross-project", + volumeSnapshotter: VolumeSnapshotter{ + log: logrus.New(), + volumeProject: "velero-gcp-2", + }, + volumeHandle: "projects/velero-gcp/zones/us-central1-f/disks/pvc-a970184f-6cc1-4769-85ad-61dcaf8bf51d", + expectedResult: true, + }, + { + name: "Volume is not created cross-project", + volumeSnapshotter: VolumeSnapshotter{ + log: logrus.New(), + volumeProject: "velero-gcp", + }, + volumeHandle: "projects/velero-gcp/zones/us-central1-f/disks/pvc-a970184f-6cc1-4769-85ad-61dcaf8bf51d", + expectedResult: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require.Equal(t, test.expectedResult, test.volumeSnapshotter.IsVolumeCreatedCrossProjects(test.volumeHandle)) + }) + } +}