Skip to content

Commit 933bbff

Browse files
committed
Adding multi-cluster support via array of cluster configs based on topology Signed-off-by: Devin Ridge <dridge@globalnoc.iu.edu>
Signed-off-by: Devin Ridge <dridge@globalnoc.iu.edu>
1 parent e6ca34a commit 933bbff

7 files changed

Lines changed: 250 additions & 36 deletions

File tree

charts/ceph-csi-rbd/templates/storageclass.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ metadata:
1515
{{- with .Values.commonLabels }}{{ toYaml . | trim | nindent 4 }}{{- end }}
1616
provisioner: {{ .Values.driverName }}
1717
parameters:
18+
{{- if .Values.storageClass.clusterID }}
1819
clusterID: {{ .Values.storageClass.clusterID }}
20+
{{- end }}
21+
{{- if .Values.storageClass.clusterTopologyConfigMap }}
22+
clusterTopologyConfigMap: {{ .Values.storageClass.clusterTopologyConfigMap }}
23+
{{- end }}
1924
imageFeatures: {{ .Values.storageClass.imageFeatures }}
2025
{{- if .Values.storageClass.pool }}
2126
pool: {{ .Values.storageClass.pool }}

charts/ceph-csi-rbd/values.yaml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -365,11 +365,17 @@ storageClass:
365365
# storageclass.kubernetes.io/is-default-class: "true"
366366
annotations: {}
367367

368-
# (required) String representing a Ceph cluster to provision storage from.
369-
# Should be unique across all Ceph clusters in use for provisioning,
370-
# cannot be greater than 36 bytes in length, and should remain immutable for
371-
# the lifetime of the StorageClass in use.
372-
clusterID: <cluster-ID>
368+
# (required unless clusterTopologyConfigMap is set) String representing a Ceph
369+
# cluster to provision storage from. Should be unique across all Ceph clusters
370+
# in use for provisioning, cannot be greater than 36 bytes in length, and
371+
# should remain immutable for the lifetime of the StorageClass in use.
372+
clusterID: ""
373+
374+
# (optional) ConfigMap name containing clusterTopology entries used for
375+
# topology-based cluster selection. The ConfigMap must live in the same
376+
# namespace as the Ceph-CSI pods and provide a config.json with
377+
# `clusterTopology` entries.
378+
clusterTopologyConfigMap: ""
373379

374380
# (optional) If you want to use erasure coded pool with RBD, you need to
375381
# create two pools. one erasure coded and one replicated.

internal/rbd/controllerserver.go

