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 <ndevos@ibm.com>
This commit is contained in:
Niels de Vos 2024-09-12 15:02:55 +02:00 committed by mergify[bot]
parent 9808408340
commit 20fadf2016
2 changed files with 139 additions and 0 deletions

View File

@ -20,9 +20,11 @@ import (
"errors" "errors"
"fmt" "fmt"
librbd "github.com/ceph/go-ceph/rbd"
"github.com/container-storage-interface/spec/lib/go/csi" "github.com/container-storage-interface/spec/lib/go/csi"
"google.golang.org/protobuf/types/known/timestamppb" "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"
"github.com/ceph/ceph-csi/internal/util/log" "github.com/ceph/ceph-csi/internal/util/log"
) )
@ -176,3 +178,135 @@ func undoSnapshotCloning(
return err 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
}

View File

@ -21,6 +21,8 @@ import (
"time" "time"
"github.com/container-storage-interface/spec/lib/go/csi" "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 //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 converts the Volume to a Mirror.
ToMirror() (Mirror, error) 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)
} }