diff --git a/docs/cinder-csi-plugin/multi-region-clouds.md b/docs/cinder-csi-plugin/multi-region-clouds.md index 7ad6a9b80b..362cb4423d 100644 --- a/docs/cinder-csi-plugin/multi-region-clouds.md +++ b/docs/cinder-csi-plugin/multi-region-clouds.md @@ -2,9 +2,9 @@ ### Multi cluster Configuration file -Create a configuration file with a subsection per openstack cluster to manage (pay attention to enable ignore-volume-az in BlockStorage section). +Create a configuration file with a subsection per openstack cluster to manage. -Example of configuration with 3 regions (The default is backward compatible with mono cluster configuration but not mandatory). +Example of configuration with 3 zones (The default is backward compatible with mono cluster configuration but not mandatory). ```yaml apiVersion: v1 kind: Secret @@ -16,64 +16,37 @@ stringData: cloud.conf: |- [BlockStorage] bs-version=v3 - ignore-volume-az=True + ignore-volume-az=false [Global] - auth-url="https://auth.cloud.openstackcluster.region-default.local/v3" - username="region-default-username" - password="region-default-password" + auth-url="https://auth.cloud.openstackcluster.zone-default.local/v3" + username="zone-default-username" + password="zone-default-password" region="default" - tenant-id="region-default-tenant-id" - tenant-name="region-default-tenant-name" + tenant-id="zone-default-tenant-id" + tenant-name="zone-default-tenant-name" domain-name="Default" - [Global "region-one"] - auth-url="https://auth.cloud.openstackcluster.region-one.local/v3" - username="region-one-username" - password="region-one-password" + [Global "zone-one"] + auth-url="https://auth.cloud.openstackcluster.zone-one.local/v3" + username="zone-one-username" + password="zone-one-password" region="one" - tenant-id="region-one-tenant-id" - tenant-name="region-one-tenant-name" + tenant-id="zone-one-tenant-id" + tenant-name="zone-one-tenant-name" domain-name="Default" - [Global "region-two"] - auth-url="https://auth.cloud.openstackcluster.region-two.local/v3" - username="region-two-username" - password="region-two-password" + [Global "zone-two"] + auth-url="https://auth.cloud.openstackcluster.zone-two.local/v3" + username="zone-two-username" + password="zone-two-password" region="two" - tenant-id="region-two-tenant-id" - tenant-name="region-two-tenant-name" + tenant-id="zone-two-tenant-id" + tenant-name="zone-two-tenant-name" domain-name="Default" ``` - -### Create region/cloud secrets - -Create a secret per openstack cluster which contains a key `cloud` and as value the subsection's name of corresponding openstack cluster in configuration file. - -These secrets are referenced in storageClass definitions to identify openstack cluster associated to the storageClass. - -```yaml -apiVersion: v1 -kind: Secret -metadata: - name: openstack-config-region-one - namespace: kube-system -type: Opaque -stringData: - cloud: region-one ---- -apiVersion: v1 -kind: Secret -metadata: - name: openstack-config-region-two - namespace: kube-system -type: Opaque -stringData: - cloud: region-two -``` - ### Create storage Class for dedicated cluster ```yaml @@ -82,55 +55,17 @@ kind: StorageClass metadata: annotations: storageclass.kubernetes.io/is-default-class: "true" - name: sc-region-one -allowVolumeExpansion: true -allowedTopologies: -- matchLabelExpressions: - - key: topology.cinder.csi.openstack.org/zone - values: - - nova - - key: topology.kubernetes.io/region - values: - - region-one -parameters: - csi.storage.k8s.io/controller-publish-secret-name: openstack-config-region-one - csi.storage.k8s.io/controller-publish-secret-namespace: kube-system - csi.storage.k8s.io/node-publish-secret-name: openstack-config-region-one - csi.storage.k8s.io/node-publish-secret-namespace: kube-system - csi.storage.k8s.io/node-stage-secret-name: openstack-config-region-one - csi.storage.k8s.io/node-stage-secret-namespace: kube-system - csi.storage.k8s.io/provisioner-secret-name: openstack-config-region-one - csi.storage.k8s.io/provisioner-secret-namespace: kube-system - csi.storage.k8s.io/controller-expand-secret-name: openstack-config-region-one - csi.storage.k8s.io/controller-expand-secret-namespace: kube-system -provisioner: cinder.csi.openstack.org -reclaimPolicy: Delete -volumeBindingMode: Immediate ---- -apiVersion: storage.k8s.io/v1 -kind: StorageClass -metadata: - name: sc-region-two + name: sc-multi-zones allowVolumeExpansion: true allowedTopologies: - matchLabelExpressions: - key: topology.cinder.csi.openstack.org/zone values: - nova - - key: topology.kubernetes.io/region + - key: topology.kubernetes.io/zone values: - - region-two -parameters: - csi.storage.k8s.io/controller-publish-secret-name: openstack-config-region-two - csi.storage.k8s.io/controller-publish-secret-namespace: kube-system - csi.storage.k8s.io/node-publish-secret-name: openstack-config-region-two - csi.storage.k8s.io/node-publish-secret-namespace: kube-system - csi.storage.k8s.io/node-stage-secret-name: openstack-config-region-two - csi.storage.k8s.io/node-stage-secret-namespace: kube-system - csi.storage.k8s.io/provisioner-secret-name: openstack-config-region-two - csi.storage.k8s.io/provisioner-secret-namespace: kube-system - csi.storage.k8s.io/controller-expand-secret-name: openstack-config-region-two - csi.storage.k8s.io/controller-expand-secret-namespace: kube-system + - zone-one + - zone-two provisioner: cinder.csi.openstack.org reclaimPolicy: Delete volumeBindingMode: Immediate @@ -138,27 +73,27 @@ volumeBindingMode: Immediate ### Create a csi-cinder-nodeplugin daemonset per cluster openstack -Daemonsets should deploy pods on nodes from proper openstack context. We suppose that the node have a label `topology.kubernetes.io/region` with the openstack cluster name as value (you could manage this with kubespray, manually, whatever, it should be great to implement this in openstack cloud controller manager). +Daemonsets should deploy pods on nodes from proper openstack context. We suppose that the node have a label `topology.kubernetes.io/zone` with the openstack cluster name as value (you could manage this with kubespray, manually, whatever, it should be great to implement this in openstack cloud controller manager). Do as follows: - Use nodeSelector to match proper nodes labels -- Add cli argument `--additional-topology topology.kubernetes.io/region=region-one`, which should match node labels, to container cinder-csi-plugin -- Add cli argument `--cloud-name="region-one"`, which should match configuration file subsection name, to container cinder-csi-plugin. +- Add cli argument `--additional-topology topology.kubernetes.io/zone=zone-one`, which should match node labels, to container cinder-csi-plugin +- Add cli argument `--cloud-name="zone-one"`, which should match configuration file subsection name, to container cinder-csi-plugin. ```yaml apiVersion: apps/v1 kind: DaemonSet metadata: - name: csi-cinder-nodeplugin-region-one + name: csi-cinder-nodeplugin-zone-one namespace: kube-system spec: selector: matchLabels: - app: csi-cinder-nodeplugin-region-one + app: csi-cinder-nodeplugin-zone-one template: metadata: labels: - app: csi-cinder-nodeplugin-region-one + app: csi-cinder-nodeplugin-zone-one spec: containers: - name: node-driver-registrar @@ -171,9 +106,9 @@ spec: - /bin/cinder-csi-plugin - --endpoint=$(CSI_ENDPOINT) - --cloud-config=$(CLOUD_CONFIG) - - --cloud-name="region-one" + - --cloud-name="zone-one" - --additional-topology - - topology.kubernetes.io/region=region-one + - topology.kubernetes.io/zone=zone-one env: - name: CSI_ENDPOINT value: unix://csi/csi.sock @@ -187,7 +122,7 @@ spec: readOnly: true ... nodeSelector: - topology.kubernetes.io/region: region-one + topology.kubernetes.io/zone: zone-one volumes: ... - name: secret-cinderplugin @@ -199,16 +134,16 @@ spec: apiVersion: apps/v1 kind: DaemonSet metadata: - name: csi-cinder-nodeplugin-region-two + name: csi-cinder-nodeplugin-zone-two namespace: kube-system spec: selector: matchLabels: - app: csi-cinder-nodeplugin-region-two + app: csi-cinder-nodeplugin-zone-two template: metadata: labels: - app: csi-cinder-nodeplugin-region-two + app: csi-cinder-nodeplugin-zone-two spec: containers: - name: node-driver-registrar @@ -221,9 +156,9 @@ spec: - /bin/cinder-csi-plugin - --endpoint=$(CSI_ENDPOINT) - --cloud-config=$(CLOUD_CONFIG) - - --cloud-name="region-two" + - --cloud-name="zone-two" - --additional-topology - - topology.kubernetes.io/region=region-two + - topology.kubernetes.io/zone=zone-two env: - name: CSI_ENDPOINT value: unix://csi/csi.sock @@ -237,7 +172,7 @@ spec: readOnly: true ... nodeSelector: - topology.kubernetes.io/region: region-two + topology.kubernetes.io/zone: zone-two volumes: ... - name: secret-cinderplugin @@ -251,7 +186,7 @@ spec: Enable Topology feature-gate on container csi-provisioner of csi-cinder-controllerplugin deployment by adding cli argument ``--feature-gates="Topology=true" -Add cli argument `--cloud-name="region-one"` for each managed openstack cluster, name should match configuration file subsection name, to container `cinder-csi-plugin`. +Add cli argument `--cloud-name="zone-one"` for each managed openstack cluster, name should match configuration file subsection name, to container `cinder-csi-plugin`. ```yaml @@ -288,8 +223,8 @@ spec: - --endpoint=$(CSI_ENDPOINT) - --cloud-config=$(CLOUD_CONFIG) - --cluster=$(CLUSTER_NAME) - - --cloud-name="region-one" - - --cloud-name="region-two" + - --cloud-name="zone-one" + - --cloud-name="zone-two" env: - name: CSI_ENDPOINT value: unix://csi/csi.sock diff --git a/pkg/csi/cinder/controllerserver.go b/pkg/csi/cinder/controllerserver.go index f1d4905665..7fc1437ca6 100644 --- a/pkg/csi/cinder/controllerserver.go +++ b/pkg/csi/cinder/controllerserver.go @@ -55,18 +55,30 @@ const ( func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { klog.V(4).Infof("CreateVolume: called with args %+v", protosanitizer.StripSecrets(req)) - // Volume cloud - volCloud := req.GetSecrets()["cloud"] - cloud, cloudExist := cs.Clouds[volCloud] - if !cloudExist { - return nil, status.Error(codes.InvalidArgument, "[CreateVolume] specified cloud undefined") - } - // Volume Name volName := req.GetName() volCapabilities := req.GetVolumeCapabilities() volParams := req.GetParameters() + // Volume Type + volType := volParams["type"] + + var volAvailability string + var volCloudAvailability string + if cs.Driver.withTopology { + // First check if volAvailability is already specified, if not get preferred from Topology + // Required, incase vol AZ is different from node AZ + volAvailability = volParams["availability"] + if volAvailability == "" { + accessibleTopologyReq := req.GetAccessibilityRequirements() + // Check from Topology + if accessibleTopologyReq != nil { + volCloudAvailability = sharedcsi.GetAZFromTopology(withTopologyKey, accessibleTopologyReq) // = zone define from the node label topology.kubernetes.io/zone=zonex + volAvailability = sharedcsi.GetAZFromTopology(topologyKey, accessibleTopologyReq) // = nova define from Openstack + } + } + } + if len(volName) == 0 { return nil, status.Error(codes.InvalidArgument, "[CreateVolume] missing Volume Name") } @@ -82,21 +94,11 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol } volSizeGB := int(util.RoundUpSize(volSizeBytes, 1024*1024*1024)) - // Volume Type - volType := volParams["type"] - - var volAvailability string - if cs.Driver.withTopology { - // First check if volAvailability is already specified, if not get preferred from Topology - // Required, incase vol AZ is different from node AZ - volAvailability = volParams["availability"] - if volAvailability == "" { - accessibleTopologyReq := req.GetAccessibilityRequirements() - // Check from Topology - if accessibleTopologyReq != nil { - volAvailability = sharedcsi.GetAZFromTopology(topologyKey, accessibleTopologyReq) - } - } + // Volume cloud + cloud, cloudExist := cs.Clouds[volCloudAvailability] + if !cloudExist { + // return nil, status.Error(codes.InvalidArgument, "[CreateVolume] specified cloud undefined") + return nil, status.Errorf(codes.InvalidArgument, "[CreateVolume] specified cloud undefined the error came from here %v", volAvailability) } ignoreVolumeAZ := cloud.GetBlockStorageOpts().IgnoreVolumeAZ @@ -253,19 +255,24 @@ func (d *controllerServer) ControllerModifyVolume(ctx context.Context, req *csi. func (cs *controllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) { klog.V(4).Infof("DeleteVolume: called with args %+v", protosanitizer.StripSecrets(req)) + // Get VolumeID to be deleted + volID := req.GetVolumeId() + if len(volID) == 0 { + return nil, status.Error(codes.InvalidArgument, "DeleteVolume Volume ID must be provided") + } + // Volume cloud - volCloud := req.GetSecrets()["cloud"] + volCloud, err := GetAZFromVolumID(cs, ctx, volID) + if err != nil { + klog.V(4).Infof("GetAZFromVolumID called with args %+v", err) + } + cloud, cloudExist := cs.Clouds[volCloud] if !cloudExist { return nil, status.Errorf(codes.InvalidArgument, "[DeleteVolume] specified cloud \"%s\" undefined", volCloud) } - // Volume Delete - volID := req.GetVolumeId() - if len(volID) == 0 { - return nil, status.Error(codes.InvalidArgument, "DeleteVolume Volume ID must be provided") - } - err := cloud.DeleteVolume(ctx, volID) + err = cloud.DeleteVolume(ctx, volID) if err != nil { if cpoerrors.IsNotFound(err) { klog.V(3).Infof("Volume %s is already deleted.", volID) @@ -283,13 +290,6 @@ func (cs *controllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVol func (cs *controllerServer) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) { klog.V(4).Infof("ControllerPublishVolume: called with args %+v", protosanitizer.StripSecrets(req)) - // Volume cloud - volCloud := req.GetSecrets()["cloud"] - cloud, cloudExist := cs.Clouds[volCloud] - if !cloudExist { - return nil, status.Error(codes.InvalidArgument, "[ControllerPublishVolume] specified cloud undefined") - } - // Volume Attach instanceID := req.GetNodeId() volumeID := req.GetVolumeId() @@ -305,7 +305,17 @@ func (cs *controllerServer) ControllerPublishVolume(ctx context.Context, req *cs return nil, status.Error(codes.InvalidArgument, "[ControllerPublishVolume] Volume capability must be provided") } - _, err := cloud.GetVolume(ctx, volumeID) + volCloud, err := GetAZFromVolumID(cs, ctx, volumeID) + if err != nil { + klog.V(4).Infof("Cannot get volCloud called with args %+v", err) + } + + cloud, cloudExist := cs.Clouds[volCloud] + if !cloudExist { + return nil, status.Error(codes.InvalidArgument, "[ControllerPublishVolume] specified cloud undefined") + } + + _, err = cloud.GetVolume(ctx, volumeID) if err != nil { if cpoerrors.IsNotFound(err) { return nil, status.Errorf(codes.NotFound, "[ControllerPublishVolume] Volume %s not found", volumeID) @@ -354,13 +364,6 @@ func (cs *controllerServer) ControllerPublishVolume(ctx context.Context, req *cs func (cs *controllerServer) ControllerUnpublishVolume(ctx context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) { klog.V(4).Infof("ControllerUnpublishVolume: called with args %+v", protosanitizer.StripSecrets(req)) - // Volume cloud - volCloud := req.GetSecrets()["cloud"] - cloud, cloudExist := cs.Clouds[volCloud] - if !cloudExist { - return nil, status.Error(codes.InvalidArgument, "[ControllerUnpublishVolume] specified cloud undefined") - } - // Volume Detach instanceID := req.GetNodeId() volumeID := req.GetVolumeId() @@ -368,7 +371,19 @@ func (cs *controllerServer) ControllerUnpublishVolume(ctx context.Context, req * if len(volumeID) == 0 { return nil, status.Error(codes.InvalidArgument, "[ControllerUnpublishVolume] Volume ID must be provided") } - _, err := cloud.GetInstanceByID(ctx, instanceID) + + volCloud, err := GetAZFromVolumID(cs, ctx, volumeID) + if err != nil { + klog.V(4).Infof("GetAZFromVolumID: called with args %+v", err) + } + + // Volume cloud + cloud, cloudExist := cs.Clouds[volCloud] + if !cloudExist { + return nil, status.Error(codes.InvalidArgument, "[ControllerUnpublishVolume] specified cloud undefined") + } + + _, err = cloud.GetInstanceByID(ctx, instanceID) if err != nil { if cpoerrors.IsNotFound(err) { klog.V(3).Infof("ControllerUnpublishVolume assuming volume %s is detached, because node %s does not exist", volumeID, instanceID) @@ -489,13 +504,6 @@ func (cs *controllerServer) ListVolumes(ctx context.Context, req *csi.ListVolume func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, error) { klog.V(4).Infof("CreateSnapshot: called with args %+v", protosanitizer.StripSecrets(req)) - // Volume cloud - volCloud := req.GetSecrets()["cloud"] - cloud, cloudExist := cs.Clouds[volCloud] - if !cloudExist { - return nil, status.Error(codes.InvalidArgument, "[CreateSnapshot] specified cloud undefined") - } - name := req.Name volumeID := req.GetSourceVolumeId() snapshotType := req.Parameters[openstack.SnapshotType] @@ -530,6 +538,18 @@ func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS if snapshotType != "snapshot" && snapshotType != "backup" { return nil, status.Error(codes.InvalidArgument, "Snapshot type must be 'backup', 'snapshot' or not defined") } + + // Volume cloud + volCloud, err := GetAZFromVolumID(cs, ctx, volumeID) + if err != nil { + klog.V(4).Infof("GetAZFromVolumID: called with args %+v", err) + } + + cloud, cloudExist := cs.Clouds[volCloud] + if !cloudExist { + return nil, status.Error(codes.InvalidArgument, "[CreateSnapshot] specified cloud undefined") + } + var backupsAreEnabled bool backupsAreEnabled, err = cloud.BackupsAreEnabled() klog.V(4).Infof("Backups enabled: %v", backupsAreEnabled) @@ -746,19 +766,23 @@ func (cs *controllerServer) createBackup(ctx context.Context, cloud openstack.IO func (cs *controllerServer) DeleteSnapshot(ctx context.Context, req *csi.DeleteSnapshotRequest) (*csi.DeleteSnapshotResponse, error) { klog.V(4).Infof("DeleteSnapshot: called with args %+v", protosanitizer.StripSecrets(req)) - // Volume cloud - volCloud := req.GetSecrets()["cloud"] - cloud, cloudExist := cs.Clouds[volCloud] - if !cloudExist { - return nil, status.Error(codes.InvalidArgument, "[DeleteSnapshot] specified cloud undefined") - } - id := req.GetSnapshotId() if id == "" { return nil, status.Error(codes.InvalidArgument, "Snapshot ID must be provided in DeleteSnapshot request") } + // Volume cloud + volCloud, err := GetAZFromSnapshotID(cs, ctx, id) + if err != nil { + klog.V(4).Infof("GetAZFromSnapshotID: called with args %+v", err) + } + + // Volume cloud + cloud, cloudExist := cs.Clouds[volCloud] + if !cloudExist { + return nil, status.Error(codes.InvalidArgument, "[DeleteSnapshot] specified cloud undefined") + } // If volumeSnapshot object was linked to a cinder backup, delete the backup. back, err := cloud.GetBackupByID(ctx, id) if err == nil && back != nil { @@ -783,14 +807,19 @@ func (cs *controllerServer) DeleteSnapshot(ctx context.Context, req *csi.DeleteS } func (cs *controllerServer) ListSnapshots(ctx context.Context, req *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) { + + snapshotID := req.GetSnapshotId() + // Volume cloud + volCloud, err := GetAZFromSnapshotID(cs, ctx, snapshotID) + if err != nil { + klog.V(4).Infof("GetAZFromSnapshotID zone called with args %+v", err) + } + // Volume cloud - volCloud := req.GetSecrets()["cloud"] cloud, cloudExist := cs.Clouds[volCloud] if !cloudExist { - return nil, status.Error(codes.InvalidArgument, "[DeleteSnapshot] specified cloud undefined") + return nil, status.Error(codes.InvalidArgument, "[ListSnapshots] specified cloud undefined") } - - snapshotID := req.GetSnapshotId() if len(snapshotID) != 0 { snap, err := cloud.GetSnapshotByID(ctx, snapshotID) if err != nil { @@ -823,7 +852,6 @@ func (cs *controllerServer) ListSnapshots(ctx context.Context, req *csi.ListSnap filters := map[string]string{} var slist []snapshots.Snapshot - var err error var nextPageToken string // Add the filters @@ -877,12 +905,6 @@ func (cs *controllerServer) ControllerGetCapabilities(ctx context.Context, req * } func (cs *controllerServer) ValidateVolumeCapabilities(ctx context.Context, req *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) { - // Volume cloud - volCloud := req.GetSecrets()["cloud"] - cloud, cloudExist := cs.Clouds[volCloud] - if !cloudExist { - return nil, status.Error(codes.InvalidArgument, "[ValidateVolumeCapabilities] specified cloud undefined") - } reqVolCap := req.GetVolumeCapabilities() @@ -895,7 +917,18 @@ func (cs *controllerServer) ValidateVolumeCapabilities(ctx context.Context, req return nil, status.Error(codes.InvalidArgument, "ValidateVolumeCapabilities Volume ID must be provided") } - _, err := cloud.GetVolume(ctx, volumeID) + volCloud, err := GetAZFromVolumID(cs, ctx, volumeID) + if err != nil { + klog.V(4).Infof("GetAZFromVolumID: called with args %+v", err) + } + + // Volume cloud + cloud, cloudExist := cs.Clouds[volCloud] + if !cloudExist { + return nil, status.Error(codes.InvalidArgument, "[ControllerUnpublishVolume] specified cloud undefined") + } + + _, err = cloud.GetVolume(ctx, volumeID) if err != nil { if cpoerrors.IsNotFound(err) { return nil, status.Errorf(codes.NotFound, "ValidateVolumeCapabilities Volume %s not found", volumeID) @@ -971,13 +1004,6 @@ func (cs *controllerServer) ControllerGetVolume(ctx context.Context, req *csi.Co func (cs *controllerServer) ControllerExpandVolume(ctx context.Context, req *csi.ControllerExpandVolumeRequest) (*csi.ControllerExpandVolumeResponse, error) { klog.V(4).Infof("ControllerExpandVolume: called with args %+v", protosanitizer.StripSecrets(req)) - // Volume cloud - volCloud := req.GetSecrets()["cloud"] - cloud, cloudExist := cs.Clouds[volCloud] - if !cloudExist { - return nil, status.Error(codes.InvalidArgument, "[ControllerExpandVolume] specified cloud undefined") - } - volumeID := req.GetVolumeId() if len(volumeID) == 0 { return nil, status.Error(codes.InvalidArgument, "Volume ID not provided") @@ -995,6 +1021,17 @@ func (cs *controllerServer) ControllerExpandVolume(ctx context.Context, req *csi return nil, status.Error(codes.OutOfRange, "After round-up, volume size exceeds the limit specified") } + volCloud, err := GetAZFromVolumID(cs, ctx, volumeID) + if err != nil { + klog.V(4).Infof("GetAZFromVolumID: called with args %+v", err) + } + + // Volume cloud + cloud, cloudExist := cs.Clouds[volCloud] + if !cloudExist { + return nil, status.Error(codes.InvalidArgument, "[ControllerExpandVolume] specified cloud undefined") + } + volume, err := cloud.GetVolume(ctx, volumeID) if err != nil { if cpoerrors.IsNotFound(err) { @@ -1100,3 +1137,36 @@ func getCreateVolumeResponse(vol *volumes.Volume, volCtx map[string]string, igno return resp } + +func GetAZFromID(cs *controllerServer, ctx context.Context, id string, fetchFunc func(cloud openstack.IOpenStack, ctx context.Context, id string) (interface{}, error), resourceType string) (string, error) { + klog.V(4).Infof("GetAZFromID: called with args %+v", id) + + defer func() { klog.V(1).Infof("detected AZ from the %s ID: %s", resourceType, id) }() + + for volCloud, cloud := range cs.Clouds { + resource, err := fetchFunc(cloud, ctx, id) + if err != nil { + if cpoerrors.IsNotFound(err) { + continue + } + return "", status.Errorf(codes.Internal, "GetAZFromID failed with error %v", err) + } + + if resource != nil { + return volCloud, nil + } + } + return "", status.Errorf(codes.NotFound, "%s %s not found in any cloud", resourceType, id) +} + +func GetAZFromVolumID(cs *controllerServer, ctx context.Context, volumeID string) (string, error) { + return GetAZFromID(cs, ctx, volumeID, func(cloud openstack.IOpenStack, ctx context.Context, id string) (interface{}, error) { + return cloud.GetVolume(ctx, id) + }, "volume") +} + +func GetAZFromSnapshotID(cs *controllerServer, ctx context.Context, snapshotID string) (string, error) { + return GetAZFromID(cs, ctx, snapshotID, func(cloud openstack.IOpenStack, ctx context.Context, id string) (interface{}, error) { + return cloud.GetSnapshotByID(ctx, id) + }, "snapshot") +} diff --git a/pkg/csi/cinder/driver.go b/pkg/csi/cinder/driver.go index 04356a0eba..c2d3f8636f 100644 --- a/pkg/csi/cinder/driver.go +++ b/pkg/csi/cinder/driver.go @@ -22,7 +22,7 @@ import ( "github.com/container-storage-interface/spec/lib/go/csi" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "k8s.io/client-go/listers/core/v1" + v1 "k8s.io/client-go/listers/core/v1" "k8s.io/cloud-provider-openstack/pkg/csi/cinder/openstack" "k8s.io/cloud-provider-openstack/pkg/util/metadata" "k8s.io/cloud-provider-openstack/pkg/util/mount" @@ -31,8 +31,9 @@ import ( ) const ( - driverName = "cinder.csi.openstack.org" - topologyKey = "topology." + driverName + "/zone" + driverName = "cinder.csi.openstack.org" + topologyKey = "topology." + driverName + "/zone" + withTopologyKey = "topology.kubernetes.io/zone" // maxVolumesPerNode is the maximum number of volumes that can be attached to a node maxVolumesPerNode = 256