Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/transfer-pvc/indirect.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ func (t *TransferPVCCommand) runIndirect() error {
// Create destination PVC
fmt.Fprintf(os.Stderr, "[2/6] Creating destination PVC ...\n")
destPVC := t.buildDestinationPVC(srcPVC)
err = destClient.Create(context.TODO(), destPVC, &client.CreateOptions{})
if err != nil && !errors.IsAlreadyExists(err) {
err = t.createDestinationPVC(context.TODO(), destClient, destPVC)
if err != nil {
log.Debugf("Unable to create destination PVC %s/%s: %v", destPVC.Namespace, destPVC.Name, err)
return fmt.Errorf("unable to create destination PVC: %w", err)
}
Expand Down
33 changes: 31 additions & 2 deletions cmd/transfer-pvc/transfer-pvc.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,8 +421,8 @@ func (t *TransferPVCCommand) run() (retErr error) {
log.Infof("Phase 2: Creating destination PVC")
phases.Start("Creating destination PVC")
destPVC := t.buildDestinationPVC(srcPVC)
err = destClient.Create(context.TODO(), destPVC, &client.CreateOptions{})
if err != nil && !errors.IsAlreadyExists(err) {
err = t.createDestinationPVC(context.TODO(), destClient, destPVC)
if err != nil {
log.Errorf("Unable to create destination PVC %s/%s: %v", t.PVC.Namespace.destination, t.PVC.Name.destination, err)
return phases.Fail(err, "unable to create destination PVC")
}
Expand Down Expand Up @@ -1290,6 +1290,35 @@ func (t *TransferPVCCommand) buildDestinationPVC(sourcePVC *corev1.PersistentVol
return pvc
}

// createDestinationPVC creates the destination PVC, validating the storage
// class of an existing PVC when --dest-storage-class was requested.
func (t *TransferPVCCommand) createDestinationPVC(ctx context.Context, c client.Client, pvc *corev1.PersistentVolumeClaim) error {
Comment thread
stillalearner marked this conversation as resolved.
if err := c.Create(ctx, pvc, &client.CreateOptions{}); err == nil {
return nil
} else if !errors.IsAlreadyExists(err) {
return fmt.Errorf("creating destination PVC %q: %w", pvc.Name, err)
}

if t.PVC.StorageClassName == "" {
return nil
}

existing := &corev1.PersistentVolumeClaim{}
if err := c.Get(ctx, client.ObjectKeyFromObject(pvc), existing); err != nil {
return fmt.Errorf("getting existing destination PVC %q: %w", pvc.Name, err)
}

existingStorageClass := ""
if existing.Spec.StorageClassName != nil {
existingStorageClass = *existing.Spec.StorageClassName
}
if existingStorageClass != t.PVC.StorageClassName {
return fmt.Errorf("destination PVC %q already exists with StorageClass %q but --dest-storage-class %q was requested; delete the existing PVC or omit --dest-storage-class to use the existing PVC as-is", pvc.Name, existingStorageClass, t.PVC.StorageClassName)
}

return nil
}

func stripServerManagedPVCAnnotations(annotations map[string]string) map[string]string {
if len(annotations) == 0 {
return nil
Expand Down
47 changes: 47 additions & 0 deletions cmd/transfer-pvc/transfer-pvc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,53 @@ func newTestScheme() *runtime.Scheme {
return s
}

func TestCreateDestinationPVC(t *testing.T) {
storageClass := func(name string) *string { return &name }
tests := []struct {
name string
requestedStorageClass string
existingStorageClass *string
wantErr string
}{
{
name: "rejects existing PVC with a different requested storage class",
requestedStorageClass: "standard-v2",
existingStorageClass: storageClass("crane-sc02-target"),
wantErr: `destination PVC "test-pvc" already exists with StorageClass "crane-sc02-target" but --dest-storage-class "standard-v2" was requested`,
},
{
name: "accepts existing PVC with requested storage class",
requestedStorageClass: "standard-v2",
existingStorageClass: storageClass("standard-v2"),
},
{
name: "accepts existing PVC when no storage class was requested",
existingStorageClass: storageClass("crane-sc02-target"),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
existing := &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{Name: "test-pvc", Namespace: "test-ns"},
Spec: corev1.PersistentVolumeClaimSpec{StorageClassName: tt.existingStorageClass},
}
c := fake.NewClientBuilder().WithScheme(newTestScheme()).WithObjects(existing).Build()
cmd := &TransferPVCCommand{Flags: Flags{PVC: PvcFlags{StorageClassName: tt.requestedStorageClass}}}

err := cmd.createDestinationPVC(context.Background(), c, &corev1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{Name: "test-pvc", Namespace: "test-ns"},
})
if tt.wantErr == "" && err != nil {
t.Fatalf("createDestinationPVC() unexpected error = %v", err)
}
if tt.wantErr != "" && (err == nil || !strings.Contains(err.Error(), tt.wantErr)) {
t.Errorf("createDestinationPVC() error = %v, want to contain %q", err, tt.wantErr)
}
})
}
}

func Test_parseSourceDestinationMapping(t *testing.T) {
tests := []struct {
name string
Expand Down
Loading