diff --git a/changelogs/unreleased/7070-blackpiglet b/changelogs/unreleased/7070-blackpiglet new file mode 100644 index 000000000..75843b730 --- /dev/null +++ b/changelogs/unreleased/7070-blackpiglet @@ -0,0 +1 @@ +Add VolumeInfo metadata structures. \ No newline at end of file diff --git a/design/pv_backup_info.md b/design/pv_backup_info.md index 287000de7..771f8255c 100644 --- a/design/pv_backup_info.md +++ b/design/pv_backup_info.md @@ -35,44 +35,50 @@ The `restoreItem` function can decode the _backup-name_-volumes-info.json file t ## Detailed Design ### The VolumeInfo structure -_backup-name_-volumes-info.json file is a structure that contains an array of structure `VolumeInfo` and a VolumeInfo version parameter. The version is used to support multiple VolumeInfo version, and make the backward-compatible support possible. The current version is `1`. +_backup-name_-volumes-info.json file is a structure that contains an array of structure `VolumeInfo`. -The 1 version of `VolumeInfo` definition is: ``` golang -type VolumeInfoV1 struct { - PVCName string // The PVC's name. The format should be / +type VolumeInfo struct { + PVCName string // The PVC's name. + PVCNamespace string // The PVC's namespace. PVName string // The PV name. - BackupMethod string // The way the volume data is backed up. The valid value includes `VeleroNativeSnapshot`, `PodVolumeBackup`, `CSISnapshot` and `Skipped`. - SnapshotDataMovement bool // Whether the volume's snapshot data is moved to specified storage. + BackupMethod string // The way the volume data is backed up. The valid value includes `VeleroNativeSnapshot`, `PodVolumeBackup` and `CSISnapshot`. + SnapshotDataMoved bool // Whether the volume's snapshot data is moved to specified storage. + Skipped boolean // Whether the Volume is skipped in this backup. SkippedReason string // The reason for the volume is skipped in the backup. StartTimestamp *metav1.Time // Snapshot starts timestamp. + OperationID string // The Async Operation's ID. + CSISnapshotInfo CSISnapshotInfo - SnapshotDataMoveInfo SnapshotDataMoveInfo + SnapshotDataMovementInfo SnapshotDataMovementInfo NativeSnapshotInfo VeleroNativeSnapshotInfo PVBInfo PodVolumeBackupInfo + PVInfo PVInfo } // CSISnapshotInfo is used for displaying the CSI snapshot status type CSISnapshotInfo struct { - SnapshotHandle string // The actual snapshot ID. It can be the cloud provider's snapshot ID or the file-system uploader's snapshot. + SnapshotHandle string // It's the storage provider's snapshot ID for CSI. Size int64 // The snapshot corresponding volume size. Some of the volume backup methods cannot retrieve the data by current design, for example, the Velero native snapshot. Driver string // The name of the CSI driver. VSCName string // The name of the VolumeSnapshotContent. } -// SnapshotDataMoveInfo is used for displaying the snapshot data mover status. -type SnapshotDataMoveInfo struct { +// SnapshotDataMovementInfo is used for displaying the snapshot data mover status. +type SnapshotDataMovementInfo struct { DataMover string // The data mover used by the backup. The valid values are `velero` and ``(equals to `velero`). UploaderType string // The type of the uploader that uploads the snapshot data. The valid values are `kopia` and `restic`. It's useful for file-system backup and snapshot data mover. - RetainedSnapshot string // The name or ID of the snapshot associated object(SAO). + RetainedSnapshot string // The name or ID of the snapshot associated object(SAO). SAO is used to support local snapshots for the snapshot data mover, e.g. it could be a VolumeSnapshot for CSI snapshot data moign/pv_backup_info. + SnapshotHandle string // It's the filesystem repository's snapshot ID. + } // VeleroNativeSnapshotInfo is used for displaying the Velero native snapshot status. type VeleroNativeSnapshotInfo struct { - SnapshotHandle string // The actual snapshot ID. It can be the cloud provider's snapshot ID or the file-system uploader's snapshot. + SnapshotHandle string // It's the storage provider's snapshot ID for the Velero-native snapshot. Size int64 // The snapshot corresponding volume size. Some of the volume backup methods cannot retrieve the data by current design, for example, the Velero native snapshot. VolumeType string // The cloud provider snapshot volume type. @@ -82,33 +88,20 @@ type VeleroNativeSnapshotInfo struct { // PodVolumeBackupInfo is used for displaying the PodVolumeBackup snapshot status. type PodVolumeBackupInfo struct { - SnapshotHandle string // The actual snapshot ID. It can be the cloud provider's snapshot ID or the file-system uploader's snapshot. + SnapshotHandle string // It's the file-system uploader's snapshot ID for PodVolumeBackup. Size int64 // The snapshot corresponding volume size. Some of the volume backup methods cannot retrieve the data by current design, for example, the Velero native snapshot. UploaderType string // The type of the uploader that uploads the data. The valid values are `kopia` and `restic`. It's useful for file-system backup and snapshot data mover. - VolumeName string // The PVC's corresponding volume name used by Pod - PodName string // The Pod name mounting this PVC. The format should be /. -} -``` - -To make Velero support multiple versions of VolumeInfo, Velero needs to create a structure to include the version and the versioned volume information together. The information is persisted in the metadata file during backup creation. When reading the VolumeInfo metadata file, Velero reads the version information first by un-marshalling the metadata file by structure `VolumeInfoVersion`, then decide to use which version of the VolumeInfos according to the version value. - -If there is need to introduce a non compatible change to the VolumeInfo, a new version of `VolumeInfos` and `VolumeInfo` are needed. For example, version 2 is created, then `VolumeInfosV2` and `VolumeInfoV2` structures are needed. - -Only when there a non-backward-compatible change introduced in the `VolumeInfo` structure, the `version`'s version will be incremented. - -``` golang -type VolumeInfoVersion struct { - Version string + VolumeName string // The PVC's corresponding volume name used by Pod: https://github.com/kubernetes/kubernetes/blob/e4b74dd12fa8cb63c174091d5536a10b8ec19d34/pkg/apis/core/types.go#L48 + PodName string // The Pod name mounting this PVC. The format should be /. + NodeName string // The PVB-taken k8s node's name. } -type VolumeInfosV2 struct { - Infos []VolumeInfoV2 - Version string // VolumeInfo structure's version information. -} - -type VolumeInfoV2 struct { - ... +// PVInfo is used to store some PV information modified after creation. +// Those information are lost after PV recreation. +type PVInfo struct { + ReclaimPolicy string // ReclaimPolicy of PV. It could be different from the referenced StorageClass. + Labels map[string]string // The PV's labels should be kept after recreation. } ``` @@ -164,11 +157,12 @@ After introducing the VolumeInfo array, the following logic will be added. ... case CSISnapshot: ... - case Skipped: - // Check whether the Velero server should restore the PV depending on the DeletionPolicy setting. default: // Need to check whether the volume is backed up by the SnapshotDataMover. if volumeInfo.SnapshotDataMovement: + + // Check whether the Velero server should restore the PV depending on the DeletionPolicy setting. + if volumeInfo.Skipped: ``` ### How the VolumeInfo metadata file is deleted diff --git a/pkg/controller/backup_controller.go b/pkg/controller/backup_controller.go index 0c222dc9c..746c9d789 100644 --- a/pkg/controller/backup_controller.go +++ b/pkg/controller/backup_controller.go @@ -796,6 +796,7 @@ func persistBackup(backup *pkgbackup.Request, ) []error { persistErrs := []error{} backupJSON := new(bytes.Buffer) + volumeInfos := make([]volume.VolumeInfo, 0) if err := encode.To(backup.Backup, "json", backupJSON); err != nil { persistErrs = append(persistErrs, errors.Wrap(err, "error encoding backup")) @@ -842,6 +843,11 @@ func persistBackup(backup *pkgbackup.Request, persistErrs = append(persistErrs, errs...) } + volumeInfoJSON, errs := encode.ToJSONGzip(volumeInfos, "backup volumes information") + if errs != nil { + persistErrs = append(persistErrs, errs...) + } + if len(persistErrs) > 0 { // Don't upload the JSON files or backup tarball if encoding to json fails. backupJSON = nil @@ -853,6 +859,7 @@ func persistBackup(backup *pkgbackup.Request, csiSnapshotContentsJSON = nil csiSnapshotClassesJSON = nil backupResult = nil + volumeInfoJSON = nil } backupInfo := persistence.BackupInfo{ @@ -868,6 +875,7 @@ func persistBackup(backup *pkgbackup.Request, CSIVolumeSnapshots: csiSnapshotJSON, CSIVolumeSnapshotContents: csiSnapshotContentsJSON, CSIVolumeSnapshotClasses: csiSnapshotClassesJSON, + BackupVolumeInfo: volumeInfoJSON, } if err := backupStore.PutBackup(backupInfo); err != nil { persistErrs = append(persistErrs, err) diff --git a/pkg/persistence/object_store.go b/pkg/persistence/object_store.go index 4e0ddbe91..26bddc565 100644 --- a/pkg/persistence/object_store.go +++ b/pkg/persistence/object_store.go @@ -50,7 +50,8 @@ type BackupInfo struct { BackupResourceList, CSIVolumeSnapshots, CSIVolumeSnapshotContents, - CSIVolumeSnapshotClasses io.Reader + CSIVolumeSnapshotClasses, + BackupVolumeInfo io.Reader } // BackupStore defines operations for creating, retrieving, and deleting @@ -270,6 +271,7 @@ func (s *objectBackupStore) PutBackup(info BackupInfo) error { s.layout.getCSIVolumeSnapshotContentsKey(info.Name): info.CSIVolumeSnapshotContents, s.layout.getCSIVolumeSnapshotClassesKey(info.Name): info.CSIVolumeSnapshotClasses, s.layout.getBackupResultsKey(info.Name): info.BackupResults, + s.layout.getBackupVolumeInfoKey(info.Name): info.BackupVolumeInfo, } for key, reader := range backupObjs { @@ -491,6 +493,25 @@ func (s *objectBackupStore) GetPodVolumeBackups(name string) ([]*velerov1api.Pod return podVolumeBackups, nil } +func (s *objectBackupStore) GetBackupVolumeInfos(name string) (*volume.VolumeInfos, error) { + var volumeInfos *volume.VolumeInfos + + res, err := tryGet(s.objectStore, s.bucket, s.layout.getBackupVolumeInfoKey(name)) + if err != nil { + return volumeInfos, err + } + if res == nil { + return volumeInfos, nil + } + defer res.Close() + + if err := decode(res, &volumeInfos); err != nil { + return volumeInfos, err + } + + return volumeInfos, nil +} + func (s *objectBackupStore) GetBackupContents(name string) (io.ReadCloser, error) { return s.objectStore.GetObject(s.bucket, s.layout.getBackupContentsKey(name)) } diff --git a/pkg/persistence/object_store_layout.go b/pkg/persistence/object_store_layout.go index 134562337..fff920d0a 100644 --- a/pkg/persistence/object_store_layout.go +++ b/pkg/persistence/object_store_layout.go @@ -128,3 +128,7 @@ func (l *ObjectStoreLayout) getCSIVolumeSnapshotClassesKey(backup string) string func (l *ObjectStoreLayout) getBackupResultsKey(backup string) string { return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-results.gz", backup)) } + +func (l *ObjectStoreLayout) getBackupVolumeInfoKey(backup string) string { + return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-volumeinfos.json.gz", backup)) +} diff --git a/pkg/persistence/object_store_test.go b/pkg/persistence/object_store_test.go index de632d924..6c149f188 100644 --- a/pkg/persistence/object_store_test.go +++ b/pkg/persistence/object_store_test.go @@ -227,6 +227,7 @@ func TestPutBackup(t *testing.T) { snapshots io.Reader backupItemOperations io.Reader resourceList io.Reader + backupVolumeInfo io.Reader expectedErr string expectedKeys []string }{ @@ -239,6 +240,7 @@ func TestPutBackup(t *testing.T) { snapshots: newStringReadSeeker("snapshots"), backupItemOperations: newStringReadSeeker("backupItemOperations"), resourceList: newStringReadSeeker("resourceList"), + backupVolumeInfo: newStringReadSeeker("backupVolumeInfo"), expectedErr: "", expectedKeys: []string{ "backups/backup-1/velero-backup.json", @@ -248,6 +250,7 @@ func TestPutBackup(t *testing.T) { "backups/backup-1/backup-1-volumesnapshots.json.gz", "backups/backup-1/backup-1-itemoperations.json.gz", "backups/backup-1/backup-1-resource-list.json.gz", + "backups/backup-1/backup-1-volumeinfos.json.gz", }, }, { @@ -260,6 +263,7 @@ func TestPutBackup(t *testing.T) { snapshots: newStringReadSeeker("snapshots"), backupItemOperations: newStringReadSeeker("backupItemOperations"), resourceList: newStringReadSeeker("resourceList"), + backupVolumeInfo: newStringReadSeeker("backupVolumeInfo"), expectedErr: "", expectedKeys: []string{ "prefix-1/backups/backup-1/velero-backup.json", @@ -269,6 +273,7 @@ func TestPutBackup(t *testing.T) { "prefix-1/backups/backup-1/backup-1-volumesnapshots.json.gz", "prefix-1/backups/backup-1/backup-1-itemoperations.json.gz", "prefix-1/backups/backup-1/backup-1-resource-list.json.gz", + "prefix-1/backups/backup-1/backup-1-volumeinfos.json.gz", }, }, { @@ -280,6 +285,7 @@ func TestPutBackup(t *testing.T) { snapshots: newStringReadSeeker("snapshots"), backupItemOperations: newStringReadSeeker("backupItemOperations"), resourceList: newStringReadSeeker("resourceList"), + backupVolumeInfo: newStringReadSeeker("backupVolumeInfo"), expectedErr: "error readers return errors", expectedKeys: []string{"backups/backup-1/backup-1-logs.gz"}, }, @@ -291,6 +297,7 @@ func TestPutBackup(t *testing.T) { snapshots: newStringReadSeeker("snapshots"), backupItemOperations: newStringReadSeeker("backupItemOperations"), resourceList: newStringReadSeeker("resourceList"), + backupVolumeInfo: newStringReadSeeker("backupVolumeInfo"), expectedErr: "error readers return errors", expectedKeys: []string{"backups/backup-1/backup-1-logs.gz"}, }, @@ -303,6 +310,7 @@ func TestPutBackup(t *testing.T) { snapshots: newStringReadSeeker("snapshots"), backupItemOperations: newStringReadSeeker("backupItemOperations"), resourceList: newStringReadSeeker("resourceList"), + backupVolumeInfo: newStringReadSeeker("backupVolumeInfo"), expectedErr: "", expectedKeys: []string{ "backups/backup-1/velero-backup.json", @@ -311,23 +319,26 @@ func TestPutBackup(t *testing.T) { "backups/backup-1/backup-1-volumesnapshots.json.gz", "backups/backup-1/backup-1-itemoperations.json.gz", "backups/backup-1/backup-1-resource-list.json.gz", + "backups/backup-1/backup-1-volumeinfos.json.gz", }, }, { - name: "data should be uploaded even when metadata is nil", - metadata: nil, - contents: newStringReadSeeker("contents"), - log: newStringReadSeeker("log"), - podVolumeBackup: newStringReadSeeker("podVolumeBackup"), - snapshots: newStringReadSeeker("snapshots"), - resourceList: newStringReadSeeker("resourceList"), - expectedErr: "", + name: "data should be uploaded even when metadata is nil", + metadata: nil, + contents: newStringReadSeeker("contents"), + log: newStringReadSeeker("log"), + podVolumeBackup: newStringReadSeeker("podVolumeBackup"), + snapshots: newStringReadSeeker("snapshots"), + resourceList: newStringReadSeeker("resourceList"), + backupVolumeInfo: newStringReadSeeker("backupVolumeInfo"), + expectedErr: "", expectedKeys: []string{ "backups/backup-1/backup-1.tar.gz", "backups/backup-1/backup-1-logs.gz", "backups/backup-1/backup-1-podvolumebackups.json.gz", "backups/backup-1/backup-1-volumesnapshots.json.gz", "backups/backup-1/backup-1-resource-list.json.gz", + "backups/backup-1/backup-1-volumeinfos.json.gz", }, }, } @@ -345,6 +356,7 @@ func TestPutBackup(t *testing.T) { VolumeSnapshots: tc.snapshots, BackupItemOperations: tc.backupItemOperations, BackupResourceList: tc.resourceList, + BackupVolumeInfo: tc.backupVolumeInfo, } err := harness.PutBackup(backupInfo) @@ -1045,6 +1057,90 @@ func TestNewObjectBackupStoreGetterConfig(t *testing.T) { } } +func TestGetBackupVolumeInfos(t *testing.T) { + tests := []struct { + name string + volumeInfo *volume.VolumeInfos + volumeInfoStr string + expectedErr string + expectedResult []volume.VolumeInfo + }{ + { + name: "No VolumeInfos, expect no error.", + }, + { + name: "Valid VolumeInfo, should pass.", + volumeInfo: &volume.VolumeInfos{ + VolumeInfos: []volume.VolumeInfo{ + { + PVCName: "pvcName", + PVName: "pvName", + Skipped: true, + SnapshotDataMoved: false, + }, + }, + }, + expectedResult: []volume.VolumeInfo{ + { + PVCName: "pvcName", + PVName: "pvName", + Skipped: true, + SnapshotDataMoved: false, + }, + }, + }, + { + name: "Invalid VolumeInfo string, should also pass.", + volumeInfoStr: `{"volumeInfos": [{"abc": "123", "def": "456", "pvcName": "pvcName"}]}`, + expectedResult: []volume.VolumeInfo{ + { + PVCName: "pvcName", + }, + }, + }, + } + + harness := newObjectBackupStoreTestHarness("test-bucket", "") + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if tc.volumeInfo != nil { + obj := new(bytes.Buffer) + gzw := gzip.NewWriter(obj) + + require.NoError(t, json.NewEncoder(gzw).Encode(tc.volumeInfo)) + require.NoError(t, gzw.Close()) + harness.objectStore.PutObject(harness.bucket, "backups/test-backup/test-backup-volumeinfos.json.gz", obj) + } + + if tc.volumeInfoStr != "" { + obj := new(bytes.Buffer) + gzw := gzip.NewWriter(obj) + _, err := gzw.Write([]byte(tc.volumeInfoStr)) + require.NoError(t, err) + + require.NoError(t, gzw.Close()) + harness.objectStore.PutObject(harness.bucket, "backups/test-backup/test-backup-volumeinfos.json.gz", obj) + } + + result, err := harness.GetBackupVolumeInfos("test-backup") + if tc.expectedErr != "" { + require.Equal(t, tc.expectedErr, err.Error()) + } else { + if err != nil { + fmt.Println(err.Error()) + } + require.NoError(t, err) + } + + if len(tc.expectedResult) > 0 { + require.Equal(t, tc.expectedResult, result.VolumeInfos) + } + + }) + } +} + func encodeToBytes(obj runtime.Object) []byte { res, err := encode.Encode(obj, "json") if err != nil { diff --git a/pkg/volume/volume_info_common.go b/pkg/volume/volume_info_common.go new file mode 100644 index 000000000..cfca31df9 --- /dev/null +++ b/pkg/volume/volume_info_common.go @@ -0,0 +1,147 @@ +/* +Copyright 2018 the Velero 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 volume + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +type VolumeBackupMethod string + +const ( + NativeSnapshot VolumeBackupMethod = "NativeSnapshot" + PodVolumeBackup VolumeBackupMethod = "PodVolumeBackup" + CSISnapshot VolumeBackupMethod = "CSISnapshot" +) + +type VolumeInfoVersion struct { + Version string `json:"version"` +} + +type VolumeInfos struct { + VolumeInfos []VolumeInfo `json:"volumeInfos"` +} + +type VolumeInfo struct { + // The PVC's name. + PVCName string `json:"pvcName,omitempty"` + + // The PVC's namespace + PVCNamespace string `json:"pvcNamespace,omitempty"` + + // The PV name. + PVName string `json:"pvName,omitempty"` + + // The way the volume data is backed up. The valid value includes `VeleroNativeSnapshot`, `PodVolumeBackup` and `CSISnapshot`. + BackupMethod VolumeBackupMethod `json:"backupMethod,omitempty"` + + // Whether the volume's snapshot data is moved to specified storage. + SnapshotDataMoved bool `json:"snapshotDataMoved"` + + // Whether the local snapshot is preserved after snapshot is moved. + PreserveLocalSnapshot bool `json:"preserveLocalSnapshot"` + + // Whether the Volume is skipped in this backup. + Skipped bool `json:"skipped"` + + // The reason for the volume is skipped in the backup. + SkippedReason string `json:"skippedReason,omitempty"` + + // Snapshot starts timestamp. + StartTimestamp *metav1.Time `json:"startTimestamp,omitempty"` + + // The Async Operation's ID. + OperationID string `json:"operationID,omitempty"` + + CSISnapshotInfo CSISnapshotInfo `json:"csiSnapshotInfo,omitempty"` + SnapshotDataMovementInfo SnapshotDataMovementInfo `json:"snapshotDataMovementInfo,omitempty"` + NativeSnapshotInfo NativeSnapshotInfo `json:"nativeSnapshotInfo,omitempty"` + PVBInfo PodVolumeBackupInfo `json:"pvbInfo,omitempty"` +} + +// CSISnapshotInfo is used for displaying the CSI snapshot status +type CSISnapshotInfo struct { + // It's the storage provider's snapshot ID for CSI. + SnapshotHandle string `json:"snapshotHandle"` + + // The snapshot corresponding volume size. Some of the volume backup methods cannot retrieve the data by current design, for example, the Velero native snapshot. + Size int64 `json:"size"` + + // The name of the CSI driver. + Driver string `json:"driver"` + + // The name of the VolumeSnapshotContent. + VSCName string `json:"vscName"` +} + +// SnapshotDataMovementInfo is used for displaying the snapshot data mover status. +type SnapshotDataMovementInfo struct { + // The data mover used by the backup. The valid values are `velero` and ``(equals to `velero`). + DataMover string `json:"dataMover"` + + // The type of the uploader that uploads the snapshot data. The valid values are `kopia` and `restic`. It's useful for file-system backup and snapshot data mover. + UploaderType string `json:"uploaderType"` + + // The name or ID of the snapshot associated object(SAO). + // SAO is used to support local snapshots for the snapshot data mover, + // e.g. it could be a VolumeSnapshot for CSI snapshot data movement. + RetainedSnapshot string `json:"retainedSnapshot"` + + // It's the filesystem repository's snapshot ID. + SnapshotHandle string `json:"snapshotHandle"` +} + +// NativeSnapshotInfo is used for displaying the Velero native snapshot status. +// A Velero Native Snapshot is a cloud storage snapshot taken by the Velero native +// plugins, e.g. velero-plugin-for-aws, velero-plugin-for-gcp, and +// velero-plugin-for-microsoft-azure. +type NativeSnapshotInfo struct { + // It's the storage provider's snapshot ID for the Velero-native snapshot. + SnapshotHandle string `json:"snapshotHandle"` + + // The snapshot corresponding volume size. Some of the volume backup methods cannot retrieve the data by current design, for example, the Velero native snapshot. + Size int64 `json:"size"` + + // The cloud provider snapshot volume type. + VolumeType string `json:"volumeType"` + + // The cloud provider snapshot volume's availability zones. + VolumeAZ string `json:"volumeAZ"` + + // The cloud provider snapshot volume's IOPS. + IOPS string `json:"iops"` +} + +// PodVolumeBackupInfo is used for displaying the PodVolumeBackup snapshot status. +type PodVolumeBackupInfo struct { + // It's the file-system uploader's snapshot ID for PodVolumeBackup. + SnapshotHandle string `json:"snapshotHandle"` + + // The snapshot corresponding volume size. Some of the volume backup methods cannot retrieve the data by current design, for example, the Velero native snapshot. + Size int64 `json:"size"` + + // The type of the uploader that uploads the data. The valid values are `kopia` and `restic`. It's useful for file-system backup and snapshot data mover. + UploaderType string `json:"uploaderType"` + + // The PVC's corresponding volume name used by Pod + // https://github.com/kubernetes/kubernetes/blob/e4b74dd12fa8cb63c174091d5536a10b8ec19d34/pkg/apis/core/types.go#L48 + VolumeName string `json:"volumeName"` + + // The Pod name mounting this PVC. The format should be /. + PodName string `json:"podName"` + + // The PVB-taken k8s node's name. + NodeName string `json:"nodeName"` +}