From 4786756b6cdf409598762e76ef3f7b99036a5a38 Mon Sep 17 00:00:00 2001 From: Aniruddha Basak Date: Fri, 19 Jun 2026 15:31:13 +0200 Subject: [PATCH] update stackit client in csi blockstorage --- cmd/stackit-csi-plugin/main.go | 28 +- pkg/csi/blockstorage/controllerserver.go | 109 ++- pkg/csi/blockstorage/controllerserver_test.go | 156 ++-- pkg/csi/blockstorage/driver.go | 4 +- pkg/csi/blockstorage/sanity_test.go | 41 +- pkg/csi/blockstorage/utils.go | 4 +- pkg/stackit/client/factory.go | 57 +- pkg/stackit/client/iaas.go | 543 +++++++++++ pkg/stackit/client/labels.go | 13 + pkg/stackit/client/mock/iaas_mock.go | 874 +++++++++++++++++- pkg/stackit/client/mock/mock.go | 8 +- pkg/stackit/client/utils.go | 71 ++ 12 files changed, 1728 insertions(+), 180 deletions(-) create mode 100644 pkg/stackit/client/labels.go create mode 100644 pkg/stackit/client/utils.go diff --git a/cmd/stackit-csi-plugin/main.go b/cmd/stackit-csi-plugin/main.go index d952bd60..3200b29e 100644 --- a/cmd/stackit-csi-plugin/main.go +++ b/cmd/stackit-csi-plugin/main.go @@ -6,15 +6,16 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "k8s.io/component-base/cli" - "k8s.io/klog/v2" - "github.com/stackitcloud/cloud-provider-stackit/pkg/csi" "github.com/stackitcloud/cloud-provider-stackit/pkg/csi/blockstorage" "github.com/stackitcloud/cloud-provider-stackit/pkg/csi/util/mount" - "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit" + "github.com/stackitcloud/cloud-provider-stackit/pkg/metrics" + stackitclient "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/client" "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/metadata" "github.com/stackitcloud/cloud-provider-stackit/pkg/version" + sdkconfig "github.com/stackitcloud/stackit-sdk-go/core/config" + "k8s.io/component-base/cli" + "k8s.io/klog/v2" ) var ( @@ -73,7 +74,7 @@ func main() { cmd.PersistentFlags().BoolVar(&provideNodeService, "provide-node-service", true, "If set to true then the CSI driver does provide the node service (default: true)") - stackit.AddExtraFlags(pflag.CommandLine) + stackitclient.AddExtraFlags(pflag.CommandLine) code := cli.Run(cmd) os.Exit(code) @@ -89,29 +90,32 @@ func handle() { if provideControllerService { var err error - cfg, err := stackit.GetConfigFromFile(cloudConfig) + cfg, err := stackitclient.GetConfigFromFile(cloudConfig) if err != nil { klog.Fatal(err) } - iaasClient, err := stackit.CreateIaaSClient(&cfg) - if err != nil { - klog.Fatalf("Failed to create IaaS client: %v", err) + iaasOpts := []sdkconfig.ConfigurationOption{ + sdkconfig.WithHTTPClient(metrics.NewInstrumentedHTTPClient()), + } + + if cfg.Global.APIEndpoints.IaasAPI != "" { + iaasOpts = append(iaasOpts, sdkconfig.WithEndpoint(cfg.Global.APIEndpoints.IaasAPI)) } - stackitProvider, err := stackit.CreateSTACKITProvider(iaasClient, &cfg) + iaasClient, err := stackitclient.New(cfg.Global.Region, cfg.Global.ProjectID).IaaS(iaasOpts) if err != nil { klog.Fatalf("Failed to create STACKIT provider: %v", err) } - d.SetupControllerService(stackitProvider) + d.SetupControllerService(iaasClient) } if provideNodeService { // Initialize mount mountProvider := mount.GetMountProvider() - cfg, err := stackit.GetConfigFromFile(cloudConfig) + cfg, err := stackitclient.GetConfigFromFile(cloudConfig) if err != nil { klog.Fatal(err) } diff --git a/pkg/csi/blockstorage/controllerserver.go b/pkg/csi/blockstorage/controllerserver.go index ef962edd..272a3936 100644 --- a/pkg/csi/blockstorage/controllerserver.go +++ b/pkg/csi/blockstorage/controllerserver.go @@ -26,7 +26,10 @@ import ( "github.com/container-storage-interface/spec/lib/go/csi" "github.com/go-viper/mapstructure/v2" "github.com/kubernetes-csi/csi-lib-utils/protosanitizer" + sharedcsi "github.com/stackitcloud/cloud-provider-stackit/pkg/csi" "github.com/stackitcloud/cloud-provider-stackit/pkg/csi/util" + stackitclient "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/client" + "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/stackiterrors" iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -34,15 +37,11 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/klog/v2" "k8s.io/utils/ptr" - - sharedcsi "github.com/stackitcloud/cloud-provider-stackit/pkg/csi" - "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit" - "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/stackiterrors" ) type controllerServer struct { Driver *Driver - Instance stackit.IaasClient + Instance stackitclient.IaaSClient csi.UnimplementedControllerServer } @@ -127,7 +126,7 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol if volSizeGB != *vols[0].Size { return nil, status.Error(codes.AlreadyExists, "Volume Already exists with same name and different capacity") } - if *vols[0].Status != stackit.VolumeAvailableStatus { + if *vols[0].Status != stackitclient.VolumeAvailableStatus { return nil, status.Error(codes.Internal, fmt.Sprintf("Volume %s is not in available state", *vols[0].Id)) } klog.V(4).Infof("Volume %s already exists in Availability Zone: %s of size %d GiB", *vols[0].Id, vols[0].AvailabilityZone, *vols[0].Size) @@ -151,21 +150,21 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol var sourceVolID string var sourceBackupID string var sourceSnapshotID string - var volumeSourceType stackit.VolumeSourceTypes + var volumeSourceType stackitclient.VolumeSourceTypes if content != nil && content.GetSnapshot() != nil { // Backups and Snapshots are the same for Kubernetes sourceSnapshotID = content.GetSnapshot().GetSnapshotId() sourceBackupID = content.GetSnapshot().GetSnapshotId() // By default, we try to clone volumes from snapshots - volumeSourceType = stackit.SnapshotSource + volumeSourceType = stackitclient.SnapshotSource - snap, err := cloud.GetSnapshotByID(ctx, sourceSnapshotID) + snap, err := cloud.GetSnapshot(ctx, sourceSnapshotID) if stackiterrors.IgnoreNotFound(err) != nil { return nil, status.Errorf(codes.Internal, "Failed to retrieve the source snapshot %s: %v", sourceSnapshotID, err) } // If the snapshot exists but is not yet available, fail. - if err == nil && *snap.Status != stackit.SnapshotReadyStatus { + if err == nil && *snap.Status != stackitclient.SnapshotReadyStatus { return nil, status.Errorf(codes.Unavailable, "VolumeContentSource Snapshot %s is not yet available. status: %s", sourceSnapshotID, *snap.Status) } // Only continue checking if the Snapshot is found @@ -184,17 +183,17 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol // check if a Backup with the same ID exists if stackiterrors.IsNotFound(err) { var back *iaas.Backup - back, err = cloud.GetBackupByID(ctx, sourceBackupID) + back, err = cloud.GetBackup(ctx, sourceBackupID) if err != nil { // If there is an error getting the backup as well, fail. return nil, status.Errorf(codes.NotFound, "VolumeContentSource Snapshot or Backup with ID %s not found", sourceBackupID) } - if *back.Status != stackit.SnapshotReadyStatus { + if *back.Status != stackitclient.SnapshotReadyStatus { // If the backup exists but is not yet available, fail. return nil, status.Errorf(codes.Unavailable, "VolumeContentSource Backup %s is not yet available. status: %s", sourceBackupID, *back.Status) } // If an available backup is found, create the volume from the backup. Implies that a Snapshot was not found. - volumeSourceType = stackit.BackupSource + volumeSourceType = stackitclient.BackupSource } } @@ -211,7 +210,7 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol if volAvailability != sourceVolume.AvailabilityZone { return nil, status.Errorf(codes.ResourceExhausted, "Volume must be in the same availability zone as source Volume. Got %s Required: %s", volAvailability, sourceVolume.AvailabilityZone) } - volumeSourceType = stackit.VolumeSource + volumeSourceType = stackitclient.VolumeSource } opts := &iaas.CreateVolumePayload{ @@ -225,7 +224,7 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol // Only set CreateVolumePayload.Source when actually creating volume from source/snapshot/backup if volumeSourceType != "" { - if volumeSourceType == stackit.SnapshotSource || volumeSourceType == stackit.VolumeSource { + if volumeSourceType == stackitclient.SnapshotSource || volumeSourceType == stackitclient.VolumeSource { // Changing the performance class while restoring from Snapshot or Volume is not supported opts.PerformanceClass = nil } @@ -241,7 +240,7 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol // The encryption config is already set for volumes created from snapshot or volume. We MUST never set it when // restoring from snapshot or volume. // TODO: Unclear if BackupSource is the same as the above or is actually changeable. IaaS is testing. - if volParams.Encrypted != nil && (volumeSourceType == "" || volumeSourceType == stackit.BackupSource) { + if volParams.Encrypted != nil && (volumeSourceType == "" || volumeSourceType == stackitclient.BackupSource) { encrypted, err := strconv.ParseBool(*volParams.Encrypted) if err != nil { return nil, status.Error(codes.InvalidArgument, "parameter encrypted must be of type boolean") @@ -259,7 +258,7 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol return nil, status.Errorf(codes.Internal, "CreateVolume failed with error %v", err) } - targetStatus := []string{stackit.VolumeAvailableStatus} + targetStatus := []string{stackitclient.VolumeAvailableStatus} // Recheck after: 0s (immediate), 20s, 45.6s, 78.36s, 120.31s err = cloud.WaitVolumeTargetStatusWithCustomBackoff(ctx, *vol.Id, targetStatus, &wait.Backoff{ @@ -360,7 +359,7 @@ func (cs *controllerServer) ControllerPublishVolume(ctx context.Context, req *cs return nil, status.Errorf(codes.Internal, "[ControllerPublishVolume] get volume failed with error %v", err) } - _, err = cloud.GetInstanceByID(ctx, instanceID) + _, err = cloud.GetServer(ctx, instanceID) if err != nil { if stackiterrors.IsNotFound(err) { return nil, status.Errorf(codes.NotFound, "[ControllerPublishVolume] Instance %s not found", instanceID) @@ -368,12 +367,11 @@ func (cs *controllerServer) ControllerPublishVolume(ctx context.Context, req *cs return nil, status.Errorf(codes.Internal, "[ControllerPublishVolume] GetInstanceByID failed with error %v", err) } - _, err = cloud.AttachVolume(ctx, instanceID, volumeID) + payload := iaas.AddVolumeToServerPayload{ + DeleteOnTermination: new(false), + } + _, err = cloud.AttachVolume(ctx, instanceID, volumeID, payload) if err != nil { - // Trigger's an immediate `NodeGetInfo` RPC call when MutableCSINodeAllocatableCount is enabled - if stackiterrors.IsTooManyDevicesError(err) { - return nil, status.Errorf(codes.ResourceExhausted, "[ControllerPublishVolume] Node can't accept any more volumes %v. All PCIe lanes are exhausted!", err) - } klog.Errorf("Failed to AttachVolume: %v", err) return nil, status.Errorf(codes.Internal, "[ControllerPublishVolume] Attach Volume failed with error %v", err) } @@ -401,7 +399,7 @@ func (cs *controllerServer) ControllerUnpublishVolume(ctx context.Context, req * if volumeID == "" { return nil, status.Error(codes.InvalidArgument, "[ControllerUnpublishVolume] Volume ID must be provided") } - _, err := cloud.GetInstanceByID(ctx, instanceID) + _, err := cloud.GetServer(ctx, instanceID) if err != nil { if stackiterrors.IsNotFound(err) { klog.V(3).Infof("ControllerUnpublishVolume assuming volume %s is detached, because node %s does not exist", volumeID, instanceID) @@ -478,9 +476,9 @@ func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS name := req.Name volumeID := req.GetSourceVolumeId() - snapshotType := req.Parameters[stackit.SnapshotType] + snapshotType := req.Parameters[stackitclient.SnapshotType] filters := map[string]string{"Name": name} - backupMaxDurationSecondsPerGB := stackit.BackupMaxDurationSecondsPerGBDefault + backupMaxDurationSecondsPerGB := stackitclient.BackupMaxDurationSecondsPerGBDefault // Current time, used for CreatedAt var ctime *timestamppb.Timestamp @@ -539,7 +537,7 @@ func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS } // Get the max duration to wait in seconds per GB of snapshot and fail if parsing fails - if item, ok := (req.Parameters)[stackit.BackupMaxDurationPerGB]; ok { + if item, ok := (req.Parameters)[stackitclient.BackupMaxDurationPerGB]; ok { backupMaxDurationSecondsPerGB, err = strconv.Atoi(item) if err != nil { klog.Errorf("Setting backup-max-duration-seconds-per-gb failed due to a parsing error: %v", err) @@ -550,7 +548,7 @@ func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS // Create the snapshot if the backup does not already exist and wait for it to be ready if !backupAlreadyExists { - snap, err = cs.createSnapshot(ctx, cloud, name, volumeID, req.Parameters) + snap, err = cs.createSnapshot(ctx, name, volumeID, req.Parameters) if err != nil { return nil, err } @@ -603,7 +601,7 @@ func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS } // Necessary to get all the backup information, including size. - backup, err = cloud.GetBackupByID(ctx, *backup.Id) + backup, err = cloud.GetBackup(ctx, *backup.Id) if err != nil { klog.Errorf("Failed to GetBackupByID after backup creation: %v", err) return nil, status.Error(codes.Internal, fmt.Sprintf("GetBackupByID failed with error %v", err)) @@ -626,12 +624,12 @@ func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS }, nil } -func (cs *controllerServer) createSnapshot(ctx context.Context, cloud stackit.IaasClient, name, volumeID string, parameters map[string]string) (*iaas.Snapshot, error) { //nolint:lll // looks weird when shortened +func (cs *controllerServer) createSnapshot(ctx context.Context, name, volumeID string, parameters map[string]string) (*iaas.Snapshot, error) { //nolint:lll // looks weird when shortened filters := map[string]string{} filters["Name"] = name // List existing snapshots with the same name - snapshots, _, err := cloud.ListSnapshots(ctx, filters) + snapshots, _, err := cs.Instance.ListSnapshots(ctx, filters) if err != nil { klog.Errorf("Failed to query for existing Snapshot during CreateSnapshot: %v", err) return nil, status.Error(codes.Internal, "Failed to get snapshots") @@ -661,15 +659,16 @@ func (cs *controllerServer) createSnapshot(ctx context.Context, cloud stackit.Ia // properties := map[string]string{blockStorageCSIClusterIDKey: cs.Driver.clusterID} properties := map[string]string{} - // see https://github.com/kubernetes-csi/external-snapshotter/pull/375/ - // Also, we don't want to tag every param, but we do honor the RecognizedCSISnapshotterParams - for _, mKey := range sharedcsi.RecognizedCSISnapshotterParams { - if v, ok := parameters[mKey]; ok { - properties[mKey] = v - } + // TODO: Add support for sharedcsi.RecognizedCSISnapshotterParams later. + payload := &iaas.CreateSnapshotPayload{ + Name: new(name), + VolumeId: volumeID, + } + if len(properties) > 0 { + payload.Labels = stackitclient.LabelsFromTags(properties) } - snap, err := cloud.CreateSnapshot(ctx, name, volumeID, properties) + snap, err := cs.Instance.CreateSnapshot(ctx, payload) if err != nil { klog.Errorf("Failed to Create snapshot: %v", err) return nil, status.Errorf(codes.Internal, "CreateSnapshot failed with error %v", err) @@ -680,7 +679,7 @@ func (cs *controllerServer) createSnapshot(ctx context.Context, cloud stackit.Ia return snap, nil } -func (cs *controllerServer) createBackup(ctx context.Context, cloud stackit.IaasClient, name, volumeID string, snap *iaas.Snapshot, parameters map[string]string) (*iaas.Backup, error) { //nolint:lll // looks weird when shortened +func (cs *controllerServer) createBackup(ctx context.Context, cloud stackitclient.IaaSClient, name, volumeID string, snap *iaas.Snapshot, parameters map[string]string) (*iaas.Backup, error) { //nolint:lll // looks weird when shortened // Add cluster ID to the snapshot metadata // TODO: Use once IaaS has extended the label regex to allow for forward slashes and dots // properties := map[string]string{blockStorageCSIClusterIDKey: cs.Driver.clusterID} @@ -688,7 +687,7 @@ func (cs *controllerServer) createBackup(ctx context.Context, cloud stackit.Iaas // see https://github.com/kubernetes-csi/external-snapshotter/pull/375/ // Also, we don't want to tag every param, but we do honor the RecognizedCSISnapshotterParams - for _, mKey := range append(sharedcsi.RecognizedCSISnapshotterParams, stackit.SnapshotType) { + for _, mKey := range append(sharedcsi.RecognizedCSISnapshotterParams, stackitclient.SnapshotType) { if v, ok := parameters[mKey]; ok { properties[mKey] = v } @@ -716,7 +715,7 @@ func (cs *controllerServer) DeleteSnapshot(ctx context.Context, req *csi.DeleteS } // If volumeSnapshot object was linked to a cinder backup, delete the backup. - back, err := cloud.GetBackupByID(ctx, id) + back, err := cloud.GetBackup(ctx, id) if err == nil && back != nil { err = cloud.DeleteBackup(ctx, id) if err != nil { @@ -743,7 +742,7 @@ func (cs *controllerServer) ListSnapshots(ctx context.Context, req *csi.ListSnap snapshotID := req.GetSnapshotId() if snapshotID != "" { - snap, err := cloud.GetSnapshotByID(ctx, snapshotID) + snap, err := cloud.GetSnapshot(ctx, snapshotID) if err != nil { if stackiterrors.IsNotFound(err) { klog.V(3).Infof("Snapshot %s not found", snapshotID) @@ -785,7 +784,7 @@ func (cs *controllerServer) ListSnapshots(ctx context.Context, req *csi.ListSnap } // Only retrieve snapshots that are available - filters["Status"] = stackit.SnapshotReadyStatus + filters["Status"] = stackitclient.SnapshotReadyStatus slist, nextPageToken, err = cloud.ListSnapshots(ctx, filters) if err != nil { klog.Errorf("Failed to ListSnapshots: %v", err) @@ -944,13 +943,13 @@ func (cs *controllerServer) ControllerExpandVolume(ctx context.Context, req *csi }, nil } - err = cloud.ExpandVolume(ctx, volumeID, *volume.Status, volSizeGB) + err = cloud.ExpandVolume(ctx, volumeID, *volume.Status, iaas.ResizeVolumePayload{Size: volSizeGB}) if err != nil { return nil, status.Errorf(codes.Internal, "Could not resize volume %q to size %v: %v", volumeID, volSizeGB, err) } // we need wait for the volume to be available or InUse, it might be error_extending in some scenario - targetStatus := []string{stackit.VolumeAvailableStatus, stackit.VolumeAttachedStatus} + targetStatus := []string{stackitclient.VolumeAvailableStatus, stackitclient.VolumeAttachedStatus} err = cloud.WaitVolumeTargetStatus(ctx, volumeID, targetStatus) if err != nil { klog.Errorf("Failed to WaitVolumeTargetStatus of volume %s: %v", volumeID, err) @@ -967,13 +966,13 @@ func (cs *controllerServer) ControllerExpandVolume(ctx context.Context, req *csi func getCreateVolumeResponse(vol *iaas.Volume) *csi.CreateVolumeResponse { var volsrc *csi.VolumeContentSource - var volumeSourceType stackit.VolumeSourceTypes + var volumeSourceType stackitclient.VolumeSourceTypes volCnx := map[string]string{} if vol.Source != nil { - volumeSourceType = stackit.VolumeSourceTypes(vol.Source.Type) + volumeSourceType = stackitclient.VolumeSourceTypes(vol.Source.Type) switch volumeSourceType { - case stackit.VolumeSource: + case stackitclient.VolumeSource: volCnx[ResizeRequired] = "true" volsrc = &csi.VolumeContentSource{ @@ -983,7 +982,7 @@ func getCreateVolumeResponse(vol *iaas.Volume) *csi.CreateVolumeResponse { }, }, } - case stackit.BackupSource: + case stackitclient.BackupSource: volCnx[ResizeRequired] = "true" volsrc = &csi.VolumeContentSource{ @@ -993,7 +992,7 @@ func getCreateVolumeResponse(vol *iaas.Volume) *csi.CreateVolumeResponse { }, }, } - case stackit.SnapshotSource: + case stackitclient.SnapshotSource: volCnx[ResizeRequired] = "true" volsrc = &csi.VolumeContentSource{ @@ -1025,14 +1024,14 @@ func getCreateVolumeResponse(vol *iaas.Volume) *csi.CreateVolumeResponse { return resp } -// determineSourceIDForSourceType returns the correct sourceID for the given stackit.VolumeSourceTypes -func determineSourceIDForSourceType(srcType stackit.VolumeSourceTypes, sourceSnapshotID, sourceVolID string) string { +// determineSourceIDForSourceType returns the correct sourceID for the given stackitclient.VolumeSourceTypes +func determineSourceIDForSourceType(srcType stackitclient.VolumeSourceTypes, sourceSnapshotID, sourceVolID string) string { switch srcType { - case stackit.BackupSource: + case stackitclient.BackupSource: return sourceSnapshotID - case stackit.SnapshotSource: + case stackitclient.SnapshotSource: return sourceSnapshotID - case stackit.VolumeSource: + case stackitclient.VolumeSource: return sourceVolID default: return "" diff --git a/pkg/csi/blockstorage/controllerserver_test.go b/pkg/csi/blockstorage/controllerserver_test.go index f745c2d9..f36adbb9 100644 --- a/pkg/csi/blockstorage/controllerserver_test.go +++ b/pkg/csi/blockstorage/controllerserver_test.go @@ -11,23 +11,23 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/stackitcloud/cloud-provider-stackit/pkg/csi/util" + stackitclient "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/client" + stackitclientmock "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/client/mock" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api" "go.uber.org/mock/gomock" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/timestamppb" - - "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit" ) var _ = Describe("ControllerServer test", Ordered, func() { var ( fakeCs *controllerServer - iaasClient *stackit.MockIaasClient + iaasClient *stackitclientmock.MockIaaSClient FakeEndpoint = "tcp://127.0.0.1:10000" FakeCluster = "cluster" - expandTargetStatus = []string{stackit.VolumeAvailableStatus, stackit.VolumeAttachedStatus} + expandTargetStatus = []string{stackitclient.VolumeAvailableStatus, stackitclient.VolumeAttachedStatus} stdCapRange = &csi.CapacityRange{ RequiredBytes: util.GIBIBYTE * 20, } @@ -51,7 +51,7 @@ var _ = Describe("ControllerServer test", Ordered, func() { d := NewDriver(&DriverOpts{Endpoint: FakeEndpoint, ClusterID: FakeCluster}) mockCtrl := gomock.NewController(GinkgoT()) - iaasClient = stackit.NewMockIaasClient(mockCtrl) + iaasClient = stackitclientmock.NewMockIaaSClient(mockCtrl) fakeCs = NewControllerServer(d, iaasClient) }) @@ -188,7 +188,7 @@ var _ = Describe("ControllerServer test", Ordered, func() { Id: new("existing-available-volume-id"), Name: new("new volume"), Size: new(int64(20)), - Status: new(stackit.VolumeAvailableStatus), + Status: new(stackitclient.VolumeAvailableStatus), AvailabilityZone: "eu01", }, }, nil) @@ -212,7 +212,7 @@ var _ = Describe("ControllerServer test", Ordered, func() { Id: new("existing-available-volume-id"), Name: new("new volume"), Size: new(int64(30)), - Status: new(stackit.VolumeAvailableStatus), + Status: new(stackitclient.VolumeAvailableStatus), AvailabilityZone: "eu01", }, }, nil) @@ -234,7 +234,7 @@ var _ = Describe("ControllerServer test", Ordered, func() { Id: new("existing-available-volume-id"), Name: new("new volume"), Size: new(int64(20)), - Status: new(stackit.VolumeAttachedStatus), + Status: new(stackitclient.VolumeAttachedStatus), AvailabilityZone: "eu01", }, }, nil) @@ -296,7 +296,7 @@ var _ = Describe("ControllerServer test", Ordered, func() { }, } - iaasClient.EXPECT().GetSnapshotByID(gomock.Any(), "snapshot-id").Return(&iaas.Snapshot{ + iaasClient.EXPECT().GetSnapshot(gomock.Any(), "snapshot-id").Return(&iaas.Snapshot{ Id: new("snapshot-id"), Status: new("AVAILABLE"), VolumeId: "snapshot-volume-id", @@ -305,17 +305,23 @@ var _ = Describe("ControllerServer test", Ordered, func() { Id: new("snapshot-volume-id"), AvailabilityZone: "eu01", }, nil) - iaasClient.EXPECT().CreateVolume(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, opts *iaas.CreateVolumePayload) { + iaasClient.EXPECT(). + CreateVolume(gomock.Any(), gomock.Any()). + DoAndReturn(func(_ context.Context, opts *iaas.CreateVolumePayload) (*iaas.Volume, error) { Expect(opts.Source.Id).To(Equal("snapshot-id")) Expect(opts.Source.Type).To(Equal("snapshot")) - }). - Return(&iaas.Volume{ - Id: new("volume-id"), - Name: new("new volume"), - AvailabilityZone: "eu01", - Size: new(int64(20)), - }, nil) + + volumeID := "volume-id" + name := "new volume" + size := int64(20) + + return &iaas.Volume{ + Id: &volumeID, + Name: &name, + AvailabilityZone: "eu01", + Size: &size, + }, nil + }) iaasClient.EXPECT().WaitVolumeTargetStatusWithCustomBackoff(gomock.Any(), "volume-id", gomock.Any(), gomock.Any()).Return(nil) _, err := fakeCs.CreateVolume(context.Background(), req) @@ -331,7 +337,7 @@ var _ = Describe("ControllerServer test", Ordered, func() { }, } - iaasClient.EXPECT().GetSnapshotByID(gomock.Any(), "snapshot-id").Return(nil, fmt.Errorf("injected error")) + iaasClient.EXPECT().GetSnapshot(gomock.Any(), "snapshot-id").Return(nil, fmt.Errorf("injected error")) _, err := fakeCs.CreateVolume(context.Background(), req) Expect(err).To(HaveOccurred()) @@ -347,7 +353,7 @@ var _ = Describe("ControllerServer test", Ordered, func() { }, } - iaasClient.EXPECT().GetSnapshotByID(gomock.Any(), "snapshot-id").Return(&iaas.Snapshot{ + iaasClient.EXPECT().GetSnapshot(gomock.Any(), "snapshot-id").Return(&iaas.Snapshot{ Id: new("snapshot-id"), Status: new("creating"), }, nil) @@ -367,25 +373,31 @@ var _ = Describe("ControllerServer test", Ordered, func() { }, } - iaasClient.EXPECT().GetSnapshotByID(gomock.Any(), "snapshot-id").Return(nil, + iaasClient.EXPECT().GetSnapshot(gomock.Any(), "snapshot-id").Return(nil, &oapierror.GenericOpenAPIError{ StatusCode: http.StatusNotFound, }) - iaasClient.EXPECT().GetBackupByID(gomock.Any(), "snapshot-id").Return(&iaas.Backup{ + iaasClient.EXPECT().GetBackup(gomock.Any(), "snapshot-id").Return(&iaas.Backup{ Status: new("AVAILABLE"), AvailabilityZone: new("eu01"), }, nil) - iaasClient.EXPECT().CreateVolume(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, opts *iaas.CreateVolumePayload) { + iaasClient.EXPECT(). + CreateVolume(gomock.Any(), gomock.Any()). + DoAndReturn(func(_ context.Context, opts *iaas.CreateVolumePayload) (*iaas.Volume, error) { Expect(opts.Source.Id).To(Equal("snapshot-id")) Expect(opts.Source.Type).To(Equal("backup")) - }). - Return(&iaas.Volume{ - Id: new("volume-id"), - Name: new("new volume"), - AvailabilityZone: "eu01", - Size: new(int64(20)), - }, nil) + + volumeID := "volume-id" + name := "new volume" + size := int64(20) + + return &iaas.Volume{ + Id: &volumeID, + Name: &name, + AvailabilityZone: "eu01", + Size: &size, + }, nil + }) iaasClient.EXPECT().WaitVolumeTargetStatusWithCustomBackoff(gomock.Any(), "volume-id", gomock.Any(), gomock.Any()).Return(nil) _, err := fakeCs.CreateVolume(context.Background(), req) @@ -406,7 +418,7 @@ var _ = Describe("ControllerServer test", Ordered, func() { }, } - iaasClient.EXPECT().GetSnapshotByID(gomock.Any(), "snapshot-id").Return(&iaas.Snapshot{ + iaasClient.EXPECT().GetSnapshot(gomock.Any(), "snapshot-id").Return(&iaas.Snapshot{ Id: new("snapshot-id"), VolumeId: "volume-id", Status: new("AVAILABLE"), @@ -432,11 +444,11 @@ var _ = Describe("ControllerServer test", Ordered, func() { }, } - iaasClient.EXPECT().GetSnapshotByID(gomock.Any(), "snapshot-id").Return(nil, + iaasClient.EXPECT().GetSnapshot(gomock.Any(), "snapshot-id").Return(nil, &oapierror.GenericOpenAPIError{ StatusCode: http.StatusNotFound, }) - iaasClient.EXPECT().GetBackupByID(gomock.Any(), "snapshot-id").Return(nil, + iaasClient.EXPECT().GetBackup(gomock.Any(), "snapshot-id").Return(nil, &oapierror.GenericOpenAPIError{ StatusCode: http.StatusNotFound, }) @@ -456,11 +468,11 @@ var _ = Describe("ControllerServer test", Ordered, func() { }, } - iaasClient.EXPECT().GetSnapshotByID(gomock.Any(), "snapshot-id").Return(nil, + iaasClient.EXPECT().GetSnapshot(gomock.Any(), "snapshot-id").Return(nil, &oapierror.GenericOpenAPIError{ StatusCode: http.StatusNotFound, }) - iaasClient.EXPECT().GetBackupByID(gomock.Any(), "snapshot-id").Return(&iaas.Backup{ + iaasClient.EXPECT().GetBackup(gomock.Any(), "snapshot-id").Return(&iaas.Backup{ Status: new("creating"), }, nil) @@ -484,17 +496,23 @@ var _ = Describe("ControllerServer test", Ordered, func() { Status: new("AVAILABLE"), AvailabilityZone: "eu01", }, nil) - iaasClient.EXPECT().CreateVolume(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, opts *iaas.CreateVolumePayload) { + iaasClient.EXPECT(). + CreateVolume(gomock.Any(), gomock.Any()). + DoAndReturn(func(_ context.Context, opts *iaas.CreateVolumePayload) (*iaas.Volume, error) { Expect(opts.Source.Id).To(Equal("volume-source-id")) Expect(opts.Source.Type).To(Equal("volume")) - }). - Return(&iaas.Volume{ - Id: new("volume-id"), - Name: new("new volume"), - AvailabilityZone: "eu01", - Size: new(int64(20)), - }, nil) + + name := "new volume" + volumeID := "volume-id" + size := int64(20) + + return &iaas.Volume{ + Id: &volumeID, + Name: &name, + AvailabilityZone: "eu01", + Size: &size, + }, nil + }) iaasClient.EXPECT().WaitVolumeTargetStatusWithCustomBackoff(gomock.Any(), "volume-id", gomock.Any(), gomock.Any()).Return(nil) _, err := fakeCs.CreateVolume(context.Background(), req) @@ -649,8 +667,8 @@ var _ = Describe("ControllerServer test", Ordered, func() { VolumeCapability: stdVolCap, } iaasClient.EXPECT().GetVolume(gomock.Any(), req.VolumeId).Return(&iaas.Volume{}, nil) - iaasClient.EXPECT().GetInstanceByID(gomock.Any(), "fake").Return(&iaas.Server{}, nil) - iaasClient.EXPECT().AttachVolume(gomock.Any(), req.NodeId, req.VolumeId).Return(req.VolumeId, nil) + iaasClient.EXPECT().GetServer(gomock.Any(), "fake").Return(&iaas.Server{}, nil) + iaasClient.EXPECT().AttachVolume(gomock.Any(), req.NodeId, req.VolumeId, gomock.Any()).Return(req.VolumeId, nil) iaasClient.EXPECT().WaitDiskAttached(gomock.Any(), req.NodeId, req.VolumeId).Return(nil) _, err := fakeCs.ControllerPublishVolume(context.Background(), req) Expect(err).To(Not(HaveOccurred())) @@ -662,7 +680,7 @@ var _ = Describe("ControllerServer test", Ordered, func() { VolumeId: "fake", NodeId: "fake", } - iaasClient.EXPECT().GetInstanceByID(gomock.Any(), "fake").Return(&iaas.Server{}, nil) + iaasClient.EXPECT().GetServer(gomock.Any(), "fake").Return(&iaas.Server{}, nil) iaasClient.EXPECT().DetachVolume(gomock.Any(), req.NodeId, req.VolumeId).Return(nil) iaasClient.EXPECT().WaitDiskDetached(gomock.Any(), req.NodeId, req.VolumeId).Return(nil) _, err := fakeCs.ControllerUnpublishVolume(context.Background(), req) @@ -694,9 +712,9 @@ var _ = Describe("ControllerServer test", Ordered, func() { volSizeGB := util.RoundUpSize(req.GetCapacityRange().GetRequiredBytes(), util.GIBIBYTE) iaasClient.EXPECT().GetVolume(gomock.Any(), req.VolumeId).Return(&iaas.Volume{ Size: new(int64(10)), - Status: new(stackit.VolumeAvailableStatus), + Status: new(stackitclient.VolumeAvailableStatus), }, nil) - iaasClient.EXPECT().ExpandVolume(gomock.Any(), req.VolumeId, stackit.VolumeAvailableStatus, volSizeGB).Return(nil) + iaasClient.EXPECT().ExpandVolume(gomock.Any(), req.VolumeId, stackitclient.VolumeAvailableStatus, iaas.ResizeVolumePayload{Size: volSizeGB}).Return(nil) iaasClient.EXPECT().WaitVolumeTargetStatus(gomock.Any(), req.VolumeId, expandTargetStatus).Return(nil) _, err := fakeCs.ControllerExpandVolume(context.Background(), req) Expect(err).To(Not(HaveOccurred())) @@ -706,12 +724,16 @@ var _ = Describe("ControllerServer test", Ordered, func() { VolumeId: "fake", CapacityRange: stdCapRange, } + volSizeGB := util.RoundUpSize(req.GetCapacityRange().GetRequiredBytes(), util.GIBIBYTE) iaasClient.EXPECT().GetVolume(gomock.Any(), req.VolumeId).Return(&iaas.Volume{ Size: new(int64(10)), Status: new("ERROR"), }, nil) - iaasClient.EXPECT().ExpandVolume(gomock.Any(), req.VolumeId, "ERROR", volSizeGB).Return(fmt.Errorf("volume cannot be resized, when status is ERROR")) + iaasClient.EXPECT().ExpandVolume(gomock.Any(), req.VolumeId, "ERROR", iaas.ResizeVolumePayload{ + Size: volSizeGB, + }).Return(fmt.Errorf("volume cannot be resized, when status is ERROR")) + _, err := fakeCs.ControllerExpandVolume(context.Background(), req) Expect(err).To(HaveOccurred()) Expect(status.Convert(err).Code()).To(Equal(codes.Internal)) @@ -730,8 +752,6 @@ var _ = Describe("ControllerServer test", Ordered, func() { }) It("should create backup successfully", func() { // TODO: Use once IaaS has extended the label regex to allow for forward slashes and dots - // properties := map[string]string{blockStorageCSIClusterIDKey: "cluster"} - properties := map[string]string{} expectedSnap := &iaas.Snapshot{ Id: new("fake-snapshot"), Name: new("fake-snapshot"), @@ -753,14 +773,14 @@ var _ = Describe("ControllerServer test", Ordered, func() { // Backups are created from snapshots iaasClient.EXPECT().ListSnapshots(gomock.Any(), gomock.Any()).Return([]iaas.Snapshot{}, "", nil) - iaasClient.EXPECT().CreateSnapshot(gomock.Any(), "fake-snapshot", req.SourceVolumeId, properties).Return(expectedSnap, nil) + iaasClient.EXPECT().CreateSnapshot(gomock.Any(), gomock.Any()).Return(expectedSnap, nil) iaasClient.EXPECT().WaitSnapshotReady(gomock.Any(), "fake-snapshot").Return(expectedSnap.Status, nil) // Actually create the backup from the snapshot iaasClient.EXPECT().CreateBackup(gomock.Any(), "fake-snapshot", req.GetSourceVolumeId(), "fake-snapshot", gomock.Any()).Return(expectedBackup, nil) - iaasClient.EXPECT().WaitBackupReady(gomock.Any(), "fake-backup", *expectedSnap.Size, stackit.BackupMaxDurationSecondsPerGBDefault). + iaasClient.EXPECT().WaitBackupReady(gomock.Any(), "fake-backup", *expectedSnap.Size, stackitclient.BackupMaxDurationSecondsPerGBDefault). Return(new("AVAILABLE"), nil) - iaasClient.EXPECT().GetBackupByID(gomock.Any(), "fake-backup").Return(expectedBackup, nil) + iaasClient.EXPECT().GetBackup(gomock.Any(), "fake-backup").Return(expectedBackup, nil) // Remove the snapshot after the backup is created iaasClient.EXPECT().DeleteSnapshot(gomock.Any(), "fake-snapshot").Return(nil) @@ -780,8 +800,8 @@ var _ = Describe("ControllerServer test", Ordered, func() { } iaasClient.EXPECT().ListBackups(gomock.Any(), gomock.Any()).Return([]iaas.Backup{*expectedBackup}, nil) - iaasClient.EXPECT().WaitBackupReady(gomock.Any(), "fake-backup", int64(0), stackit.BackupMaxDurationSecondsPerGBDefault).Return(new("AVAILABLE"), nil) - iaasClient.EXPECT().GetBackupByID(gomock.Any(), "fake-backup").Return(expectedBackup, nil) + iaasClient.EXPECT().WaitBackupReady(gomock.Any(), "fake-backup", int64(0), stackitclient.BackupMaxDurationSecondsPerGBDefault).Return(new("AVAILABLE"), nil) + iaasClient.EXPECT().GetBackup(gomock.Any(), "fake-backup").Return(expectedBackup, nil) // Remove the snapshot after the backup is created iaasClient.EXPECT().DeleteSnapshot(gomock.Any(), *expectedBackup.SnapshotId).Return(nil) @@ -817,16 +837,14 @@ var _ = Describe("ControllerServer test", Ordered, func() { }) It("should honor custom wait time for backup creation", func() { req.Parameters = map[string]string{ - stackit.BackupMaxDurationPerGB: "120", - stackit.SnapshotType: "backup", + stackitclient.BackupMaxDurationPerGB: "120", + stackitclient.SnapshotType: "backup", } - customWaitTime, err := strconv.Atoi((req.Parameters)[stackit.BackupMaxDurationPerGB]) + customWaitTime, err := strconv.Atoi((req.Parameters)[stackitclient.BackupMaxDurationPerGB]) Expect(err).To(Not(HaveOccurred())) // TODO: Use once IaaS has extended the label regex to allow for forward slashes and dots - // properties := map[string]string{blockStorageCSIClusterIDKey: "cluster"} - properties := map[string]string{} expectedSnap := &iaas.Snapshot{ Id: new("fake-snapshot"), Name: new("fake-snapshot"), @@ -848,13 +866,13 @@ var _ = Describe("ControllerServer test", Ordered, func() { // Backups are created from snapshots iaasClient.EXPECT().ListSnapshots(gomock.Any(), gomock.Any()).Return([]iaas.Snapshot{}, "", nil) - iaasClient.EXPECT().CreateSnapshot(gomock.Any(), "fake-snapshot", req.SourceVolumeId, properties).Return(expectedSnap, nil) + iaasClient.EXPECT().CreateSnapshot(gomock.Any(), gomock.Any()).Return(expectedSnap, nil) iaasClient.EXPECT().WaitSnapshotReady(gomock.Any(), "fake-snapshot").Return(expectedSnap.Status, nil) // Actually create the backup from the snapshot iaasClient.EXPECT().CreateBackup(gomock.Any(), "fake-snapshot", req.GetSourceVolumeId(), "fake-snapshot", gomock.Any()).Return(expectedBackup, nil) iaasClient.EXPECT().WaitBackupReady(gomock.Any(), "fake-backup", *expectedSnap.Size, customWaitTime).Return(new("AVAILABLE"), nil) - iaasClient.EXPECT().GetBackupByID(gomock.Any(), "fake-backup").Return(expectedBackup, nil) + iaasClient.EXPECT().GetBackup(gomock.Any(), "fake-backup").Return(expectedBackup, nil) // Remove the snapshot after the backup is created iaasClient.EXPECT().DeleteSnapshot(gomock.Any(), "fake-snapshot").Return(nil) @@ -880,13 +898,9 @@ var _ = Describe("ControllerServer test", Ordered, func() { Size: new(int64(10)), CreatedAt: new(time.Now()), } - // TODO: Use once IaaS has extended the label regex to allow for forward slashes and dots - // properties := map[string]string{blockStorageCSIClusterIDKey: "cluster"} - properties := map[string]string{} - // TODO: Again filters are not implemented yet by the API iaasClient.EXPECT().ListSnapshots(gomock.Any(), gomock.Any()).Return([]iaas.Snapshot{}, "", nil) - iaasClient.EXPECT().CreateSnapshot(gomock.Any(), "fake-snapshot", req.SourceVolumeId, properties).Return(expectedSnap, nil) + iaasClient.EXPECT().CreateSnapshot(gomock.Any(), gomock.Any()).Return(expectedSnap, nil) iaasClient.EXPECT().WaitSnapshotReady(gomock.Any(), "fake-snapshot").Return(expectedSnap.Status, nil) _, err := fakeCs.CreateSnapshot(context.Background(), req) Expect(err).ToNot(HaveOccurred()) @@ -954,7 +968,7 @@ var _ = Describe("ControllerServer test", Ordered, func() { }, }, } - iaasClient.EXPECT().GetSnapshotByID(gomock.Any(), "special-snapshot").Return(&iaas.Snapshot{ + iaasClient.EXPECT().GetSnapshot(gomock.Any(), "special-snapshot").Return(&iaas.Snapshot{ Id: new("special-snapshot"), VolumeId: "fake", Size: new(int64(10)), diff --git a/pkg/csi/blockstorage/driver.go b/pkg/csi/blockstorage/driver.go index 56fd1818..db641777 100644 --- a/pkg/csi/blockstorage/driver.go +++ b/pkg/csi/blockstorage/driver.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/container-storage-interface/spec/lib/go/csi" - "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit" + stackitclient "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/client" stackitconfig "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/config" corev1 "k8s.io/client-go/listers/core/v1" "k8s.io/klog/v2" @@ -134,7 +134,7 @@ func (d *Driver) AddNodeServiceCapabilities(nl []csi.NodeServiceCapability_RPC_T return nil } -func (d *Driver) SetupControllerService(instance stackit.IaasClient) { +func (d *Driver) SetupControllerService(instance stackitclient.IaaSClient) { klog.Info("Providing controller service") d.cs = NewControllerServer(d, instance) } diff --git a/pkg/csi/blockstorage/sanity_test.go b/pkg/csi/blockstorage/sanity_test.go index b17b0b6e..fcd34efa 100644 --- a/pkg/csi/blockstorage/sanity_test.go +++ b/pkg/csi/blockstorage/sanity_test.go @@ -12,16 +12,16 @@ import ( "github.com/google/uuid" "github.com/kubernetes-csi/csi-test/v5/pkg/sanity" . "github.com/onsi/ginkgo/v2" - stackitconfig "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/config" - mountutils "k8s.io/mount-utils" - exec "k8s.io/utils/exec/testing" - "github.com/stackitcloud/cloud-provider-stackit/pkg/csi/util/mount" - "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit" + stackitclient "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/client" + stackitclientmock "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/client/mock" + stackitconfig "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/config" "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/metadata" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api" "go.uber.org/mock/gomock" + mountutils "k8s.io/mount-utils" + exec "k8s.io/utils/exec/testing" ) var _ = Describe("CSI sanity test", Ordered, func() { @@ -29,7 +29,7 @@ var _ = Describe("CSI sanity test", Ordered, func() { var ( driver *Driver opts *DriverOpts - iaasClient *stackit.MockIaasClient + iaasClient *stackitclientmock.MockIaaSClient mountMock *mount.MockIMount metadataMock *metadata.MockIMetadata FakeEndpoint string @@ -57,7 +57,7 @@ var _ = Describe("CSI sanity test", Ordered, func() { }) // --- Initialize Mocks --- - iaasClient = stackit.NewMockIaasClient(ctrl) + iaasClient = stackitclientmock.NewMockIaaSClient(ctrl) mountMock = mount.NewMockIMount(ctrl) metadataMock = metadata.NewMockIMetadata(ctrl) @@ -90,7 +90,7 @@ var _ = Describe("CSI sanity test", Ordered, func() { Id: new(uuid.New().String()), // Create a random ID Name: opts.Name, Size: size, - Status: new(stackit.VolumeAvailableStatus), + Status: new(stackitclient.VolumeAvailableStatus), AvailabilityZone: opts.AvailabilityZone, Source: opts.Source, } @@ -161,23 +161,21 @@ var _ = Describe("CSI sanity test", Ordered, func() { iaasClient.EXPECT().CreateSnapshot( gomock.Any(), // context - gomock.Any(), // name - gomock.Any(), // volID - gomock.Any(), // tags - ).DoAndReturn(func(_ context.Context, name string, volID string, _ map[string]string) (*iaas.Snapshot, error) { + gomock.Any(), // payload + ).DoAndReturn(func(_ context.Context, payload *iaas.CreateSnapshotPayload) (*iaas.Snapshot, error) { newSnap := &iaas.Snapshot{ Id: new(uuid.New().String()), - Name: new(name), - Status: new(stackit.SnapshotReadyStatus), + Name: payload.Name, + Status: new(stackitclient.SnapshotReadyStatus), CreatedAt: new(time.Now()), Size: new(int64(10)), // 10 GiB - VolumeId: volID, + VolumeId: payload.VolumeId, } createdSnapshots[*newSnap.Id] = newSnap return newSnap, nil }).AnyTimes() - iaasClient.EXPECT().GetSnapshotByID( + iaasClient.EXPECT().GetSnapshot( gomock.Any(), // context gomock.Any(), // snapshotID ).DoAndReturn(func(_ context.Context, snapshotID string) (*iaas.Snapshot, error) { @@ -249,7 +247,7 @@ var _ = Describe("CSI sanity test", Ordered, func() { gomock.Any(), // context gomock.Any(), // snapshotID ).Return( - new(string(stackit.SnapshotReadyStatus)), + new(stackitclient.SnapshotReadyStatus), nil, ).AnyTimes() @@ -274,7 +272,7 @@ var _ = Describe("CSI sanity test", Ordered, func() { return newBackup, nil }).AnyTimes() - iaasClient.EXPECT().GetBackupByID( + iaasClient.EXPECT().GetBackup( gomock.Any(), // context gomock.Any(), // backupID ).DoAndReturn(func(_ context.Context, backupID string) (*iaas.Backup, error) { @@ -306,7 +304,7 @@ var _ = Describe("CSI sanity test", Ordered, func() { // --- 4. Mock IaaS Client (Instances & Attach/Detach) --- - iaasClient.EXPECT().GetInstanceByID( + iaasClient.EXPECT().GetServer( gomock.Any(), // context gomock.Any(), // instanceID ).DoAndReturn(func(_ context.Context, instanceID string) (*iaas.Server, error) { @@ -324,7 +322,8 @@ var _ = Describe("CSI sanity test", Ordered, func() { gomock.Any(), // context gomock.Any(), // instanceID gomock.Any(), // volumeID - ).DoAndReturn(func(_ context.Context, instanceID string, volumeID string) (string, error) { + gomock.Any(), // payload + ).DoAndReturn(func(_ context.Context, instanceID string, volumeID string, _ iaas.AddVolumeToServerPayload) (string, error) { vol, ok := createdVolumes[volumeID] if !ok { return "", &oapierror.GenericOpenAPIError{StatusCode: http.StatusNotFound} @@ -458,4 +457,4 @@ var _ = Describe("CSI sanity test", Ordered, func() { }) }) -}) +}) \ No newline at end of file diff --git a/pkg/csi/blockstorage/utils.go b/pkg/csi/blockstorage/utils.go index 8fc02143..cafa546e 100644 --- a/pkg/csi/blockstorage/utils.go +++ b/pkg/csi/blockstorage/utils.go @@ -7,7 +7,7 @@ import ( "sync/atomic" "github.com/stackitcloud/cloud-provider-stackit/pkg/csi/util/mount" - "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit" + stackitclient "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/client" stackitconfig "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/config" "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/metadata" @@ -44,7 +44,7 @@ func NewVolumeCapabilityAccessMode(mode csi.VolumeCapability_AccessMode_Mode) *c } //revive:disable:unexported-return -func NewControllerServer(d *Driver, instance stackit.IaasClient) *controllerServer { +func NewControllerServer(d *Driver, instance stackitclient.IaaSClient) *controllerServer { return &controllerServer{ Driver: d, Instance: instance, diff --git a/pkg/stackit/client/factory.go b/pkg/stackit/client/factory.go index 70263c58..770aeb59 100644 --- a/pkg/stackit/client/factory.go +++ b/pkg/stackit/client/factory.go @@ -1,11 +1,33 @@ package client -import sdkconfig "github.com/stackitcloud/stackit-sdk-go/core/config" +import ( + "io" + "os" + + "github.com/spf13/pflag" + stackitconfig "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/config" + sdkconfig "github.com/stackitcloud/stackit-sdk-go/core/config" + "gopkg.in/yaml.v3" + "k8s.io/klog/v2" +) + +func AddExtraFlags(fs *pflag.FlagSet) { + fs.StringArrayVar(&userAgentData, "user-agent", nil, "Extra data to add to STACKIT SDK user-agent. Use multiple times to add more than one component.") +} + +const ( + UserAgent = "cloud-provider-stackit" +) + +// userAgentData is used to add extra information to the STACKIT SDK user-agent +var ( + userAgentData []string +) // Factory produces clients for various STACKIT services. type Factory interface { // LoadBalancing returns a STACKIT load balancing service client. - LoadBalancing(options []sdkconfig.ConfigurationOption) (LoadBalancingClient, error) + LoadBalancing(ptions []sdkconfig.ConfigurationOption) (LoadBalancingClient, error) // IaaS returns a STACKIT IaaS service client. IaaS(options []sdkconfig.ConfigurationOption) (IaaSClient, error) @@ -30,3 +52,34 @@ func (f factory) LoadBalancing(options []sdkconfig.ConfigurationOption) (LoadBal func (f factory) IaaS(options []sdkconfig.ConfigurationOption) (IaaSClient, error) { return NewIaaSClient(f.StackitRegion, f.StackitProjectID, options) } + +func GetConfigFromFile(path string) (stackitconfig.CSIConfig, error) { + var cfg stackitconfig.CSIConfig + + config, err := os.Open(path) + if err != nil { + klog.ErrorS(err, "Failed to open stackitconfig file", "path", path) + return cfg, err + } + defer config.Close() + + return GetConfig(config) +} + +func GetConfig(reader io.Reader) (stackitconfig.CSIConfig, error) { + var cfg stackitconfig.CSIConfig + + content, err := io.ReadAll(reader) + if err != nil { + klog.ErrorS(err, "Failed to read config content") + return cfg, err + } + + err = yaml.Unmarshal(content, &cfg) + if err != nil { + klog.ErrorS(err, "Failed to parse config as YAML") + return cfg, err + } + + return cfg, nil +} \ No newline at end of file diff --git a/pkg/stackit/client/iaas.go b/pkg/stackit/client/iaas.go index 9cdcaf54..f3756493 100644 --- a/pkg/stackit/client/iaas.go +++ b/pkg/stackit/client/iaas.go @@ -2,9 +2,17 @@ package client import ( "context" + "errors" + "fmt" + "slices" + "time" + "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/stackiterrors" sdkconfig "github.com/stackitcloud/stackit-sdk-go/core/config" iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/klog/v2" + "k8s.io/utils/ptr" ) type iaasClient struct { @@ -16,8 +24,77 @@ type iaasClient struct { type IaaSClient interface { GetServer(ctx context.Context, serverID string) (*iaas.Server, error) ListServers(ctx context.Context) (*[]iaas.Server, error) + + CreateSnapshot(ctx context.Context, payload *iaas.CreateSnapshotPayload) (*iaas.Snapshot, error) + ListSnapshots(ctx context.Context, filters map[string]string) ([]iaas.Snapshot, string, error) + DeleteSnapshot(ctx context.Context, snapshotID string) error + GetSnapshot(ctx context.Context, snapshotID string) (*iaas.Snapshot, error) + WaitSnapshotReady(ctx context.Context, snapshotID string) (*string, error) + + CreateBackup(ctx context.Context, name, volID, snapshotID string, tags map[string]string) (*iaas.Backup, error) + ListBackups(ctx context.Context, filters map[string]string) ([]iaas.Backup, error) + DeleteBackup(ctx context.Context, backupID string) error + GetBackup(ctx context.Context, backupID string) (*iaas.Backup, error) + WaitBackupReady(ctx context.Context, backupID string, snapshotSize int64, backupMaxDurationSecondsPerGB int) (*string, error) + + CreateVolume(ctx context.Context, payload *iaas.CreateVolumePayload) (*iaas.Volume, error) + DeleteVolume(ctx context.Context, volumeID string) error + AttachVolume(ctx context.Context, serverID, volumeID string, payload iaas.AddVolumeToServerPayload) (string, error) + DetachVolume(ctx context.Context, serverID, volumeID string) error + GetVolume(ctx context.Context, volumeID string) (*iaas.Volume, error) + GetVolumesByName(ctx context.Context, volName string) ([]iaas.Volume, error) + ListVolumes(ctx context.Context, _ int, _ string) ([]iaas.Volume, string, error) + ExpandVolume(ctx context.Context, volumeID, volumeStatus string, payload iaas.ResizeVolumePayload) error + WaitVolumeTargetStatus(ctx context.Context, volumeID string, tStatus []string) error + WaitDiskAttached(ctx context.Context, instanceID, volumeID string) error + WaitDiskDetached(ctx context.Context, instanceID, volumeID string) error + WaitVolumeTargetStatusWithCustomBackoff(ctx context.Context, volumeID string, tStatus []string, backoff *wait.Backoff) error } +const ( + VolumeAvailableStatus = "AVAILABLE" + VolumeAttachedStatus = "ATTACHED" + operationFinishInitDelay = 1 * time.Second + operationFinishFactor = 1.1 + operationFinishSteps = 10 + diskAttachInitDelay = 1 * time.Second + diskAttachFactor = 1.2 + diskAttachSteps = 15 + diskDetachInitDelay = 1 * time.Second + diskDetachFactor = 1.2 + diskDetachSteps = 13 + VolumeDescription = "Created by STACKIT CSI driver" +) + +const ( + BackupDescription = "Created by STACKIT CSI driver" + backupReadyStatus = "AVAILABLE" + backupErrorStatus = "error" + BackupMaxDurationSecondsPerGBDefault = 20 + BackupMaxDurationPerGB = "backup-max-duration-seconds-per-gb" + backupBaseDurationSeconds = 30 + backupReadyCheckIntervalSeconds = 7 +) + +const ( + SnapshotReadyStatus = "AVAILABLE" + snapReadyDuration = 1 * time.Second + snapReadyFactor = 1.2 + snapReadySteps = 10 + + SnapshotType = "type" +) + +type VolumeSourceTypes string + +const ( + VolumeSource VolumeSourceTypes = "volume" + SnapshotSource VolumeSourceTypes = "snapshot" + BackupSource VolumeSourceTypes = "backup" +) + +var volumeErrorStates = [...]string{"ERROR", "ERROR_RESIZING", "ERROR_DELETING"} + func NewIaaSClient(region, projectID string, options []sdkconfig.ConfigurationOption) (IaaSClient, error) { apiClient, err := iaas.NewAPIClient(options...) if err != nil { @@ -46,3 +123,469 @@ func (i *iaasClient) ListServers(ctx context.Context) (*[]iaas.Server, error) { return &resp.Items, nil }) } + +//nolint:dupl // SDK request execution and response-ID wrapping pattern intentionally repeated for typed API methods. +func (i *iaasClient) CreateSnapshot(ctx context.Context, payload *iaas.CreateSnapshotPayload) (*iaas.Snapshot, error) { + return withResponseID(ctx, func(ctx context.Context) (*iaas.Snapshot, error) { + return i.Client. + CreateSnapshot(ctx, i.projectID, i.region). + CreateSnapshotPayload(*payload). + Execute() + }) +} + +func (i *iaasClient) ListSnapshots(ctx context.Context, filters map[string]string) ([]iaas.Snapshot, string, error) { + resp, err := withResponseID(ctx, func(ctx context.Context) (*iaas.SnapshotListResponse, error) { + return i.Client.ListSnapshotsInProject(ctx, i.projectID, i.region).Execute() + }) + if err != nil { + return nil, "", err + } + + filteredSnapshots := FilterSnapshots(resp.Items, filters) + + return filteredSnapshots, "", nil +} + +func (i *iaasClient) DeleteSnapshot(ctx context.Context, snapshotID string) error { + _, err := withResponseID(ctx, func(ctx context.Context) (any, error) { + return nil, i.Client.DeleteSnapshot(ctx, i.projectID, i.region, snapshotID).Execute() + }) + return err +} + +func (i *iaasClient) GetSnapshot(ctx context.Context, snapshotID string) (*iaas.Snapshot, error) { + return withResponseID(ctx, func(ctx context.Context) (*iaas.Snapshot, error) { + return i.Client.GetSnapshot(ctx, i.projectID, i.region, snapshotID).Execute() + }) +} + +func (i *iaasClient) WaitSnapshotReady(ctx context.Context, snapshotID string) (*string, error) { + backoff := wait.Backoff{ + Duration: snapReadyDuration, + Factor: snapReadyFactor, + Steps: snapReadySteps, + } + + err := wait.ExponentialBackoff(backoff, func() (bool, error) { + ready, err := i.snapshotIsReady(ctx, snapshotID) + if err != nil { + return false, err + } + + return ready, nil + }) + + if wait.Interrupted(err) { + err = fmt.Errorf("timeout, Snapshot %s is still not Ready %v", snapshotID, err) + } + + snap, _ := i.GetSnapshot(ctx, snapshotID) + + if snap != nil { + return snap.Status, err + } + + return new("Failed to get Snapshot status"), err +} + +func (i *iaasClient) snapshotIsReady(ctx context.Context, snapshotID string) (bool, error) { + snapshot, err := withResponseID(ctx, func(ctx context.Context) (*iaas.Snapshot, error) { + return i.Client.GetSnapshot(ctx, i.projectID, i.region, snapshotID).Execute() + }) + if err != nil { + return false, err + } + + return *snapshot.Status == SnapshotReadyStatus, nil +} + +func (i *iaasClient) CreateBackup(ctx context.Context, name, volID, snapshotID string, tags map[string]string) (*iaas.Backup, error) { + payload, err := BuildCreateBackupPayload(name, volID, snapshotID, tags) + if err != nil { + return nil, err + } + + return withResponseID(ctx, func(ctx context.Context) (*iaas.Backup, error) { + return i.Client. + CreateBackup(ctx, i.projectID, i.region). + CreateBackupPayload(payload). + Execute() + }) +} + +func BuildCreateBackupPayload(name, volID, snapshotID string, tags map[string]string) (iaas.CreateBackupPayload, error) { + if name == "" { + return iaas.CreateBackupPayload{}, errors.New("backup name cannot be empty") + } + + if volID == "" && snapshotID == "" { + return iaas.CreateBackupPayload{}, errors.New("either volID or snapshotID must be provided") + } + + var backupSource VolumeSourceTypes + var backupSourceID string + if volID != "" { + backupSource = VolumeSource + backupSourceID = volID + } + if snapshotID != "" { + backupSource = SnapshotSource + backupSourceID = snapshotID + } + + opts := iaas.CreateBackupPayload{ + Name: new(name), + Description: new(BackupDescription), + Source: iaas.BackupSource{ + Type: string(backupSource), + Id: backupSourceID, + }, + } + if tags != nil { + opts.Labels = LabelsFromTags(tags) + } + + return opts, nil +} + +func (i *iaasClient) ListBackups(ctx context.Context, filters map[string]string) ([]iaas.Backup, error) { + resp, err := withResponseID(ctx, func(ctx context.Context) (*iaas.BackupListResponse, error) { + return i.Client.ListBackups(ctx, i.projectID, i.region).Execute() + }) + if err != nil { + return nil, err + } + + filteredBackups := FilterBackups(resp.Items, filters) + + return filteredBackups, nil +} + +func (i *iaasClient) DeleteBackup(ctx context.Context, backupID string) error { + _, err := withResponseID(ctx, func(ctx context.Context) (any, error) { + return nil, i.Client.DeleteBackup(ctx, i.projectID, i.region, backupID).Execute() + }) + return err +} + +func (i *iaasClient) GetBackup(ctx context.Context, backupID string) (*iaas.Backup, error) { + return withResponseID(ctx, func(ctx context.Context) (*iaas.Backup, error) { + return i.Client.GetBackup(ctx, i.projectID, i.region, backupID).Execute() + }) +} + +func (i *iaasClient) WaitBackupReady(ctx context.Context, backupID string, snapshotSize int64, backupMaxDurationSecondsPerGB int) (*string, error) { + duration := time.Duration(int64(backupMaxDurationSecondsPerGB)*snapshotSize + backupBaseDurationSeconds) + err := i.waitBackupReadyWithContext(backupID, duration) + if err != nil { + if errors.Is(err, context.DeadlineExceeded) { + return nil, fmt.Errorf("timeout, Backup %s is still not Ready: %w", backupID, err) + } + return nil, err + } + + backup, err := i.GetBackup(ctx, backupID) + if err != nil { + return nil, err + } + + if backup != nil { + return backup.Status, nil + } + + return new("Failed to get backup status"), nil +} + +func (i *iaasClient) waitBackupReadyWithContext(backupID string, duration time.Duration) error { + ctx, cancel := context.WithTimeout(context.Background(), duration*time.Second) + defer cancel() + var done bool + var err error + ticker := time.NewTicker(backupReadyCheckIntervalSeconds * time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + done, err = i.backupIsReady(ctx, backupID) + if err != nil { + return err + } + + if done { + return nil + } + case <-ctx.Done(): + return ctx.Err() + } + } +} + +// Supporting function for waitBackupReadyWithContext(). +// Returns true when the backup is ready. +func (i *iaasClient) backupIsReady(ctx context.Context, backupID string) (bool, error) { + backup, err := i.GetBackup(ctx, backupID) + if err != nil { + return false, err + } + + if *backup.Status == backupErrorStatus { + return false, errors.New("backup is in error state") + } + + return *backup.Status == backupReadyStatus, nil +} + +func (i *iaasClient) CreateVolume(ctx context.Context, payload *iaas.CreateVolumePayload) (*iaas.Volume, error) { + payload.Description = new(VolumeDescription) + + return withResponseID(ctx, func(ctx context.Context) (*iaas.Volume, error) { + return i.Client.CreateVolume(ctx, i.projectID, i.region).CreateVolumePayload(*payload).Execute() + }) +} + +func (i *iaasClient) DeleteVolume(ctx context.Context, volumeID string) error { + used, err := i.diskIsUsed(ctx, volumeID) + if err != nil { + return err + } + if used { + return fmt.Errorf("cannot delete the volume %q, it's still attached to a node", volumeID) + } + + _, err = withResponseID(ctx, func(ctx context.Context) (any, error) { + return nil, i.Client.DeleteVolume(ctx, i.projectID, i.region, volumeID).Execute() + }) + return err +} + +func (i *iaasClient) AttachVolume(ctx context.Context, serverID, volumeID string, payload iaas.AddVolumeToServerPayload) (string, error) { + volume, err := i.GetVolume(ctx, volumeID) + if err != nil { + return "", err + } + + if volume.ServerId != nil && serverID == *volume.ServerId { + klog.V(4).Infof("Disk %s is already attached to instance %s", volumeID, serverID) + return *volume.Id, nil + } + + _, err = withResponseID(ctx, func(ctx context.Context) (any, error) { + return i.Client. + AddVolumeToServer(ctx, i.projectID, i.region, serverID, volumeID). + AddVolumeToServerPayload(payload). + Execute() + }) + if err != nil { + return "", err + } + + return volume.GetId(), nil +} + +func (i *iaasClient) GetVolume(ctx context.Context, volumeID string) (*iaas.Volume, error) { + return withResponseID(ctx, func(ctx context.Context) (*iaas.Volume, error) { + return i.Client.GetVolume(ctx, i.projectID, i.region, volumeID).Execute() + }) +} + +func (i *iaasClient) GetVolumesByName(ctx context.Context, volName string) ([]iaas.Volume, error) { + resp, err := withResponseID(ctx, func(ctx context.Context) (*iaas.VolumeListResponse, error) { + return i.Client.ListVolumes(ctx, i.projectID, i.region).Execute() + }) + if err != nil { + return nil, err + } + + filterMap := map[string]string{"Name": volName} + filteredVolumes := FilterVolumes(resp.Items, filterMap) + + return filteredVolumes, nil +} + +func (i *iaasClient) ListVolumes(ctx context.Context, _ int, _ string) ([]iaas.Volume, string, error) { + // TODO: Add support for pagination when IaaS adds it + resp, err := withResponseID(ctx, func(ctx context.Context) (*iaas.VolumeListResponse, error) { + return i.Client.ListVolumes(ctx, i.projectID, i.region).Execute() + }) + if err != nil { + return nil, "", err + } + + return resp.Items, "", nil +} + +func (i *iaasClient) ExpandVolume(ctx context.Context, volumeID, volumeStatus string, payload iaas.ResizeVolumePayload) error { + switch volumeStatus { + case VolumeAttachedStatus, VolumeAvailableStatus: + _, err := withResponseID(ctx, func(ctx context.Context) (any, error) { + return nil, i.Client. + ResizeVolume(ctx, i.projectID, i.region, volumeID). + ResizeVolumePayload(payload). + Execute() + }) + return err + + default: + return fmt.Errorf("volume cannot be resized, when status is %s", volumeStatus) + } +} + +func (i *iaasClient) WaitVolumeTargetStatus(ctx context.Context, volumeID string, tStatus []string) error { + backoff := wait.Backoff{ + Duration: operationFinishInitDelay, + Factor: operationFinishFactor, + Steps: operationFinishSteps, + } + + waitErr := wait.ExponentialBackoff(backoff, func() (bool, error) { + vol, err := i.GetVolume(ctx, volumeID) + if err != nil { + return false, err + } + if slices.Contains(tStatus, *vol.Status) { + return true, nil + } + for _, eState := range volumeErrorStates { + if *vol.Status == eState { + return false, fmt.Errorf("volume is in Error State : %s", ptr.Deref(vol.Status, "")) + } + } + return false, nil + }) + + if wait.Interrupted(waitErr) { + waitErr = fmt.Errorf("timeout on waiting for volume %s status to be in %v", volumeID, tStatus) + } + + return waitErr +} + +func (i *iaasClient) WaitDiskAttached(ctx context.Context, instanceID, volumeID string) error { + backoff := wait.Backoff{ + Duration: diskAttachInitDelay, + Factor: diskAttachFactor, + Steps: diskAttachSteps, + } + + err := wait.ExponentialBackoff(backoff, func() (bool, error) { + attached, err := i.diskIsAttached(ctx, instanceID, volumeID) + if err != nil && !stackiterrors.IsNotFound(err) { + // if this is a race condition indicate the volume is deleted + // during sleep phase, ignore the error and return attach=false + return false, err + } + return attached, nil + }) + + if wait.Interrupted(err) { + err = fmt.Errorf("volume %q failed to be attached within the allowed time", volumeID) + } + + return err +} + +func (i *iaasClient) WaitDiskDetached(ctx context.Context, instanceID, volumeID string) error { + backoff := wait.Backoff{ + Duration: diskDetachInitDelay, + Factor: diskDetachFactor, + Steps: diskDetachSteps, + } + + err := wait.ExponentialBackoff(backoff, func() (bool, error) { + attached, err := i.diskIsAttached(ctx, instanceID, volumeID) + if err != nil { + return false, err + } + return !attached, nil + }) + + if wait.Interrupted(err) { + err = fmt.Errorf("volume %q failed to detach within the allowed time", volumeID) + } + + return err +} + +func (i *iaasClient) DetachVolume(ctx context.Context, serverID, volumeID string) error { + volume, err := i.GetVolume(ctx, volumeID) + if err != nil { + return err + } + + if *volume.Status == VolumeAvailableStatus { + klog.V(2).Infof("Volume: %s has been detached from compute: %s ", *volume.Id, serverID) + return nil + } + + if *volume.Status != VolumeAttachedStatus { + return fmt.Errorf("can not detach volume %s, its status is %s", *volume.Name, *volume.Status) + } + + if volume.ServerId != nil && *volume.ServerId == serverID { + _, err := withResponseID(ctx, func(ctx context.Context) (any, error) { + err := i.Client.RemoveVolumeFromServer(ctx, i.projectID, i.region, serverID, volumeID).Execute() + if err != nil { + return nil, fmt.Errorf("failed to detach volume %s from compute %s : %w", *volume.Name, serverID, err) + } + return nil, nil + }) + if err != nil { + return err + } + + klog.V(2).Infof("Successfully detached volume: %s from compute: %s", *volume.Id, serverID) + } + + return nil +} + +func (i *iaasClient) WaitVolumeTargetStatusWithCustomBackoff(ctx context.Context, volumeID string, tStatus []string, backoff *wait.Backoff) error { + waitErr := wait.ExponentialBackoff(*backoff, func() (bool, error) { + vol, err := i.GetVolume(ctx, volumeID) + if err != nil { + return false, err + } + if slices.Contains(tStatus, *vol.Status) { + return true, nil + } + for _, eState := range volumeErrorStates { + if *vol.Status == eState { + return false, fmt.Errorf("volume is in error state: %s", *vol.Status) + } + } + return false, nil + }) + + if wait.Interrupted(waitErr) { + waitErr = fmt.Errorf("timeout on waiting for volume %s status to be in %v", volumeID, tStatus) + } + + return waitErr +} + +// diskIsAttached queries if a volume is attached to a compute instance +func (i *iaasClient) diskIsAttached(ctx context.Context, instanceID, volumeID string) (bool, error) { + volume, err := i.GetVolume(ctx, volumeID) + if err != nil { + return false, err + } + + if volume.ServerId != nil && *volume.ServerId == instanceID { + return true, nil + } + return false, nil +} + +// diskIsUsed returns true whether a disk is attached to any node +func (i *iaasClient) diskIsUsed(ctx context.Context, volumeID string) (bool, error) { + volume, err := i.GetVolume(ctx, volumeID) + if err != nil { + return false, err + } + + diskUsed := volume.ServerId != nil && *volume.ServerId != "" + + return diskUsed, nil +} diff --git a/pkg/stackit/client/labels.go b/pkg/stackit/client/labels.go new file mode 100644 index 00000000..011ab993 --- /dev/null +++ b/pkg/stackit/client/labels.go @@ -0,0 +1,13 @@ +package client + +func LabelsFromTags(tags map[string]string) map[string]any { + // Create a new map of type map[string]any + l := make(map[string]any, len(tags)) + + // Convert each value from string to any + for key, value := range tags { + l[key] = value + } + + return l +} \ No newline at end of file diff --git a/pkg/stackit/client/mock/iaas_mock.go b/pkg/stackit/client/mock/iaas_mock.go index 029dc7d8..eb130e35 100644 --- a/pkg/stackit/client/mock/iaas_mock.go +++ b/pkg/stackit/client/mock/iaas_mock.go @@ -15,6 +15,7 @@ import ( v2api "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api" gomock "go.uber.org/mock/gomock" + wait "k8s.io/apimachinery/pkg/util/wait" ) // MockIaaSClient is a mock of IaaSClient interface. @@ -41,41 +42,582 @@ func (m *MockIaaSClient) EXPECT() *MockIaaSClientMockRecorder { return m.recorder } +// AttachVolume mocks base method. +func (m *MockIaaSClient) AttachVolume(ctx context.Context, serverID, volumeID string, payload v2api.AddVolumeToServerPayload) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AttachVolume", ctx, serverID, volumeID, payload) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AttachVolume indicates an expected call of AttachVolume. +func (mr *MockIaaSClientMockRecorder) AttachVolume(ctx, serverID, volumeID, payload any) *MockIaaSClientAttachVolumeCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AttachVolume", reflect.TypeOf((*MockIaaSClient)(nil).AttachVolume), ctx, serverID, volumeID, payload) + return &MockIaaSClientAttachVolumeCall{Call: call} +} + +// MockIaaSClientAttachVolumeCall wrap *gomock.Call +type MockIaaSClientAttachVolumeCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockIaaSClientAttachVolumeCall) Return(arg0 string, arg1 error) *MockIaaSClientAttachVolumeCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockIaaSClientAttachVolumeCall) Do(f func(context.Context, string, string, v2api.AddVolumeToServerPayload) (string, error)) *MockIaaSClientAttachVolumeCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockIaaSClientAttachVolumeCall) DoAndReturn(f func(context.Context, string, string, v2api.AddVolumeToServerPayload) (string, error)) *MockIaaSClientAttachVolumeCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// CreateBackup mocks base method. +func (m *MockIaaSClient) CreateBackup(ctx context.Context, name, volID, snapshotID string, tags map[string]string) (*v2api.Backup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateBackup", ctx, name, volID, snapshotID, tags) + ret0, _ := ret[0].(*v2api.Backup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateBackup indicates an expected call of CreateBackup. +func (mr *MockIaaSClientMockRecorder) CreateBackup(ctx, name, volID, snapshotID, tags any) *MockIaaSClientCreateBackupCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBackup", reflect.TypeOf((*MockIaaSClient)(nil).CreateBackup), ctx, name, volID, snapshotID, tags) + return &MockIaaSClientCreateBackupCall{Call: call} +} + +// MockIaaSClientCreateBackupCall wrap *gomock.Call +type MockIaaSClientCreateBackupCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockIaaSClientCreateBackupCall) Return(arg0 *v2api.Backup, arg1 error) *MockIaaSClientCreateBackupCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockIaaSClientCreateBackupCall) Do(f func(context.Context, string, string, string, map[string]string) (*v2api.Backup, error)) *MockIaaSClientCreateBackupCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockIaaSClientCreateBackupCall) DoAndReturn(f func(context.Context, string, string, string, map[string]string) (*v2api.Backup, error)) *MockIaaSClientCreateBackupCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// CreateSnapshot mocks base method. +func (m *MockIaaSClient) CreateSnapshot(ctx context.Context, payload *v2api.CreateSnapshotPayload) (*v2api.Snapshot, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateSnapshot", ctx, payload) + ret0, _ := ret[0].(*v2api.Snapshot) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateSnapshot indicates an expected call of CreateSnapshot. +func (mr *MockIaaSClientMockRecorder) CreateSnapshot(ctx, payload any) *MockIaaSClientCreateSnapshotCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSnapshot", reflect.TypeOf((*MockIaaSClient)(nil).CreateSnapshot), ctx, payload) + return &MockIaaSClientCreateSnapshotCall{Call: call} +} + +// MockIaaSClientCreateSnapshotCall wrap *gomock.Call +type MockIaaSClientCreateSnapshotCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockIaaSClientCreateSnapshotCall) Return(arg0 *v2api.Snapshot, arg1 error) *MockIaaSClientCreateSnapshotCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockIaaSClientCreateSnapshotCall) Do(f func(context.Context, *v2api.CreateSnapshotPayload) (*v2api.Snapshot, error)) *MockIaaSClientCreateSnapshotCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockIaaSClientCreateSnapshotCall) DoAndReturn(f func(context.Context, *v2api.CreateSnapshotPayload) (*v2api.Snapshot, error)) *MockIaaSClientCreateSnapshotCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// CreateVolume mocks base method. +func (m *MockIaaSClient) CreateVolume(ctx context.Context, payload *v2api.CreateVolumePayload) (*v2api.Volume, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateVolume", ctx, payload) + ret0, _ := ret[0].(*v2api.Volume) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateVolume indicates an expected call of CreateVolume. +func (mr *MockIaaSClientMockRecorder) CreateVolume(ctx, payload any) *MockIaaSClientCreateVolumeCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateVolume", reflect.TypeOf((*MockIaaSClient)(nil).CreateVolume), ctx, payload) + return &MockIaaSClientCreateVolumeCall{Call: call} +} + +// MockIaaSClientCreateVolumeCall wrap *gomock.Call +type MockIaaSClientCreateVolumeCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockIaaSClientCreateVolumeCall) Return(arg0 *v2api.Volume, arg1 error) *MockIaaSClientCreateVolumeCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockIaaSClientCreateVolumeCall) Do(f func(context.Context, *v2api.CreateVolumePayload) (*v2api.Volume, error)) *MockIaaSClientCreateVolumeCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockIaaSClientCreateVolumeCall) DoAndReturn(f func(context.Context, *v2api.CreateVolumePayload) (*v2api.Volume, error)) *MockIaaSClientCreateVolumeCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// DeleteBackup mocks base method. +func (m *MockIaaSClient) DeleteBackup(ctx context.Context, backupID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteBackup", ctx, backupID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteBackup indicates an expected call of DeleteBackup. +func (mr *MockIaaSClientMockRecorder) DeleteBackup(ctx, backupID any) *MockIaaSClientDeleteBackupCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBackup", reflect.TypeOf((*MockIaaSClient)(nil).DeleteBackup), ctx, backupID) + return &MockIaaSClientDeleteBackupCall{Call: call} +} + +// MockIaaSClientDeleteBackupCall wrap *gomock.Call +type MockIaaSClientDeleteBackupCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockIaaSClientDeleteBackupCall) Return(arg0 error) *MockIaaSClientDeleteBackupCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockIaaSClientDeleteBackupCall) Do(f func(context.Context, string) error) *MockIaaSClientDeleteBackupCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockIaaSClientDeleteBackupCall) DoAndReturn(f func(context.Context, string) error) *MockIaaSClientDeleteBackupCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// DeleteSnapshot mocks base method. +func (m *MockIaaSClient) DeleteSnapshot(ctx context.Context, snapshotID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteSnapshot", ctx, snapshotID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteSnapshot indicates an expected call of DeleteSnapshot. +func (mr *MockIaaSClientMockRecorder) DeleteSnapshot(ctx, snapshotID any) *MockIaaSClientDeleteSnapshotCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSnapshot", reflect.TypeOf((*MockIaaSClient)(nil).DeleteSnapshot), ctx, snapshotID) + return &MockIaaSClientDeleteSnapshotCall{Call: call} +} + +// MockIaaSClientDeleteSnapshotCall wrap *gomock.Call +type MockIaaSClientDeleteSnapshotCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockIaaSClientDeleteSnapshotCall) Return(arg0 error) *MockIaaSClientDeleteSnapshotCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockIaaSClientDeleteSnapshotCall) Do(f func(context.Context, string) error) *MockIaaSClientDeleteSnapshotCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockIaaSClientDeleteSnapshotCall) DoAndReturn(f func(context.Context, string) error) *MockIaaSClientDeleteSnapshotCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// DeleteVolume mocks base method. +func (m *MockIaaSClient) DeleteVolume(ctx context.Context, volumeID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteVolume", ctx, volumeID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteVolume indicates an expected call of DeleteVolume. +func (mr *MockIaaSClientMockRecorder) DeleteVolume(ctx, volumeID any) *MockIaaSClientDeleteVolumeCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteVolume", reflect.TypeOf((*MockIaaSClient)(nil).DeleteVolume), ctx, volumeID) + return &MockIaaSClientDeleteVolumeCall{Call: call} +} + +// MockIaaSClientDeleteVolumeCall wrap *gomock.Call +type MockIaaSClientDeleteVolumeCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockIaaSClientDeleteVolumeCall) Return(arg0 error) *MockIaaSClientDeleteVolumeCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockIaaSClientDeleteVolumeCall) Do(f func(context.Context, string) error) *MockIaaSClientDeleteVolumeCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockIaaSClientDeleteVolumeCall) DoAndReturn(f func(context.Context, string) error) *MockIaaSClientDeleteVolumeCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// DetachVolume mocks base method. +func (m *MockIaaSClient) DetachVolume(ctx context.Context, serverID, volumeID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DetachVolume", ctx, serverID, volumeID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DetachVolume indicates an expected call of DetachVolume. +func (mr *MockIaaSClientMockRecorder) DetachVolume(ctx, serverID, volumeID any) *MockIaaSClientDetachVolumeCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DetachVolume", reflect.TypeOf((*MockIaaSClient)(nil).DetachVolume), ctx, serverID, volumeID) + return &MockIaaSClientDetachVolumeCall{Call: call} +} + +// MockIaaSClientDetachVolumeCall wrap *gomock.Call +type MockIaaSClientDetachVolumeCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockIaaSClientDetachVolumeCall) Return(arg0 error) *MockIaaSClientDetachVolumeCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockIaaSClientDetachVolumeCall) Do(f func(context.Context, string, string) error) *MockIaaSClientDetachVolumeCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockIaaSClientDetachVolumeCall) DoAndReturn(f func(context.Context, string, string) error) *MockIaaSClientDetachVolumeCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// ExpandVolume mocks base method. +func (m *MockIaaSClient) ExpandVolume(ctx context.Context, volumeID, volumeStatus string, payload v2api.ResizeVolumePayload) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExpandVolume", ctx, volumeID, volumeStatus, payload) + ret0, _ := ret[0].(error) + return ret0 +} + +// ExpandVolume indicates an expected call of ExpandVolume. +func (mr *MockIaaSClientMockRecorder) ExpandVolume(ctx, volumeID, volumeStatus, payload any) *MockIaaSClientExpandVolumeCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExpandVolume", reflect.TypeOf((*MockIaaSClient)(nil).ExpandVolume), ctx, volumeID, volumeStatus, payload) + return &MockIaaSClientExpandVolumeCall{Call: call} +} + +// MockIaaSClientExpandVolumeCall wrap *gomock.Call +type MockIaaSClientExpandVolumeCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockIaaSClientExpandVolumeCall) Return(arg0 error) *MockIaaSClientExpandVolumeCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockIaaSClientExpandVolumeCall) Do(f func(context.Context, string, string, v2api.ResizeVolumePayload) error) *MockIaaSClientExpandVolumeCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockIaaSClientExpandVolumeCall) DoAndReturn(f func(context.Context, string, string, v2api.ResizeVolumePayload) error) *MockIaaSClientExpandVolumeCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// GetBackup mocks base method. +func (m *MockIaaSClient) GetBackup(ctx context.Context, backupID string) (*v2api.Backup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBackup", ctx, backupID) + ret0, _ := ret[0].(*v2api.Backup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBackup indicates an expected call of GetBackup. +func (mr *MockIaaSClientMockRecorder) GetBackup(ctx, backupID any) *MockIaaSClientGetBackupCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBackup", reflect.TypeOf((*MockIaaSClient)(nil).GetBackup), ctx, backupID) + return &MockIaaSClientGetBackupCall{Call: call} +} + +// MockIaaSClientGetBackupCall wrap *gomock.Call +type MockIaaSClientGetBackupCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockIaaSClientGetBackupCall) Return(arg0 *v2api.Backup, arg1 error) *MockIaaSClientGetBackupCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockIaaSClientGetBackupCall) Do(f func(context.Context, string) (*v2api.Backup, error)) *MockIaaSClientGetBackupCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockIaaSClientGetBackupCall) DoAndReturn(f func(context.Context, string) (*v2api.Backup, error)) *MockIaaSClientGetBackupCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // GetServer mocks base method. func (m *MockIaaSClient) GetServer(ctx context.Context, serverID string) (*v2api.Server, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetServer", ctx, serverID) - ret0, _ := ret[0].(*v2api.Server) + ret := m.ctrl.Call(m, "GetServer", ctx, serverID) + ret0, _ := ret[0].(*v2api.Server) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetServer indicates an expected call of GetServer. +func (mr *MockIaaSClientMockRecorder) GetServer(ctx, serverID any) *MockIaaSClientGetServerCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServer", reflect.TypeOf((*MockIaaSClient)(nil).GetServer), ctx, serverID) + return &MockIaaSClientGetServerCall{Call: call} +} + +// MockIaaSClientGetServerCall wrap *gomock.Call +type MockIaaSClientGetServerCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockIaaSClientGetServerCall) Return(arg0 *v2api.Server, arg1 error) *MockIaaSClientGetServerCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockIaaSClientGetServerCall) Do(f func(context.Context, string) (*v2api.Server, error)) *MockIaaSClientGetServerCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockIaaSClientGetServerCall) DoAndReturn(f func(context.Context, string) (*v2api.Server, error)) *MockIaaSClientGetServerCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// GetSnapshot mocks base method. +func (m *MockIaaSClient) GetSnapshot(ctx context.Context, snapshotID string) (*v2api.Snapshot, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSnapshot", ctx, snapshotID) + ret0, _ := ret[0].(*v2api.Snapshot) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSnapshot indicates an expected call of GetSnapshot. +func (mr *MockIaaSClientMockRecorder) GetSnapshot(ctx, snapshotID any) *MockIaaSClientGetSnapshotCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSnapshot", reflect.TypeOf((*MockIaaSClient)(nil).GetSnapshot), ctx, snapshotID) + return &MockIaaSClientGetSnapshotCall{Call: call} +} + +// MockIaaSClientGetSnapshotCall wrap *gomock.Call +type MockIaaSClientGetSnapshotCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockIaaSClientGetSnapshotCall) Return(arg0 *v2api.Snapshot, arg1 error) *MockIaaSClientGetSnapshotCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockIaaSClientGetSnapshotCall) Do(f func(context.Context, string) (*v2api.Snapshot, error)) *MockIaaSClientGetSnapshotCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockIaaSClientGetSnapshotCall) DoAndReturn(f func(context.Context, string) (*v2api.Snapshot, error)) *MockIaaSClientGetSnapshotCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// GetVolume mocks base method. +func (m *MockIaaSClient) GetVolume(ctx context.Context, volumeID string) (*v2api.Volume, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetVolume", ctx, volumeID) + ret0, _ := ret[0].(*v2api.Volume) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetServer indicates an expected call of GetServer. -func (mr *MockIaaSClientMockRecorder) GetServer(ctx, serverID any) *MockIaaSClientGetServerCall { +// GetVolume indicates an expected call of GetVolume. +func (mr *MockIaaSClientMockRecorder) GetVolume(ctx, volumeID any) *MockIaaSClientGetVolumeCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServer", reflect.TypeOf((*MockIaaSClient)(nil).GetServer), ctx, serverID) - return &MockIaaSClientGetServerCall{Call: call} + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVolume", reflect.TypeOf((*MockIaaSClient)(nil).GetVolume), ctx, volumeID) + return &MockIaaSClientGetVolumeCall{Call: call} } -// MockIaaSClientGetServerCall wrap *gomock.Call -type MockIaaSClientGetServerCall struct { +// MockIaaSClientGetVolumeCall wrap *gomock.Call +type MockIaaSClientGetVolumeCall struct { *gomock.Call } // Return rewrite *gomock.Call.Return -func (c *MockIaaSClientGetServerCall) Return(arg0 *v2api.Server, arg1 error) *MockIaaSClientGetServerCall { +func (c *MockIaaSClientGetVolumeCall) Return(arg0 *v2api.Volume, arg1 error) *MockIaaSClientGetVolumeCall { c.Call = c.Call.Return(arg0, arg1) return c } // Do rewrite *gomock.Call.Do -func (c *MockIaaSClientGetServerCall) Do(f func(context.Context, string) (*v2api.Server, error)) *MockIaaSClientGetServerCall { +func (c *MockIaaSClientGetVolumeCall) Do(f func(context.Context, string) (*v2api.Volume, error)) *MockIaaSClientGetVolumeCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockIaaSClientGetServerCall) DoAndReturn(f func(context.Context, string) (*v2api.Server, error)) *MockIaaSClientGetServerCall { +func (c *MockIaaSClientGetVolumeCall) DoAndReturn(f func(context.Context, string) (*v2api.Volume, error)) *MockIaaSClientGetVolumeCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// GetVolumesByName mocks base method. +func (m *MockIaaSClient) GetVolumesByName(ctx context.Context, volName string) ([]v2api.Volume, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetVolumesByName", ctx, volName) + ret0, _ := ret[0].([]v2api.Volume) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetVolumesByName indicates an expected call of GetVolumesByName. +func (mr *MockIaaSClientMockRecorder) GetVolumesByName(ctx, volName any) *MockIaaSClientGetVolumesByNameCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVolumesByName", reflect.TypeOf((*MockIaaSClient)(nil).GetVolumesByName), ctx, volName) + return &MockIaaSClientGetVolumesByNameCall{Call: call} +} + +// MockIaaSClientGetVolumesByNameCall wrap *gomock.Call +type MockIaaSClientGetVolumesByNameCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockIaaSClientGetVolumesByNameCall) Return(arg0 []v2api.Volume, arg1 error) *MockIaaSClientGetVolumesByNameCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockIaaSClientGetVolumesByNameCall) Do(f func(context.Context, string) ([]v2api.Volume, error)) *MockIaaSClientGetVolumesByNameCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockIaaSClientGetVolumesByNameCall) DoAndReturn(f func(context.Context, string) ([]v2api.Volume, error)) *MockIaaSClientGetVolumesByNameCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// ListBackups mocks base method. +func (m *MockIaaSClient) ListBackups(ctx context.Context, filters map[string]string) ([]v2api.Backup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListBackups", ctx, filters) + ret0, _ := ret[0].([]v2api.Backup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListBackups indicates an expected call of ListBackups. +func (mr *MockIaaSClientMockRecorder) ListBackups(ctx, filters any) *MockIaaSClientListBackupsCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListBackups", reflect.TypeOf((*MockIaaSClient)(nil).ListBackups), ctx, filters) + return &MockIaaSClientListBackupsCall{Call: call} +} + +// MockIaaSClientListBackupsCall wrap *gomock.Call +type MockIaaSClientListBackupsCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockIaaSClientListBackupsCall) Return(arg0 []v2api.Backup, arg1 error) *MockIaaSClientListBackupsCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockIaaSClientListBackupsCall) Do(f func(context.Context, map[string]string) ([]v2api.Backup, error)) *MockIaaSClientListBackupsCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockIaaSClientListBackupsCall) DoAndReturn(f func(context.Context, map[string]string) ([]v2api.Backup, error)) *MockIaaSClientListBackupsCall { c.Call = c.Call.DoAndReturn(f) return c } @@ -118,3 +660,313 @@ func (c *MockIaaSClientListServersCall) DoAndReturn(f func(context.Context) (*[] c.Call = c.Call.DoAndReturn(f) return c } + +// ListSnapshots mocks base method. +func (m *MockIaaSClient) ListSnapshots(ctx context.Context, filters map[string]string) ([]v2api.Snapshot, string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListSnapshots", ctx, filters) + ret0, _ := ret[0].([]v2api.Snapshot) + ret1, _ := ret[1].(string) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// ListSnapshots indicates an expected call of ListSnapshots. +func (mr *MockIaaSClientMockRecorder) ListSnapshots(ctx, filters any) *MockIaaSClientListSnapshotsCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListSnapshots", reflect.TypeOf((*MockIaaSClient)(nil).ListSnapshots), ctx, filters) + return &MockIaaSClientListSnapshotsCall{Call: call} +} + +// MockIaaSClientListSnapshotsCall wrap *gomock.Call +type MockIaaSClientListSnapshotsCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockIaaSClientListSnapshotsCall) Return(arg0 []v2api.Snapshot, arg1 string, arg2 error) *MockIaaSClientListSnapshotsCall { + c.Call = c.Call.Return(arg0, arg1, arg2) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockIaaSClientListSnapshotsCall) Do(f func(context.Context, map[string]string) ([]v2api.Snapshot, string, error)) *MockIaaSClientListSnapshotsCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockIaaSClientListSnapshotsCall) DoAndReturn(f func(context.Context, map[string]string) ([]v2api.Snapshot, string, error)) *MockIaaSClientListSnapshotsCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// ListVolumes mocks base method. +func (m *MockIaaSClient) ListVolumes(ctx context.Context, arg1 int, arg2 string) ([]v2api.Volume, string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListVolumes", ctx, arg1, arg2) + ret0, _ := ret[0].([]v2api.Volume) + ret1, _ := ret[1].(string) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// ListVolumes indicates an expected call of ListVolumes. +func (mr *MockIaaSClientMockRecorder) ListVolumes(ctx, arg1, arg2 any) *MockIaaSClientListVolumesCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListVolumes", reflect.TypeOf((*MockIaaSClient)(nil).ListVolumes), ctx, arg1, arg2) + return &MockIaaSClientListVolumesCall{Call: call} +} + +// MockIaaSClientListVolumesCall wrap *gomock.Call +type MockIaaSClientListVolumesCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockIaaSClientListVolumesCall) Return(arg0 []v2api.Volume, arg1 string, arg2 error) *MockIaaSClientListVolumesCall { + c.Call = c.Call.Return(arg0, arg1, arg2) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockIaaSClientListVolumesCall) Do(f func(context.Context, int, string) ([]v2api.Volume, string, error)) *MockIaaSClientListVolumesCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockIaaSClientListVolumesCall) DoAndReturn(f func(context.Context, int, string) ([]v2api.Volume, string, error)) *MockIaaSClientListVolumesCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// WaitBackupReady mocks base method. +func (m *MockIaaSClient) WaitBackupReady(ctx context.Context, backupID string, snapshotSize int64, backupMaxDurationSecondsPerGB int) (*string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WaitBackupReady", ctx, backupID, snapshotSize, backupMaxDurationSecondsPerGB) + ret0, _ := ret[0].(*string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WaitBackupReady indicates an expected call of WaitBackupReady. +func (mr *MockIaaSClientMockRecorder) WaitBackupReady(ctx, backupID, snapshotSize, backupMaxDurationSecondsPerGB any) *MockIaaSClientWaitBackupReadyCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitBackupReady", reflect.TypeOf((*MockIaaSClient)(nil).WaitBackupReady), ctx, backupID, snapshotSize, backupMaxDurationSecondsPerGB) + return &MockIaaSClientWaitBackupReadyCall{Call: call} +} + +// MockIaaSClientWaitBackupReadyCall wrap *gomock.Call +type MockIaaSClientWaitBackupReadyCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockIaaSClientWaitBackupReadyCall) Return(arg0 *string, arg1 error) *MockIaaSClientWaitBackupReadyCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockIaaSClientWaitBackupReadyCall) Do(f func(context.Context, string, int64, int) (*string, error)) *MockIaaSClientWaitBackupReadyCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockIaaSClientWaitBackupReadyCall) DoAndReturn(f func(context.Context, string, int64, int) (*string, error)) *MockIaaSClientWaitBackupReadyCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// WaitDiskAttached mocks base method. +func (m *MockIaaSClient) WaitDiskAttached(ctx context.Context, instanceID, volumeID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WaitDiskAttached", ctx, instanceID, volumeID) + ret0, _ := ret[0].(error) + return ret0 +} + +// WaitDiskAttached indicates an expected call of WaitDiskAttached. +func (mr *MockIaaSClientMockRecorder) WaitDiskAttached(ctx, instanceID, volumeID any) *MockIaaSClientWaitDiskAttachedCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitDiskAttached", reflect.TypeOf((*MockIaaSClient)(nil).WaitDiskAttached), ctx, instanceID, volumeID) + return &MockIaaSClientWaitDiskAttachedCall{Call: call} +} + +// MockIaaSClientWaitDiskAttachedCall wrap *gomock.Call +type MockIaaSClientWaitDiskAttachedCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockIaaSClientWaitDiskAttachedCall) Return(arg0 error) *MockIaaSClientWaitDiskAttachedCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockIaaSClientWaitDiskAttachedCall) Do(f func(context.Context, string, string) error) *MockIaaSClientWaitDiskAttachedCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockIaaSClientWaitDiskAttachedCall) DoAndReturn(f func(context.Context, string, string) error) *MockIaaSClientWaitDiskAttachedCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// WaitDiskDetached mocks base method. +func (m *MockIaaSClient) WaitDiskDetached(ctx context.Context, instanceID, volumeID string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WaitDiskDetached", ctx, instanceID, volumeID) + ret0, _ := ret[0].(error) + return ret0 +} + +// WaitDiskDetached indicates an expected call of WaitDiskDetached. +func (mr *MockIaaSClientMockRecorder) WaitDiskDetached(ctx, instanceID, volumeID any) *MockIaaSClientWaitDiskDetachedCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitDiskDetached", reflect.TypeOf((*MockIaaSClient)(nil).WaitDiskDetached), ctx, instanceID, volumeID) + return &MockIaaSClientWaitDiskDetachedCall{Call: call} +} + +// MockIaaSClientWaitDiskDetachedCall wrap *gomock.Call +type MockIaaSClientWaitDiskDetachedCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockIaaSClientWaitDiskDetachedCall) Return(arg0 error) *MockIaaSClientWaitDiskDetachedCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockIaaSClientWaitDiskDetachedCall) Do(f func(context.Context, string, string) error) *MockIaaSClientWaitDiskDetachedCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockIaaSClientWaitDiskDetachedCall) DoAndReturn(f func(context.Context, string, string) error) *MockIaaSClientWaitDiskDetachedCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// WaitSnapshotReady mocks base method. +func (m *MockIaaSClient) WaitSnapshotReady(ctx context.Context, snapshotID string) (*string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WaitSnapshotReady", ctx, snapshotID) + ret0, _ := ret[0].(*string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WaitSnapshotReady indicates an expected call of WaitSnapshotReady. +func (mr *MockIaaSClientMockRecorder) WaitSnapshotReady(ctx, snapshotID any) *MockIaaSClientWaitSnapshotReadyCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitSnapshotReady", reflect.TypeOf((*MockIaaSClient)(nil).WaitSnapshotReady), ctx, snapshotID) + return &MockIaaSClientWaitSnapshotReadyCall{Call: call} +} + +// MockIaaSClientWaitSnapshotReadyCall wrap *gomock.Call +type MockIaaSClientWaitSnapshotReadyCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockIaaSClientWaitSnapshotReadyCall) Return(arg0 *string, arg1 error) *MockIaaSClientWaitSnapshotReadyCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockIaaSClientWaitSnapshotReadyCall) Do(f func(context.Context, string) (*string, error)) *MockIaaSClientWaitSnapshotReadyCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockIaaSClientWaitSnapshotReadyCall) DoAndReturn(f func(context.Context, string) (*string, error)) *MockIaaSClientWaitSnapshotReadyCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// WaitVolumeTargetStatus mocks base method. +func (m *MockIaaSClient) WaitVolumeTargetStatus(ctx context.Context, volumeID string, tStatus []string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WaitVolumeTargetStatus", ctx, volumeID, tStatus) + ret0, _ := ret[0].(error) + return ret0 +} + +// WaitVolumeTargetStatus indicates an expected call of WaitVolumeTargetStatus. +func (mr *MockIaaSClientMockRecorder) WaitVolumeTargetStatus(ctx, volumeID, tStatus any) *MockIaaSClientWaitVolumeTargetStatusCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitVolumeTargetStatus", reflect.TypeOf((*MockIaaSClient)(nil).WaitVolumeTargetStatus), ctx, volumeID, tStatus) + return &MockIaaSClientWaitVolumeTargetStatusCall{Call: call} +} + +// MockIaaSClientWaitVolumeTargetStatusCall wrap *gomock.Call +type MockIaaSClientWaitVolumeTargetStatusCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockIaaSClientWaitVolumeTargetStatusCall) Return(arg0 error) *MockIaaSClientWaitVolumeTargetStatusCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockIaaSClientWaitVolumeTargetStatusCall) Do(f func(context.Context, string, []string) error) *MockIaaSClientWaitVolumeTargetStatusCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockIaaSClientWaitVolumeTargetStatusCall) DoAndReturn(f func(context.Context, string, []string) error) *MockIaaSClientWaitVolumeTargetStatusCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// WaitVolumeTargetStatusWithCustomBackoff mocks base method. +func (m *MockIaaSClient) WaitVolumeTargetStatusWithCustomBackoff(ctx context.Context, volumeID string, tStatus []string, backoff *wait.Backoff) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WaitVolumeTargetStatusWithCustomBackoff", ctx, volumeID, tStatus, backoff) + ret0, _ := ret[0].(error) + return ret0 +} + +// WaitVolumeTargetStatusWithCustomBackoff indicates an expected call of WaitVolumeTargetStatusWithCustomBackoff. +func (mr *MockIaaSClientMockRecorder) WaitVolumeTargetStatusWithCustomBackoff(ctx, volumeID, tStatus, backoff any) *MockIaaSClientWaitVolumeTargetStatusWithCustomBackoffCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitVolumeTargetStatusWithCustomBackoff", reflect.TypeOf((*MockIaaSClient)(nil).WaitVolumeTargetStatusWithCustomBackoff), ctx, volumeID, tStatus, backoff) + return &MockIaaSClientWaitVolumeTargetStatusWithCustomBackoffCall{Call: call} +} + +// MockIaaSClientWaitVolumeTargetStatusWithCustomBackoffCall wrap *gomock.Call +type MockIaaSClientWaitVolumeTargetStatusWithCustomBackoffCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockIaaSClientWaitVolumeTargetStatusWithCustomBackoffCall) Return(arg0 error) *MockIaaSClientWaitVolumeTargetStatusWithCustomBackoffCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockIaaSClientWaitVolumeTargetStatusWithCustomBackoffCall) Do(f func(context.Context, string, []string, *wait.Backoff) error) *MockIaaSClientWaitVolumeTargetStatusWithCustomBackoffCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockIaaSClientWaitVolumeTargetStatusWithCustomBackoffCall) DoAndReturn(f func(context.Context, string, []string, *wait.Backoff) error) *MockIaaSClientWaitVolumeTargetStatusWithCustomBackoffCall { + c.Call = c.Call.DoAndReturn(f) + return c +} diff --git a/pkg/stackit/client/mock/mock.go b/pkg/stackit/client/mock/mock.go index 25af6cc2..aa5cbf0b 100644 --- a/pkg/stackit/client/mock/mock.go +++ b/pkg/stackit/client/mock/mock.go @@ -57,16 +57,16 @@ func (mr *MockFactoryMockRecorder) IaaS(options any) *gomock.Call { } // LoadBalancing mocks base method. -func (m *MockFactory) LoadBalancing(options []config.ConfigurationOption) (client.LoadBalancingClient, error) { +func (m *MockFactory) LoadBalancing(ptions []config.ConfigurationOption) (client.LoadBalancingClient, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "LoadBalancing", options) + ret := m.ctrl.Call(m, "LoadBalancing", ptions) ret0, _ := ret[0].(client.LoadBalancingClient) ret1, _ := ret[1].(error) return ret0, ret1 } // LoadBalancing indicates an expected call of LoadBalancing. -func (mr *MockFactoryMockRecorder) LoadBalancing(options any) *gomock.Call { +func (mr *MockFactoryMockRecorder) LoadBalancing(ptions any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadBalancing", reflect.TypeOf((*MockFactory)(nil).LoadBalancing), options) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadBalancing", reflect.TypeOf((*MockFactory)(nil).LoadBalancing), ptions) } diff --git a/pkg/stackit/client/utils.go b/pkg/stackit/client/utils.go new file mode 100644 index 00000000..86e9a0c1 --- /dev/null +++ b/pkg/stackit/client/utils.go @@ -0,0 +1,71 @@ +package client + +import ( + iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api" +) + +func FilterVolumes(volumes []iaas.Volume, filters map[string]string) []iaas.Volume { + filteredVolumes := make([]iaas.Volume, 0) + + if filters == nil { + return volumes + } + + for i := range volumes { + volume := &volumes[i] + if val, ok := filters["Name"]; ok && val != volume.GetName() { + continue + } + filteredVolumes = append(filteredVolumes, *volume) + } + + return filteredVolumes +} + +//nolint:dupl // We don't feel like doing generics to undupe this. +func FilterSnapshots(snapshots []iaas.Snapshot, filters map[string]string) []iaas.Snapshot { + filteredSnapshots := make([]iaas.Snapshot, 0) + + if filters == nil { + return snapshots + } + + for _, obj := range snapshots { + if val, ok := filters["Status"]; ok && val != obj.GetStatus() { + continue + } + if val, ok := filters["VolumeID"]; ok && val != obj.GetVolumeId() { + continue + } + if val, ok := filters["Name"]; ok && val != obj.GetName() { + continue + } + filteredSnapshots = append(filteredSnapshots, obj) + } + + return filteredSnapshots +} + +//nolint:dupl // We don't feel like doing generics to undupe this. +func FilterBackups(backups []iaas.Backup, filters map[string]string) []iaas.Backup { + filteredBackups := make([]iaas.Backup, 0) + + if filters == nil { + return backups + } + + for _, obj := range backups { + if val, ok := filters["Status"]; ok && val != obj.GetStatus() { + continue + } + if val, ok := filters["VolumeID"]; ok && val != obj.GetVolumeId() { + continue + } + if val, ok := filters["Name"]; ok && val != obj.GetName() { + continue + } + filteredBackups = append(filteredBackups, obj) + } + + return filteredBackups +} \ No newline at end of file