Lines changed: 70 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,17 @@ func (cs *ControllerServer) validateVolumeReq(ctx context.Context, req *csi.Crea
8080
}
8181
options := req.GetParameters()
8282
if value, ok := options["clusterID"]; !ok || value == "" {
83-
return status.Error(codes.InvalidArgument, "empty cluster ID to provision volume from")
83+
if _, ok := options["clusterTopologyConfigMap"]; !ok {
84+
return status.Error(codes.InvalidArgument, "empty cluster ID to provision volume from")
85+
}
8486
}
8587
poolValue, poolOK := options["pool"]
8688
topologyConstrainedPoolsValue, topologyOK := options["topologyConstrainedPools"]
89+
_, clusterTopologyOK := options["clusterTopologyConfigMap"]
8790
if !poolOK {
8891
if topologyOK && topologyConstrainedPoolsValue == "" {
8992
return status.Error(codes.InvalidArgument, "empty pool name or topologyConstrainedPools to provision volume")
90-
} else if !topologyOK {
93+
} else if !topologyOK && !clusterTopologyOK {
9194
return status.Error(codes.InvalidArgument, "missing or empty pool name to provision volume from")
9295
}
9396
} else if poolValue == "" {
@@ -154,7 +157,6 @@ func validateStriping(parameters map[string]string) error {
154157
func (cs *ControllerServer) parseVolCreateRequest(
155158
ctx context.Context,
156159
req *csi.CreateVolumeRequest,
157-
cr *util.Credentials,
158160
) (*rbdVolume, error) {
159161
// TODO (sbezverk) Last check for not exceeding total storage capacity
160162

@@ -229,17 +231,22 @@ func (cs *ControllerServer) parseVolCreateRequest(
229231
return nil, status.Error(codes.InvalidArgument, err.Error())
230232
}
231233

234+
// store cluster topology information from the request if present
235+
rbdVol.ClusterTopologies, _, err = util.GetClusterTopologiesFromRequest(req)
236+
if err != nil {
237+
return nil, status.Error(codes.InvalidArgument, err.Error())
238+
}
239+
232240
// parse QOS parameters from mutable parameters
233241
err = rbdVol.SetQOS(ctx, req.GetMutableParameters())
234242
if err != nil {
235243
return nil, status.Error(codes.InvalidArgument, err.Error())
236244
}
237245

238-
err = rbdVol.Connect(cr)
246+
// Get QosParameters from SC if qos configuration existing in SC
247+
err = rbdVol.SetQOS(ctx, req.GetParameters())
239248
if err != nil {
240-
log.ErrorLog(ctx, "failed to connect to volume %v: %v", rbdVol.RbdImageName, err)
241-
242-
return nil, status.Error(codes.Internal, err.Error())
249+
return nil, status.Error(codes.InvalidArgument, err.Error())
243250
}
244251

245252
// NOTE: rbdVol does not contain VolID and RbdImageName populated, everything
@@ -277,6 +284,10 @@ func (rbdVol *rbdVolume) ToCSI(ctx context.Context) (*csi.Volume, error) {
277284
vol.VolumeContext["dataPool"] = rbdVol.DataPool
278285
}
279286

287+
if rbdVol.ClusterSecretName != "" {
288+
vol.VolumeContext["clusterSecretName"] = rbdVol.ClusterSecretName
289+
}
290+
280291
if rbdVol.Topology != nil {
281292
vol.AccessibleTopology = []*csi.Topology{
282293
{
@@ -363,19 +374,48 @@ func (cs *ControllerServer) CreateVolume(
363374
return nil, err
364375
}
365376

377+
rbdVol, err := cs.parseVolCreateRequest(ctx, req)
378+
if err != nil {
379+
return nil, err
380+
}
381+
defer rbdVol.Destroy(ctx)
382+
383+
selectedCluster := util.ClusterTopology{}
384+
if rbdVol.ClusterTopologies != nil {
385+
selectedCluster, _, err = util.FindClusterAndTopology(rbdVol.ClusterTopologies, rbdVol.TopologyRequirement)
386+
if err != nil {
387+
return nil, status.Error(codes.InvalidArgument, err.Error())
388+
}
389+
if selectedCluster.ClusterID == "" {
390+
return nil, status.Error(codes.InvalidArgument, "no matching cluster found for provided topology requirements")
391+
}
392+
// persist selected secret for volume context
393+
rbdVol.ClusterSecretName = selectedCluster.SecretName
394+
}
395+
396+
secrets := req.GetSecrets()
397+
if len(secrets) == 0 && selectedCluster.SecretName != "" {
398+
namespace, nsErr := util.GetPodNamespace()
399+
if nsErr != nil {
400+
return nil, status.Error(codes.InvalidArgument, nsErr.Error())
401+
}
402+
secrets, err = k8s.GetSecret(selectedCluster.SecretName, namespace)
403+
if err != nil {
404+
return nil, status.Error(codes.InvalidArgument, err.Error())
405+
}
406+
}
407+
if len(secrets) == 0 {
408+
return nil, status.Error(codes.InvalidArgument, "missing credentials for provisioning")
409+
}
410+
366411
// TODO: create/get a connection from the ConnPool, and do not pass the
367412
// credentials to any of the utility functions.
368413

369-
cr, err := util.NewUserCredentialsWithMigration(req.GetSecrets())
414+
cr, err := util.NewUserCredentialsWithMigration(secrets)
370415
if err != nil {
371416
return nil, status.Error(codes.InvalidArgument, err.Error())
372417
}
373418
defer cr.DeleteCredentials()
374-
rbdVol, err := cs.parseVolCreateRequest(ctx, req, cr)
375-
if err != nil {
376-
return nil, err
377-
}
378-
defer rbdVol.Destroy(ctx)
379419
// Existence and conflict checks
380420
if acquired := cs.VolumeLocks.TryAcquire(req.GetName()); !acquired {
381421
log.ErrorLog(ctx, util.VolumeOperationAlreadyExistsFmt, req.GetName())
@@ -400,6 +440,13 @@ func (cs *ControllerServer) CreateVolume(
400440
return nil, status.Error(codes.Internal, err.Error())
401441
}
402442

443+
err = rbdVol.Connect(cr)
444+
if err != nil {
445+
log.ErrorLog(ctx, "failed to connect to volume %v: %v", rbdVol.RbdImageName, err)
446+
447+
return nil, status.Error(codes.Internal, err.Error())
448+
}
449+
403450
found, err := rbdVol.Exists(ctx, parentVol)
404451
if err != nil {
405452
return nil, getGRPCErrorForCreateVolume(err)
@@ -869,8 +916,8 @@ func checkContentSource(
869916
return nil, nil, status.Error(codes.NotFound, "volume cannot be empty")
870917
}
871918
volID := vol.GetVolumeId()
872-
if err := util.ValidateVolumeID(volID, true); err != nil {
873-
return nil, nil, status.Error(codes.InvalidArgument, err.Error())
919+
if volID == "" {
920+
return nil, nil, status.Errorf(codes.NotFound, "volume ID cannot be empty")
874921
}
875922
rbdvol, err := GenVolFromVolID(ctx, volID, cr, req.GetSecrets())
876923
if err != nil {
@@ -958,8 +1005,8 @@ func (cs *ControllerServer) DeleteVolume(
9581005

9591006
// For now the image get unconditionally deleted, but here retention policy can be checked
9601007
volumeID := req.GetVolumeId()
961-
if err := util.ValidateVolumeID(volumeID, true); err != nil {
962-
return nil, status.Error(codes.InvalidArgument, err.Error())
1008+
if volumeID == "" {
1009+
return nil, status.Error(codes.InvalidArgument, "empty volume ID in request")
9631010
}
9641011

9651012
cr, err := util.NewUserCredentialsWithMigration(req.GetSecrets())
@@ -1122,8 +1169,8 @@ func (cs *ControllerServer) ValidateVolumeCapabilities(
11221169
ctx context.Context,
11231170
req *csi.ValidateVolumeCapabilitiesRequest,
11241171
) (*csi.ValidateVolumeCapabilitiesResponse, error) {
1125-
if err := util.ValidateVolumeID(req.GetVolumeId(), util.IsStaticVol(req.GetVolumeContext())); err != nil {
1126-
return nil, status.Error(codes.InvalidArgument, err.Error())
1172+
if req.GetVolumeId() == "" {
1173+
return nil, status.Error(codes.InvalidArgument, "empty volume ID in request")
11271174
}
11281175

11291176
if len(req.GetVolumeCapabilities()) == 0 {
@@ -1596,8 +1643,8 @@ func (cs *ControllerServer) ControllerExpandVolume(
15961643
}
15971644

15981645
volID := req.GetVolumeId()
1599-
if err := util.ValidateVolumeID(volID, true); err != nil {
1600-
return nil, status.Error(codes.InvalidArgument, err.Error())
1646+
if volID == "" {
1647+
return nil, status.Error(codes.InvalidArgument, "volume ID cannot be empty")
16011648
}
16021649

16031650
capRange := req.GetCapacityRange()
@@ -1778,8 +1825,8 @@ func (cs *ControllerServer) ControllerUnpublishVolume(
17781825
if !k8s.RunsOnKubernetes() {
17791826
return &csi.ControllerUnpublishVolumeResponse{}, nil
17801827
}
1781-
if err := util.ValidateVolumeID(req.GetVolumeId(), true); err != nil {
1782-
return nil, status.Error(codes.InvalidArgument, err.Error())
1828+
if req.GetVolumeId() == "" {
1829+
return nil, status.Error(codes.InvalidArgument, "Volume ID cannot be empty")
17831830
}
17841831

17851832
volumeId := req.GetVolumeId()

internal/rbd/rbd_journal.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,29 @@ func updateTopologyConstraints(rbdVol *rbdVolume, rbdSnap *rbdSnapshot) error {
472472

473473
return nil
474474
}
475+
if rbdVol.ClusterTopologies != nil {
476+
cluster, topology, err := util.FindClusterAndTopology(rbdVol.ClusterTopologies, rbdVol.TopologyRequirement)
477+
if err != nil {
478+
return err
479+
}
480+
if cluster.ClusterID == "" {
481+
return fmt.Errorf("no matching cluster found for provided topology requirements")
482+
}
483+
rbdVol.ClusterID = cluster.ClusterID
484+
rbdVol.Monitors = cluster.Monitors
485+
rbdVol.Pool = cluster.Pool
486+
rbdVol.DataPool = cluster.DataPool
487+
rbdVol.JournalPool = cluster.Pool
488+
rbdVol.Topology = topology
489+
rbdVol.ClusterSecretName = cluster.SecretName
490+
rbdVol.RadosNamespace, err = util.GetRBDRadosNamespace(util.CsiConfigFile, rbdVol.ClusterID)
491+
if err != nil {
492+
return err
493+
}
494+
495+
return nil
496+
}
497+
475498
// update request based on topology constrained parameters (if present)
476499
poolName, dataPoolName, topology, err := util.FindPoolAndTopology(rbdVol.TopologyPools, rbdVol.TopologyRequirement)
477500
if err != nil {
@@ -667,7 +690,7 @@ func RegenerateJournal(
667690
}
668691
}
669692
// Update Metadata on reattach of the same old PV
670-
parameters := k8s.PrepareVolumeMetadata(claimName, owner, requestName)
693+
parameters := k8s.PrepareVolumeMetadata(claimName, owner, "")
671694
err = rbdVol.setAllMetadata(parameters)
672695
if err != nil {
673696
return "", fmt.Errorf("failed to set volume metadata: %w", err)

internal/rbd/rbd_util.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ type rbdVolume struct {
183183
// VolName and MonValueFromSecret are retained from older plugin versions (<= 1.0.0)
184184
// for backward compatibility reasons
185185
TopologyPools *[]util.TopologyConstrainedPool
186+
ClusterTopologies *[]util.ClusterTopology
186187
TopologyRequirement *csi.TopologyRequirement
187188
Topology map[string]string
188189
// DataPool is where the data for images in `Pool` are stored, this is used as the `--data-pool`
@@ -197,6 +198,7 @@ type rbdVolume struct {
197198
LogStrategy string
198199
VolName string
199200
MonValueFromSecret string
201+
ClusterSecretName string
200202
// Network namespace file path to execute nsenter command
201203
NetNamespaceFilePath string
202204
// RequestedVolSize has the size of the volume requested by the user and
@@ -1159,7 +1161,7 @@ func genSnapFromSnapID(
11591161
}()
11601162

11611163
if imageAttributes.KmsID != "" && imageAttributes.EncryptionType == crypto.EncryptionTypeBlock {
1162-
err = rbdSnap.configureBlockEncryption(imageAttributes.KmsID, secrets, nil)
1164+
err = rbdSnap.configureBlockEncryption(imageAttributes.KmsID, secrets)
11631165
if err != nil {
11641166
return rbdSnap, fmt.Errorf("failed to configure block encryption for "+
11651167
"%q: %w", rbdSnap, err)
@@ -1261,7 +1263,7 @@ func generateVolumeFromVolumeID(
12611263
rbdVol.Owner = imageAttributes.Owner
12621264

12631265
if imageAttributes.KmsID != "" && imageAttributes.EncryptionType == crypto.EncryptionTypeBlock {
1264-
err = rbdVol.configureBlockEncryption(imageAttributes.KmsID, secrets, nil)
1266+
err = rbdVol.configureBlockEncryption(imageAttributes.KmsID, secrets)
12651267
if err != nil {
12661268
return rbdVol, err
12671269
}
@@ -1440,7 +1442,9 @@ func genVolFromVolumeOptions(
14401442
rbdVol.Pool, ok = volOptions["pool"]
14411443
if !ok {
14421444
if _, ok = volOptions["topologyConstrainedPools"]; !ok {
1443-
return nil, errors.New("empty pool name or topologyConstrainedPools to provision volume")
1445+
if _, ok = volOptions["clusterTopologyConfigMap"]; !ok {
1446+
return nil, errors.New("empty pool name, topologyConstrainedPools, or clusterTopologyConfigMap to provision volume")
1447+
}
14441448
}
14451449
}
14461450

@@ -1451,18 +1455,24 @@ func genVolFromVolumeOptions(
14511455

14521456
clusterID, err := util.GetClusterID(volOptions)
14531457
if err != nil {
1454-
return nil, err
1458+
if _, ok := volOptions["clusterTopologyConfigMap"]; !ok {
1459+
return nil, err
1460+
}
14551461
}
14561462
rbdVol.Monitors, rbdVol.ClusterID, err = util.GetMonsAndClusterID(ctx, clusterID, checkClusterIDMapping)
14571463
if err != nil {
1458-
log.ErrorLog(ctx, "failed getting mons (%s)", err)
1464+
if _, ok := volOptions["clusterTopologyConfigMap"]; !ok {
1465+
log.ErrorLog(ctx, "failed getting mons (%s)", err)
14591466

1460-
return nil, err
1467+
return nil, err
1468+
}
14611469
}
14621470

14631471
rbdVol.RadosNamespace, err = util.GetRBDRadosNamespace(util.CsiConfigFile, rbdVol.ClusterID)
14641472
if err != nil {
1465-
return nil, err
1473+
if _, ok := volOptions["clusterTopologyConfigMap"]; !ok {
1474+
return nil, err
1475+
}
14661476
}
14671477
if rbdVol.Mounter, ok = volOptions["mounter"]; !ok {
14681478
rbdVol.Mounter = rbdDefaultMounter

internal/util/podnamespace.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package util
2+
3+
import (
4+
"fmt"
5+
"os"
6+
)
7+
8+
const (
9+
// podNamespaceEnv ENV should be set in the cephcsi container.
10+
podNamespaceEnv = "POD_NAMESPACE"
11+
)
12+
13+
// GetPodNamespace reads the POD_NAMESPACE environment variable to discover the
14+
// namespace the driver pod is running in.
15+
func GetPodNamespace() (string, error) {
16+
ns := os.Getenv(podNamespaceEnv)
17+
if ns == "" {
18+
return "", fmt.Errorf("%q is not set in the environment", podNamespaceEnv)
19+
}
20+
21+
return ns, nil
22+
}

0 commit comments

Comments
 (0)