From 455a90e9f40250e408ba8ec454805dc17e36c1b5 Mon Sep 17 00:00:00 2001 From: Niels de Vos Date: Wed, 21 Aug 2024 15:36:08 +0200 Subject: [PATCH] rbd: add VolumeGroupSnapshot type The VolumeGroupSnapshot type will be used by the rbd.Manager to create, inspect and delete VolumeGroupSnapshos. Signed-off-by: Niels de Vos --- internal/rbd/group/group_snapshot.go | 238 ++++++++++++++++++++ internal/rbd/types/volume_group_snapshot.go | 48 ++++ 2 files changed, 286 insertions(+) create mode 100644 internal/rbd/group/group_snapshot.go create mode 100644 internal/rbd/types/volume_group_snapshot.go diff --git a/internal/rbd/group/group_snapshot.go b/internal/rbd/group/group_snapshot.go new file mode 100644 index 000000000..7abb237ad --- /dev/null +++ b/internal/rbd/group/group_snapshot.go @@ -0,0 +1,238 @@ +/* +Copyright 2024 The Ceph-CSI Authors. + +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 group + +import ( + "context" + "fmt" + + "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" +) + +// volumeGroupSnapshot handles all requests for 'rbd group snap' operations. +type volumeGroupSnapshot struct { + commonVolumeGroup + + // snapshots is a list of rbd-images that are part of the group. The ID + // of each snapshot is stored in the journal. + snapshots []types.Snapshot + + // snapshotsToFree contains Snapshots that were resolved during + // GetVolumeGroupSnapshot. + snapshotsToFree []types.Snapshot +} + +// verify that volumeGroupSnapshot implements the VolumeGroupSnapshot interface. +var _ types.VolumeGroupSnapshot = &volumeGroupSnapshot{} + +// GetVolumeGroupSnapshot initializes a new VolumeGroupSnapshot object that can +// be used to inspect and delete a group of snapshots that was created by a +// VolumeGroup. +func GetVolumeGroupSnapshot( + ctx context.Context, + id string, + csiDriver string, + creds *util.Credentials, + snapshotResolver types.SnapshotResolver, +) (types.VolumeGroupSnapshot, error) { + cleanVGS := true + + vgs := &volumeGroupSnapshot{} + err := vgs.initCommonVolumeGroup(ctx, id, csiDriver, creds) + if err != nil { + return nil, fmt.Errorf("failed to initialize volume group snapshot with id %q: %w", id, err) + } + defer func() { + if cleanVGS { + vgs.Destroy(ctx) + } + }() + + attrs, err := vgs.getVolumeGroupAttributes(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get volume attributes for id %q: %w", vgs, err) + } + + var snapshots []types.Snapshot + // it is needed to free the previously allocated snapshots in case of an error + defer func() { + // snapshotsToFree is empty in case of an error, let .Destroy() handle it otherwise + if len(vgs.snapshotsToFree) > 0 { + return + } + + for _, s := range snapshots { + s.Destroy(ctx) + } + }() + for snapID := range attrs.VolumeMap { + snap, err := snapshotResolver.GetSnapshotByID(ctx, snapID) + if err != nil { + // free the previously allocated snapshots + for _, s := range snapshots { + s.Destroy(ctx) + } + + return nil, fmt.Errorf("failed to resolve snapshot image %q for volume group snapshot %q: %w", snapID, vgs, err) + } + + log.DebugLog(ctx, "resolved snapshot id %q to snapshot %q", snapID, snap) + + snapshots = append(snapshots, snap) + } + + vgs.snapshots = snapshots + // all allocated snapshots need to be free'd at Destroy() time + vgs.snapshotsToFree = snapshots + + cleanVGS = false + log.DebugLog(ctx, "GetVolumeGroupSnapshot(%s) returns %+v", id, *vgs) + + return vgs, nil +} + +// NewVolumeGroupSnapshot creates a new VolumeGroupSnapshot object with the +// given slice of Snapshots and adds the objectmapping to the journal. +func NewVolumeGroupSnapshot( + ctx context.Context, + id string, + csiDriver string, + creds *util.Credentials, + snapshots []types.Snapshot, +) (types.VolumeGroupSnapshot, error) { + cleanupVGS := true + + vgs := &volumeGroupSnapshot{} + err := vgs.initCommonVolumeGroup(ctx, id, csiDriver, creds) + if err != nil { + return nil, fmt.Errorf("failed to initialize volume group snapshot with id %q: %w", id, err) + } + defer func() { + if cleanupVGS { + vgs.Destroy(ctx) + } + }() + + vgs.snapshots = snapshots + vgs.snapshotsToFree = snapshots + + _ /* attrs */, err = vgs.getVolumeGroupAttributes(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get volume attributes for id %q: %w", vgs, err) + } + + volumeMap := make(map[string]string, len(snapshots)) + + // add the CSI handles of each snapshot to the journal + for _, snapshot := range snapshots { + handle, snapErr := snapshot.GetID(ctx) + if snapErr != nil { + return nil, fmt.Errorf("failed to get ID for snapshot %q of volume group snapshot %q: %w", snapshot, vgs, snapErr) + } + + name, snapErr := snapshot.GetName(ctx) + if snapErr != nil { + return nil, fmt.Errorf("failed to get name for snapshot %q of volume group snapshot %q: %w", snapshot, vgs, snapErr) + } + + volumeMap[handle] = name + } + + j, err := vgs.getJournal(ctx) + if err != nil { + return nil, err + } + + err = j.AddVolumesMapping(ctx, vgs.pool, vgs.objectUUID, volumeMap) + if err != nil { + return nil, fmt.Errorf("failed to add volume mapping for volume group snapshot %q: %w", vgs, err) + } + + // all done successfully, no need to cleanup the returned vgs + cleanupVGS = false + + return vgs, nil +} + +// ToCSI creates a CSI type for the VolumeGroupSnapshot. +func (vgs *volumeGroupSnapshot) ToCSI(ctx context.Context) (*csi.VolumeGroupSnapshot, error) { + snapshots, err := vgs.ListSnapshots(ctx) + if err != nil { + return nil, fmt.Errorf("failed to list snapshots for volume group %q: %w", vgs, err) + } + + csiSnapshots := make([]*csi.Snapshot, len(snapshots)) + for i, snap := range snapshots { + csiSnapshots[i], err = snap.ToCSI(ctx) + if err != nil { + return nil, fmt.Errorf("failed to convert snapshot %q to CSI type: %w", snap, err) + } + } + + id, err := vgs.GetID(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get id for volume group snapshot %q: %w", vgs, err) + } + + ct, err := vgs.GetCreationTime(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get creation time for volume group snapshot %q: %w", vgs, err) + } + + return &csi.VolumeGroupSnapshot{ + GroupSnapshotId: id, + Snapshots: csiSnapshots, + CreationTime: timestamppb.New(*ct), + ReadyToUse: true, + }, nil +} + +// Destroy frees the resources used by the volumeGroupSnapshot. +func (vgs *volumeGroupSnapshot) Destroy(ctx context.Context) { + // free the volumes that were allocated in GetVolumeGroup() + if len(vgs.snapshotsToFree) > 0 { + for _, volume := range vgs.snapshotsToFree { + volume.Destroy(ctx) + } + vgs.snapshotsToFree = make([]types.Snapshot, 0) + } + + vgs.commonVolumeGroup.Destroy(ctx) +} + +// Delete removes all snapshots and eventually the volume group snapshot. +func (vgs *volumeGroupSnapshot) Delete(ctx context.Context) error { + for _, snapshot := range vgs.snapshots { + log.DebugLog(ctx, "deleting snapshot image %q for volume group snapshot %q", snapshot, vgs) + + err := snapshot.Delete(ctx) + if err != nil { + return fmt.Errorf("failed to delete snapshot %q as part of volume groups snapshot %q: %w", snapshot, vgs, err) + } + } + + return vgs.commonVolumeGroup.Delete(ctx) +} + +func (vgs *volumeGroupSnapshot) ListSnapshots(ctx context.Context) ([]types.Snapshot, error) { + return vgs.snapshots, nil +} diff --git a/internal/rbd/types/volume_group_snapshot.go b/internal/rbd/types/volume_group_snapshot.go new file mode 100644 index 000000000..7d9d42e28 --- /dev/null +++ b/internal/rbd/types/volume_group_snapshot.go @@ -0,0 +1,48 @@ +/* +Copyright 2024 The Ceph-CSI Authors. + +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 types + +import ( + "context" + "time" + + "github.com/container-storage-interface/spec/lib/go/csi" +) + +// VolumeGroupSnapshot provide functions to inspect and modify a +// VolumeGroupSnapshot. The rbd.Manager can be used to create or otherwise +// obtain a VolumeGroupSnapshot struct. +type VolumeGroupSnapshot interface { + journalledObject + + // Destroy frees the resources used by the VolumeGroupSnapshot. + Destroy(ctx context.Context) + + // Delete removes the VolumeGroupSnapshot from the storage backend. + Delete(ctx context.Context) error + + // ToCSI returns the VolumeGroupSnapshot struct in CSI format. + ToCSI(ctx context.Context) (*csi.VolumeGroupSnapshot, error) + + // GetCreationTime retrurns the time when the VolumeGroupSnapshot was + // created. + GetCreationTime(ctx context.Context) (*time.Time, error) + + // ListSnapshots returns a slice with all Snapshots in the + // VolumeGroupSnapshot. + ListSnapshots(ctx context.Context) ([]Snapshot, error) +}