From 20fadf2016a363f19037221f6c686b273cb20fc2 Mon Sep 17 00:00:00 2001 From: Niels de Vos Date: Thu, 12 Sep 2024 15:02:55 +0200 Subject: [PATCH] rbd: add `rbdVolume.NewSnapshotByID` to clone images by RBD snapshot-id The NewSnapshotByID() function makes it possible to clone a new Snapshot from an existing RBD-image and the ID of an RBD-snapshot on that image. This will be used by the VolumeGroupSnapshot feature, where the ID of an RBD-snapshot is obtained for the RBD-snapshot on the RBD-images. Signed-off-by: Niels de Vos --- internal/rbd/snapshot.go | 134 +++++++++++++++++++++++++++++++++++ internal/rbd/types/volume.go | 5 ++ 2 files changed, 139 insertions(+) diff --git a/internal/rbd/snapshot.go b/internal/rbd/snapshot.go index e903a74f3..084bf4d72 100644 --- a/internal/rbd/snapshot.go +++ b/internal/rbd/snapshot.go @@ -20,9 +20,11 @@ import ( "errors" "fmt" + librbd "github.com/ceph/go-ceph/rbd" "github.com/container-storage-interface/spec/lib/go/csi" "google.golang.org/protobuf/types/known/timestamppb" + "github.com/ceph/ceph-csi/internal/rbd/types" "github.com/ceph/ceph-csi/internal/util" "github.com/ceph/ceph-csi/internal/util/log" ) @@ -176,3 +178,135 @@ func undoSnapshotCloning( return err } + +// NewSnapshotByID creates a new rbdSnapshot from the rbdVolume. +// +// Parameters: +// - name of the new rbd-image backing the snapshot +// - id of the rbd-snapshot to clone +// +// FIXME: When resolving the Snapshot, the RbdImageName will be set to the name +// of the parent image. This is can cause issues when not accounting for that +// and the Snapshot is deleted; instead of deleting the snapshot image, the +// parent image is removed. +// +//nolint:gocyclo,cyclop // TODO: reduce complexity. +func (rv *rbdVolume) NewSnapshotByID( + ctx context.Context, + cr *util.Credentials, + name string, + id uint64, +) (types.Snapshot, error) { + snap := rv.toSnapshot() + snap.RequestName = name + + srcVolID, err := rv.GetID(ctx) + if err != nil { + return nil, err + } + + snap.SourceVolumeID = srcVolID + + // reserveSnap sets snap.{RbdSnapName,ReservedID,VolID} + err = reserveSnap(ctx, snap, rv, cr) + if err != nil { + return nil, fmt.Errorf("failed to create a reservation in the journal for snapshot image %q: %w", snap, err) + } + defer func() { + if err != nil { + undoErr := undoSnapReservation(ctx, snap, cr) + if undoErr != nil { + log.WarningLog(ctx, "failed undoing reservation of snapshot %q: %v", name, undoErr) + } + } + }() + + // a new snapshot image will be created, needs to have a unique name + snap.RbdImageName = snap.RbdSnapName + + err = rv.Connect(cr) + if err != nil { + return nil, err + } + + err = rv.openIoctx() + if err != nil { + return nil, err + } + + options, err := rv.constructImageOptions(ctx) + if err != nil { + return nil, err + } + defer options.Destroy() + + err = options.SetUint64(librbd.ImageOptionCloneFormat, 2) + if err != nil { + return nil, err + } + + // indicator to remove the snapshot after a failure + removeSnap := true + var snapImage *librbd.Snapshot + + log.DebugLog(ctx, "going to clone snapshot image %q from image %q with snapshot ID %d", snap, rv, id) + + err = librbd.CloneImageByID(rv.ioctx, rv.RbdImageName, id, rv.ioctx, snap.RbdImageName, options) + if err != nil && !errors.Is(librbd.ErrExist, err) { + log.ErrorLog(ctx, "failed to clone snapshot %q with id %d: %v", snap, id, err) + + return nil, fmt.Errorf("failed to clone %q with snapshot id %d as new image %q: %w", rv.RbdImageName, id, snap, err) + } + defer func() { + if !removeSnap { + // success, no need to remove the snapshot image + return + } + + if snapImage != nil { + err = snapImage.Remove() + if err != nil { + log.ErrorLog(ctx, "failed to remove snapshot of image %q after failure: %v", snap, err) + } + } + + err = librbd.RemoveImage(rv.ioctx, snap.RbdImageName) + if err != nil { + log.ErrorLog(ctx, "failed to remove snapshot image %q after failure: %v", snap, err) + } + }() + + // update the snapshot image in the journal, after the image info is updated + j, err := snapJournal.Connect(snap.Monitors, snap.RadosNamespace, cr) + if err != nil { + return nil, fmt.Errorf("snapshot image %q failed to connect to journal: %w", snap, err) + } + defer j.Destroy() + + err = snap.Connect(cr) + if err != nil { + return nil, fmt.Errorf("failed to connect snapshot image %q: %w", snap, err) + } + defer snap.Destroy(ctx) + + image, err := snap.open() + if err != nil { + return nil, fmt.Errorf("failed to open snapshot image %q: %w", snap, err) + } + defer image.Close() + + snapImage, err = image.CreateSnapshot(snap.RbdSnapName) + if err != nil && !errors.Is(librbd.ErrExist, err) { + return nil, fmt.Errorf("failed to create snapshot on image %q: %w", snap, err) + } + + err = snap.repairImageID(ctx, j, true) + if err != nil { + return nil, fmt.Errorf("failed to repair image id for snapshot image %q: %w", snap, err) + } + + // all ok, don't remove the snapshot image in a defer statement + removeSnap = false + + return snap, nil +} diff --git a/internal/rbd/types/volume.go b/internal/rbd/types/volume.go index fb2b66275..1f235bf16 100644 --- a/internal/rbd/types/volume.go +++ b/internal/rbd/types/volume.go @@ -21,6 +21,8 @@ import ( "time" "github.com/container-storage-interface/spec/lib/go/csi" + + "github.com/ceph/ceph-csi/internal/util" ) //nolint:interfacebloat // more than 10 methods are needed for the interface @@ -59,4 +61,7 @@ type Volume interface { // ToMirror converts the Volume to a Mirror. ToMirror() (Mirror, error) + + // NewSnapshotByID creates a new Snapshot object based on the details of the Volume. + NewSnapshotByID(ctx context.Context, cr *util.Credentials, name string, id uint64) (Snapshot, error) }