diff --git a/Makefile b/Makefile index ef13b5fa..0466e1e5 100644 --- a/Makefile +++ b/Makefile @@ -134,11 +134,6 @@ mocks: $(MOCKGEN) @$(MOCKGEN) -destination ./pkg/mock/iaas/iaas.go -package iaas github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api DefaultAPI # client mocks - # TODO: delete later for cleanup old stackit client PR - @$(MOCKGEN) -destination ./pkg/stackit/iaas_mock.go -package stackit ./pkg/stackit IaasClient - @$(MOCKGEN) -destination ./pkg/stackit/loadbalancer_mock.go -package stackit ./pkg/stackit LoadbalancerClient - @$(MOCKGEN) -destination ./pkg/stackit/server_mock.go -package stackit ./pkg/stackit NodeClient - @$(MOCKGEN) -destination ./pkg/stackit/metadata/metadata_mock.go -package metadata ./pkg/stackit/metadata IMetadata @$(MOCKGEN) -destination ./pkg/csi/util/mount/mount_mock.go -package mount ./pkg/csi/util/mount IMount diff --git a/pkg/stackit/backups.go b/pkg/stackit/backups.go deleted file mode 100644 index 84294519..00000000 --- a/pkg/stackit/backups.go +++ /dev/null @@ -1,182 +0,0 @@ -package stackit - -import ( - "context" - "errors" - "fmt" - "net/http" - "time" - - "github.com/stackitcloud/stackit-sdk-go/core/runtime" - iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api" - wait "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api/wait" - - "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/stackiterrors" -) - -const ( - backupReadyStatus = "AVAILABLE" - backupErrorStatus = "error" - backupDescription = "Created by STACKIT CSI driver" - BackupMaxDurationSecondsPerGBDefault = 20 - BackupMaxDurationPerGB = "backup-max-duration-seconds-per-gb" - backupBaseDurationSeconds = 30 - backupReadyCheckIntervalSeconds = 7 -) - -func (os *iaasClient) CreateBackup(ctx context.Context, name, volID, snapshotID string, tags map[string]string) (*iaas.Backup, error) { - opts, err := buildCreateBackupPayload(name, volID, snapshotID, tags) - if err != nil { - return nil, err - } - - var httpResp *http.Response - ctxWithHTTPResp := runtime.WithCaptureHTTPResponse(ctx, &httpResp) - backup, err := os.iaas.CreateBackup(ctxWithHTTPResp, os.projectID, os.region).CreateBackupPayload(opts).Execute() - if err != nil { - if httpResp != nil { - reqID := httpResp.Header.Get(wait.XRequestIDHeader) - return nil, stackiterrors.WrapErrorWithResponseID(err, reqID) - } - return nil, err - } - - return backup, nil -} - -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 (os *iaasClient) ListBackups(ctx context.Context, filters map[string]string) ([]iaas.Backup, error) { - var httpResp *http.Response - ctxWithHTTPResp := runtime.WithCaptureHTTPResponse(ctx, &httpResp) - // TODO: Add API filter once available. - backups, err := os.iaas.ListBackups(ctxWithHTTPResp, os.projectID, os.region).Execute() - if err != nil { - if httpResp != nil { - reqID := httpResp.Header.Get(wait.XRequestIDHeader) - return nil, stackiterrors.WrapErrorWithResponseID(err, reqID) - } - return nil, err - } - - filteredBackups := filterBackups(backups.Items, filters) - return filteredBackups, nil -} - -func (os *iaasClient) DeleteBackup(ctx context.Context, backupID string) error { - var httpResp *http.Response - ctxWithHTTPResp := runtime.WithCaptureHTTPResponse(ctx, &httpResp) - err := os.iaas.DeleteBackup(ctxWithHTTPResp, os.projectID, backupID, os.region).Execute() - if err != nil { - if httpResp != nil { - reqID := httpResp.Header.Get(wait.XRequestIDHeader) - return stackiterrors.WrapErrorWithResponseID(err, reqID) - } - return err - } - return nil -} - -func (os *iaasClient) GetBackupByID(ctx context.Context, backupID string) (*iaas.Backup, error) { - var httpResp *http.Response - ctxWithHTTPResp := runtime.WithCaptureHTTPResponse(ctx, &httpResp) - backup, err := os.iaas.GetBackup(ctxWithHTTPResp, os.projectID, os.region, backupID).Execute() - if err != nil { - if httpResp != nil { - reqID := httpResp.Header.Get(wait.XRequestIDHeader) - return nil, stackiterrors.WrapErrorWithResponseID(err, reqID) - } - return nil, err - } - return backup, nil -} - -func (os *iaasClient) WaitBackupReady(ctx context.Context, backupID string, snapshotSize int64, backupMaxDurationSecondsPerGB int) (*string, error) { - var err error - - duration := time.Duration(int64(backupMaxDurationSecondsPerGB)*snapshotSize + backupBaseDurationSeconds) - err = os.waitBackupReadyWithContext(backupID, duration) - if errors.Is(err, context.DeadlineExceeded) { - err = fmt.Errorf("timeout, Backup %s is still not Ready: %v", backupID, err) - } - - back, _ := os.GetBackupByID(ctx, backupID) - - if back != nil { - return back.Status, err - } - return new("Failed to get backup status"), err -} - -func (os *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 = os.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 (os *iaasClient) backupIsReady(ctx context.Context, backupID string) (bool, error) { - backup, err := os.GetBackupByID(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 -} diff --git a/pkg/stackit/backups_test.go b/pkg/stackit/backups_test.go deleted file mode 100644 index 7c110c43..00000000 --- a/pkg/stackit/backups_test.go +++ /dev/null @@ -1,187 +0,0 @@ -package stackit - -import ( - "context" - "fmt" - "os" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - stackitconfig "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/config" - iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api" - "go.uber.org/mock/gomock" - - mock "github.com/stackitcloud/cloud-provider-stackit/pkg/mock/iaas" -) - -var _ = Describe("Backup", func() { - var ( - err error - mockCtrl *gomock.Controller - mockAPI *mock.MockDefaultAPI - openStack IaasClient - config *stackitconfig.CSIConfig - ) - - const projectID = "project-id" - const region = "eu01" - - BeforeEach(func() { - t := GinkgoT() - mockCtrl = gomock.NewController(t) - mockAPI = mock.NewMockDefaultAPI(mockCtrl) - t.Setenv("STACKIT_REGION", region) - Expect(os.Getenv("STACKIT_REGION")).To(Equal(region)) - }) - - Context("buildCreateBackupPayload", func() { - DescribeTable("successful payload variants", - func( - name, volID, snapshotID string, - tags map[string]string, - expectedPayload iaas.CreateBackupPayload, - ) { - actualPayload, err := buildCreateBackupPayload(name, volID, snapshotID, tags) - Expect(err).ToNot(HaveOccurred()) - Expect(actualPayload).To(Equal(expectedPayload)) - }, - Entry( - "with volume source and nil tags", - "expected-name", "volume-id", "", nil, - iaas.CreateBackupPayload{ - Name: new("expected-name"), - Description: new(backupDescription), - Source: iaas.BackupSource{ - Type: "volume", - Id: "volume-id", - }, - Labels: nil, - }, - ), - Entry( - "with snapshot source and special characters in tags", - "expected-name", "", "snapshot-id", - map[string]string{ - "special": "tag with spaces and !@#$%^&*()", - "normal": "value", - }, - iaas.CreateBackupPayload{ - Name: new("expected-name"), - Description: new(backupDescription), - Source: iaas.BackupSource{ - Type: "snapshot", - Id: "snapshot-id", - }, - Labels: map[string]any{ - "special": "tag with spaces and !@#$%^&*()", - "normal": "value", - }, - }, - ), - Entry( - "with empty tags map", - "expected-name", "volume-id", "", map[string]string{}, - iaas.CreateBackupPayload{ - Name: new("expected-name"), - Description: new(backupDescription), - Source: iaas.BackupSource{ - Type: "volume", - Id: "volume-id", - }, - Labels: map[string]any{}, - }, - ), - Entry( - "with long backup name", - "very-long-backup-name-"+string(make([]byte, 200)), "volume-id", "", nil, - iaas.CreateBackupPayload{ - Name: new("very-long-backup-name-" + string(make([]byte, 200))), - Description: new(backupDescription), - Source: iaas.BackupSource{ - Type: "volume", - Id: "volume-id", - }, - }, - ), - Entry( - "when both volume and snapshot are provided snapshot wins", - "expected-name", "volume-id", "snapshot-id", nil, - iaas.CreateBackupPayload{ - Name: new("expected-name"), - Description: new(backupDescription), - Source: iaas.BackupSource{ - Type: "snapshot", - Id: "snapshot-id", - }, - }, - ), - ) - - DescribeTable("validation error variants", - func(name, volID, snapshotID, expectedError string) { - actualPayload, err := buildCreateBackupPayload(name, volID, snapshotID, nil) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal(expectedError)) - Expect(actualPayload).To(Equal(iaas.CreateBackupPayload{})) - }, - Entry("empty name", "", "volume-id", "", "backup name cannot be empty"), - Entry("missing volume and snapshot IDs", "expected-name", "", "", "either volID or snapshotID must be provided"), - ) - }) - - Context("CreateBackup", func() { - BeforeEach(func() { - config = &stackitconfig.CSIConfig{ - Global: stackitconfig.GlobalOpts{ - ProjectID: projectID, - }, - } - openStack, err = CreateSTACKITProvider(mockAPI, config) - Expect(err).ToNot(HaveOccurred()) - }) - - It("returns backup on successful API call and uses configured project and region", func() { - mockCreateBackup(mockAPI) - - backup, err := openStack.CreateBackup(context.Background(), "expected-name", "volume-id", "", nil) - Expect(err).ToNot(HaveOccurred()) - Expect(backup).ToNot(BeNil()) - Expect(backup.Id).ToNot(BeNil()) - Expect(*backup.Id).To(Equal("expected backup")) - }) - - It("returns error when API fails", func() { - mockAPI.EXPECT().CreateBackup(gomock.Any(), projectID, region).Return(iaas.ApiCreateBackupRequest{ApiService: mockAPI}) - mockAPI.EXPECT().CreateBackupExecute(gomock.Any()).Return(nil, fmt.Errorf("API error")) - - backup, err := openStack.CreateBackup(context.Background(), "expected-name", "volume-id", "", nil) - Expect(err).To(HaveOccurred()) - Expect(backup).To(BeNil()) - }) - - DescribeTable("returns error when payload is invalid", - func(name, volID, snapshotID, expectedError string) { - mockAPI.EXPECT().CreateBackup(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) - mockAPI.EXPECT().CreateBackupExecute(gomock.Any()).Times(0) - - backup, err := openStack.CreateBackup(context.Background(), name, volID, snapshotID, nil) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal(expectedError)) - Expect(backup).To(BeNil()) - }, - Entry("empty name", "", "volume-id", "", "backup name cannot be empty"), - Entry("missing volume and snapshot IDs", "expected-name", "", "", "either volID or snapshotID must be provided"), - ) - }) - -}) - -func mockCreateBackup(mockAPI *mock.MockDefaultAPI) { - const ( - projectID = "project-id" - region = "eu01" - ) - - mockAPI.EXPECT().CreateBackup(gomock.Any(), projectID, region).Return(iaas.ApiCreateBackupRequest{ApiService: mockAPI}) - mockAPI.EXPECT().CreateBackupExecute(gomock.Any()).Return(&iaas.Backup{Id: new("expected backup")}, nil) -} diff --git a/pkg/stackit/client.go b/pkg/stackit/client.go deleted file mode 100644 index 12960832..00000000 --- a/pkg/stackit/client.go +++ /dev/null @@ -1,218 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package stackit - -import ( - "context" - "errors" - "fmt" - "io" - "net/http" - "os" - "strings" - - stackitconfig "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/config" - sdkconfig "github.com/stackitcloud/stackit-sdk-go/core/config" - oapiError "github.com/stackitcloud/stackit-sdk-go/core/oapierror" - iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api" - loadbalancer "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer/v2api" - "gopkg.in/yaml.v3" - "k8s.io/apimachinery/pkg/util/wait" - - "github.com/stackitcloud/cloud-provider-stackit/pkg/version" - - "github.com/spf13/pflag" - "k8s.io/klog/v2" -) - -// userAgentData is used to add extra information to the STACKIT SDK user-agent -var ( - userAgentData []string - ErrorNotFound = errors.New("not found") -) - -// AddExtraFlags is called by the main package to add component specific command line flags -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.") -} - -type IaasClient interface { - CreateVolume(context.Context, *iaas.CreateVolumePayload) (*iaas.Volume, error) - DeleteVolume(ctx context.Context, volumeID string) error - AttachVolume(ctx context.Context, instanceID, volumeID string) (string, error) - ListVolumes(ctx context.Context, limit int, startingToken string) ([]iaas.Volume, string, error) - WaitDiskAttached(ctx context.Context, instanceID string, volumeID string) error - DetachVolume(ctx context.Context, instanceID, volumeID string) error - WaitDiskDetached(ctx context.Context, instanceID string, volumeID string) error - WaitVolumeTargetStatus(ctx context.Context, volumeID string, tStatus []string) error - GetVolume(ctx context.Context, volumeID string) (*iaas.Volume, error) - GetVolumesByName(ctx context.Context, name string) ([]iaas.Volume, error) - GetVolumeByName(ctx context.Context, name string) (*iaas.Volume, error) - CreateSnapshot(ctx context.Context, name, volID string, tags map[string]string) (*iaas.Snapshot, error) - ListSnapshots(ctx context.Context, filters map[string]string) ([]iaas.Snapshot, string, error) - DeleteSnapshot(ctx context.Context, snapID string) error - GetSnapshotByID(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 - GetBackupByID(ctx context.Context, backupID string) (*iaas.Backup, error) - WaitBackupReady(ctx context.Context, backupID string, snapshotSize int64, backupMaxDurationSecondsPerGB int) (*string, error) - GetInstanceByID(ctx context.Context, instanceID string) (*iaas.Server, error) - ExpandVolume(ctx context.Context, volumeID string, status string, size int64) error - GetBlockStorageOpts() stackitconfig.BlockStorageOpts - WaitVolumeTargetStatusWithCustomBackoff(ctx context.Context, volumeID string, targetStatus []string, backoff *wait.Backoff) error -} - -type LoadbalancerClient interface { - GetLoadBalancer(ctx context.Context, projectID string, name string) (*loadbalancer.LoadBalancer, error) - // DeleteLoadBalancer returns no error if the load balancer doesn't exist. - DeleteLoadBalancer(ctx context.Context, projectID string, name string) error - // CreateLoadBalancer returns ErrorNotFound if the project is not enabled. - CreateLoadBalancer(ctx context.Context, projectID string, loadbalancer *loadbalancer.CreateLoadBalancerPayload) (*loadbalancer.LoadBalancer, error) - UpdateLoadBalancer(ctx context.Context, projectID, name string, update *loadbalancer.UpdateLoadBalancerPayload) (*loadbalancer.LoadBalancer, error) - UpdateTargetPool(ctx context.Context, projectID string, name string, targetPoolName string, payload loadbalancer.UpdateTargetPoolPayload) error - CreateCredentials(ctx context.Context, projectID string, payload loadbalancer.CreateCredentialsPayload) (*loadbalancer.CreateCredentialsResponse, error) - ListCredentials(ctx context.Context, projectID string) (*loadbalancer.ListCredentialsResponse, error) - GetCredentials(ctx context.Context, projectID string, credentialRef string) (*loadbalancer.GetCredentialsResponse, error) - UpdateCredentials(ctx context.Context, projectID, credentialRef string, payload loadbalancer.UpdateCredentialsPayload) error - DeleteCredentials(ctx context.Context, projectID string, credentialRef string) error -} - -// NodeClient is the API client wrapper for the cloud-controller-manager's node-controller -type NodeClient interface { - GetServer(ctx context.Context, projectID, region, serverID string) (*iaas.Server, error) - DeleteServer(ctx context.Context, projectID, region, serverID string) error - CreateServer(ctx context.Context, projectID string, region string, create *iaas.CreateServerPayload) (*iaas.Server, error) - UpdateServer(ctx context.Context, projectID, region, serverID string, update *iaas.UpdateServerPayload) (*iaas.Server, error) - ListServers(ctx context.Context, projectID, region string) (*[]iaas.Server, error) -} - -type iaasClient struct { - iaas iaas.DefaultAPI - projectID string - region string - bsOpts stackitconfig.BlockStorageOpts -} - -type lbClient struct { - client loadbalancer.DefaultAPI - region string -} - -type nodeClient struct { - client *iaas.APIClient -} - -//nolint:gocritic // The openstack package currently shadows but will be renamed anyway. -func (os *iaasClient) GetBlockStorageOpts() stackitconfig.BlockStorageOpts { - return os.bsOpts -} - -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 -} - -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) -} - -// CreateSTACKITProvider creates STACKIT Instance -func CreateSTACKITProvider(client iaas.DefaultAPI, cfg *stackitconfig.CSIConfig) (IaasClient, error) { - region := os.Getenv("STACKIT_REGION") - if region == "" { - panic("STACKIT_REGION environment variable not set") - } - // Init iaasClient - instance := &iaasClient{ - iaas: client, - bsOpts: cfg.BlockStorage, - projectID: cfg.Global.ProjectID, - region: region, - } - - return instance, nil -} - -func CreateIaaSClient(cfg *stackitconfig.CSIConfig) (iaas.DefaultAPI, error) { - var userAgent []string - var opts []sdkconfig.ConfigurationOption - userAgent = append(userAgent, fmt.Sprintf("%s/%s", "block-storage-csi-driver", version.Version)) - for _, data := range userAgentData { - // Prepend userAgents - userAgent = append([]string{data}, userAgent...) - } - klog.V(4).Infof("Using user-agent: %s", userAgent) - - if cfg.Global.APIEndpoints.IaasAPI != "" { - opts = append(opts, sdkconfig.WithEndpoint(cfg.Global.APIEndpoints.IaasAPI)) - } - - opts = append(opts, sdkconfig.WithUserAgent(strings.Join(userAgent, " "))) - - client, err := iaas.NewAPIClient(opts...) - if err != nil { - return nil, err - } - return client.DefaultAPI, nil -} - -func NewLoadbalancerClient(cl loadbalancer.DefaultAPI, region string) (LoadbalancerClient, error) { - return &lbClient{ - client: cl, - region: region, - }, nil -} - -func NewNodeClient(cl *iaas.APIClient) (NodeClient, error) { - return &nodeClient{client: cl}, nil -} - -func isOpenAPINotFound(err error) bool { - apiErr := &oapiError.GenericOpenAPIError{} - if !errors.As(err, &apiErr) { - return false - } - return apiErr.StatusCode == http.StatusNotFound -} - -func IsNotFound(err error) bool { - return errors.Is(err, ErrorNotFound) -} diff --git a/pkg/stackit/client_test.go b/pkg/stackit/client/factory_test.go similarity index 99% rename from pkg/stackit/client_test.go rename to pkg/stackit/client/factory_test.go index 2fe6d1a3..5b095c70 100644 --- a/pkg/stackit/client_test.go +++ b/pkg/stackit/client/factory_test.go @@ -1,4 +1,4 @@ -package stackit +package client import ( "fmt" diff --git a/pkg/stackit/client/iaas_test.go b/pkg/stackit/client/iaas_test.go new file mode 100644 index 00000000..172b15d6 --- /dev/null +++ b/pkg/stackit/client/iaas_test.go @@ -0,0 +1,608 @@ +package client + +import ( + "context" + "fmt" + "net/http" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + oapiError "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api" + "go.uber.org/mock/gomock" + + mock "github.com/stackitcloud/cloud-provider-stackit/pkg/mock/iaas" +) + +var _ = Describe("Server", func() { + var ( + mockCtrl *gomock.Controller + mockIaaSClient *mock.MockDefaultAPI + client *iaasClient + ) + + const ( + serverID = "server-uuid-123" + ) + + BeforeEach(func() { + mockCtrl = gomock.NewController(GinkgoT()) + mockIaaSClient = mock.NewMockDefaultAPI(mockCtrl) + + client = &iaasClient{ + Client: mockIaaSClient, + } + }) + + AfterEach(func() { + mockCtrl.Finish() + }) + + Context("GetServer", func() { + It("returns a server on success", func() { + mockIaaSClient.EXPECT(). + GetServer(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(iaas.ApiGetServerRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().GetServerExecute(gomock.Any()).Return(&iaas.Server{Id: new(serverID), Name: "my-server"}, nil) + + server, err := client.GetServer(context.Background(), "my-server") + Expect(err).ToNot(HaveOccurred()) + Expect(*server.Id).To(Equal(serverID)) + }) + + It("returns ErrorNotFound when API returns 404", func() { + mockIaaSClient.EXPECT(). + GetServer(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(iaas.ApiGetServerRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().GetServerExecute(gomock.Any()).Return(nil, &oapiError.GenericOpenAPIError{ + StatusCode: http.StatusNotFound, + }) + + _, err := client.GetServer(context.Background(), "my-server") + Expect(err).To(HaveOccurred()) + }) + }) + + Context("ListServers", func() { + It("returns a list of servers with details", func() { + mockItems := []iaas.Server{ + {Id: new("id-1"), Name: "server-1"}, + } + + mockIaaSClient.EXPECT(). + ListServers(gomock.Any(), gomock.Any(), gomock.Any()). + Return(iaas.ApiListServersRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().ListServersExecute(gomock.Any()).Return(&iaas.ServerListResponse{Items: mockItems}, nil) + + resp, err := client.ListServers(context.Background()) + Expect(err).ToNot(HaveOccurred()) + items := *resp + Expect(items).To(HaveLen(1)) + Expect(*items[0].Id).To(Equal("id-1")) + }) + }) +}) + +var _ = Describe("Snapshot", func() { + var ( + mockCtrl *gomock.Controller + mockIaaSClient *mock.MockDefaultAPI + client *iaasClient + ) + + const ( + snapshotID = "snapshot-uuid-123" + ) + + BeforeEach(func() { + mockCtrl = gomock.NewController(GinkgoT()) + mockIaaSClient = mock.NewMockDefaultAPI(mockCtrl) + + client = &iaasClient{ + Client: mockIaaSClient, + } + }) + + AfterEach(func() { + mockCtrl.Finish() + }) + + Context("CreateSnapshot", func() { + It("successfully creates a snapshot", func() { + mockIaaSClient.EXPECT(). + CreateSnapshot(gomock.Any(), gomock.Any(), gomock.Any()). + Return(iaas.ApiCreateSnapshotRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().CreateSnapshotExecute(gomock.Any()).Return(&iaas.Snapshot{Id: new(snapshotID)}, nil) + + payload := iaas.CreateSnapshotPayload{Name: new("new-snapshot")} + snapshot, err := client.CreateSnapshot(context.Background(), payload) + Expect(err).ToNot(HaveOccurred()) + Expect(*snapshot.Id).To(Equal(snapshotID)) + }) + }) + + Context("ListSnapshots", func() { + It("returns a filtered list of snapshots", func() { + mockItems := &iaas.SnapshotListResponse{ + Items: []iaas.Snapshot{ + {Id: new("id-1"), Name: new("snap-1")}, + {Id: new("id-2"), Name: new("snap-2")}, + }, + } + + mockIaaSClient.EXPECT(). + ListSnapshotsInProject(gomock.Any(), gomock.Any(), gomock.Any()). + Return(iaas.ApiListSnapshotsInProjectRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().ListSnapshotsInProjectExecute(gomock.Any()).Return(mockItems, nil) + + resp, _, err := client.ListSnapshots(context.Background(), nil) + Expect(err).ToNot(HaveOccurred()) + Expect(resp).To(HaveLen(2)) + }) + + snapShotListResponse := iaas.SnapshotListResponse{ + Items: []iaas.Snapshot{ + { + Id: new("fake-snapshot"), + Name: new("fake-snapshot"), + VolumeId: "some-special-volume", + Status: new("ERROR"), + }, + { + Id: new("fake-snapshot2"), + Name: new("fake-snapshot2"), + VolumeId: "some-special-volume", + Status: new("AVAILABLE"), + }, + { + Id: new("wrong snapshot"), + Name: new("wrong snapshot"), + VolumeId: "another-special-volume", + Status: new("AVAILABLE"), + }, + }, + } + + DescribeTable("should return a filtered list of snapshots", + func(filters map[string]string, expectedSnaps []iaas.Snapshot) { + mockIaaSClient.EXPECT().ListSnapshotsInProject(gomock.Any(), gomock.Any(), gomock.Any()).Return(iaas.ApiListSnapshotsInProjectRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().ListSnapshotsInProjectExecute(gomock.Any()).Return(&snapShotListResponse, nil) + + snaps, _, err := client.ListSnapshots(context.Background(), filters) + Expect(err).ToNot(HaveOccurred()) + Expect(snaps).To(Equal(expectedSnaps)) + }, + Entry("filter by VolumeID", + map[string]string{"VolumeID": "some-special-volume"}, + []iaas.Snapshot{ + { + Id: new("fake-snapshot"), + Name: new("fake-snapshot"), + VolumeId: "some-special-volume", + Status: new("ERROR"), + }, + { + Id: new("fake-snapshot2"), + Name: new("fake-snapshot2"), + VolumeId: "some-special-volume", + Status: new("AVAILABLE"), + }, + }, + ), + Entry("filter by name", + map[string]string{"Name": "fake-snapshot"}, + []iaas.Snapshot{ + { + Id: new("fake-snapshot"), + Name: new("fake-snapshot"), + VolumeId: "some-special-volume", + Status: new("ERROR"), + }, + }, + ), + Entry("filter by status and name", + map[string]string{"Name": "fake-snapshot2", "Status": "AVAILABLE"}, + []iaas.Snapshot{ + { + Id: new("fake-snapshot2"), + Name: new("fake-snapshot2"), + VolumeId: "some-special-volume", + Status: new("AVAILABLE"), + }, + }, + ), + Entry("no filters", + map[string]string{}, + snapShotListResponse.Items, + ), + ) + }) + + Context("GetSnapshot", func() { + It("returns a specific snapshot on success", func() { + mockIaaSClient.EXPECT(). + GetSnapshot(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(iaas.ApiGetSnapshotRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().GetSnapshotExecute(gomock.Any()).Return(&iaas.Snapshot{Id: new(snapshotID), Status: new("AVAILABLE")}, nil) + + snapshot, err := client.GetSnapshot(context.Background(), snapshotID) + Expect(err).ToNot(HaveOccurred()) + Expect(*snapshot.Id).To(Equal(snapshotID)) + }) + }) + + Context("DeleteSnapshot", func() { + It("deletes the snapshot successfully", func() { + mockIaaSClient.EXPECT(). + DeleteSnapshot(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(iaas.ApiDeleteSnapshotRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().DeleteSnapshotExecute(gomock.Any()).Return(nil) + + err := client.DeleteSnapshot(context.Background(), snapshotID) + Expect(err).ToNot(HaveOccurred()) + }) + }) + + Context("WaitSnapshotReady", func() { + It("returns the current status of the snapshot", func() { + mockIaaSClient.EXPECT(). + GetSnapshot(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(iaas.ApiGetSnapshotRequest{ApiService: mockIaaSClient}).AnyTimes() + mockIaaSClient.EXPECT().GetSnapshotExecute(gomock.Any()).Return(&iaas.Snapshot{Id: new(snapshotID), Status: new("AVAILABLE")}, nil).AnyTimes() + + status, err := client.WaitSnapshotReady(context.Background(), snapshotID) + Expect(err).ToNot(HaveOccurred()) + Expect(*status).To(Equal("AVAILABLE")) + }) + + It("returns an error if the snapshot retrieval fails", func() { + mockIaaSClient.EXPECT(). + GetSnapshot(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(iaas.ApiGetSnapshotRequest{ApiService: mockIaaSClient}).AnyTimes() + mockIaaSClient.EXPECT().GetSnapshotExecute(gomock.Any()).Return(nil, fmt.Errorf("api error")).AnyTimes() + + status, err := client.WaitSnapshotReady(context.Background(), snapshotID) + Expect(err).To(HaveOccurred()) + Expect(status).ToNot(BeNil()) + Expect(*status).To(Equal("Failed to get Snapshot status")) + }) + }) +}) + +var _ = Describe("Backup", func() { + var ( + mockCtrl *gomock.Controller + mockIaaSClient *mock.MockDefaultAPI + client *iaasClient + ) + + BeforeEach(func() { + mockCtrl = gomock.NewController(GinkgoT()) + mockIaaSClient = mock.NewMockDefaultAPI(mockCtrl) + + client = &iaasClient{ + Client: mockIaaSClient, + } + }) + + AfterEach(func() { + mockCtrl.Finish() + }) + + Context("buildCreateBackupPayload", func() { + DescribeTable("successful payload variants", + func(name, volID, snapshotID string, tags map[string]string, expectedPayload iaas.CreateBackupPayload) { + actualPayload, err := BuildCreateBackupPayload(name, volID, snapshotID, tags) + Expect(err).ToNot(HaveOccurred()) + Expect(actualPayload).To(Equal(expectedPayload)) + }, + Entry("with volume source and nil tags", "expected-name", "volume-id", "", nil, iaas.CreateBackupPayload{ + Name: new("expected-name"), + Description: new(BackupDescription), + Source: iaas.BackupSource{Type: "volume", Id: "volume-id"}, + Labels: nil, + }), + Entry("with snapshot source and special characters in tags", "expected-name", "", "snapshot-id", + map[string]string{ + "special": "tag with spaces and !@#$%^&*()", + "normal": "value", + }, + iaas.CreateBackupPayload{ + Name: new("expected-name"), + Description: new(BackupDescription), + Source: iaas.BackupSource{Type: "snapshot", Id: "snapshot-id"}, + Labels: map[string]any{ + "special": "tag with spaces and !@#$%^&*()", + "normal": "value", + }, + }, + ), + Entry("with empty tags map", "expected-name", "volume-id", "", map[string]string{}, iaas.CreateBackupPayload{ + Name: new("expected-name"), + Description: new(BackupDescription), + Source: iaas.BackupSource{Type: "volume", Id: "volume-id"}, + Labels: map[string]any{}, + }), + Entry("with long backup name", "very-long-backup-name-"+string(make([]byte, 200)), "volume-id", "", nil, iaas.CreateBackupPayload{ + Name: new("very-long-backup-name-" + string(make([]byte, 200))), + Description: new(BackupDescription), + Source: iaas.BackupSource{Type: "volume", Id: "volume-id"}, + }), + Entry("when both volume and snapshot are provided snapshot wins", "expected-name", "volume-id", "snapshot-id", nil, iaas.CreateBackupPayload{ + Name: new("expected-name"), + Description: new(BackupDescription), + Source: iaas.BackupSource{Type: "snapshot", Id: "snapshot-id"}, + }), + ) + + DescribeTable("validation error variants", + func(name, volID, snapshotID, expectedError string) { + actualPayload, err := BuildCreateBackupPayload(name, volID, snapshotID, nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal(expectedError)) + Expect(actualPayload).To(Equal(iaas.CreateBackupPayload{})) + }, + Entry("empty name", "", "volume-id", "", "backup name cannot be empty"), + Entry("missing volume and snapshot IDs", "expected-name", "", "", "either volID or snapshotID must be provided"), + ) + }) + + Context("CreateBackup API Calls", func() { + It("returns backup on successful API call", func() { + mockIaaSClient.EXPECT(). + CreateBackup(gomock.Any(), gomock.Any(), gomock.Any()). + Return(iaas.ApiCreateBackupRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().CreateBackupExecute(gomock.Any()).Return(&iaas.Backup{Id: new("expected-backup-id")}, nil) + + backup, err := client.CreateBackup(context.Background(), "expected-name", "volume-id", "", nil) + + Expect(err).ToNot(HaveOccurred()) + Expect(*backup.Id).To(Equal("expected-backup-id")) + }) + + It("returns error when API fails", func() { + mockIaaSClient.EXPECT(). + CreateBackup(gomock.Any(), gomock.Any(), gomock.Any()). + Return(iaas.ApiCreateBackupRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().CreateBackupExecute(gomock.Any()).Return(nil, fmt.Errorf("API error")) + + _, err := client.CreateBackup(context.Background(), "expected-name", "volume-id", "", nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("API error")) + }) + + DescribeTable("returns error when payload is invalid", + func(name, volID, snapshotID, expectedError string) { + mockIaaSClient.EXPECT().CreateBackup(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) + mockIaaSClient.EXPECT().CreateBackupExecute(gomock.Any()).Times(0) + + backup, err := client.CreateBackup(context.Background(), name, volID, snapshotID, nil) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal(expectedError)) + Expect(backup).To(BeNil()) + }, + Entry("empty name", "", "volume-id", "", "backup name cannot be empty"), + Entry("missing volume and snapshot IDs", "expected-name", "", "", "either volID or snapshotID must be provided"), + ) + }) + + Context("ListBackups", func() { + It("returns a filtered list of backups on success", func() { + mockBackups := &iaas.BackupListResponse{ + Items: []iaas.Backup{ + {Id: new("id-1"), Name: new("backup-1")}, + {Id: new("id-2"), Name: new("backup-2")}, + }, + } + + mockIaaSClient.EXPECT(). + ListBackups(gomock.Any(), gomock.Any(), gomock.Any()). + Return(iaas.ApiListBackupsRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().ListBackupsExecute(gomock.Any()).Return(mockBackups, nil) + + backups, err := client.ListBackups(context.Background(), nil) + + Expect(err).ToNot(HaveOccurred()) + Expect(backups).To(HaveLen(2)) + Expect(*backups[0].Id).To(Equal("id-1")) + }) + + It("returns error when list API fails", func() { + mockIaaSClient.EXPECT(). + ListBackups(gomock.Any(), gomock.Any(), gomock.Any()). + Return(iaas.ApiListBackupsRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().ListBackupsExecute(gomock.Any()).Return(nil, fmt.Errorf("execute error")) + + _, err := client.ListBackups(context.Background(), nil) + Expect(err).To(HaveOccurred()) + }) + }) + + Context("GetBackup", func() { + It("returns a specific backup", func() { + mockIaaSClient.EXPECT(). + GetBackup(gomock.Any(), gomock.Any(), gomock.Any(), "backup-id"). + Return(iaas.ApiGetBackupRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().GetBackupExecute(gomock.Any()).Return(&iaas.Backup{Id: new("backup-id")}, nil) + + backup, err := client.GetBackup(context.Background(), "backup-id") + Expect(err).ToNot(HaveOccurred()) + Expect(*backup.Id).To(Equal("backup-id")) + }) + }) + + Context("DeleteBackup", func() { + It("calls delete successfully", func() { + mockIaaSClient.EXPECT(). + DeleteBackup(gomock.Any(), gomock.Any(), gomock.Any(), "backup-id"). + Return(iaas.ApiDeleteBackupRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().DeleteBackupExecute(gomock.Any()).Return(nil) + + err := client.DeleteBackup(context.Background(), "backup-id") + Expect(err).ToNot(HaveOccurred()) + }) + + It("returns error if delete fails", func() { + mockIaaSClient.EXPECT(). + DeleteBackup(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(iaas.ApiDeleteBackupRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().DeleteBackupExecute(gomock.Any()).Return(fmt.Errorf("delete failed")) + + err := client.DeleteBackup(context.Background(), "any-id") + Expect(err).To(HaveOccurred()) + }) + }) + + Context("WaitBackupReady", func() { + It("returns the backup status when it becomes ready", func() { + mockIaaSClient.EXPECT(). + GetBackup(gomock.Any(), gomock.Any(), gomock.Any(), "backup-id"). + Return(iaas.ApiGetBackupRequest{ApiService: mockIaaSClient}).AnyTimes() + mockIaaSClient.EXPECT().GetBackupExecute(gomock.Any()).Return(&iaas.Backup{Id: new("backup-id"), Status: new(backupReadyStatus)}, nil).AnyTimes() + + status, err := client.WaitBackupReady(context.Background(), "backup-id", 1, 1) + Expect(err).ToNot(HaveOccurred()) + Expect(*status).To(Equal(backupReadyStatus)) + }) + + It("returns error on timeout or wait failure", func() { + mockIaaSClient.EXPECT(). + GetBackup(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(iaas.ApiGetBackupRequest{ApiService: mockIaaSClient}).AnyTimes() + mockIaaSClient.EXPECT().GetBackupExecute(gomock.Any()).Return(nil, fmt.Errorf("timeout waiting for backup")).AnyTimes() + + status, err := client.WaitBackupReady(context.Background(), "id", 1, 1) + Expect(err).To(HaveOccurred()) + Expect(status).NotTo(BeNil()) + }) + }) +}) + +var _ = Describe("Volume", func() { + var ( + mockCtrl *gomock.Controller + mockIaaSClient *mock.MockDefaultAPI + client *iaasClient + ) + + const ( + projectID = "project-id" + region = "eu01" + volumeID = "vol-123" + serverID = "server-123" + ) + + BeforeEach(func() { + mockCtrl = gomock.NewController(GinkgoT()) + mockIaaSClient = mock.NewMockDefaultAPI(mockCtrl) + + client = &iaasClient{ + Client: mockIaaSClient, + } + }) + + AfterEach(func() { + mockCtrl.Finish() + }) + + Context("Volume Lifecycle", func() { + It("CreateVolume successfully calls the API", func() { + mockIaaSClient.EXPECT().CreateVolume(gomock.Any(), gomock.Any(), gomock.Any()). + Return(iaas.ApiCreateVolumeRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().CreateVolumeExecute(gomock.Any()).Return(&iaas.Volume{Id: new(volumeID)}, nil) + + vol, err := client.CreateVolume(context.Background(), iaas.CreateVolumePayload{}) + Expect(err).ToNot(HaveOccurred()) + Expect(*vol.Id).To(Equal(volumeID)) + }) + + It("GetVolume returns a specific volume", func() { + mockIaaSClient.EXPECT().GetVolume(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(iaas.ApiGetVolumeRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().GetVolumeExecute(gomock.Any()).Return(&iaas.Volume{Id: new(volumeID), Name: new("test-vol")}, nil) + + vol, err := client.GetVolume(context.Background(), volumeID) + Expect(err).ToNot(HaveOccurred()) + Expect(*vol.Name).To(Equal("test-vol")) + }) + + It("DeleteVolume fails if volume is still attached (diskIsUsed logic)", func() { + mockIaaSClient.EXPECT().GetVolume(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(iaas.ApiGetVolumeRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().GetVolumeExecute(gomock.Any()).Return(&iaas.Volume{Id: new(volumeID), ServerId: new(serverID)}, nil) + + err := client.DeleteVolume(context.Background(), volumeID) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("still attached")) + }) + }) + + Context("Attach/Detach Volume", func() { + It("AttachVolume calls API when not already attached", func() { + mockIaaSClient.EXPECT().GetVolume(gomock.Any(), gomock.Any(), gomock.Any(), volumeID). + Return(iaas.ApiGetVolumeRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().GetVolumeExecute(gomock.Any()).Return(&iaas.Volume{Id: new(volumeID), ServerId: nil}, nil) + + mockIaaSClient.EXPECT().AddVolumeToServer(gomock.Any(), gomock.Any(), gomock.Any(), serverID, volumeID). + Return(iaas.ApiAddVolumeToServerRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().AddVolumeToServerExecute(gomock.Any()).Return( + &iaas.VolumeAttachment{VolumeId: new(volumeID), ServerId: new(serverID)}, nil) + + id, err := client.AttachVolume(context.Background(), serverID, volumeID, iaas.AddVolumeToServerPayload{}) + Expect(err).ToNot(HaveOccurred()) + Expect(id).To(Equal(volumeID)) + }) + + It("DetachVolume fails if status is not Available", func() { + mockIaaSClient.EXPECT().GetVolume(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(iaas.ApiGetVolumeRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().GetVolumeExecute(gomock.Any()).Return(&iaas.Volume{Name: new("volume-1"), Id: new(volumeID), Status: new("CREATING")}, nil) + + err := client.DetachVolume(context.Background(), serverID, volumeID) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("its status is CREATING")) + }) + }) + + Context("ExpandVolume", func() { + It("successfully resizes an Available volume", func() { + mockIaaSClient.EXPECT(). + ResizeVolume(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(iaas.ApiResizeVolumeRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().ResizeVolumeExecute(gomock.Any()).Return(nil) + + err := client.ExpandVolume(context.Background(), volumeID, VolumeAvailableStatus, iaas.ResizeVolumePayload{}) + Expect(err).ToNot(HaveOccurred()) + }) + + It("errors when volume is in a bad state for resize", func() { + err := client.ExpandVolume(context.Background(), volumeID, "ERROR", iaas.ResizeVolumePayload{}) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("cannot be resized")) + }) + }) + + Context("Waiting Logic", func() { + It("WaitVolumeTargetStatus returns nil when target status is reached", func() { + mockIaaSClient.EXPECT(). + GetVolume(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(iaas.ApiGetVolumeRequest{ApiService: mockIaaSClient}) + mockIaaSClient.EXPECT().GetVolumeExecute(gomock.Any()).Return(&iaas.Volume{Id: new(volumeID), Status: new("available")}, nil) + + err := client.WaitVolumeTargetStatus(context.Background(), volumeID, []string{"available"}) + Expect(err).ToNot(HaveOccurred()) + }) + + It("WaitDiskAttached returns error on timeout", func() { + mockIaaSClient.EXPECT(). + GetVolume(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(iaas.ApiGetVolumeRequest{ + ApiService: mockIaaSClient, + }) + mockIaaSClient.EXPECT().GetVolumeExecute(gomock.Any()).Return(nil, fmt.Errorf("timeout")) + + err := client.WaitDiskAttached(context.Background(), serverID, volumeID) + Expect(err).To(HaveOccurred()) + }) + }) +}) diff --git a/pkg/stackit/client/loadbalancer_test.go b/pkg/stackit/client/loadbalancer_test.go new file mode 100644 index 00000000..15aca888 --- /dev/null +++ b/pkg/stackit/client/loadbalancer_test.go @@ -0,0 +1,131 @@ +package client + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + loadbalancer "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer/v2api" + "go.uber.org/mock/gomock" + + mock "github.com/stackitcloud/cloud-provider-stackit/pkg/mock/loadbalancer" +) + +var _ = Describe("LoadBalancer", func() { + var ( + mockCtrl *gomock.Controller + mockLBClient *mock.MockDefaultAPI + client *loadBalancingClient + ) + + const ( + lbName = "my-lb" + ) + + BeforeEach(func() { + mockCtrl = gomock.NewController(GinkgoT()) + mockLBClient = mock.NewMockDefaultAPI(mockCtrl) + + client = &loadBalancingClient{ + Client: mockLBClient, + } + }) + + AfterEach(func() { + mockCtrl.Finish() + }) + + Context("LoadBalancer Management", func() { + It("CreateLoadBalancer successfully calls the API", func() { + payload := &loadbalancer.CreateLoadBalancerPayload{Name: new(lbName)} + mockLBClient.EXPECT(). + CreateLoadBalancer(gomock.Any(), gomock.Any(), gomock.Any()). + Return(loadbalancer.ApiCreateLoadBalancerRequest{ApiService: mockLBClient}) + mockLBClient.EXPECT().CreateLoadBalancerExecute(gomock.Any()).Return(&loadbalancer.LoadBalancer{Name: new(lbName)}, nil) + + lb, err := client.CreateLoadBalancer(context.Background(), payload) + Expect(err).ToNot(HaveOccurred()) + Expect(*lb.Name).To(Equal(lbName)) + }) + + It("GetLoadBalancer returns a specific LB", func() { + mockLBClient.EXPECT(). + GetLoadBalancer(gomock.Any(), gomock.Any(), gomock.Any(), lbName). + Return(loadbalancer.ApiGetLoadBalancerRequest{ApiService: mockLBClient}) + mockLBClient.EXPECT().GetLoadBalancerExecute(gomock.Any()).Return(&loadbalancer.LoadBalancer{Name: new(lbName)}, nil) + + lb, err := client.GetLoadBalancer(context.Background(), lbName) + Expect(err).ToNot(HaveOccurred()) + Expect(*lb.Name).To(Equal(lbName)) + }) + + It("UpdateLoadBalancer calls API successfully", func() { + mockLBClient.EXPECT(). + UpdateLoadBalancer(gomock.Any(), gomock.Any(), gomock.Any(), lbName). + Return(loadbalancer.ApiUpdateLoadBalancerRequest{ApiService: mockLBClient}) + mockLBClient.EXPECT().UpdateLoadBalancerExecute(gomock.Any()).Return(nil, nil) + + _, err := client.UpdateLoadBalancer(context.Background(), lbName, &loadbalancer.UpdateLoadBalancerPayload{}) + Expect(err).ToNot(HaveOccurred()) + }) + + It("DeleteLoadBalancer calls API successfully", func() { + mockLBClient.EXPECT(). + DeleteLoadBalancer(gomock.Any(), gomock.Any(), gomock.Any(), lbName). + Return(loadbalancer.ApiDeleteLoadBalancerRequest{ApiService: mockLBClient}) + mockLBClient.EXPECT().DeleteLoadBalancerExecute(gomock.Any()).Return(nil, nil) + + err := client.DeleteLoadBalancer(context.Background(), lbName) + Expect(err).ToNot(HaveOccurred()) + }) + }) + + Context("Target Pools", func() { + It("UpdateTargetPool calls API successfully", func() { + payload := loadbalancer.UpdateTargetPoolPayload{} + mockLBClient.EXPECT(). + UpdateTargetPool(gomock.Any(), gomock.Any(), gomock.Any(), lbName, "pool-1"). + Return(loadbalancer.ApiUpdateTargetPoolRequest{ApiService: mockLBClient}) + mockLBClient.EXPECT().UpdateTargetPoolExecute(gomock.Any()).Return(nil, nil) + + err := client.UpdateTargetPool(context.Background(), lbName, "pool-1", payload) + Expect(err).ToNot(HaveOccurred()) + }) + }) + + Context("Credentials", func() { + It("CreateCredentials returns response on success", func() { + mockLBClient.EXPECT(). + CreateCredentials(gomock.Any(), gomock.Any(), gomock.Any()). + Return(loadbalancer.ApiCreateCredentialsRequest{ApiService: mockLBClient}) + mockLBClient.EXPECT().CreateCredentialsExecute(gomock.Any()).Return(&loadbalancer.CreateCredentialsResponse{Credential: &loadbalancer.CredentialsResponse{ + DisplayName: new("cred-1"), + }}, nil) + + resp, err := client.CreateCredentials(context.Background(), loadbalancer.CreateCredentialsPayload{}) + Expect(err).ToNot(HaveOccurred()) + Expect(*resp.Credential.DisplayName).To(Equal("cred-1")) + }) + + It("ListCredentials returns all credentials", func() { + mockLBClient.EXPECT(). + ListCredentials(gomock.Any(), gomock.Any(), gomock.Any()). + Return(loadbalancer.ApiListCredentialsRequest{ApiService: mockLBClient}) + mockLBClient.EXPECT().ListCredentialsExecute(gomock.Any()).Return(&loadbalancer.ListCredentialsResponse{Credentials: []loadbalancer.CredentialsResponse{{DisplayName: new("cred-1")}}}, nil) + + resp, err := client.ListCredentials(context.Background()) + Expect(err).ToNot(HaveOccurred()) + Expect(resp.Credentials).To(HaveLen(1)) + }) + + It("DeleteCredentials calls API successfully", func() { + mockLBClient.EXPECT(). + DeleteCredentials(gomock.Any(), gomock.Any(), gomock.Any(), "cred-ref"). + Return(loadbalancer.ApiDeleteCredentialsRequest{ApiService: mockLBClient}) + mockLBClient.EXPECT().DeleteCredentialsExecute(gomock.Any()).Return(nil, nil) + + err := client.DeleteCredentials(context.Background(), "cred-ref") + Expect(err).ToNot(HaveOccurred()) + }) + }) +}) diff --git a/pkg/stackit/suite_test.go b/pkg/stackit/client/suite_test.go similarity index 92% rename from pkg/stackit/suite_test.go rename to pkg/stackit/client/suite_test.go index 5450af33..99c512b7 100644 --- a/pkg/stackit/suite_test.go +++ b/pkg/stackit/client/suite_test.go @@ -1,4 +1,4 @@ -package stackit +package client import ( "testing" diff --git a/pkg/stackit/filter_test.go b/pkg/stackit/client/utils_test.go similarity index 71% rename from pkg/stackit/filter_test.go rename to pkg/stackit/client/utils_test.go index 13bd5b47..e2a6aa9c 100644 --- a/pkg/stackit/filter_test.go +++ b/pkg/stackit/client/utils_test.go @@ -1,4 +1,4 @@ -package stackit +package client import ( . "github.com/onsi/ginkgo/v2" @@ -7,7 +7,7 @@ import ( ) var _ = Describe("Filter", func() { - Describe("filterBackups", func() { + Describe("FilterBackups", func() { var ( backups []iaas.Backup filters map[string]string @@ -23,13 +23,13 @@ var _ = Describe("Filter", func() { }) It("should return all backups when filters is nil", func() { - result := filterBackups(backups, nil) + result := FilterBackups(backups, nil) Expect(result).To(Equal(backups)) }) It("should filter by Status", func() { filters["Status"] = "available" - result := filterBackups(backups, filters) + result := FilterBackups(backups, filters) Expect(result).To(HaveLen(2)) Expect(*result[0].Name).To(Equal("backup-1")) Expect(*result[1].Name).To(Equal("backup-3")) @@ -37,14 +37,14 @@ var _ = Describe("Filter", func() { It("should filter by VolumeID", func() { filters["VolumeID"] = "vol-2" - result := filterBackups(backups, filters) + result := FilterBackups(backups, filters) Expect(result).To(HaveLen(1)) Expect(*result[0].Name).To(Equal("backup-2")) }) It("should filter by Name", func() { filters["Name"] = "backup-1" - result := filterBackups(backups, filters) + result := FilterBackups(backups, filters) Expect(result).To(HaveLen(1)) Expect(*result[0].Name).To(Equal("backup-1")) }) @@ -52,14 +52,14 @@ var _ = Describe("Filter", func() { It("should filter by multiple criteria", func() { filters["Status"] = "available" filters["VolumeID"] = "vol-1" - result := filterBackups(backups, filters) + result := FilterBackups(backups, filters) Expect(result).To(HaveLen(2)) Expect(*result[0].Name).To(Equal("backup-1")) Expect(*result[1].Name).To(Equal("backup-3")) }) }) - Describe("filterVolumes", func() { + Describe("FilterVolumes", func() { var ( volumes []iaas.Volume filters map[string]string @@ -75,20 +75,20 @@ var _ = Describe("Filter", func() { }) It("should return all volumes when filters is nil", func() { - result := filterVolumes(volumes, nil) + result := FilterVolumes(volumes, nil) Expect(result).To(Equal(volumes)) }) It("should filter by Name", func() { filters["Name"] = "volume-1" - result := filterVolumes(volumes, filters) + result := FilterVolumes(volumes, filters) Expect(result).To(HaveLen(2)) Expect(*result[0].Name).To(Equal("volume-1")) Expect(*result[1].Name).To(Equal("volume-1")) }) }) - Describe("filterSnapshots", func() { + Describe("FilterSnapshots", func() { var ( snapshots []iaas.Snapshot filters map[string]string @@ -104,13 +104,13 @@ var _ = Describe("Filter", func() { }) It("should return all snapshots when filters is nil", func() { - result := filterSnapshots(snapshots, nil) + result := FilterSnapshots(snapshots, nil) Expect(result).To(Equal(snapshots)) }) It("should filter by Status", func() { filters["Status"] = "available" - result := filterSnapshots(snapshots, filters) + result := FilterSnapshots(snapshots, filters) Expect(result).To(HaveLen(2)) Expect(*result[0].Name).To(Equal("snapshot-1")) Expect(*result[1].Name).To(Equal("snapshot-3")) @@ -118,14 +118,14 @@ var _ = Describe("Filter", func() { It("should filter by VolumeID", func() { filters["VolumeID"] = "vol-2" - result := filterSnapshots(snapshots, filters) + result := FilterSnapshots(snapshots, filters) Expect(result).To(HaveLen(1)) Expect(*result[0].Name).To(Equal("snapshot-2")) }) It("should filter by Name", func() { filters["Name"] = "snapshot-1" - result := filterSnapshots(snapshots, filters) + result := FilterSnapshots(snapshots, filters) Expect(result).To(HaveLen(1)) Expect(*result[0].Name).To(Equal("snapshot-1")) }) @@ -133,10 +133,42 @@ var _ = Describe("Filter", func() { It("should filter by multiple criteria", func() { filters["Status"] = "available" filters["VolumeID"] = "vol-1" - result := filterSnapshots(snapshots, filters) + result := FilterSnapshots(snapshots, filters) Expect(result).To(HaveLen(2)) Expect(*result[0].Name).To(Equal("snapshot-1")) Expect(*result[1].Name).To(Equal("snapshot-3")) }) }) }) + +var _ = Describe("Labels", func() { + Describe("labelsFromTags", func() { + It("should convert tags to labels", func() { + tags := map[string]string{ + "key1": "value1", + "key2": "value2", + } + + labels := LabelsFromTags(tags) + + Expect(labels).To(HaveKeyWithValue("key1", "value1")) + Expect(labels).To(HaveKeyWithValue("key2", "value2")) + }) + + It("should handle empty tags", func() { + tags := map[string]string{} + + labels := LabelsFromTags(tags) + + Expect(labels).To(BeEmpty()) + }) + + It("should handle nil tags", func() { + var tags map[string]string + + labels := LabelsFromTags(tags) + + Expect(labels).To(BeEmpty()) + }) + }) +}) diff --git a/pkg/stackit/filter.go b/pkg/stackit/filter.go deleted file mode 100644 index e91e5853..00000000 --- a/pkg/stackit/filter.go +++ /dev/null @@ -1,73 +0,0 @@ -package stackit - -import ( - iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api" -) - -// TODO: Remove this once the IaaS API supports filtering by name, status, and volume ID. - -//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 -} - -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 -} diff --git a/pkg/stackit/iaas_mock.go b/pkg/stackit/iaas_mock.go deleted file mode 100644 index 397d3cf3..00000000 --- a/pkg/stackit/iaas_mock.go +++ /dev/null @@ -1,411 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: ./pkg/stackit (interfaces: IaasClient) -// -// Generated by this command: -// -// mockgen -destination ./pkg/stackit/iaas_mock.go -package stackit ./pkg/stackit IaasClient -// - -// Package stackit is a generated GoMock package. -package stackit - -import ( - context "context" - reflect "reflect" - - config "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/config" - 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. -type MockIaasClient struct { - ctrl *gomock.Controller - recorder *MockIaasClientMockRecorder - isgomock struct{} -} - -// MockIaasClientMockRecorder is the mock recorder for MockIaasClient. -type MockIaasClientMockRecorder struct { - mock *MockIaasClient -} - -// NewMockIaasClient creates a new mock instance. -func NewMockIaasClient(ctrl *gomock.Controller) *MockIaasClient { - mock := &MockIaasClient{ctrl: ctrl} - mock.recorder = &MockIaasClientMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockIaasClient) EXPECT() *MockIaasClientMockRecorder { - return m.recorder -} - -// AttachVolume mocks base method. -func (m *MockIaasClient) AttachVolume(ctx context.Context, instanceID, volumeID string) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AttachVolume", ctx, instanceID, volumeID) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// AttachVolume indicates an expected call of AttachVolume. -func (mr *MockIaasClientMockRecorder) AttachVolume(ctx, instanceID, volumeID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AttachVolume", reflect.TypeOf((*MockIaasClient)(nil).AttachVolume), ctx, instanceID, volumeID) -} - -// 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) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBackup", reflect.TypeOf((*MockIaasClient)(nil).CreateBackup), ctx, name, volID, snapshotID, tags) -} - -// CreateSnapshot mocks base method. -func (m *MockIaasClient) CreateSnapshot(ctx context.Context, name, volID string, tags map[string]string) (*v2api.Snapshot, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateSnapshot", ctx, name, volID, tags) - ret0, _ := ret[0].(*v2api.Snapshot) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateSnapshot indicates an expected call of CreateSnapshot. -func (mr *MockIaasClientMockRecorder) CreateSnapshot(ctx, name, volID, tags any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSnapshot", reflect.TypeOf((*MockIaasClient)(nil).CreateSnapshot), ctx, name, volID, tags) -} - -// CreateVolume mocks base method. -func (m *MockIaasClient) CreateVolume(arg0 context.Context, arg1 *v2api.CreateVolumePayload) (*v2api.Volume, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateVolume", arg0, arg1) - ret0, _ := ret[0].(*v2api.Volume) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateVolume indicates an expected call of CreateVolume. -func (mr *MockIaasClientMockRecorder) CreateVolume(arg0, arg1 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateVolume", reflect.TypeOf((*MockIaasClient)(nil).CreateVolume), arg0, arg1) -} - -// 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) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBackup", reflect.TypeOf((*MockIaasClient)(nil).DeleteBackup), ctx, backupID) -} - -// DeleteSnapshot mocks base method. -func (m *MockIaasClient) DeleteSnapshot(ctx context.Context, snapID string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteSnapshot", ctx, snapID) - ret0, _ := ret[0].(error) - return ret0 -} - -// DeleteSnapshot indicates an expected call of DeleteSnapshot. -func (mr *MockIaasClientMockRecorder) DeleteSnapshot(ctx, snapID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSnapshot", reflect.TypeOf((*MockIaasClient)(nil).DeleteSnapshot), ctx, snapID) -} - -// 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) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteVolume", reflect.TypeOf((*MockIaasClient)(nil).DeleteVolume), ctx, volumeID) -} - -// DetachVolume mocks base method. -func (m *MockIaasClient) DetachVolume(ctx context.Context, instanceID, volumeID string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DetachVolume", ctx, instanceID, volumeID) - ret0, _ := ret[0].(error) - return ret0 -} - -// DetachVolume indicates an expected call of DetachVolume. -func (mr *MockIaasClientMockRecorder) DetachVolume(ctx, instanceID, volumeID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DetachVolume", reflect.TypeOf((*MockIaasClient)(nil).DetachVolume), ctx, instanceID, volumeID) -} - -// ExpandVolume mocks base method. -func (m *MockIaasClient) ExpandVolume(ctx context.Context, volumeID, status string, size int64) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ExpandVolume", ctx, volumeID, status, size) - ret0, _ := ret[0].(error) - return ret0 -} - -// ExpandVolume indicates an expected call of ExpandVolume. -func (mr *MockIaasClientMockRecorder) ExpandVolume(ctx, volumeID, status, size any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExpandVolume", reflect.TypeOf((*MockIaasClient)(nil).ExpandVolume), ctx, volumeID, status, size) -} - -// GetBackupByID mocks base method. -func (m *MockIaasClient) GetBackupByID(ctx context.Context, backupID string) (*v2api.Backup, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetBackupByID", ctx, backupID) - ret0, _ := ret[0].(*v2api.Backup) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetBackupByID indicates an expected call of GetBackupByID. -func (mr *MockIaasClientMockRecorder) GetBackupByID(ctx, backupID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBackupByID", reflect.TypeOf((*MockIaasClient)(nil).GetBackupByID), ctx, backupID) -} - -// GetBlockStorageOpts mocks base method. -func (m *MockIaasClient) GetBlockStorageOpts() config.BlockStorageOpts { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetBlockStorageOpts") - ret0, _ := ret[0].(config.BlockStorageOpts) - return ret0 -} - -// GetBlockStorageOpts indicates an expected call of GetBlockStorageOpts. -func (mr *MockIaasClientMockRecorder) GetBlockStorageOpts() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockStorageOpts", reflect.TypeOf((*MockIaasClient)(nil).GetBlockStorageOpts)) -} - -// GetInstanceByID mocks base method. -func (m *MockIaasClient) GetInstanceByID(ctx context.Context, instanceID string) (*v2api.Server, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetInstanceByID", ctx, instanceID) - ret0, _ := ret[0].(*v2api.Server) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetInstanceByID indicates an expected call of GetInstanceByID. -func (mr *MockIaasClientMockRecorder) GetInstanceByID(ctx, instanceID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstanceByID", reflect.TypeOf((*MockIaasClient)(nil).GetInstanceByID), ctx, instanceID) -} - -// GetSnapshotByID mocks base method. -func (m *MockIaasClient) GetSnapshotByID(ctx context.Context, snapshotID string) (*v2api.Snapshot, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetSnapshotByID", ctx, snapshotID) - ret0, _ := ret[0].(*v2api.Snapshot) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetSnapshotByID indicates an expected call of GetSnapshotByID. -func (mr *MockIaasClientMockRecorder) GetSnapshotByID(ctx, snapshotID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSnapshotByID", reflect.TypeOf((*MockIaasClient)(nil).GetSnapshotByID), ctx, snapshotID) -} - -// 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 -} - -// GetVolume indicates an expected call of GetVolume. -func (mr *MockIaasClientMockRecorder) GetVolume(ctx, volumeID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVolume", reflect.TypeOf((*MockIaasClient)(nil).GetVolume), ctx, volumeID) -} - -// GetVolumeByName mocks base method. -func (m *MockIaasClient) GetVolumeByName(ctx context.Context, name string) (*v2api.Volume, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetVolumeByName", ctx, name) - ret0, _ := ret[0].(*v2api.Volume) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetVolumeByName indicates an expected call of GetVolumeByName. -func (mr *MockIaasClientMockRecorder) GetVolumeByName(ctx, name any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVolumeByName", reflect.TypeOf((*MockIaasClient)(nil).GetVolumeByName), ctx, name) -} - -// GetVolumesByName mocks base method. -func (m *MockIaasClient) GetVolumesByName(ctx context.Context, name string) ([]v2api.Volume, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetVolumesByName", ctx, name) - ret0, _ := ret[0].([]v2api.Volume) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetVolumesByName indicates an expected call of GetVolumesByName. -func (mr *MockIaasClientMockRecorder) GetVolumesByName(ctx, name any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVolumesByName", reflect.TypeOf((*MockIaasClient)(nil).GetVolumesByName), ctx, name) -} - -// 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) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListBackups", reflect.TypeOf((*MockIaasClient)(nil).ListBackups), ctx, filters) -} - -// 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) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListSnapshots", reflect.TypeOf((*MockIaasClient)(nil).ListSnapshots), ctx, filters) -} - -// ListVolumes mocks base method. -func (m *MockIaasClient) ListVolumes(ctx context.Context, limit int, startingToken string) ([]v2api.Volume, string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListVolumes", ctx, limit, startingToken) - 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, limit, startingToken any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListVolumes", reflect.TypeOf((*MockIaasClient)(nil).ListVolumes), ctx, limit, startingToken) -} - -// 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) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitBackupReady", reflect.TypeOf((*MockIaasClient)(nil).WaitBackupReady), ctx, backupID, snapshotSize, backupMaxDurationSecondsPerGB) -} - -// 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) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitDiskAttached", reflect.TypeOf((*MockIaasClient)(nil).WaitDiskAttached), ctx, instanceID, volumeID) -} - -// 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) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitDiskDetached", reflect.TypeOf((*MockIaasClient)(nil).WaitDiskDetached), ctx, instanceID, volumeID) -} - -// 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) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitSnapshotReady", reflect.TypeOf((*MockIaasClient)(nil).WaitSnapshotReady), ctx, snapshotID) -} - -// 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) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitVolumeTargetStatus", reflect.TypeOf((*MockIaasClient)(nil).WaitVolumeTargetStatus), ctx, volumeID, tStatus) -} - -// WaitVolumeTargetStatusWithCustomBackoff mocks base method. -func (m *MockIaasClient) WaitVolumeTargetStatusWithCustomBackoff(ctx context.Context, volumeID string, targetStatus []string, backoff *wait.Backoff) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "WaitVolumeTargetStatusWithCustomBackoff", ctx, volumeID, targetStatus, backoff) - ret0, _ := ret[0].(error) - return ret0 -} - -// WaitVolumeTargetStatusWithCustomBackoff indicates an expected call of WaitVolumeTargetStatusWithCustomBackoff. -func (mr *MockIaasClientMockRecorder) WaitVolumeTargetStatusWithCustomBackoff(ctx, volumeID, targetStatus, backoff any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitVolumeTargetStatusWithCustomBackoff", reflect.TypeOf((*MockIaasClient)(nil).WaitVolumeTargetStatusWithCustomBackoff), ctx, volumeID, targetStatus, backoff) -} diff --git a/pkg/stackit/instances.go b/pkg/stackit/instances.go deleted file mode 100644 index 0c22b83b..00000000 --- a/pkg/stackit/instances.go +++ /dev/null @@ -1,25 +0,0 @@ -package stackit - -import ( - "context" - "net/http" - - "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/stackiterrors" - "github.com/stackitcloud/stackit-sdk-go/core/runtime" - iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api" - wait "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api/wait" -) - -func (os *iaasClient) GetInstanceByID(ctx context.Context, instanceID string) (*iaas.Server, error) { - var httpResp *http.Response - ctxWithHTTPResp := runtime.WithCaptureHTTPResponse(ctx, &httpResp) - server, err := os.iaas.GetServer(ctxWithHTTPResp, os.projectID, os.region, instanceID).Execute() - if err != nil { - if httpResp != nil { - reqID := httpResp.Header.Get(wait.XRequestIDHeader) - return nil, stackiterrors.WrapErrorWithResponseID(err, reqID) - } - return nil, err - } - return server, nil -} diff --git a/pkg/stackit/labels.go b/pkg/stackit/labels.go deleted file mode 100644 index 0948daac..00000000 --- a/pkg/stackit/labels.go +++ /dev/null @@ -1,15 +0,0 @@ -package stackit - -type labels map[string]any - -func labelsFromTags(tags map[string]string) labels { - // 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 labels(l) -} diff --git a/pkg/stackit/labels_test.go b/pkg/stackit/labels_test.go deleted file mode 100644 index 00753051..00000000 --- a/pkg/stackit/labels_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package stackit - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("Labels", func() { - Describe("labelsFromTags", func() { - It("should convert tags to labels", func() { - tags := map[string]string{ - "key1": "value1", - "key2": "value2", - } - - labels := labelsFromTags(tags) - - Expect(labels).To(HaveKeyWithValue("key1", "value1")) - Expect(labels).To(HaveKeyWithValue("key2", "value2")) - }) - - It("should handle empty tags", func() { - tags := map[string]string{} - - labels := labelsFromTags(tags) - - Expect(labels).To(BeEmpty()) - }) - - It("should handle nil tags", func() { - var tags map[string]string - - labels := labelsFromTags(tags) - - Expect(labels).To(BeEmpty()) - }) - }) -}) diff --git a/pkg/stackit/loadbalancer.go b/pkg/stackit/loadbalancer.go deleted file mode 100644 index 6820c60a..00000000 --- a/pkg/stackit/loadbalancer.go +++ /dev/null @@ -1,76 +0,0 @@ -package stackit - -import ( - "context" - - "github.com/google/uuid" - loadbalancer "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer/v2api" -) - -func (cl lbClient) GetLoadBalancer(ctx context.Context, projectID, name string) (*loadbalancer.LoadBalancer, error) { - lb, err := cl.client.GetLoadBalancer(ctx, projectID, cl.region, name).Execute() - if isOpenAPINotFound(err) { - return lb, ErrorNotFound - } - return lb, err -} - -// DeleteLoadBalancer returns no error if the load balancer doesn't exist. -func (cl lbClient) DeleteLoadBalancer(ctx context.Context, projectID, name string) error { - _, err := cl.client.DeleteLoadBalancer(ctx, projectID, cl.region, name).Execute() - return err -} - -// CreateLoadBalancer returns ErrorNotFound if the project is not enabled. -func (cl lbClient) CreateLoadBalancer(ctx context.Context, projectID string, create *loadbalancer.CreateLoadBalancerPayload) (*loadbalancer.LoadBalancer, error) { - lb, err := cl.client.CreateLoadBalancer(ctx, projectID, cl.region). - CreateLoadBalancerPayload(*create). - XRequestID(uuid.NewString()). - Execute() - if isOpenAPINotFound(err) { - return lb, ErrorNotFound - } - return lb, err -} - -func (cl lbClient) UpdateLoadBalancer(ctx context.Context, projectID, name string, update *loadbalancer.UpdateLoadBalancerPayload) ( - *loadbalancer.LoadBalancer, error, -) { - return cl.client.UpdateLoadBalancer(ctx, projectID, cl.region, name). - UpdateLoadBalancerPayload(*update). - Execute() -} - -func (cl lbClient) UpdateTargetPool(ctx context.Context, projectID, name, targetPoolName string, payload loadbalancer.UpdateTargetPoolPayload) error { - _, err := cl.client.UpdateTargetPool(ctx, projectID, cl.region, name, targetPoolName). - UpdateTargetPoolPayload(payload). - Execute() - return err -} - -func (cl lbClient) ListCredentials(ctx context.Context, projectID string) (*loadbalancer.ListCredentialsResponse, error) { - return cl.client.ListCredentials(ctx, projectID, cl.region).Execute() -} - -func (cl lbClient) GetCredentials(ctx context.Context, projectID, credentialsRef string) (*loadbalancer.GetCredentialsResponse, error) { - return cl.client.GetCredentials(ctx, projectID, cl.region, credentialsRef).Execute() -} - -func (cl lbClient) CreateCredentials(ctx context.Context, projectID string, payload loadbalancer.CreateCredentialsPayload) (*loadbalancer.CreateCredentialsResponse, error) { //nolint:lll // looks weird when shortened - return cl.client.CreateCredentials(ctx, projectID, cl.region). - CreateCredentialsPayload(payload). - XRequestID(uuid.NewString()). - Execute() -} - -func (cl lbClient) UpdateCredentials(ctx context.Context, projectID, credentialsRef string, payload loadbalancer.UpdateCredentialsPayload) error { - _, err := cl.client.UpdateCredentials(ctx, projectID, cl.region, credentialsRef). - UpdateCredentialsPayload(payload). - Execute() - return err -} - -func (cl lbClient) DeleteCredentials(ctx context.Context, projectID, credentialsRef string) error { - _, err := cl.client.DeleteCredentials(ctx, projectID, cl.region, credentialsRef).Execute() - return err -} diff --git a/pkg/stackit/loadbalancer_mock.go b/pkg/stackit/loadbalancer_mock.go deleted file mode 100644 index 18dbf72a..00000000 --- a/pkg/stackit/loadbalancer_mock.go +++ /dev/null @@ -1,188 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: ./pkg/stackit (interfaces: LoadbalancerClient) -// -// Generated by this command: -// -// mockgen -destination ./pkg/stackit/loadbalancer_mock.go -package stackit ./pkg/stackit LoadbalancerClient -// - -// Package stackit is a generated GoMock package. -package stackit - -import ( - context "context" - reflect "reflect" - - v2api "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer/v2api" - gomock "go.uber.org/mock/gomock" -) - -// MockLoadbalancerClient is a mock of LoadbalancerClient interface. -type MockLoadbalancerClient struct { - ctrl *gomock.Controller - recorder *MockLoadbalancerClientMockRecorder - isgomock struct{} -} - -// MockLoadbalancerClientMockRecorder is the mock recorder for MockLoadbalancerClient. -type MockLoadbalancerClientMockRecorder struct { - mock *MockLoadbalancerClient -} - -// NewMockLoadbalancerClient creates a new mock instance. -func NewMockLoadbalancerClient(ctrl *gomock.Controller) *MockLoadbalancerClient { - mock := &MockLoadbalancerClient{ctrl: ctrl} - mock.recorder = &MockLoadbalancerClientMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockLoadbalancerClient) EXPECT() *MockLoadbalancerClientMockRecorder { - return m.recorder -} - -// CreateCredentials mocks base method. -func (m *MockLoadbalancerClient) CreateCredentials(ctx context.Context, projectID string, payload v2api.CreateCredentialsPayload) (*v2api.CreateCredentialsResponse, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateCredentials", ctx, projectID, payload) - ret0, _ := ret[0].(*v2api.CreateCredentialsResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateCredentials indicates an expected call of CreateCredentials. -func (mr *MockLoadbalancerClientMockRecorder) CreateCredentials(ctx, projectID, payload any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateCredentials", reflect.TypeOf((*MockLoadbalancerClient)(nil).CreateCredentials), ctx, projectID, payload) -} - -// CreateLoadBalancer mocks base method. -func (m *MockLoadbalancerClient) CreateLoadBalancer(ctx context.Context, projectID string, loadbalancer *v2api.CreateLoadBalancerPayload) (*v2api.LoadBalancer, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateLoadBalancer", ctx, projectID, loadbalancer) - ret0, _ := ret[0].(*v2api.LoadBalancer) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateLoadBalancer indicates an expected call of CreateLoadBalancer. -func (mr *MockLoadbalancerClientMockRecorder) CreateLoadBalancer(ctx, projectID, loadbalancer any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLoadBalancer", reflect.TypeOf((*MockLoadbalancerClient)(nil).CreateLoadBalancer), ctx, projectID, loadbalancer) -} - -// DeleteCredentials mocks base method. -func (m *MockLoadbalancerClient) DeleteCredentials(ctx context.Context, projectID, credentialRef string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteCredentials", ctx, projectID, credentialRef) - ret0, _ := ret[0].(error) - return ret0 -} - -// DeleteCredentials indicates an expected call of DeleteCredentials. -func (mr *MockLoadbalancerClientMockRecorder) DeleteCredentials(ctx, projectID, credentialRef any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCredentials", reflect.TypeOf((*MockLoadbalancerClient)(nil).DeleteCredentials), ctx, projectID, credentialRef) -} - -// DeleteLoadBalancer mocks base method. -func (m *MockLoadbalancerClient) DeleteLoadBalancer(ctx context.Context, projectID, name string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteLoadBalancer", ctx, projectID, name) - ret0, _ := ret[0].(error) - return ret0 -} - -// DeleteLoadBalancer indicates an expected call of DeleteLoadBalancer. -func (mr *MockLoadbalancerClientMockRecorder) DeleteLoadBalancer(ctx, projectID, name any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLoadBalancer", reflect.TypeOf((*MockLoadbalancerClient)(nil).DeleteLoadBalancer), ctx, projectID, name) -} - -// GetCredentials mocks base method. -func (m *MockLoadbalancerClient) GetCredentials(ctx context.Context, projectID, credentialRef string) (*v2api.GetCredentialsResponse, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetCredentials", ctx, projectID, credentialRef) - ret0, _ := ret[0].(*v2api.GetCredentialsResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetCredentials indicates an expected call of GetCredentials. -func (mr *MockLoadbalancerClientMockRecorder) GetCredentials(ctx, projectID, credentialRef any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCredentials", reflect.TypeOf((*MockLoadbalancerClient)(nil).GetCredentials), ctx, projectID, credentialRef) -} - -// GetLoadBalancer mocks base method. -func (m *MockLoadbalancerClient) GetLoadBalancer(ctx context.Context, projectID, name string) (*v2api.LoadBalancer, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLoadBalancer", ctx, projectID, name) - ret0, _ := ret[0].(*v2api.LoadBalancer) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLoadBalancer indicates an expected call of GetLoadBalancer. -func (mr *MockLoadbalancerClientMockRecorder) GetLoadBalancer(ctx, projectID, name any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLoadBalancer", reflect.TypeOf((*MockLoadbalancerClient)(nil).GetLoadBalancer), ctx, projectID, name) -} - -// ListCredentials mocks base method. -func (m *MockLoadbalancerClient) ListCredentials(ctx context.Context, projectID string) (*v2api.ListCredentialsResponse, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListCredentials", ctx, projectID) - ret0, _ := ret[0].(*v2api.ListCredentialsResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListCredentials indicates an expected call of ListCredentials. -func (mr *MockLoadbalancerClientMockRecorder) ListCredentials(ctx, projectID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListCredentials", reflect.TypeOf((*MockLoadbalancerClient)(nil).ListCredentials), ctx, projectID) -} - -// UpdateCredentials mocks base method. -func (m *MockLoadbalancerClient) UpdateCredentials(ctx context.Context, projectID, credentialRef string, payload v2api.UpdateCredentialsPayload) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateCredentials", ctx, projectID, credentialRef, payload) - ret0, _ := ret[0].(error) - return ret0 -} - -// UpdateCredentials indicates an expected call of UpdateCredentials. -func (mr *MockLoadbalancerClientMockRecorder) UpdateCredentials(ctx, projectID, credentialRef, payload any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateCredentials", reflect.TypeOf((*MockLoadbalancerClient)(nil).UpdateCredentials), ctx, projectID, credentialRef, payload) -} - -// UpdateLoadBalancer mocks base method. -func (m *MockLoadbalancerClient) UpdateLoadBalancer(ctx context.Context, projectID, name string, update *v2api.UpdateLoadBalancerPayload) (*v2api.LoadBalancer, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateLoadBalancer", ctx, projectID, name, update) - ret0, _ := ret[0].(*v2api.LoadBalancer) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// UpdateLoadBalancer indicates an expected call of UpdateLoadBalancer. -func (mr *MockLoadbalancerClientMockRecorder) UpdateLoadBalancer(ctx, projectID, name, update any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLoadBalancer", reflect.TypeOf((*MockLoadbalancerClient)(nil).UpdateLoadBalancer), ctx, projectID, name, update) -} - -// UpdateTargetPool mocks base method. -func (m *MockLoadbalancerClient) UpdateTargetPool(ctx context.Context, projectID, name, targetPoolName string, payload v2api.UpdateTargetPoolPayload) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateTargetPool", ctx, projectID, name, targetPoolName, payload) - ret0, _ := ret[0].(error) - return ret0 -} - -// UpdateTargetPool indicates an expected call of UpdateTargetPool. -func (mr *MockLoadbalancerClientMockRecorder) UpdateTargetPool(ctx, projectID, name, targetPoolName, payload any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTargetPool", reflect.TypeOf((*MockLoadbalancerClient)(nil).UpdateTargetPool), ctx, projectID, name, targetPoolName, payload) -} diff --git a/pkg/stackit/loadbalancer_test.go b/pkg/stackit/loadbalancer_test.go deleted file mode 100644 index 96a83f91..00000000 --- a/pkg/stackit/loadbalancer_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package stackit - -import ( - "context" - "net/http" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/stackitcloud/stackit-sdk-go/core/oapierror" - loadbalancer "github.com/stackitcloud/stackit-sdk-go/services/loadbalancer/v2api" - "go.uber.org/mock/gomock" - "k8s.io/utils/ptr" - - mock "github.com/stackitcloud/cloud-provider-stackit/pkg/mock/loadbalancer" -) - -var _ = Describe("LBAPI Client", func() { - var ( - region = "eu01" - - mockCtrl *gomock.Controller - mockAPI *mock.MockDefaultAPI - lbClient LoadbalancerClient - ) - - BeforeEach(func() { - mockCtrl = gomock.NewController(GinkgoT()) - mockAPI = mock.NewMockDefaultAPI(mockCtrl) - - var err error - lbClient, err = NewLoadbalancerClient(mockAPI, region) - Expect(err).ToNot(HaveOccurred()) - }) - - Describe("GetLoadBalancer", func() { - It("should return the received load balancer instance", func() { - expectedName := "test LB instance" - expectedLB := &loadbalancer.LoadBalancer{Name: new(expectedName)} - mockAPI.EXPECT().GetLoadBalancer(gomock.Any(), "projectID", gomock.Any(), expectedName). - Return(loadbalancer.ApiGetLoadBalancerRequest{ApiService: mockAPI}).Times(1) - mockAPI.EXPECT().GetLoadBalancerExecute(gomock.Any()).Return(expectedLB, nil).Times(1) - - actualLB, err := lbClient.GetLoadBalancer(context.Background(), "projectID", expectedName) - Expect(err).ToNot(HaveOccurred()) - Expect(actualLB).To(Equal(expectedLB)) - actualName := ptr.Deref(actualLB.Name, "") - Expect(actualName).To(Equal(expectedName)) - }) - - It("should use the configured STACKIT region", func() { - mockAPI.EXPECT().GetLoadBalancer(gomock.Any(), gomock.Any(), region, gomock.Any()). - Return(loadbalancer.ApiGetLoadBalancerRequest{ApiService: mockAPI}).Times(1) - mockAPI.EXPECT().GetLoadBalancerExecute(gomock.Any()).Return(&loadbalancer.LoadBalancer{}, nil).Times(1) - - _, err := lbClient.GetLoadBalancer(context.Background(), "projectID", "name") - Expect(err).ToNot(HaveOccurred()) - }) - - It("should return ErrorNotFound if a GenericOpenAPIError with status 404 occurs", func() { - mockAPI.EXPECT().GetLoadBalancer(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). - Return(loadbalancer.ApiGetLoadBalancerRequest{ApiService: mockAPI}).Times(1) - mockAPI.EXPECT().GetLoadBalancerExecute(gomock.Any()).Return(nil, &oapierror.GenericOpenAPIError{StatusCode: http.StatusNotFound}).Times(1) - - actualLB, err := lbClient.GetLoadBalancer(context.Background(), "projectID", "name") - Expect(actualLB).To(BeNil()) - Expect(err).To(MatchError(ErrorNotFound)) - }) - }) -}) diff --git a/pkg/stackit/server.go b/pkg/stackit/server.go deleted file mode 100644 index 69444f33..00000000 --- a/pkg/stackit/server.go +++ /dev/null @@ -1,39 +0,0 @@ -package stackit - -import ( - "context" - - iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api" -) - -func (cl nodeClient) GetServer(ctx context.Context, projectID, region, serverID string) (*iaas.Server, error) { - server, err := cl.client.DefaultAPI.GetServer(ctx, projectID, region, serverID).Details(true).Execute() - if isOpenAPINotFound(err) { - return server, ErrorNotFound - } - return server, err -} - -func (cl nodeClient) DeleteServer(ctx context.Context, projectID, region, serverID string) error { - return cl.client.DefaultAPI.DeleteServer(ctx, projectID, region, serverID).Execute() -} - -func (cl nodeClient) CreateServer(ctx context.Context, projectID, region string, create *iaas.CreateServerPayload) (*iaas.Server, error) { - server, err := cl.client.DefaultAPI.CreateServer(ctx, projectID, region).CreateServerPayload(*create).Execute() - if isOpenAPINotFound(err) { - return server, ErrorNotFound - } - return server, err -} - -func (cl nodeClient) UpdateServer(ctx context.Context, projectID, region, serverID string, update *iaas.UpdateServerPayload) (*iaas.Server, error) { - return cl.client.DefaultAPI.UpdateServer(ctx, projectID, region, serverID).UpdateServerPayload(*update).Execute() -} - -func (cl nodeClient) ListServers(ctx context.Context, projectID, region string) (*[]iaas.Server, error) { - resp, err := cl.client.DefaultAPI.ListServers(ctx, projectID, region).Details(true).Execute() - if err != nil { - return nil, err - } - return &resp.Items, nil -} diff --git a/pkg/stackit/server_mock.go b/pkg/stackit/server_mock.go deleted file mode 100644 index 0de0902c..00000000 --- a/pkg/stackit/server_mock.go +++ /dev/null @@ -1,116 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: ./pkg/stackit (interfaces: NodeClient) -// -// Generated by this command: -// -// mockgen -destination ./pkg/stackit/server_mock.go -package stackit ./pkg/stackit NodeClient -// - -// Package stackit is a generated GoMock package. -package stackit - -import ( - context "context" - reflect "reflect" - - v2api "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api" - gomock "go.uber.org/mock/gomock" -) - -// MockNodeClient is a mock of NodeClient interface. -type MockNodeClient struct { - ctrl *gomock.Controller - recorder *MockNodeClientMockRecorder - isgomock struct{} -} - -// MockNodeClientMockRecorder is the mock recorder for MockNodeClient. -type MockNodeClientMockRecorder struct { - mock *MockNodeClient -} - -// NewMockNodeClient creates a new mock instance. -func NewMockNodeClient(ctrl *gomock.Controller) *MockNodeClient { - mock := &MockNodeClient{ctrl: ctrl} - mock.recorder = &MockNodeClientMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockNodeClient) EXPECT() *MockNodeClientMockRecorder { - return m.recorder -} - -// CreateServer mocks base method. -func (m *MockNodeClient) CreateServer(ctx context.Context, projectID, region string, create *v2api.CreateServerPayload) (*v2api.Server, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateServer", ctx, projectID, region, create) - ret0, _ := ret[0].(*v2api.Server) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateServer indicates an expected call of CreateServer. -func (mr *MockNodeClientMockRecorder) CreateServer(ctx, projectID, region, create any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateServer", reflect.TypeOf((*MockNodeClient)(nil).CreateServer), ctx, projectID, region, create) -} - -// DeleteServer mocks base method. -func (m *MockNodeClient) DeleteServer(ctx context.Context, projectID, region, serverID string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteServer", ctx, projectID, region, serverID) - ret0, _ := ret[0].(error) - return ret0 -} - -// DeleteServer indicates an expected call of DeleteServer. -func (mr *MockNodeClientMockRecorder) DeleteServer(ctx, projectID, region, serverID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteServer", reflect.TypeOf((*MockNodeClient)(nil).DeleteServer), ctx, projectID, region, serverID) -} - -// GetServer mocks base method. -func (m *MockNodeClient) GetServer(ctx context.Context, projectID, region, serverID string) (*v2api.Server, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetServer", ctx, projectID, region, serverID) - ret0, _ := ret[0].(*v2api.Server) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetServer indicates an expected call of GetServer. -func (mr *MockNodeClientMockRecorder) GetServer(ctx, projectID, region, serverID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServer", reflect.TypeOf((*MockNodeClient)(nil).GetServer), ctx, projectID, region, serverID) -} - -// ListServers mocks base method. -func (m *MockNodeClient) ListServers(ctx context.Context, projectID, region string) (*[]v2api.Server, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListServers", ctx, projectID, region) - ret0, _ := ret[0].(*[]v2api.Server) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ListServers indicates an expected call of ListServers. -func (mr *MockNodeClientMockRecorder) ListServers(ctx, projectID, region any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListServers", reflect.TypeOf((*MockNodeClient)(nil).ListServers), ctx, projectID, region) -} - -// UpdateServer mocks base method. -func (m *MockNodeClient) UpdateServer(ctx context.Context, projectID, region, serverID string, update *v2api.UpdateServerPayload) (*v2api.Server, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateServer", ctx, projectID, region, serverID, update) - ret0, _ := ret[0].(*v2api.Server) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// UpdateServer indicates an expected call of UpdateServer. -func (mr *MockNodeClientMockRecorder) UpdateServer(ctx, projectID, region, serverID, update any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateServer", reflect.TypeOf((*MockNodeClient)(nil).UpdateServer), ctx, projectID, region, serverID, update) -} diff --git a/pkg/stackit/snapshots.go b/pkg/stackit/snapshots.go deleted file mode 100644 index 3dd8b5dd..00000000 --- a/pkg/stackit/snapshots.go +++ /dev/null @@ -1,133 +0,0 @@ -package stackit - -import ( - "context" - "fmt" - "net/http" - "time" - - "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/stackiterrors" - "github.com/stackitcloud/stackit-sdk-go/core/runtime" - iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api" - sdkWait "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api/wait" - "k8s.io/apimachinery/pkg/util/wait" -) - -const ( - SnapshotReadyStatus = "AVAILABLE" - snapReadyDuration = 1 * time.Second - snapReadyFactor = 1.2 - snapReadySteps = 10 - - SnapshotType = "type" - SnapshotAvailabilityZone = "availability" -) - -func (os *iaasClient) CreateSnapshot(ctx context.Context, name, volID string, tags map[string]string) (*iaas.Snapshot, error) { - opts := iaas.CreateSnapshotPayload{ - VolumeId: volID, - Name: new(name), - } - if tags != nil { - opts.Labels = labelsFromTags(tags) - } - var httpResp *http.Response - ctxWithHTTPResp := runtime.WithCaptureHTTPResponse(ctx, &httpResp) - snapshot, err := os.iaas.CreateSnapshot(ctxWithHTTPResp, os.projectID, os.region).CreateSnapshotPayload(opts).Execute() - if err != nil { - if httpResp != nil { - reqID := httpResp.Header.Get(sdkWait.XRequestIDHeader) - return nil, stackiterrors.WrapErrorWithResponseID(err, reqID) - } - return nil, err - } - - return snapshot, nil -} - -func (os *iaasClient) ListSnapshots(ctx context.Context, filters map[string]string) ([]iaas.Snapshot, string, error) { - var httpResp *http.Response - ctxWithHTTPResp := runtime.WithCaptureHTTPResponse(ctx, &httpResp) - // TODO: Add API filter once available. - snaps, err := os.iaas.ListSnapshotsInProject(ctxWithHTTPResp, os.projectID, os.region).Execute() - if err != nil { - if httpResp != nil { - reqID := httpResp.Header.Get(sdkWait.XRequestIDHeader) - return nil, "", stackiterrors.WrapErrorWithResponseID(err, reqID) - } - return nil, "", err - } - - filteredSnaps := filterSnapshots(snaps.Items, filters) - return filteredSnaps, "", nil -} - -func (os *iaasClient) DeleteSnapshot(ctx context.Context, snapID string) error { - var httpResp *http.Response - ctxWithHTTPResp := runtime.WithCaptureHTTPResponse(ctx, &httpResp) - err := os.iaas.DeleteSnapshot(ctxWithHTTPResp, os.projectID, os.region, snapID).Execute() - if err != nil { - if httpResp != nil { - reqID := httpResp.Header.Get(sdkWait.XRequestIDHeader) - return stackiterrors.WrapErrorWithResponseID(err, reqID) - } - return err - } - return nil -} - -func (os *iaasClient) GetSnapshotByID(ctx context.Context, snapshotID string) (*iaas.Snapshot, error) { - var httpResp *http.Response - ctxWithHTTPResp := runtime.WithCaptureHTTPResponse(ctx, &httpResp) - snap, err := os.iaas.GetSnapshot(ctxWithHTTPResp, os.projectID, os.region, snapshotID).Execute() - if err != nil { - if httpResp != nil { - reqID := httpResp.Header.Get(sdkWait.XRequestIDHeader) - return nil, stackiterrors.WrapErrorWithResponseID(err, reqID) - } - return nil, err - } - return snap, nil -} - -func (os *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 := os.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, _ := os.GetSnapshotByID(ctx, snapshotID) - - if snap != nil { - return snap.Status, err - } - return new("Failed to get snapshot status"), err -} - -func (os *iaasClient) snapshotIsReady(ctx context.Context, snapshotID string) (bool, error) { - var httpResp *http.Response - ctxWithHTTPResp := runtime.WithCaptureHTTPResponse(ctx, &httpResp) - snap, err := os.iaas.GetSnapshot(ctxWithHTTPResp, os.projectID, os.region, snapshotID).Execute() - if err != nil { - if httpResp != nil { - reqID := httpResp.Header.Get(sdkWait.XRequestIDHeader) - return false, stackiterrors.WrapErrorWithResponseID(err, reqID) - } - return false, err - } - - return *snap.Status == SnapshotReadyStatus, nil -} diff --git a/pkg/stackit/snapshots_test.go b/pkg/stackit/snapshots_test.go deleted file mode 100644 index 017201d5..00000000 --- a/pkg/stackit/snapshots_test.go +++ /dev/null @@ -1,124 +0,0 @@ -package stackit - -import ( - "context" - "os" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - stackitconfig "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/config" - iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api" - "go.uber.org/mock/gomock" - - mock "github.com/stackitcloud/cloud-provider-stackit/pkg/mock/iaas" -) - -var _ = Describe("Snapshot", func() { - var ( - err error - mockCtrl *gomock.Controller - mockAPI *mock.MockDefaultAPI - stackitClient IaasClient - config *stackitconfig.CSIConfig - ) - const projectID = "project-id" - const region = "eu01" - - BeforeEach(func() { - t := GinkgoT() - mockCtrl = gomock.NewController(t) - mockAPI = mock.NewMockDefaultAPI(mockCtrl) - t.Setenv("STACKIT_REGION", region) - Expect(os.Getenv("STACKIT_REGION")).To(Equal(region)) - }) - - Context("ListSnapshot", func() { - - snapShotListResponse := iaas.SnapshotListResponse{ - Items: []iaas.Snapshot{ - { - Id: new("fake-snapshot"), - Name: new("fake-snapshot"), - VolumeId: "some-special-volume", - Status: new("ERROR"), - }, - { - Id: new("fake-snapshot2"), - Name: new("fake-snapshot2"), - VolumeId: "some-special-volume", - Status: new("AVAILABLE"), - }, - { - Id: new("wrong snapshot"), - Name: new("wrong snapshot"), - VolumeId: "another-special-volume", - Status: new("AVAILABLE"), - }, - }, - } - - BeforeEach(func() { - config = &stackitconfig.CSIConfig{ - Global: stackitconfig.GlobalOpts{ - ProjectID: projectID, - }, - } - stackitClient, err = CreateSTACKITProvider(mockAPI, config) - Expect(err).ToNot(HaveOccurred()) - }) - - DescribeTable("should return a filtered list of snapshots", - func(filters map[string]string, expectedSnaps []iaas.Snapshot) { - mockAPI.EXPECT().ListSnapshotsInProject(gomock.Any(), config.Global.ProjectID, region).Return(iaas.ApiListSnapshotsInProjectRequest{ApiService: mockAPI}) - mockAPI.EXPECT().ListSnapshotsInProjectExecute(gomock.Any()).Return(&snapShotListResponse, nil) - - snaps, _, err := stackitClient.ListSnapshots(context.Background(), filters) - Expect(err).ToNot(HaveOccurred()) - Expect(snaps).To(Equal(expectedSnaps)) - }, - Entry("filter by VolumeID", - map[string]string{"VolumeID": "some-special-volume"}, - []iaas.Snapshot{ - { - Id: new("fake-snapshot"), - Name: new("fake-snapshot"), - VolumeId: "some-special-volume", - Status: new("ERROR"), - }, - { - Id: new("fake-snapshot2"), - Name: new("fake-snapshot2"), - VolumeId: "some-special-volume", - Status: new("AVAILABLE"), - }, - }, - ), - Entry("filter by name", - map[string]string{"Name": "fake-snapshot"}, - []iaas.Snapshot{ - { - Id: new("fake-snapshot"), - Name: new("fake-snapshot"), - VolumeId: "some-special-volume", - Status: new("ERROR"), - }, - }, - ), - Entry("filter by status and name", - map[string]string{"Name": "fake-snapshot2", "Status": "AVAILABLE"}, - []iaas.Snapshot{ - { - Id: new("fake-snapshot2"), - Name: new("fake-snapshot2"), - VolumeId: "some-special-volume", - Status: new("AVAILABLE"), - }, - }, - ), - Entry("no filters", - map[string]string{}, - snapShotListResponse.Items, - ), - ) - }) -}) diff --git a/pkg/stackit/types.go b/pkg/stackit/types.go deleted file mode 100644 index 1dc9c44b..00000000 --- a/pkg/stackit/types.go +++ /dev/null @@ -1,9 +0,0 @@ -package stackit - -type VolumeSourceTypes string - -const ( - VolumeSource VolumeSourceTypes = "volume" - SnapshotSource VolumeSourceTypes = "snapshot" - BackupSource VolumeSourceTypes = "backup" -) diff --git a/pkg/stackit/volumes.go b/pkg/stackit/volumes.go deleted file mode 100644 index 5813e54a..00000000 --- a/pkg/stackit/volumes.go +++ /dev/null @@ -1,344 +0,0 @@ -package stackit - -import ( - "context" - "fmt" - "net/http" - "slices" - "time" - - "github.com/stackitcloud/cloud-provider-stackit/pkg/stackit/stackiterrors" - "github.com/stackitcloud/stackit-sdk-go/core/runtime" - iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api" - sdkWait "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api/wait" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/klog/v2" - "k8s.io/utils/ptr" -) - -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" -) - -var volumeErrorStates = [...]string{"ERROR", "ERROR_RESIZING", "ERROR_DELETING"} - -func (os *iaasClient) CreateVolume(ctx context.Context, payload *iaas.CreateVolumePayload) (*iaas.Volume, error) { - payload.Description = new(VolumeDescription) - var httpResp *http.Response - ctxWithHTTPResp := runtime.WithCaptureHTTPResponse(ctx, &httpResp) - req, err := os.iaas.CreateVolume(ctxWithHTTPResp, os.projectID, os.region).CreateVolumePayload(*payload).Execute() - if err != nil { - if httpResp != nil { - reqID := httpResp.Header.Get(sdkWait.XRequestIDHeader) - return nil, stackiterrors.WrapErrorWithResponseID(err, reqID) - } - return nil, err - } - - return req, nil -} - -func (os *iaasClient) DeleteVolume(ctx context.Context, volumeID string) error { - used, err := os.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) - } - - var httpResp *http.Response - ctxWithHTTPResp := runtime.WithCaptureHTTPResponse(ctx, &httpResp) - err = os.iaas.DeleteVolume(ctxWithHTTPResp, os.projectID, os.region, volumeID).Execute() - if err != nil { - if httpResp != nil { - reqID := httpResp.Header.Get(sdkWait.XRequestIDHeader) - return stackiterrors.WrapErrorWithResponseID(err, reqID) - } - return err - } - - return err -} - -func (os *iaasClient) AttachVolume(ctx context.Context, instanceID, volumeID string) (string, error) { - volume, err := os.GetVolume(ctx, volumeID) - if err != nil { - return "", err - } - - if volume.ServerId != nil && instanceID == *volume.ServerId { - klog.V(4).Infof("Disk %s is already attached to instance %s", volumeID, instanceID) - return *volume.Id, nil - } - payload := iaas.AddVolumeToServerPayload{ - DeleteOnTermination: new(false), - } - var httpResp *http.Response - ctxWithHTTPResp := runtime.WithCaptureHTTPResponse(ctx, &httpResp) - _, err = os.iaas.AddVolumeToServer(ctxWithHTTPResp, os.projectID, os.region, instanceID, volumeID).AddVolumeToServerPayload(payload).Execute() - if err != nil { - if httpResp != nil { - reqID := httpResp.Header.Get(sdkWait.XRequestIDHeader) - return "", stackiterrors.WrapErrorWithResponseID(err, reqID) - } - return "", err - } - return *volume.Id, err -} - -// WaitVolumeTargetStatusWithCustomBackoff waits for volume to be in target state with custom backoff -func (os *iaasClient) WaitVolumeTargetStatusWithCustomBackoff(ctx context.Context, volumeID string, tStatus []string, backoff *wait.Backoff) error { - waitErr := wait.ExponentialBackoff(*backoff, func() (bool, error) { - vol, err := os.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 -} - -func (os *iaasClient) ListVolumes(ctx context.Context, _ int, _ string) ([]iaas.Volume, string, error) { - // TODO: Add support for pagination when IaaS adds it - var httpResp *http.Response - ctxWithHTTPResp := runtime.WithCaptureHTTPResponse(ctx, &httpResp) - volumes, err := os.iaas.ListVolumes(ctxWithHTTPResp, os.projectID, os.region).Execute() - if err != nil { - if httpResp != nil { - reqID := httpResp.Header.Get(sdkWait.XRequestIDHeader) - return nil, "", stackiterrors.WrapErrorWithResponseID(err, reqID) - } - return nil, "", err - } - - return volumes.Items, "", err -} - -func (os *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 := os.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 (os *iaasClient) DetachVolume(ctx context.Context, instanceID, volumeID string) error { - volume, err := os.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, instanceID) - return nil - } - - if *volume.Status != VolumeAttachedStatus { - return fmt.Errorf("can not detach volume %s, its status is %s", *volume.Name, *volume.Status) - } - var httpResp *http.Response - ctxWithHTTPResp := runtime.WithCaptureHTTPResponse(ctx, &httpResp) - if volume.ServerId != nil && *volume.ServerId == instanceID { - err = os.iaas.RemoveVolumeFromServer(ctxWithHTTPResp, os.projectID, os.region, instanceID, volumeID).Execute() - if err != nil { - if httpResp != nil { - reqID := httpResp.Header.Get(sdkWait.XRequestIDHeader) - return stackiterrors.WrapErrorWithResponseID(fmt.Errorf("failed to detach volume %s from compute %s : %v", *volume.Id, instanceID, err), reqID) - } - return err - } - klog.V(2).Infof("Successfully detached volume: %s from compute: %s", *volume.Id, instanceID) - return nil - } - - // Disk has no attachments or not attached to provided compute - return nil -} - -func (os *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 := os.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 -} - -// diskIsUsed returns true whether a disk is attached to any node -func (os *iaasClient) diskIsUsed(ctx context.Context, volumeID string) (bool, error) { - volume, err := os.GetVolume(ctx, volumeID) - if err != nil { - return false, err - } - - diskUsed := volume.ServerId != nil && *volume.ServerId != "" - - return diskUsed, nil -} - -// diskIsAttached queries if a volume is attached to a compute instance -func (os *iaasClient) diskIsAttached(ctx context.Context, instanceID, volumeID string) (bool, error) { - volume, err := os.GetVolume(ctx, volumeID) - if err != nil { - return false, err - } - - if volume.ServerId != nil && *volume.ServerId == instanceID { - return true, nil - } - return false, nil -} - -func (os *iaasClient) GetVolume(ctx context.Context, volumeID string) (*iaas.Volume, error) { - var httpResp *http.Response - ctxWithHTTPResp := runtime.WithCaptureHTTPResponse(ctx, &httpResp) - vol, err := os.iaas.GetVolume(ctxWithHTTPResp, os.projectID, os.region, volumeID).Execute() - if err != nil { - if httpResp != nil { - reqID := httpResp.Header.Get(sdkWait.XRequestIDHeader) - return nil, stackiterrors.WrapErrorWithResponseID(err, reqID) - } - return nil, err - } - return vol, nil -} - -func (os *iaasClient) GetVolumesByName(ctx context.Context, volName string) ([]iaas.Volume, error) { - var httpResp *http.Response - ctxWithHTTPResp := runtime.WithCaptureHTTPResponse(ctx, &httpResp) - // TODO: Add API filter once available. - volumes, err := os.iaas.ListVolumes(ctxWithHTTPResp, os.projectID, os.region).Execute() - if err != nil { - if httpResp != nil { - reqID := httpResp.Header.Get(sdkWait.XRequestIDHeader) - return nil, stackiterrors.WrapErrorWithResponseID(err, reqID) - } - return nil, err - } - - filterMap := map[string]string{"Name": volName} - filteredVolumes := filterVolumes(volumes.Items, filterMap) - - return filteredVolumes, nil -} - -func (os *iaasClient) GetVolumeByName(ctx context.Context, name string) (*iaas.Volume, error) { - vols, err := os.GetVolumesByName(ctx, name) - if err != nil { - return nil, err - } - - if len(vols) == 0 { - return nil, stackiterrors.ErrNotFound - } - - if len(vols) > 1 { - return nil, fmt.Errorf("found %d volumes with name %q", len(vols), name) - } - - return &vols[0], nil -} - -func (os *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 := os.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 (os *iaasClient) ExpandVolume(ctx context.Context, volumeID, volumeStatus string, newSize int64) error { - extendOpts := iaas.ResizeVolumePayload{Size: newSize} - var httpResp *http.Response - ctxWithHTTPResp := runtime.WithCaptureHTTPResponse(ctx, &httpResp) - - switch volumeStatus { - case VolumeAttachedStatus, VolumeAvailableStatus: - resizeErr := os.iaas.ResizeVolume(ctxWithHTTPResp, os.projectID, os.region, volumeID).ResizeVolumePayload(extendOpts).Execute() - if resizeErr != nil { - if httpResp != nil { - reqID := httpResp.Header.Get(sdkWait.XRequestIDHeader) - return stackiterrors.WrapErrorWithResponseID(resizeErr, reqID) - } - return resizeErr - } - return nil - default: - return fmt.Errorf("volume cannot be resized, when status is %s", volumeStatus) - } -}