mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-01-03 11:45:20 +00:00
Update CSI plugin common branch flow and add mechanism to determine VGSClass
Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com>
This commit is contained in:
@@ -135,78 +135,101 @@ flowchart TD
|
||||
```
|
||||
|
||||
#### Updates to CSI PVC plugin:
|
||||
- When a PVC has a VGS label and no VS (created via VGS) exists:
|
||||
- Create VGS:
|
||||
- This triggers creation of the corresponding VGSC, VS, and VSC objects.
|
||||
- Wait for VS Status:
|
||||
- Wait until each VS (one per PVC in the group) has its volumeGroupSnapshotName set. This confirms that the snapshot controller has done its work.
|
||||
- Update VS Objects:
|
||||
- Remove owner references and VGS-related finalizers from the VS objects (decoupling them to prevent cascading deletion).
|
||||
- Add backup metadata (BackupName, BackupUUID, PVC name) as labels. This metadata is later used to skip re-creating a VGS when another PVC of the same group is processed.
|
||||
- Patch and Cleanup:
|
||||
- Patch the VGSC deletionPolicy to Retain so that when you delete the VGSC, the underlying VSC (and the storage snapshots) remain.
|
||||
- Delete the temporary VGS and VGSC objects.
|
||||
- Branching:
|
||||
- For non‑datamover cases, skip the creation of an individual VS (since it was created via VGS) and add the VS objects as additional items.
|
||||
- For datamover cases, create DataUploads for the VS–PVC pair (using the VS created by the VGS workflow) and add those as additional items.
|
||||
|
||||
- When a PVC has a VGS label and a VS created via an earlier VGS workflow already exists:
|
||||
- List VS objects in the PVC’s namespace using labels (BackupUUID, BackupName, PVCName).
|
||||
- Verify that a VS exists and that its status shows a non‑empty volumeGroupSnapshotName.
|
||||
- If so, skip VGS (and VS) creation and continue with the legacy workflow.
|
||||
- If a VS is found but it wasn’t created by the VGS workflow (i.e. it lacks the volumeGroupSnapshotName), then the backup for that PVC is failed, resulting in a partially failed backup.
|
||||
The CSI PVC plugin now supports obtaining a VolumeSnapshot (VS) reference for a PVC in three ways, and then applies common branching for datamover and non‑datamover workflows:
|
||||
|
||||
- When a PVC does not have a VGS label:
|
||||
- The legacy workflow is followed, creating an individual VolumeSnapshot as before.
|
||||
- Scenario 1: PVC has a VGS label and no VS (created via the VGS workflow) exists for its volume group:
|
||||
- Determine VGSClass: The plugin checks for CSI driver of all the grouped PVCs and then uses the corresponding VGSClass in VGS spec. If the grouped PVC has more than one CSI driver, VGS creation is skipped and backup fails.
|
||||
- Create VGS: The plugin creates a new VolumeGroupSnapshot (VGS) for the PVC’s volume group. This action automatically triggers creation of the corresponding VGSC, VS, and VSC objects.
|
||||
- Wait for VS Status: The plugin waits until each VS (one per PVC in the group) has its `volumeGroupSnapshotName` populated. This confirms that the snapshot controller has completed its work. `CSISnapshotTimeout` will be used here.
|
||||
- Update VS Objects: Once the VS objects are provisioned, the plugin updates them by removing VGS owner references and VGS-related finalizers, and by adding backup metadata labels (including BackupName, BackupUUID, and PVC name). These labels are later used to detect an existing VS when processing another PVC of the same group.
|
||||
- Patch and Cleanup: The plugin patches the deletionPolicy of the VGSC to "Retain" (ensuring that deletion of the VGSC does not remove the underlying VSC objects or storage snapshots) and then deletes the temporary VGS and VGSC objects.
|
||||
|
||||
- Scenario 2: PVC has a VGS label and a VS created via an earlier VGS workflow already exists:
|
||||
- The plugin lists VS objects in the PVC’s namespace using backup metadata labels (BackupUID, BackupName, and PVCName).
|
||||
- It verifies that at least one VS has a non‑empty `volumeGroupSnapshotName` in its status.
|
||||
- If such a VS exists, the plugin skips creating a new VGS (or VS) and proceeds with the legacy workflow using the existing VS.
|
||||
- If a VS is found but its status does not indicate it was created by the VGS workflow (i.e. its `volumeGroupSnapshotName` is empty), the backup for that PVC is failed, resulting in a partially failed backup.
|
||||
- Scenario 3: PVC does not have a VGS label:
|
||||
- The legacy workflow is followed, and an individual VolumeSnapshot (VS) is created for the PVC.
|
||||
- Common Branching for Datamover and Non‑datamover Workflows:
|
||||
- Once a VS reference (`vsRef`) is determined—whether through the VGS workflow (Scenario 1 or 2) or the legacy workflow (Scenario 3)—the plugin then applies the common branching:
|
||||
- Non‑datamover Case: The VS reference is directly added as an additional backup item.
|
||||
|
||||
- Datamover Case: The plugin waits until the VS’s associated VSC snapshot handle is ready (using the configured CSISnapshotTimeout), then creates a DataUpload for the VS–PVC pair. The resulting DataUpload is then added as an additional backup item.
|
||||
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
%% Section 1: Accept VGS Label from User
|
||||
%% Section 1: Accept VGS Label from User
|
||||
subgraph Accept_Label
|
||||
A1[User sets VGS label]
|
||||
A2[User labels PVCs before backup]
|
||||
A1 --> A2
|
||||
A1[User sets VGS label key using default velero.io/volume-group-snapshot or via server arg or Backup API spec]
|
||||
A2[User labels PVCs before backup]
|
||||
A1 --> A2
|
||||
end
|
||||
|
||||
%% Section 2: PVC ItemBlockAction Plugin
|
||||
%% Section 2: PVC ItemBlockAction Plugin Extension
|
||||
subgraph PVC_ItemBlockAction
|
||||
B1[Check PVC is bound and has VolumeName]
|
||||
B2[Add related PV to relatedItems]
|
||||
B3[Add pods mounting PVC to relatedItems]
|
||||
B4[Check if PVC has user-specified VGS label]
|
||||
B5[List PVCs in namespace matching label criteria]
|
||||
B6[Add matching PVCs to relatedItems]
|
||||
B1 --> B2 --> B3 --> B4
|
||||
B4 -- Yes --> B5
|
||||
B5 --> B6
|
||||
B1[Check PVC is bound and has VolumeName]
|
||||
B2[Add related PV to relatedItems]
|
||||
B3[Add pods mounting PVC to relatedItems]
|
||||
B4[Check if PVC has user-specified VGS label]
|
||||
B5[List PVCs in namespace matching label criteria]
|
||||
B6[Add matching PVCs to relatedItems]
|
||||
B1 --> B2 --> B3 --> B4
|
||||
B4 -- Yes --> B5
|
||||
B5 --> B6
|
||||
end
|
||||
|
||||
%% Section 3: CSI PVC Plugin Updates
|
||||
subgraph CSI_PVC_Plugin
|
||||
C1[For each PVC, check for VGS label]
|
||||
C1 -- Yes --> C2[Case 1: VGS for volume group not yet created]
|
||||
C2 -- True --> C3[Create new VGS triggering VGSC, VS and VSC creation]
|
||||
C3 --> C4[Wait for VS objects to show volumeGroupSnapshotName using CSISnapshotTimeout]
|
||||
C4 --> C5[Update VS objects: remove VGS owner refs and VGS finalizers; add BackupName, BackupUUID, PVC name as labels]
|
||||
C5 --> C6[Patch VGSC deletionPolicy to Retain]
|
||||
C6 --> C7[Delete VGS object]
|
||||
C7 --> C8[Delete VGSC]
|
||||
C8 --> C9[For non-datamover: Skip individual VS creation; add VS from VGS as additional item]
|
||||
C8 --> C10[For datamover: Create DataUpload for VS-PVC pair; add DU as additional item]
|
||||
%% Section 3: CSI PVC Plugin Updates
|
||||
subgraph CSI_PVC_Plugin
|
||||
C1[For each PVC, check for VGS label]
|
||||
C1 -- Has VGS label --> C2[Determine scenario]
|
||||
C1 -- No VGS label --> C16[Scenario 3: Legacy workflow - create individual VS]
|
||||
|
||||
C1 -- Yes --> C11[Case 2: VGS was created then deleted]
|
||||
C11 --> C12[List VS using labels BackupUID, BackupName, PVCName]
|
||||
C12 --> C13[Check if VS has non-empty volumeGroupSnapshotName]
|
||||
C13 -- Yes --> C14[Skip VGS creation; use existing VS/DU; legacy workflow continues]
|
||||
C13 -- No --> C15[Fail backup for PVC]
|
||||
%% Scenario 1: No existing VS via VGS exists
|
||||
subgraph Scenario1[Scenario 1: No existing VS via VGS]
|
||||
S1[List grouped PVCs using VGS label]
|
||||
S2[Determine CSI driver for grouped PVCs]
|
||||
S3[If single CSI driver then select matching VGSClass; else fail backup]
|
||||
S4[Create new VGS triggering VGSC, VS, and VSC creation]
|
||||
S5[Wait for VS objects to have nonempty volumeGroupSnapshotName]
|
||||
S6[Update VS objects; remove VGS owner refs and finalizers; add backup metadata labels]
|
||||
S7[Patch VGSC deletionPolicy to Retain]
|
||||
S8[Delete transient VGS and VGSC]
|
||||
S1 --> S2 --> S3 --> S4 --> S5 --> S6 --> S7 --> S8
|
||||
|
||||
end
|
||||
|
||||
C1 -- No --> C16[Case 3: PVC lacks VGS label; follow legacy workflow to create VS]
|
||||
end
|
||||
%% Scenario 2: Existing VS via VGS exists
|
||||
subgraph Scenario2[Scenario 2: Existing VS via VGS exists]
|
||||
S9[List VS objects using backup metadata - BackupUID, BackupName, PVCName]
|
||||
S10[Check if any VS has nonempty volumeGroupSnapshotName]
|
||||
S9 --> S10
|
||||
S10 -- Yes --> S11[Use existing VS]
|
||||
S10 -- No --> S12[Fail backup for PVC]
|
||||
end
|
||||
|
||||
%% Connect Main Sections
|
||||
A2 --> B1
|
||||
B6 --> C1
|
||||
C2 -- Scenario1 applies --> S1
|
||||
C2 -- Scenario2 applies --> S9
|
||||
|
||||
%% Common Branch: After obtaining a VS reference
|
||||
subgraph Common_Branch[Common Branch]
|
||||
CB1[Obtain VS reference as vsRef]
|
||||
CB2[If non-datamover, add vsRef as additional backup item]
|
||||
CB3[If datamover, wait for VSC handle and create DataUpload; add DataUpload as additional backup item]
|
||||
CB1 --> CB2
|
||||
CB1 --> CB3
|
||||
end
|
||||
|
||||
%% Connect Scenario outcomes and legacy branch to the common branch
|
||||
S8 --> CB1
|
||||
S11 --> CB1
|
||||
C16 --> CB1
|
||||
end
|
||||
|
||||
%% Overall Flow Connections
|
||||
A2 --> B1
|
||||
B6 --> C1
|
||||
|
||||
```
|
||||
|
||||
@@ -278,239 +301,243 @@ Backup workflow:
|
||||
- Updates to [CSI PVC plugin](https://github.com/vmware-tanzu/velero/blob/512199723ff95d5016b32e91e3bf06b65f57d608/pkg/backup/actions/csi/pvc_action.go#L200) (Update the Execute method):
|
||||
```go
|
||||
func (p *pvcBackupItemAction) Execute(
|
||||
item runtime.Unstructured,
|
||||
backup *velerov1api.Backup,
|
||||
item runtime.Unstructured,
|
||||
backup *velerov1api.Backup,
|
||||
) (
|
||||
runtime.Unstructured,
|
||||
[]velero.ResourceIdentifier,
|
||||
string,
|
||||
[]velero.ResourceIdentifier,
|
||||
error,
|
||||
runtime.Unstructured,
|
||||
[]velero.ResourceIdentifier,
|
||||
string,
|
||||
[]velero.ResourceIdentifier,
|
||||
error,
|
||||
) {
|
||||
p.log.Info("Starting PVCBackupItemAction")
|
||||
p.log.Info("Starting PVCBackupItemAction")
|
||||
|
||||
if valid := p.validateBackup(*backup); !valid {
|
||||
return item, nil, "", nil, nil
|
||||
}
|
||||
// Validate backup policy and PVC/PV
|
||||
if valid := p.validateBackup(*backup); !valid {
|
||||
return item, nil, "", nil, nil
|
||||
}
|
||||
|
||||
var pvc corev1api.PersistentVolumeClaim
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(
|
||||
item.UnstructuredContent(), &pvc,
|
||||
); err != nil {
|
||||
return nil, nil, "", nil, errors.WithStack(err)
|
||||
}
|
||||
var pvc corev1api.PersistentVolumeClaim
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(item.UnstructuredContent(), &pvc); err != nil {
|
||||
return nil, nil, "", nil, errors.WithStack(err)
|
||||
}
|
||||
if valid, item, err := p.validatePVCandPV(pvc, item); !valid {
|
||||
if err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
return item, nil, "", nil, nil
|
||||
}
|
||||
|
||||
if valid, item, err := p.validatePVCandPV(pvc, item); !valid {
|
||||
if err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
return item, nil, "", nil, nil
|
||||
}
|
||||
shouldSnapshot, err := volumehelper.ShouldPerformSnapshotWithBackup(
|
||||
item,
|
||||
kuberesource.PersistentVolumeClaims,
|
||||
*backup,
|
||||
p.crClient,
|
||||
p.log,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
if !shouldSnapshot {
|
||||
p.log.Debugf("CSI plugin skip snapshot for PVC %s according to VolumeHelper setting", pvc.Namespace+"/"+pvc.Name)
|
||||
return nil, nil, "", nil, nil
|
||||
}
|
||||
|
||||
shouldSnapshot, err := volumehelper.ShouldPerformSnapshotWithBackup(
|
||||
item,
|
||||
kuberesource.PersistentVolumeClaims,
|
||||
*backup,
|
||||
p.crClient,
|
||||
p.log,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
if !shouldSnapshot {
|
||||
p.log.Debugf("CSI plugin skip snapshot for PVC %s according to VolumeHelper setting", pvc.Namespace+"/"+pvc.Name)
|
||||
return nil, nil, "", nil, nil
|
||||
}
|
||||
var additionalItems []velero.ResourceIdentifier
|
||||
var operationID string
|
||||
var itemToUpdate []velero.ResourceIdentifier
|
||||
|
||||
var additionalItems []velero.ResourceIdentifier
|
||||
operationID := ""
|
||||
var itemToUpdate []velero.ResourceIdentifier
|
||||
// vsRef will be our common reference to the VolumeSnapshot (VS)
|
||||
var vsRef *corev1api.ObjectReference
|
||||
|
||||
// vsRef will be used to apply common labels/annotations
|
||||
var vsRef *corev1api.ObjectReference
|
||||
// Retrieve the VGS label key from the backup spec.
|
||||
vgsLabelKey := backup.Spec.VolumeGroupSnapshotLabelKey
|
||||
|
||||
// VGS branch: check if PVC has the VGS label key set on it.
|
||||
vgsLabelKey := backup.Spec.VolumeGroupSnapshotLabelKey
|
||||
if group, ok := pvc.Labels[vgsLabelKey]; ok && group != "" {
|
||||
p.log.Infof("PVC %s has VGS label with group %s", pvc.Name, group)
|
||||
// First, check if a VS created via a VGS workflow exists for this PVC.
|
||||
existingVS, err := p.findExistingVSForBackup(backup.UID, backup.Name, pvc.Name, pvc.Namespace)
|
||||
if err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
if existingVS != nil && existingVS.Status.VolumeGroupSnapshotName != "" {
|
||||
p.log.Infof("Existing VS %s found for PVC %s in group %s; skipping VGS creation", existingVS.Name, pvc.Name, group)
|
||||
vsRef = &corev1api.ObjectReference{
|
||||
Namespace: existingVS.Namespace,
|
||||
Name: existingVS.Name,
|
||||
}
|
||||
additionalItems = append(additionalItems, velero.ResourceIdentifier{
|
||||
GroupResource: schema.GroupResource{
|
||||
Group: "snapshot.storage.k8s.io",
|
||||
Resource: "volumesnapshots",
|
||||
},
|
||||
Namespace: existingVS.Namespace,
|
||||
Name: existingVS.Name,
|
||||
})
|
||||
} else {
|
||||
// No existing VS found for the group; execute VGS creation workflow.
|
||||
groupedPVCs, err := p.listGroupedPVCs(backup, pvc.Namespace, vgsLabelKey, group)
|
||||
if err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
pvcNames := extractPVCNames(groupedPVCs)
|
||||
newVGS, err := p.createVolumeGroupSnapshot(backup, pvc, pvcNames, vgsLabelKey, group)
|
||||
if err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
p.log.Infof("Created new VGS %s for PVC group %s", newVGS.Name, group)
|
||||
|
||||
// Wait for the VS objects created via VGS to have VolumeGroupSnapshotName in status.
|
||||
if err := p.waitForVGSAssociatedVS(newVGS, pvc.Namespace, backup.Spec.CSISnapshotTimeout.Duration); err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
// Update VS objects: remove VGS owner references and finalizers; add BackupName, BackupUUID and PVC name as labels.
|
||||
if err := p.updateVGSCreatedVS(newVGS, backup); err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
// Patch the VGSC deletionPolicy to Retain.
|
||||
if err := p.patchVGSCDeletionPolicy(newVGS, pvc.Namespace); err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
// Delete the VGS and VGSC objects to prevent cascading deletion.
|
||||
if err := p.deleteVGSAndVGSC(newVGS, pvc.Namespace); err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
|
||||
// Branch based on datamover flag.
|
||||
if !boolptr.IsSetToTrue(backup.Spec.SnapshotMoveData) {
|
||||
// Non-datamover: list VS objects created via VGS and use them.
|
||||
vsList, err := p.listVSForVGSGroup(backup, pvc.Namespace, vgsLabelKey, group)
|
||||
if err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
if len(vsList) == 0 {
|
||||
return nil, nil, "", nil, errors.New("no VS objects found for VGS group " + group)
|
||||
}
|
||||
vsRef = &corev1api.ObjectReference{
|
||||
Namespace: vsList[0].Namespace,
|
||||
Name: vsList[0].Name,
|
||||
}
|
||||
additionalItems = append(additionalItems, convertVSToResourceIdentifiers(vsList)...)
|
||||
} else {
|
||||
// Datamover: retrieve the VS for the PVC and create a DataUpload.
|
||||
vs, err := p.getVSForPVC(backup, pvc, vgsLabelKey, group)
|
||||
if err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
operationID = label.GetValidName(string(velerov1api.AsyncOperationIDPrefixDataUpload) + string(backup.UID) + "." + string(pvc.UID))
|
||||
dataUploadLog := p.log.WithFields(logrus.Fields{
|
||||
"Source PVC": fmt.Sprintf("%s/%s", pvc.Namespace, pvc.Name),
|
||||
"VolumeSnapshot": fmt.Sprintf("%s/%s", vs.Namespace, vs.Name),
|
||||
"Operation ID": operationID,
|
||||
"Backup": backup.Name,
|
||||
})
|
||||
// Wait until VS associated VSC snapshot handle is created.
|
||||
_, err = csi.WaitUntilVSCHandleIsReady(
|
||||
vs,
|
||||
p.crClient,
|
||||
p.log,
|
||||
true,
|
||||
backup.Spec.CSISnapshotTimeout.Duration,
|
||||
)
|
||||
if err != nil {
|
||||
dataUploadLog.Errorf("Fail to wait VolumeSnapshot turned to ReadyToUse: %s", err.Error())
|
||||
csi.CleanupVolumeSnapshot(vs, p.crClient, p.log)
|
||||
return nil, nil, "", nil, errors.WithStack(err)
|
||||
}
|
||||
dataUploadLog.Info("Starting data upload of backup")
|
||||
dataUpload, err := createDataUpload(
|
||||
context.Background(),
|
||||
backup,
|
||||
p.crClient,
|
||||
vs,
|
||||
&pvc,
|
||||
operationID,
|
||||
)
|
||||
if err != nil {
|
||||
dataUploadLog.WithError(err).Error("failed to submit DataUpload")
|
||||
if deleteErr := p.crClient.Delete(context.TODO(), vs); deleteErr != nil {
|
||||
if !apierrors.IsNotFound(deleteErr) {
|
||||
dataUploadLog.WithError(deleteErr).Error("fail to delete VolumeSnapshot")
|
||||
}
|
||||
}
|
||||
return item, nil, "", nil, nil
|
||||
} else {
|
||||
itemToUpdate = []velero.ResourceIdentifier{
|
||||
{
|
||||
GroupResource: schema.GroupResource{
|
||||
Group: "velero.io",
|
||||
Resource: "datauploads",
|
||||
},
|
||||
Namespace: dataUpload.Namespace,
|
||||
Name: dataUpload.Name,
|
||||
},
|
||||
}
|
||||
annotations[velerov1api.DataUploadNameAnnotation] = dataUpload.Namespace + "/" + dataUpload.Name
|
||||
dataUploadLog.Info("DataUpload is submitted successfully.")
|
||||
}
|
||||
vsRef = &corev1api.ObjectReference{
|
||||
Namespace: dataUpload.Namespace,
|
||||
Name: dataUpload.Name,
|
||||
}
|
||||
additionalItems = append(additionalItems, velero.ResourceIdentifier{
|
||||
GroupResource: schema.GroupResource{
|
||||
Group: "velero.io",
|
||||
Resource: "datauploads",
|
||||
},
|
||||
Namespace: dataUpload.Namespace,
|
||||
Name: dataUpload.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Legacy workflow: PVC does not have a VGS label; create an individual VolumeSnapshot.
|
||||
vs, err := p.createVolumeSnapshot(pvc, backup)
|
||||
if err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
vsRef = vs
|
||||
additionalItems = []velero.ResourceIdentifier{
|
||||
{
|
||||
GroupResource: kuberesource.VolumeSnapshots,
|
||||
Namespace: vs.Namespace,
|
||||
Name: vs.Name,
|
||||
},
|
||||
}
|
||||
}
|
||||
// Check if the PVC has the user-specified VGS label.
|
||||
if group, ok := pvc.Labels[vgsLabelKey]; ok && group != "" {
|
||||
p.log.Infof("PVC %s has VGS label with group %s", pvc.Name, group)
|
||||
// --- VGS branch ---
|
||||
// 1. Check if a VS created via a VGS workflow exists for this PVC.
|
||||
existingVS, err := p.findExistingVSForBackup(backup.UID, backup.Name, pvc.Name, pvc.Namespace)
|
||||
if err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
if existingVS != nil && existingVS.Status.VolumeGroupSnapshotName != "" {
|
||||
p.log.Infof("Existing VS %s found for PVC %s in group %s; skipping VGS creation", existingVS.Name, pvc.Name, group)
|
||||
vsRef = &corev1api.ObjectReference{
|
||||
Namespace: existingVS.Namespace,
|
||||
Name: existingVS.Name,
|
||||
}
|
||||
} else {
|
||||
// 2. No existing VS via VGS; execute VGS creation workflow.
|
||||
groupedPVCs, err := p.listGroupedPVCs(backup, pvc.Namespace, vgsLabelKey, group)
|
||||
if err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
pvcNames := extractPVCNames(groupedPVCs)
|
||||
// Determine the CSI driver used by the grouped PVCs.
|
||||
driver, err := p.determineCSIDriver(groupedPVCs)
|
||||
if err != nil {
|
||||
return nil, nil, "", nil, errors.Wrap(err, "failed to determine CSI driver for grouped PVCs")
|
||||
}
|
||||
if driver == "" {
|
||||
return nil, nil, "", nil, errors.New("multiple CSI drivers found for grouped PVCs; failing backup")
|
||||
}
|
||||
// Retrieve the appropriate VGSClass for the CSI driver.
|
||||
vgsClass := p.getVGSClassForDriver(driver)
|
||||
p.log.Infof("Determined CSI driver %s with VGSClass %s for PVC group %s", driver, vgsClass, group)
|
||||
|
||||
labels := map[string]string{
|
||||
velerov1api.VolumeSnapshotLabel: vsRef.Name,
|
||||
velerov1api.BackupNameLabel: backup.Name,
|
||||
}
|
||||
newVGS, err := p.createVolumeGroupSnapshot(backup, pvc, pvcNames, vgsLabelKey, group, vgsClass)
|
||||
if err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
p.log.Infof("Created new VGS %s for PVC group %s", newVGS.Name, group)
|
||||
|
||||
// Wait for the VS objects created via VGS to have volumeGroupSnapshotName in status.
|
||||
if err := p.waitForVGSAssociatedVS(newVGS, pvc.Namespace, backup.Spec.CSISnapshotTimeout.Duration); err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
// Update the VS objects: remove VGS owner references and finalizers; add backup metadata labels.
|
||||
if err := p.updateVGSCreatedVS(newVGS, backup); err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
// Patch the VGSC deletionPolicy to Retain.
|
||||
if err := p.patchVGSCDeletionPolicy(newVGS, pvc.Namespace); err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
// Delete the VGS and VGSC
|
||||
if err := p.deleteVGSAndVGSC(newVGS, pvc.Namespace); err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
// Fetch the VS that was created for this PVC via VGS.
|
||||
vs, err := p.getVSForPVC(backup, pvc, vgsLabelKey, group)
|
||||
if err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
vsRef = &corev1api.ObjectReference{
|
||||
Namespace: vs.Namespace,
|
||||
Name: vs.Name,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Legacy workflow: PVC does not have a VGS label; create an individual VS.
|
||||
vs, err := p.createVolumeSnapshot(pvc, backup)
|
||||
if err != nil {
|
||||
return nil, nil, "", nil, err
|
||||
}
|
||||
vsRef = &corev1api.ObjectReference{
|
||||
Namespace: vs.Namespace,
|
||||
Name: vs.Name,
|
||||
}
|
||||
}
|
||||
|
||||
annotations := map[string]string{
|
||||
velerov1api.VolumeSnapshotLabel: vsRef.Name,
|
||||
velerov1api.MustIncludeAdditionalItemAnnotation: "true",
|
||||
}
|
||||
// --- Common Branch ---
|
||||
// Now we have vsRef populated from one of the above cases.
|
||||
// Branch further based on backup.Spec.SnapshotMoveData.
|
||||
if boolptr.IsSetToTrue(backup.Spec.SnapshotMoveData) {
|
||||
// Datamover case:
|
||||
operationID = label.GetValidName(
|
||||
string(velerov1api.AsyncOperationIDPrefixDataUpload) + string(backup.UID) + "." + string(pvc.UID),
|
||||
)
|
||||
dataUploadLog := p.log.WithFields(logrus.Fields{
|
||||
"Source PVC": fmt.Sprintf("%s/%s", pvc.Namespace, pvc.Name),
|
||||
"VolumeSnapshot": fmt.Sprintf("%s/%s", vsRef.Namespace, vsRef.Name),
|
||||
"Operation ID": operationID,
|
||||
"Backup": backup.Name,
|
||||
})
|
||||
// Retrieve the current VS using vsRef
|
||||
vs := &snapshotv1api.VolumeSnapshot{}
|
||||
if err := p.crClient.Get(context.TODO(), crclient.ObjectKey{Namespace: vsRef.Namespace, Name: vsRef.Name}, vs); err != nil {
|
||||
return nil, nil, "", nil, errors.Wrapf(err, "failed to get VolumeSnapshot %s", vsRef.Name)
|
||||
}
|
||||
// Wait until the VS-associated VSC snapshot handle is ready.
|
||||
_, err := csi.WaitUntilVSCHandleIsReady(
|
||||
vs,
|
||||
p.crClient,
|
||||
p.log,
|
||||
true,
|
||||
backup.Spec.CSISnapshotTimeout.Duration,
|
||||
)
|
||||
if err != nil {
|
||||
dataUploadLog.Errorf("Failed to wait for VolumeSnapshot to become ReadyToUse: %s", err.Error())
|
||||
csi.CleanupVolumeSnapshot(vs, p.crClient, p.log)
|
||||
return nil, nil, "", nil, errors.WithStack(err)
|
||||
}
|
||||
dataUploadLog.Info("Starting data upload of backup")
|
||||
dataUpload, err := createDataUpload(
|
||||
context.Background(),
|
||||
backup,
|
||||
p.crClient,
|
||||
vs,
|
||||
&pvc,
|
||||
operationID,
|
||||
)
|
||||
if err != nil {
|
||||
dataUploadLog.WithError(err).Error("Failed to submit DataUpload")
|
||||
if deleteErr := p.crClient.Delete(context.TODO(), vs); deleteErr != nil && !apierrors.IsNotFound(deleteErr) {
|
||||
dataUploadLog.WithError(deleteErr).Error("Failed to delete VolumeSnapshot")
|
||||
}
|
||||
return item, nil, "", nil, nil
|
||||
}
|
||||
dataUploadLog.Info("DataUpload submitted successfully")
|
||||
itemToUpdate = []velero.ResourceIdentifier{
|
||||
{
|
||||
GroupResource: schema.GroupResource{
|
||||
Group: "velero.io",
|
||||
Resource: "datauploads",
|
||||
},
|
||||
Namespace: dataUpload.Namespace,
|
||||
Name: dataUpload.Name,
|
||||
},
|
||||
}
|
||||
annotations[velerov1api.DataUploadNameAnnotation] = dataUpload.Namespace + "/" + dataUpload.Name
|
||||
// For the datamover case, add the dataUpload as an additional item directly.
|
||||
vsRef = &corev1api.ObjectReference{
|
||||
Namespace: dataUpload.Namespace,
|
||||
Name: dataUpload.Name,
|
||||
}
|
||||
additionalItems = append(additionalItems, velero.ResourceIdentifier{
|
||||
GroupResource: schema.GroupResource{
|
||||
Group: "velero.io",
|
||||
Resource: "datauploads",
|
||||
},
|
||||
Namespace: dataUpload.Namespace,
|
||||
Name: dataUpload.Name,
|
||||
})
|
||||
} else {
|
||||
// Non-datamover case:
|
||||
// Use vsRef for snapshot purposes.
|
||||
additionalItems = append(additionalItems, convertVSToResourceIdentifiersFromRef(vsRef)...)
|
||||
p.log.Infof("VolumeSnapshot additional item added for VS %s", vsRef.Name)
|
||||
}
|
||||
|
||||
kubeutil.AddAnnotations(&pvc.ObjectMeta, annotations)
|
||||
kubeutil.AddLabels(&pvc.ObjectMeta, labels)
|
||||
// Update PVC metadata with common labels and annotations.
|
||||
labels := map[string]string{
|
||||
velerov1api.VolumeSnapshotLabel: vsRef.Name,
|
||||
velerov1api.BackupNameLabel: backup.Name,
|
||||
}
|
||||
annotations := map[string]string{
|
||||
velerov1api.VolumeSnapshotLabel: vsRef.Name,
|
||||
velerov1api.MustIncludeAdditionalItemAnnotation: "true",
|
||||
}
|
||||
kubeutil.AddAnnotations(&pvc.ObjectMeta, annotations)
|
||||
kubeutil.AddLabels(&pvc.ObjectMeta, labels)
|
||||
|
||||
p.log.Infof("Returning from PVCBackupItemAction with %d additionalItems to backup", len(additionalItems))
|
||||
for _, ai := range additionalItems {
|
||||
p.log.Debugf("%s: %s", ai.GroupResource.String(), ai.Name)
|
||||
}
|
||||
p.log.Infof("Returning from PVCBackupItemAction with %d additionalItems to backup", len(additionalItems))
|
||||
for _, ai := range additionalItems {
|
||||
p.log.Debugf("%s: %s", ai.GroupResource.String(), ai.Name)
|
||||
}
|
||||
|
||||
pvcMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&pvc)
|
||||
if err != nil {
|
||||
return nil, nil, "", nil, errors.WithStack(err)
|
||||
}
|
||||
pvcMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&pvc)
|
||||
if err != nil {
|
||||
return nil, nil, "", nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return &unstructured.Unstructured{Object: pvcMap},
|
||||
additionalItems, operationID, itemToUpdate, nil
|
||||
return &unstructured.Unstructured{Object: pvcMap},
|
||||
additionalItems, operationID, itemToUpdate, nil
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
Reference in New Issue
Block a user