diff --git a/cmd/exoscale-csi-driver/main.go b/cmd/exoscale-csi-driver/main.go index 7ddd741e..64f441fd 100644 --- a/cmd/exoscale-csi-driver/main.go +++ b/cmd/exoscale-csi-driver/main.go @@ -5,6 +5,7 @@ import ( "fmt" "os" + v3 "github.com/exoscale/egoscale/v3" "github.com/exoscale/exoscale-csi-driver/cmd/exoscale-csi-driver/buildinfo" "github.com/exoscale/exoscale-csi-driver/driver" @@ -42,6 +43,7 @@ func main() { apiKey := os.Getenv("EXOSCALE_API_KEY") apiSecret := os.Getenv("EXOSCALE_API_SECRET") + apiURL := os.Getenv("EXOSCALE_API_URL") // The node mode don't need secrets and do not interact with Exoscale API. if *mode != string(driver.NodeMode) && (apiKey == "" || apiSecret == "") { @@ -54,6 +56,7 @@ func main() { Prefix: *prefix, APIKey: apiKey, APISecret: apiSecret, + Zone: v3.URL(apiURL), }) if err != nil { klog.Error(err) diff --git a/driver/controller.go b/driver/controller.go index 54432eac..d722ff40 100644 --- a/driver/controller.go +++ b/driver/controller.go @@ -69,7 +69,6 @@ var ( exoscaleVolumeID = DriverName + "/volume-id" exoscaleVolumeName = DriverName + "/volume-name" - exoscaleVolumeZone = DriverName + "/volume-zone" ) const ( @@ -77,14 +76,16 @@ const ( ) type controllerService struct { - client *v3.Client - zone v3.URL + client *v3.Client + zone v3.URL + zoneName string } func newControllerService(client *v3.Client, nodeMeta *nodeMetadata) controllerService { return controllerService{ - client: client, - zone: nodeMeta.zone, + client: client, + zone: nodeMeta.zone, + zoneName: nodeMeta.zoneName, } } @@ -92,10 +93,7 @@ func newControllerService(client *v3.Client, nodeMeta *nodeMetadata) controllerS // This function is idempotent. func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { klog.V(4).Infof("CreateVolume") - // TODO(multizone cluster) use req.AccessibilityRequirements, - // To create block storage volume in the right zone. - // TODO(multizone cluster) fetch all zone volumes, err := d.client.ListBlockStorageVolumes(ctx) if err != nil { klog.Errorf("create block storage volume list: %v", err) @@ -107,10 +105,10 @@ func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVol if v.Name == req.Name { return &csi.CreateVolumeResponse{ Volume: &csi.Volume{ - VolumeId: exoscaleID(d.zone, v.ID), + VolumeId: v.ID.String(), // API reply in bytes then send it without conversion CapacityBytes: v.Size, - AccessibleTopology: newZoneTopology(d.zone), + AccessibleTopology: newZoneTopology(d.zoneName), }, }, nil } @@ -127,14 +125,13 @@ func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVol if srcSnapshot == nil { return nil, status.Error(codes.Internal, "error retrieving snapshot from the volumeContentSource") } - zone, snapshotID, err := getExoscaleID(srcSnapshot.SnapshotId) + snapshotID, err := v3.ParseUUID(srcSnapshot.SnapshotId) if err != nil { klog.Errorf("create volume from snapshot: %v", err) return nil, err } - client := d.client.WithURL(zone) - snapshot, err := client.GetBlockStorageSnapshot(ctx, snapshotID) + snapshot, err := d.client.GetBlockStorageSnapshot(ctx, snapshotID) if err != nil { if errors.Is(err, v3.ErrNotFound) { klog.Errorf("create volume get snapshot not found: %v", err) @@ -182,9 +179,9 @@ func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVol return &csi.CreateVolumeResponse{ Volume: &csi.Volume{ - VolumeId: exoscaleID(d.zone, opDone.Reference.ID), + VolumeId: opDone.Reference.ID.String(), CapacityBytes: sizeInBytes, - AccessibleTopology: newZoneTopology(d.zone), + AccessibleTopology: newZoneTopology(d.zoneName), ContentSource: req.GetVolumeContentSource(), }, }, nil @@ -195,14 +192,13 @@ func (d *controllerService) CreateVolume(ctx context.Context, req *csi.CreateVol func (d *controllerService) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) { klog.V(4).Infof("DeleteVolume") - zone, volumeID, err := getExoscaleID(req.VolumeId) + volumeID, err := v3.ParseUUID(req.VolumeId) if err != nil { klog.Errorf("parse exoscale volume ID %s: %v", req.VolumeId, err) return nil, err } - client := d.client.WithURL(zone) - op, err := client.DeleteBlockStorageVolume(ctx, volumeID) + op, err := d.client.DeleteBlockStorageVolume(ctx, volumeID) if err != nil { if errors.Is(err, v3.ErrNotFound) { return &csi.DeleteVolumeResponse{}, nil @@ -226,20 +222,19 @@ func (d *controllerService) DeleteVolume(ctx context.Context, req *csi.DeleteVol func (d *controllerService) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) { klog.V(4).Infof("ControllerPublishVolume") - zone, instanceID, err := getExoscaleID(req.NodeId) + instanceID, err := v3.ParseUUID(req.NodeId) if err != nil { klog.Errorf("parse node ID %s: %v", req.NodeId, err) return nil, err } - client := d.client.WithURL(zone) - _, volumeID, err := getExoscaleID(req.VolumeId) + volumeID, err := v3.ParseUUID(req.VolumeId) if err != nil { klog.Errorf("parse exoscale volume ID %s: %v", req.VolumeId, err) return nil, err } - volume, err := client.GetBlockStorageVolume(ctx, volumeID) + volume, err := d.client.GetBlockStorageVolume(ctx, volumeID) if err != nil { if errors.Is(err, v3.ErrNotFound) { return nil, status.Errorf(codes.NotFound, "volume %s not found", volumeID) @@ -255,13 +250,12 @@ func (d *controllerService) ControllerPublishVolume(ctx context.Context, req *cs PublishContext: map[string]string{ exoscaleVolumeName: volume.Name, exoscaleVolumeID: volume.ID.String(), - exoscaleVolumeZone: string(zone), }, }, nil } } - op, err := client.AttachBlockStorageVolumeToInstance(ctx, volumeID, v3.AttachBlockStorageVolumeToInstanceRequest{ + op, err := d.client.AttachBlockStorageVolumeToInstance(ctx, volumeID, v3.AttachBlockStorageVolumeToInstanceRequest{ Instance: &v3.InstanceTarget{ ID: instanceID, }, @@ -271,7 +265,7 @@ func (d *controllerService) ControllerPublishVolume(ctx context.Context, req *cs return nil, err } - _, err = client.Wait(ctx, op, v3.OperationStateSuccess) + _, err = d.client.Wait(ctx, op, v3.OperationStateSuccess) if err != nil { klog.Errorf("wait attach block storage volume %s to instance %s: %v", volumeID, instanceID, err) return nil, err @@ -281,7 +275,6 @@ func (d *controllerService) ControllerPublishVolume(ctx context.Context, req *cs PublishContext: map[string]string{ exoscaleVolumeName: volume.Name, exoscaleVolumeID: volume.ID.String(), - exoscaleVolumeZone: string(zone), }, }, nil } @@ -292,14 +285,13 @@ func (d *controllerService) ControllerPublishVolume(ctx context.Context, req *cs func (d *controllerService) ControllerUnpublishVolume(ctx context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) { klog.V(4).Infof("ControllerUnpublishVolume") - zone, volumeID, err := getExoscaleID(req.VolumeId) + volumeID, err := v3.ParseUUID(req.VolumeId) if err != nil { klog.Errorf("parse exoscale volume ID %s: %v", req.VolumeId, err) return nil, err } - client := d.client.WithURL(zone) - op, err := client.DetachBlockStorageVolume(ctx, volumeID) + op, err := d.client.DetachBlockStorageVolume(ctx, volumeID) if err != nil { if errors.Is(err, v3.ErrNotFound) || (errors.Is(err, v3.ErrInvalidRequest) && strings.Contains(err.Error(), "Volume not attached")) { @@ -325,14 +317,13 @@ func (d *controllerService) ControllerUnpublishVolume(ctx context.Context, req * // This operation MUST be idempotent. func (d *controllerService) ValidateVolumeCapabilities(ctx context.Context, req *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) { klog.V(4).Infof("ValidateVolumeCapabilities") - zone, volumeID, err := getExoscaleID(req.VolumeId) + volumeID, err := v3.ParseUUID(req.VolumeId) if err != nil { klog.Errorf("parse exoscale ID %s: %v", req.VolumeId, err) return nil, err } - client := d.client.WithURL(zone) - _, err = client.GetBlockStorageVolume(ctx, volumeID) + _, err = d.client.GetBlockStorageVolume(ctx, volumeID) if err != nil { klog.Errorf("get block storage volume %s: %v", volumeID, err) return nil, err @@ -373,7 +364,6 @@ func (d *controllerService) ListVolumes(ctx context.Context, req *csi.ListVolume } } - // TODO(multizone cluster) list in all zones. volumesResp, err := d.client.ListBlockStorageVolumes(ctx) if err != nil { klog.Errorf("list block storage volumes: %v", err) @@ -402,15 +392,15 @@ func (d *controllerService) ListVolumes(ctx context.Context, req *csi.ListVolume for _, v := range volumes { var instancesID []string if v.Instance != nil && v.Instance.ID != "" { - instancesID = append(instancesID, exoscaleID(d.zone, v.Instance.ID)) + instancesID = append(instancesID, v.Instance.ID.String()) } volumesEntries = append(volumesEntries, &csi.ListVolumesResponse_Entry{ Volume: &csi.Volume{ - VolumeId: exoscaleID(d.zone, v.ID), + VolumeId: v.ID.String(), // API reply in bytes then send it without conversion CapacityBytes: v.Size, - AccessibleTopology: newZoneTopology(d.zone), + AccessibleTopology: newZoneTopology(d.zoneName), }, Status: &csi.ListVolumesResponse_VolumeStatus{ PublishedNodeIds: instancesID, @@ -451,20 +441,19 @@ func (d *controllerService) ControllerGetCapabilities(ctx context.Context, req * func (d *controllerService) CreateSnapshot(ctx context.Context, req *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, error) { klog.V(4).Infof("CreateSnapshot") - zone, volumeID, err := getExoscaleID(req.SourceVolumeId) + volumeID, err := v3.ParseUUID(req.SourceVolumeId) if err != nil { klog.Errorf("parse exoscale ID %s: %v", req.SourceVolumeId, err) return nil, err } - client := d.client.WithURL(zone) - volume, err := client.GetBlockStorageVolume(ctx, volumeID) + volume, err := d.client.GetBlockStorageVolume(ctx, volumeID) if err != nil { klog.Errorf("create snapshot get volume %s: %v", volumeID, err) } for _, s := range volume.BlockStorageSnapshots { - snapshot, err := client.GetBlockStorageSnapshot(ctx, s.ID) + snapshot, err := d.client.GetBlockStorageSnapshot(ctx, s.ID) if err != nil { klog.Errorf("create snapshot get snapshot %s: %v", s.ID, err) } @@ -472,8 +461,8 @@ func (d *controllerService) CreateSnapshot(ctx context.Context, req *csi.CreateS if snapshot.Name == req.Name { return &csi.CreateSnapshotResponse{ Snapshot: &csi.Snapshot{ - SnapshotId: exoscaleID(zone, snapshot.ID), - SourceVolumeId: exoscaleID(zone, volume.ID), + SnapshotId: snapshot.ID.String(), + SourceVolumeId: volume.ID.String(), CreationTime: timestamppb.New(snapshot.CreatedAT), ReadyToUse: true, SizeBytes: volume.Size, @@ -482,7 +471,7 @@ func (d *controllerService) CreateSnapshot(ctx context.Context, req *csi.CreateS } } - op, err := client.CreateBlockStorageSnapshot(ctx, volume.ID, v3.CreateBlockStorageSnapshotRequest{ + op, err := d.client.CreateBlockStorageSnapshot(ctx, volume.ID, v3.CreateBlockStorageSnapshotRequest{ Name: req.Name, }) if err != nil { @@ -510,8 +499,8 @@ func (d *controllerService) CreateSnapshot(ctx context.Context, req *csi.CreateS return &csi.CreateSnapshotResponse{ Snapshot: &csi.Snapshot{ - SnapshotId: exoscaleID(zone, snapshot.ID), - SourceVolumeId: exoscaleID(zone, volume.ID), + SnapshotId: snapshot.ID.String(), + SourceVolumeId: volume.ID.String(), CreationTime: timestamppb.New(snapshot.CreatedAT), ReadyToUse: true, SizeBytes: volume.Size, @@ -523,14 +512,13 @@ func (d *controllerService) CreateSnapshot(ctx context.Context, req *csi.CreateS func (d *controllerService) DeleteSnapshot(ctx context.Context, req *csi.DeleteSnapshotRequest) (*csi.DeleteSnapshotResponse, error) { klog.V(4).Infof("DeleteSnapshot") - zone, snapshotID, err := getExoscaleID(req.SnapshotId) + snapshotID, err := v3.ParseUUID(req.SnapshotId) if err != nil { klog.Errorf("parse exoscale snapshot ID %s: %v", req.SnapshotId, err) return nil, err } - client := d.client.WithURL(zone) - op, err := client.DeleteBlockStorageSnapshot(ctx, snapshotID) + op, err := d.client.DeleteBlockStorageSnapshot(ctx, snapshotID) if err != nil { if errors.Is(err, v3.ErrNotFound) { return &csi.DeleteSnapshotResponse{}, nil @@ -538,7 +526,7 @@ func (d *controllerService) DeleteSnapshot(ctx context.Context, req *csi.DeleteS return nil, err } - if _, err := client.Wait(ctx, op, v3.OperationStateSuccess); err != nil { + if _, err := d.client.Wait(ctx, op, v3.OperationStateSuccess); err != nil { return nil, err } @@ -559,7 +547,6 @@ func (d *controllerService) ListSnapshots(ctx context.Context, req *csi.ListSnap } } - // TODO(multizone cluster) list in all zones. snapResp, err := d.client.ListBlockStorageSnapshots(ctx) if err != nil { return nil, err @@ -587,8 +574,8 @@ func (d *controllerService) ListSnapshots(ctx context.Context, req *csi.ListSnap for _, s := range snapshots { snapshotsEntries = append(snapshotsEntries, &csi.ListSnapshotsResponse_Entry{ Snapshot: &csi.Snapshot{ - SourceVolumeId: exoscaleID(d.zone, s.BlockStorageVolume.ID), - SnapshotId: exoscaleID(d.zone, s.ID), + SourceVolumeId: s.BlockStorageVolume.ID.String(), + SnapshotId: s.ID.String(), CreationTime: timestamppb.New(s.CreatedAT), ReadyToUse: true, // TODO SizeBytes @@ -611,14 +598,13 @@ func (d *controllerService) ControllerExpandVolume(ctx context.Context, req *csi // ControllerGetVolume gets a volume and return it. func (d *controllerService) ControllerGetVolume(ctx context.Context, req *csi.ControllerGetVolumeRequest) (*csi.ControllerGetVolumeResponse, error) { - zone, volumeID, err := getExoscaleID(req.VolumeId) + volumeID, err := v3.ParseUUID(req.VolumeId) if err != nil { klog.Errorf("parse exoscale ID %s: %v", req.VolumeId, err) return nil, err } - client := d.client.WithURL(zone) - volume, err := client.GetBlockStorageVolume(ctx, volumeID) + volume, err := d.client.GetBlockStorageVolume(ctx, volumeID) if err != nil { if errors.Is(err, v3.ErrNotFound) { return nil, status.Errorf(codes.NotFound, "volume %s not found", volumeID) @@ -630,12 +616,12 @@ func (d *controllerService) ControllerGetVolume(ctx context.Context, req *csi.Co var instancesID []string if volume.Instance != nil && volume.Instance.ID != "" { - instancesID = append(instancesID, exoscaleID(d.zone, volume.Instance.ID)) + instancesID = append(instancesID, volume.Instance.ID.String()) } return &csi.ControllerGetVolumeResponse{ Volume: &csi.Volume{ - VolumeId: exoscaleID(zone, volume.ID), + VolumeId: volume.ID.String(), // API reply in bytes then send it without conversion CapacityBytes: volume.Size, }, diff --git a/driver/driver.go b/driver/driver.go index 336ff498..d9e1c308 100644 --- a/driver/driver.go +++ b/driver/driver.go @@ -48,6 +48,7 @@ type DriverConfig struct { Mode Mode APIKey, APISecret string RestConfig *rest.Config + Zone v3.URL } // Driver implements the interfaces csi.IdentityServer, csi.ControllerServer and csi.NodeServer @@ -71,10 +72,14 @@ func NewDriver(config *DriverConfig) (*Driver, error) { config: config, } + var zone = nodeMeta.zone + if config.Zone != "" { + zone = config.Zone + } var client *v3.Client if config.Mode != NodeMode { client, err = v3.NewClient(config.APIKey, config.APISecret, - v3.ClientOptWithURL(nodeMeta.zone), + v3.ClientOptWithURL(zone), ) if err != nil { return nil, fmt.Errorf("new driver: %w", err) @@ -173,6 +178,7 @@ func (d *Driver) Run() error { type nodeMetadata struct { zone v3.URL + zoneName string InstanceID v3.UUID } @@ -220,6 +226,7 @@ func getExoscaleNodeMetadata() (*nodeMetadata, error) { return &nodeMetadata{ zone: zone, + zoneName: region, InstanceID: instanceID, }, nil } diff --git a/driver/helpers.go b/driver/helpers.go index c29739f0..b3010a81 100644 --- a/driver/helpers.go +++ b/driver/helpers.go @@ -4,42 +4,14 @@ import ( "fmt" "os" "path/filepath" - "strings" "github.com/container-storage-interface/spec/lib/go/csi" - - v3 "github.com/exoscale/egoscale/v3" ) -func exoscaleID(zone v3.URL, id v3.UUID) string { - z, _ := zone.Zone() - return fmt.Sprintf("%s/%s", z, id) -} - -func getExoscaleID(exoID string) (v3.URL, v3.UUID, error) { - s := strings.Split(exoID, "/") - if len(s) != 2 { - return "", "", fmt.Errorf("malformed exoscale id") - } - - id, err := v3.ParseUUID(s[1]) - if err != nil { - return "", "", err - } - - zone, ok := v3.Zones[s[0]] - if !ok { - return "", "", fmt.Errorf("invalid zone name: %s", s[0]) - } - - return zone, id, nil -} - -func newZoneTopology(zone v3.URL) []*csi.Topology { - z, _ := zone.Zone() +func newZoneTopology(zoneName string) []*csi.Topology { return []*csi.Topology{ { - Segments: map[string]string{ZoneTopologyKey: z}, + Segments: map[string]string{ZoneTopologyKey: zoneName}, }, } } diff --git a/driver/node.go b/driver/node.go index f75c2669..6ce61320 100644 --- a/driver/node.go +++ b/driver/node.go @@ -22,6 +22,7 @@ const ( type nodeService struct { nodeID v3.UUID zone v3.URL + zoneName string diskUtils *diskUtils } @@ -30,6 +31,7 @@ func newNodeService(meta *nodeMetadata) nodeService { nodeID: meta.InstanceID, zone: meta.zone, diskUtils: newDiskUtils(), + zoneName: meta.zoneName, } } @@ -50,7 +52,7 @@ func (d *nodeService) NodeStageVolume(ctx context.Context, req *csi.NodeStageVol return nil, status.Errorf(codes.InvalidArgument, "volume %s capability not supported", req.VolumeId) } - _, volumeID, err := getExoscaleID(req.VolumeId) + volumeID, err := v3.ParseUUID(req.GetVolumeId()) if err != nil { klog.Errorf("parse exoscale volume ID %s: %v", req.VolumeId, err) return nil, err @@ -112,7 +114,7 @@ func (d *nodeService) NodeStageVolume(ctx context.Context, req *csi.NodeStageVol // Specific fs cleanup or close like luks close...etc. func (d *nodeService) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) { klog.V(4).Infof("NodeUnstageVolume") - _, volumeID, err := getExoscaleID(req.GetVolumeId()) + volumeID, err := v3.ParseUUID(req.GetVolumeId()) if err != nil { return nil, err } @@ -154,7 +156,7 @@ func (d *nodeService) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstag // Mounting volume in right path...etc. func (d *nodeService) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) { // nolint:gocyclo klog.V(4).Infof("NodePublishVolume") - _, volumeID, err := getExoscaleID(req.GetVolumeId()) + volumeID, err := v3.ParseUUID(req.GetVolumeId()) if err != nil { return nil, err } @@ -304,7 +306,7 @@ func (d *nodeService) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpu // NodeGetVolumeStats returns the volume capacity statistics available for the volume func (d *nodeService) NodeGetVolumeStats(ctx context.Context, req *csi.NodeGetVolumeStatsRequest) (*csi.NodeGetVolumeStatsResponse, error) { klog.V(4).Infof("NodeGetVolumeStats") - _, volumeID, err := getExoscaleID(req.GetVolumeId()) + volumeID, err := v3.ParseUUID(req.GetVolumeId()) if err != nil { return nil, err } @@ -413,11 +415,11 @@ func (d *nodeService) NodeGetInfo(ctx context.Context, req *csi.NodeGetInfoReque klog.V(4).Infof("NodeGetInfo") return &csi.NodeGetInfoResponse{ // Store the zone and the instanceID to let the CSI controller know the zone of the node. - NodeId: exoscaleID(d.zone, d.nodeID), + NodeId: d.nodeID.String(), // TODO Will depend on Exoscale account limit, (remove const) MaxVolumesPerNode: maxVolumesPerNode, // newZoneTopology returns always len(1). - AccessibleTopology: newZoneTopology(d.zone)[0], + AccessibleTopology: newZoneTopology(d.zoneName)[0], }, nil } @@ -425,7 +427,7 @@ func (d *nodeService) NodeGetInfo(ctx context.Context, req *csi.NodeGetInfoReque // not supported yet at Exoscale Public API yet. func (d *nodeService) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandVolumeRequest) (*csi.NodeExpandVolumeResponse, error) { klog.V(4).Infof("NodeExpandVolume") - _, volumeID, err := getExoscaleID(req.GetVolumeId()) + volumeID, err := v3.ParseUUID(req.GetVolumeId()) if err != nil { return nil, err }