diff --git a/internal/journal/volumegroupjournal.go b/internal/journal/volumegroupjournal.go index 80012c887..39e92384a 100644 --- a/internal/journal/volumegroupjournal.go +++ b/internal/journal/volumegroupjournal.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + "strings" "time" "github.com/ceph/ceph-csi/internal/util" @@ -166,6 +167,28 @@ func generateVolumeGroupName(namePrefix, groupUUID string) string { return namePrefix + groupUUID } +// MakeVolumeGroupID is a utility function that takes details of a VolumeGroup +// and creates the CSI VolumeGroupId out of those. +func MakeVolumeGroupID(clusterID string, poolID int64, name, prefix string) (string, error) { + namePrefix := prefix + if prefix == "" { + namePrefix = defaultVolumeGroupNamingPrefix + } + + id, found := strings.CutPrefix(name, namePrefix) + if !found { + return "", fmt.Errorf("volume group name %q does not start with %q", name, namePrefix) + } + + handle := util.CSIIdentifier{ + ClusterID: clusterID, + LocationID: poolID, + ObjectUUID: id, + } + + return handle.ComposeCSIID() +} + /* CheckReservation checks if given request name contains a valid reservation - If there is a valid reservation, then the corresponding VolumeGroupData for diff --git a/internal/rbd/manager.go b/internal/rbd/manager.go index faa66d412..48693e64f 100644 --- a/internal/rbd/manager.go +++ b/internal/rbd/manager.go @@ -238,6 +238,21 @@ func (mgr *rbdManager) GetVolumeGroupByID(ctx context.Context, id string) (types return vg, nil } +func (mgr *rbdManager) MakeVolumeGroupID(ctx context.Context, poolID int64, name string) (string, error) { + clusterID, err := util.GetClusterID(mgr.parameters) + if err != nil { + return "", fmt.Errorf("failed to get cluster-id: %w", err) + } + + // convert the clusterid, poolid and name to an id/handle + id, err := journal.MakeVolumeGroupID(clusterID, poolID, name, mgr.getVolumeGroupNamePrefix()) + if err != nil { + return "", fmt.Errorf("failed to convert name %q to a CSI-handle: %w", name, err) + } + + return id, nil +} + func (mgr *rbdManager) CreateVolumeGroup(ctx context.Context, name string) (types.VolumeGroup, error) { creds, err := mgr.getCredentials() if err != nil { diff --git a/internal/rbd/manager_test.go b/internal/rbd/manager_test.go new file mode 100644 index 000000000..42cb35639 --- /dev/null +++ b/internal/rbd/manager_test.go @@ -0,0 +1,100 @@ +/* +Copyright 2025 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 rbd + +import ( + "context" + "testing" +) + +func TestMakeVolumeGroupID(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + + // parameters for NewManager() + parameters map[string]string + + // arguments for MakeVolumeGroupID() + poolID int64 + groupName string // should include the "volumeGroupNamePrefix" + + // results + volumeGroupID string + expectError bool + }{ + { + name: "no parameters set", + parameters: nil, + volumeGroupID: "", + expectError: true, + }, + { + name: "missing 'clusterID' parameter", + parameters: map[string]string{ + "pool": "replicapool", + "missing": "clusterID", + }, + poolID: 1, + groupName: "csi-vol-group-my-volume-group", + volumeGroupID: "", + expectError: true, + }, + { + name: "good volume group with default prefix", + parameters: map[string]string{ + "pool": "replicapool", + "clusterID": "k8s-rook-ceph", + }, + poolID: 1, + groupName: "csi-vol-group-1fac1545-2d0f-4b42-8abb-066ebc39cba9", + volumeGroupID: "0001-000d-k8s-rook-ceph-0000000000000001-1fac1545-2d0f-4b42-8abb-066ebc39cba9", + expectError: false, + }, + { + name: "volume group with altervative prefix", + parameters: map[string]string{ + "pool": "replicapool", + "clusterID": "k8s-rook-ceph", + "volumeGroupNamePrefix": "its-a-group-", + }, + poolID: 1, + groupName: "its-a-group-df4a7204-6b06-4eb3-9547-036d845f0cd3", + volumeGroupID: "0001-000d-k8s-rook-ceph-0000000000000001-df4a7204-6b06-4eb3-9547-036d845f0cd3", + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + ctx := context.TODO() + mgr := NewManager("rbd.example.org", tt.parameters, nil) + defer mgr.Destroy(ctx) + + id, err := mgr.MakeVolumeGroupID(ctx, tt.poolID, tt.groupName) + if (err != nil) != tt.expectError { + t.Logf("mgr: %+v", mgr) + t.Errorf("MakeVolumeGroupID failed unexpectedly: %v", err) + } else if id != tt.volumeGroupID { + t.Errorf("MakeVolumeGroupID returned %q, expected %q", id, tt.volumeGroupID) + } + }) + } +} diff --git a/internal/rbd/types/manager.go b/internal/rbd/types/manager.go index 458bc93dc..5416c9d28 100644 --- a/internal/rbd/types/manager.go +++ b/internal/rbd/types/manager.go @@ -32,6 +32,16 @@ type SnapshotResolver interface { GetSnapshotByID(ctx context.Context, id string) (Snapshot, error) } +// VolumeGroupResolver can be used to construct a VolumeGroup from a CSI VolumeGroupId. +type VolumeGroupResolver interface { + // MakeVolumeGroupID is called by Volume.GetVolumeGroupID to resolve + // the CSI VolumeGroupId of the VolumeGroup where the Volume belongs + // to. + // The poolID and name are details of the Ceph RBD-group for which the + // CSI VolumeGroupId should get constructed. + MakeVolumeGroupID(ctx context.Context, poolID int64, name string) (string, error) +} + // Manager provides a way for other packages to get Volumes and VolumeGroups. // It handles the operations on the backend, and makes sure the journal // reflects the expected state. @@ -42,6 +52,9 @@ type Manager interface { // SnapshotResolver is fully implemented by the Manager. SnapshotResolver + // VolumeGroupResolver is fully implemented by the Manager. + VolumeGroupResolver + // Destroy frees all resources that the Manager allocated. Destroy(ctx context.Context)