From 9dfd130c291f0371d31d320dbd5092993abaac2f Mon Sep 17 00:00:00 2001 From: Daniil Fedotov <daniil.fedotov@kasten.io> Date: Fri, 15 Mar 2024 13:00:37 -0400 Subject: [PATCH] feat!: remove unused blocksttorage providers (#2666) Currently vmware and efs blockstorage providers cannot be used from functions. We decided to eventually remove blockstorage package in favour of CSI interfaces, hence these providers will not be used in the future. BREAKING CHANGES: for applications using kanister code as a dependency this change removes two packages `github.com/kanisterio/kanister/pkg/blockstorage/vmware` and `github.com/kanisterio/kanister/pkg/blockstorage/awsefs` --- go.mod | 1 - go.sum | 4 - pkg/blockstorage/awsefs/awsefs.go | 685 -------------- pkg/blockstorage/awsefs/awsefs_test.go | 50 - pkg/blockstorage/awsefs/conversion.go | 202 ---- pkg/blockstorage/awsefs/conversion_test.go | 98 -- pkg/blockstorage/awsefs/error.go | 64 -- pkg/blockstorage/awsefs/filter.go | 41 - pkg/blockstorage/awsefs/wait.go | 156 ---- pkg/blockstorage/tags/tags.go | 13 - pkg/blockstorage/vmware/conversion.go | 85 -- pkg/blockstorage/vmware/conversion_test.go | 40 - pkg/blockstorage/vmware/vmware.go | 870 ------------------ pkg/blockstorage/vmware/vmware_manual_test.go | 53 -- pkg/blockstorage/vmware/vmware_test.go | 547 ----------- 15 files changed, 2909 deletions(-) delete mode 100644 pkg/blockstorage/awsefs/awsefs.go delete mode 100644 pkg/blockstorage/awsefs/awsefs_test.go delete mode 100644 pkg/blockstorage/awsefs/conversion.go delete mode 100644 pkg/blockstorage/awsefs/conversion_test.go delete mode 100644 pkg/blockstorage/awsefs/error.go delete mode 100644 pkg/blockstorage/awsefs/filter.go delete mode 100644 pkg/blockstorage/awsefs/wait.go delete mode 100644 pkg/blockstorage/vmware/conversion.go delete mode 100644 pkg/blockstorage/vmware/conversion_test.go delete mode 100644 pkg/blockstorage/vmware/vmware.go delete mode 100644 pkg/blockstorage/vmware/vmware_manual_test.go delete mode 100644 pkg/blockstorage/vmware/vmware_test.go diff --git a/go.mod b/go.mod index 4ff331d6c6..78e88ca309 100644 --- a/go.mod +++ b/go.mod @@ -43,7 +43,6 @@ require ( github.com/prometheus/client_model v0.6.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0 - github.com/vmware/govmomi v0.36.1 go.uber.org/zap v1.27.0 golang.org/x/oauth2 v0.18.0 gonum.org/v1/gonum v0.14.0 diff --git a/go.sum b/go.sum index 9159b67b63..cf03ebe4f1 100644 --- a/go.sum +++ b/go.sum @@ -146,8 +146,6 @@ github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dougm/pretty v0.0.0-20171025230240-2ee9d7453c02 h1:tR3jsKPiO/mb6ntzk/dJlHZtm37CPfVp1C9KIo534+4= -github.com/dougm/pretty v0.0.0-20171025230240-2ee9d7453c02/go.mod h1:7NQ3kWOx2cZOSjtcveTa5nqupVr2s6/83sG+rTlI7uA= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= @@ -526,8 +524,6 @@ github.com/studio-b12/gowebdav v0.9.0 h1:1j1sc9gQnNxbXXM4M/CebPOX4aXYtr7MojAVcN4 github.com/studio-b12/gowebdav v0.9.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE= github.com/tg123/go-htpasswd v1.2.2 h1:tmNccDsQ+wYsoRfiONzIhDm5OkVHQzN3w4FOBAlN6BY= github.com/tg123/go-htpasswd v1.2.2/go.mod h1:FcIrK0J+6zptgVwK1JDlqyajW/1B4PtuJ/FLWl7nx8A= -github.com/vmware/govmomi v0.36.1 h1:+E/nlfteQ8JvC0xhuKAfpnMsuIeGeGj7rJwqENUcWm8= -github.com/vmware/govmomi v0.36.1/go.mod h1:mtGWtM+YhTADHlCgJBiskSRPOZRsN9MSjPzaZLte/oQ= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= diff --git a/pkg/blockstorage/awsefs/awsefs.go b/pkg/blockstorage/awsefs/awsefs.go deleted file mode 100644 index fdfc2d49ba..0000000000 --- a/pkg/blockstorage/awsefs/awsefs.go +++ /dev/null @@ -1,685 +0,0 @@ -// Copyright 2019 The Kanister 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 awsefs - -import ( - "context" - "fmt" - "strings" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/backup" - awsefs "github.com/aws/aws-sdk-go/service/efs" - "github.com/aws/aws-sdk-go/service/sts" - uuid "github.com/gofrs/uuid" - "github.com/pkg/errors" - "k8s.io/apimachinery/pkg/util/rand" - - awsconfig "github.com/kanisterio/kanister/pkg/aws" - "github.com/kanisterio/kanister/pkg/blockstorage" - kantags "github.com/kanisterio/kanister/pkg/blockstorage/tags" - "github.com/kanisterio/kanister/pkg/field" - "github.com/kanisterio/kanister/pkg/log" -) - -type Efs struct { - *awsefs.EFS - *backup.Backup - accountID string - region string - role string - backupVaultName string -} - -var _ blockstorage.Provider = (*Efs)(nil) - -const ( - generalPurposePerformanceMode = awsefs.PerformanceModeGeneralPurpose - defaultPerformanceMode = generalPurposePerformanceMode - - burstingThroughputMode = awsefs.ThroughputModeBursting - defaultThroughputMode = burstingThroughputMode - - efsType = "EFS" - maxRetries = 10 -) - -var allowedMetadataKeys = map[string]bool{ - "file-system-id": true, - "Encrypted": true, - "KmsKeyId": true, - "PerformanceMode": true, - "CreationToken": true, - "newFileSystem": true, -} - -// NewEFSProvider returns a blockstorage provider for AWS EFS. -func NewEFSProvider(ctx context.Context, config map[string]string) (blockstorage.Provider, error) { - awsConfig, region, err := awsconfig.GetConfig(ctx, config) - if err != nil { - return nil, errors.Wrap(err, "Failed to get configuration for EFS") - } - s, err := session.NewSession(awsConfig) - if err != nil { - return nil, errors.Wrap(err, "Failed to create session for EFS") - } - stsCli := sts.New(s, aws.NewConfig().WithRegion(region).WithMaxRetries(maxRetries)) - user, err := stsCli.GetCallerIdentity(&sts.GetCallerIdentityInput{}) - if err != nil { - return nil, errors.Wrap(err, "Failed to get user") - } - if user.Account == nil { - return nil, errors.New("Account ID is empty") - } - accountID := *user.Account - efsCli := awsefs.New(s, aws.NewConfig().WithRegion(region).WithCredentials(awsConfig.Credentials).WithMaxRetries(maxRetries)) - backupCli := backup.New(s, aws.NewConfig().WithRegion(region).WithCredentials(awsConfig.Credentials).WithMaxRetries(maxRetries)) - - efsVault, ok := config[awsconfig.ConfigEFSVaultName] - if !ok || efsVault == "" { - return nil, errors.New("EFS vault name is empty") - } - - return &Efs{ - EFS: efsCli, - Backup: backupCli, - region: region, - accountID: accountID, - role: config[awsconfig.ConfigRole], - backupVaultName: efsVault, - }, nil -} - -func (e *Efs) Type() blockstorage.Type { - return blockstorage.TypeEFS -} - -// VolumeCreate implements interface method for EFS. It sends EFS volume create request -// to AWS EFS and waits until the file system is available. Eventually, it returns the -// volume info that is sent back from the AWS EFS. -func (e *Efs) VolumeCreate(ctx context.Context, volume blockstorage.Volume) (*blockstorage.Volume, error) { - req := &awsefs.CreateFileSystemInput{} - reqId, err := uuid.NewV4() - if err != nil { - return nil, errors.Wrap(err, "Failed to create UUID") - } - req.SetCreationToken(reqId.String()) - req.SetPerformanceMode(defaultPerformanceMode) - req.SetThroughputMode(defaultThroughputMode) - req.SetTags(convertToEFSTags(blockstorage.KeyValueToMap(volume.Tags))) - - fd, err := e.CreateFileSystemWithContext(ctx, req) - if err != nil { - return nil, errors.Wrap(err, "Failed to create EFS instance") - } - if fd.FileSystemId == nil { - return nil, errors.New("Empty filesystem ID") - } - if err = e.waitUntilFileSystemAvailable(ctx, *fd.FileSystemId); err != nil { - return nil, errors.Wrap(err, "EFS instance is not available") - } - vol, err := e.VolumeGet(ctx, *fd.FileSystemId, volume.Az) - if err != nil { - return nil, errors.Wrap(err, "Failed to get recently create EFS instance") - } - _, mountTargets, err := filterAndGetMountTargetsFromTags(blockstorage.KeyValueToMap(volume.Tags)) - if err != nil { - return nil, errors.Wrap(err, "Failed to get filtered tags and mount targets") - } - if err = e.createMountTargets(ctx, vol.ID, mountTargets); err != nil { - return nil, errors.Wrap(err, "Failed to create mount targets") - } - return vol, nil -} - -func (e *Efs) VolumeCreateFromSnapshot(ctx context.Context, snapshot blockstorage.Snapshot, tags map[string]string) (*blockstorage.Volume, error) { - reqM := &backup.GetRecoveryPointRestoreMetadataInput{} - reqM.SetBackupVaultName(e.backupVaultName) - reqM.SetRecoveryPointArn(snapshot.ID) - - respM, err := e.GetRecoveryPointRestoreMetadataWithContext(ctx, reqM) - if err != nil { - return nil, errors.Wrap(err, "Failed to get backup tag from recovery point directly") - } - rpTags := convertFromBackupTags(respM.RestoreMetadata) - rp2Tags, err := e.getBackupTags(ctx, snapshot.ID) - if err != nil { - return nil, errors.Wrap(err, "Failed to get backup tag from recovery point") - } - rpTags = kantags.Union(rpTags, rp2Tags) - // RestorePoint tags has some tags to describe saved mount targets. - // We need to get them and remove them from the tags - filteredTags, mountTargets, err := filterAndGetMountTargetsFromTags(rpTags) - if err != nil { - return nil, errors.Wrap(err, "Failed to get filtered tags and mount targets") - } - // Add some metadata which are necessary for EFS restore to function properly. - filteredTags = kantags.Union(filteredTags, efsRestoreTags()) - - req := &backup.StartRestoreJobInput{} - req.SetIamRoleArn(awsDefaultServiceBackupRole(e.accountID)) - - // Start job only allows specific keys in metadata - // https://docs.aws.amazon.com/aws-backup/latest/devguide/API_StartRestoreJob.html - for k := range filteredTags { - if !allowedMetadataKeys[k] { - delete(filteredTags, k) - } - } - req.SetMetadata(convertToBackupTags(filteredTags)) - req.SetRecoveryPointArn(snapshot.ID) - req.SetResourceType(efsType) - - resp, err := e.StartRestoreJobWithContext(ctx, req) - if err != nil { - return nil, errors.Wrap(err, "Failed to start the restore job") - } - if resp.RestoreJobId == nil { - return nil, errors.New("Empty restore job ID") - } - restoreID := *resp.RestoreJobId - if err = e.waitUntilRestoreComplete(ctx, restoreID); err != nil { - return nil, errors.Wrap(err, "Restore job failed to complete") - } - respD := &backup.DescribeRestoreJobInput{} - respD.SetRestoreJobId(restoreID) - descJob, err := e.DescribeRestoreJobWithContext(ctx, respD) - if err != nil { - return nil, errors.Wrap(err, "Failed to get description for the restore job") - } - fsID, err := efsIDFromResourceARN(*descJob.CreatedResourceArn) - if err != nil { - return nil, errors.Wrap(err, "Failed to get filesystem ID") - } - if err = e.createMountTargets(ctx, fsID, mountTargets); err != nil { - return nil, errors.Wrap(err, "Failed to create mount targets") - } - return e.VolumeGet(ctx, fsID, "") -} - -func efsRestoreTags() map[string]string { - return map[string]string{ - "newFileSystem": "true", - "CreationToken": rand.String(16), - "Encrypted": "false", - "PerformanceMode": generalPurposePerformanceMode, - } -} - -type mountTarget struct { - subnetID string - securityGroups []string -} - -type mountTargets map[string]*mountTarget - -func (e *Efs) createMountTargets(ctx context.Context, fsID string, mts mountTargets) error { - created := make([]*awsefs.MountTargetDescription, 0) - for _, v := range mts { - req := &awsefs.CreateMountTargetInput{} - req.SetFileSystemId(fsID) - req.SetSubnetId(v.subnetID) - req.SetSecurityGroups(convertListOfStrings(v.securityGroups)) - - mtd, err := e.CreateMountTargetWithContext(ctx, req) - if err != nil { - return errors.Wrap(err, "Failed to create mount target") - } - created = append(created, mtd) - } - - for _, desc := range created { - if err := e.waitUntilMountTargetReady(ctx, *desc.MountTargetId); err != nil { - return errors.Wrap(err, "Failed while waiting for Mount target to be ready") - } - } - return nil -} - -func parseMountTargetKey(key string) (string, error) { - if !strings.HasPrefix(key, mountTargetKeyPrefix) { - return "", errors.New("Malformed string for mount target key") - } - return key[len(mountTargetKeyPrefix):], nil -} - -func parseMountTargetValue(value string) (*mountTarget, error) { - // Format: - // String until the first "+" is subnetID - // After that "+" separates security groups - // Example value: - // subnet-123+securityGroup-1+securityGroup-2 - tokens := strings.Split(value, securityGroupSeparator) - if len(tokens) < 1 { - return nil, errors.New("Malformed string for mount target values") - } - subnetID := tokens[0] - sgs := make([]string, 0) - if len(tokens) > 1 { - sgs = append(sgs, tokens[1:]...) - } - return &mountTarget{ - subnetID: subnetID, - securityGroups: sgs, - }, nil -} - -func filterAndGetMountTargetsFromTags(tags map[string]string) (map[string]string, mountTargets, error) { - filteredTags := make(map[string]string) - mts := make(mountTargets) - for k, v := range tags { - if strings.HasPrefix(k, mountTargetKeyPrefix) { - id, err := parseMountTargetKey(k) - if err != nil { - return nil, nil, err - } - mt, err := parseMountTargetValue(v) - if err != nil { - return nil, nil, err - } - mts[id] = mt - } else { - // It is not a mount target tag, so pass it - filteredTags[k] = v - } - } - return filteredTags, mts, nil -} - -func (e *Efs) getBackupTags(ctx context.Context, arn string) (map[string]string, error) { - result := make(map[string]string) - for resp, req := emptyResponseRequestForListTags(); resp.NextToken != nil; req.NextToken = resp.NextToken { - var err error - req.SetResourceArn(arn) - resp, err = e.ListTagsWithContext(ctx, req) - if err != nil { - return nil, err - } - tags := convertFromBackupTags(resp.Tags) - result = kantags.Union(result, tags) - } - return result, nil -} - -func (e *Efs) VolumeDelete(ctx context.Context, volume *blockstorage.Volume) error { - mts, err := e.getMountTargets(ctx, volume.ID) - if isVolumeNotFound(err) { - return nil - } - err = e.deleteMountTargets(ctx, mts) - if err != nil { - return errors.Wrap(err, "Failed to delete mount targets") - } - - req := &awsefs.DeleteFileSystemInput{} - req.SetFileSystemId(volume.ID) - output, err := e.DeleteFileSystemWithContext(ctx, req) - if err == nil { - log.Info().Print("Delete EFS output", field.M{"output": output.String()}) - } - if isVolumeNotFound(err) { - return nil - } - return err -} - -func (e *Efs) getMountTargets(ctx context.Context, fsID string) ([]*awsefs.MountTargetDescription, error) { - mts := make([]*awsefs.MountTargetDescription, 0) - for resp, req := emptyResponseRequestForMountTargets(); resp.NextMarker != nil; req.Marker = resp.NextMarker { - var err error - req.SetFileSystemId(fsID) - resp, err = e.DescribeMountTargetsWithContext(ctx, req) - if err != nil { - return nil, err - } - mts = append(mts, resp.MountTargets...) - } - return mts, nil -} - -func (e *Efs) deleteMountTargets(ctx context.Context, mts []*awsefs.MountTargetDescription) error { - for _, mt := range mts { - req := &awsefs.DeleteMountTargetInput{} - req.SetMountTargetId(*mt.MountTargetId) - _, err := e.DeleteMountTargetWithContext(ctx, req) - if err != nil { - return err - } - err = e.waitUntilMountTargetIsDeleted(ctx, *mt.MountTargetId) - if err != nil { - return err - } - } - return nil -} - -func (e *Efs) VolumeGet(ctx context.Context, id string, zone string) (*blockstorage.Volume, error) { - desc, err := e.getFileSystemDescriptionWithID(ctx, id) - if err != nil { - return nil, errors.Wrap(err, "Failed to get EFS volume") - } - return volumeFromEFSDescription(desc, zone), nil -} - -func (e *Efs) SnapshotCopy(ctx context.Context, from blockstorage.Snapshot, to blockstorage.Snapshot) (*blockstorage.Snapshot, error) { - return nil, errors.New("Not implemented") -} - -func (e *Efs) SnapshotCopyWithArgs(ctx context.Context, from blockstorage.Snapshot, to blockstorage.Snapshot, args map[string]string) (*blockstorage.Snapshot, error) { - return nil, errors.New("Copy Snapshot with Args not implemented") -} - -func (e *Efs) SnapshotCreate(ctx context.Context, volume blockstorage.Volume, tags map[string]string) (*blockstorage.Snapshot, error) { - err := e.CreateBackupVaultWrapper() - if err != nil { - return nil, errors.Wrap(err, "Failed to setup K10 vault for AWS Backup") - } - desc, err := e.getFileSystemDescriptionWithID(ctx, volume.ID) - if err != nil { - return nil, errors.Wrap(err, "Failed to get corresponding description") - } - - req := &backup.StartBackupJobInput{} - req.SetBackupVaultName(e.backupVaultName) - req.SetIamRoleArn(awsDefaultServiceBackupRole(e.accountID)) - req.SetResourceArn(resourceARNForEFS(e.region, *desc.OwnerId, *desc.FileSystemId)) - - // Save mount points and security groups as tags - infraTags, err := e.getMountPointAndSecurityGroupTags(ctx, volume.ID) - if err != nil { - return nil, errors.Wrap(err, "Failed to get mount points and security groups") - } - allTags := kantags.Union(tags, infraTags) - req.SetRecoveryPointTags(convertToBackupTags(allTags)) - resp, err := e.StartBackupJob(req) - if err != nil { - return nil, errors.Wrap(err, "Failed to start a backup job") - } - if err = e.waitUntilRecoveryPointVisible(ctx, *resp.RecoveryPointArn); err != nil { - return nil, errors.Wrap(err, "Failed to fetch recovery point") - } - if err = e.setBackupTags(ctx, *resp.RecoveryPointArn, infraTags); err != nil { - return nil, errors.Wrap(err, "Failed to set backup tags") - } - - req2 := &backup.DescribeRecoveryPointInput{} - req2.SetBackupVaultName(e.backupVaultName) - req2.SetRecoveryPointArn(*resp.RecoveryPointArn) - describeRP, err := e.DescribeRecoveryPointWithContext(ctx, req2) - if err != nil { - return nil, errors.Wrap(err, "Failed to get recovery point information") - } - return &blockstorage.Snapshot{ - CreationTime: blockstorage.TimeStamp(*resp.CreationDate), - Encrypted: volume.Encrypted, - ID: *resp.RecoveryPointArn, - Region: e.region, - SizeInBytes: *describeRP.BackupSizeInBytes, - Tags: blockstorage.MapToKeyValue(allTags), - Volume: &volume, - Type: blockstorage.TypeEFS, - }, nil -} - -// Create a Backup Vault, also checks if vault already exist -func (e *Efs) CreateBackupVaultWrapper() error { - req := &backup.CreateBackupVaultInput{} - req.SetBackupVaultName(e.backupVaultName) - - _, err := e.CreateBackupVault(req) - if isBackupVaultAlreadyExists(err) { - return nil - } - return err -} - -func (e *Efs) SnapshotCreateWaitForCompletion(ctx context.Context, snapshot *blockstorage.Snapshot) error { - return e.waitUntilRecoveryPointCompleted(ctx, snapshot.ID) -} - -func (e *Efs) SnapshotDelete(ctx context.Context, snapshot *blockstorage.Snapshot) error { - req := &backup.DeleteRecoveryPointInput{} - req.SetBackupVaultName(e.backupVaultName) - req.SetRecoveryPointArn(snapshot.ID) - - output, err := e.DeleteRecoveryPointWithContext(ctx, req) - if err == nil { - log.Info().Print("Delete EFS snapshot", field.M{"output": output.String()}) - } - if isResourceNotFoundException(err) { - return nil - } - if isDeleteInProgress(err) { - return nil - } - return err -} - -func (e *Efs) SnapshotGet(ctx context.Context, id string) (*blockstorage.Snapshot, error) { - req := &backup.DescribeRecoveryPointInput{} - req.SetBackupVaultName(e.backupVaultName) - req.SetRecoveryPointArn(id) - - resp, err := e.DescribeRecoveryPointWithContext(ctx, req) - if err != nil { - return nil, errors.Wrap(err, "Failed to get recovery point information") - } - if resp.ResourceArn == nil { - return nil, errors.Wrap(err, "Resource ARN in recovery point is empty") - } - volID, err := efsIDFromResourceARN(*resp.ResourceArn) - if err != nil { - return nil, errors.Wrap(err, "Failed to get volume ID from recovery point ARN") - } - vol, err := e.VolumeGet(ctx, volID, "") - if err != nil && !isVolumeNotFound(err) { - return nil, errors.Wrap(err, "Failed to get filesystem") - } - return snapshotFromRecoveryPoint(resp, vol, e.region) -} - -func (e *Efs) SetTags(ctx context.Context, resource interface{}, tags map[string]string) error { - switch r := resource.(type) { - case *blockstorage.Volume: - return e.setEFSTags(ctx, r.ID, tags) - case *blockstorage.Snapshot: - return e.setBackupTags(ctx, r.ID, tags) - default: - return errors.New("Unsupported type for setting tags") - } -} - -func (e *Efs) setBackupTags(ctx context.Context, arn string, tags map[string]string) error { - if len(tags) == 0 { - return nil - } - req := &backup.TagResourceInput{ - ResourceArn: &arn, - Tags: convertToBackupTags(tags), - } - _, err := e.Backup.TagResourceWithContext(ctx, req) - return err -} - -func (e *Efs) setEFSTags(ctx context.Context, id string, tags map[string]string) error { - if len(tags) == 0 { - return nil - } - req := &awsefs.TagResourceInput{ - ResourceId: &id, - Tags: convertToEFSTags(tags), - } - _, err := e.EFS.TagResource(req) - return err -} - -func (e *Efs) VolumesList(ctx context.Context, tags map[string]string, zone string) ([]*blockstorage.Volume, error) { - result := make([]*blockstorage.Volume, 0) - for resp, req := emptyResponseRequestForFilesystems(); resp.NextMarker != nil; req.Marker = resp.NextMarker { - var err error - resp, err = e.DescribeFileSystemsWithContext(ctx, req) - if err != nil { - return nil, err - } - availables := filterAvailable(filterWithTags(resp.FileSystems, tags)) - result = append(result, volumesFromEFSDescriptions(availables, zone)...) - } - return result, nil -} - -func (e *Efs) SnapshotsList(ctx context.Context, tags map[string]string) ([]*blockstorage.Snapshot, error) { - result := make([]*blockstorage.Snapshot, 0) - for resp, req := emptyResponseRequestForBackups(); resp.NextToken != nil; req.NextToken = resp.NextToken { - var err error - req.SetBackupVaultName(e.backupVaultName) - resp, err = e.ListRecoveryPointsByBackupVaultWithContext(ctx, req) - if err != nil { - return nil, errors.Wrap(err, "Failed to list recovery points by vault") - } - snaps, err := e.SnapshotsFromRecoveryPoints(ctx, resp.RecoveryPoints) - if err != nil { - return nil, errors.Wrap(err, "Failed to get snapshots from recovery points") - } - result = append(result, blockstorage.FilterSnapshotsWithTags(snaps, tags)...) - } - return result, nil -} - -// List a limited amount of snapshots based on given limit input -func (e *Efs) SnapshotsListWLimit(ctx context.Context, tags map[string]string, limit int64) ([]*blockstorage.Snapshot, error) { - result := make([]*blockstorage.Snapshot, 0) - var err error - req := &backup.ListRecoveryPointsByBackupVaultInput{} - req.SetBackupVaultName(e.backupVaultName) - req.SetMaxResults(limit) - resp, err := e.ListRecoveryPointsByBackupVaultWithContext(ctx, req) // backup API - if err != nil { - return nil, errors.Wrap(err, "Failed to list recovery points by vault") - } - snaps, err := e.SnapshotsFromRecoveryPoints(ctx, resp.RecoveryPoints) - if err != nil { - return nil, errors.Wrap(err, "Failed to get snapshots from recovery points") - } - result = append(result, blockstorage.FilterSnapshotsWithTags(snaps, tags)...) - return result, err -} - -func (e *Efs) SnapshotsFromRecoveryPoints(ctx context.Context, rps []*backup.RecoveryPointByBackupVault) ([]*blockstorage.Snapshot, error) { - result := make([]*blockstorage.Snapshot, 0) - for _, rp := range rps { - if rp.RecoveryPointArn == nil { - return nil, errors.New("Empty ARN in recovery point") - } - tags, err := e.getBackupTags(ctx, *rp.RecoveryPointArn) - if err != nil { - return nil, errors.Wrap(err, "Failed to get backup tags") - } - volID, err := efsIDFromResourceARN(*rp.ResourceArn) - if err != nil { - return nil, errors.Wrap(err, "Failed to get volume ID from recovery point ARN") - } - // VolumeGet might return error since originating filesystem might have - // been deleted. - vol, err := e.VolumeGet(ctx, volID, "") - if err != nil && !isVolumeNotFound(err) { - return nil, errors.Wrap(err, "Failed to get filesystem") - } - snap, err := snapshotFromRecoveryPointByVault(rp, vol, tags, e.region) - if err != nil { - return nil, errors.Wrap(err, "Failed to get snapshot from the vault") - } - result = append(result, snap) - } - return result, nil -} - -func emptyResponseRequestForBackups() (*backup.ListRecoveryPointsByBackupVaultOutput, *backup.ListRecoveryPointsByBackupVaultInput) { - resp := (&backup.ListRecoveryPointsByBackupVaultOutput{}).SetNextToken("") - req := &backup.ListRecoveryPointsByBackupVaultInput{} - return resp, req -} - -func emptyResponseRequestForFilesystems() (*awsefs.DescribeFileSystemsOutput, *awsefs.DescribeFileSystemsInput) { - resp := (&awsefs.DescribeFileSystemsOutput{}).SetNextMarker("") - req := &awsefs.DescribeFileSystemsInput{} - return resp, req -} - -func emptyResponseRequestForListTags() (*backup.ListTagsOutput, *backup.ListTagsInput) { - resp := (&backup.ListTagsOutput{}).SetNextToken("") - req := &backup.ListTagsInput{} - return resp, req -} - -func emptyResponseRequestForMountTargets() (*awsefs.DescribeMountTargetsOutput, *awsefs.DescribeMountTargetsInput) { - resp := (&awsefs.DescribeMountTargetsOutput{}).SetNextMarker("") - req := &awsefs.DescribeMountTargetsInput{} - return resp, req -} - -func awsDefaultServiceBackupRole(accountID string) string { - return fmt.Sprintf("arn:aws:iam::%s:role/service-role/AWSBackupDefaultServiceRole", accountID) -} - -func resourceARNForEFS(region string, accountID string, fileSystemID string) string { - return fmt.Sprintf("arn:aws:elasticfilesystem:%s:%s:file-system/%s", region, accountID, fileSystemID) -} - -func (e *Efs) getFileSystemDescriptionWithID(ctx context.Context, id string) (*awsefs.FileSystemDescription, error) { - req := &awsefs.DescribeFileSystemsInput{} - req.SetFileSystemId(id) - - descs, err := e.DescribeFileSystemsWithContext(ctx, req) - if err != nil { - return nil, errors.Wrap(err, "Failed to get filesystem description") - } - availables := filterAvailable(descs.FileSystems) - switch len(availables) { - case 0: - return nil, errors.New("Failed to find volume") - case 1: - return descs.FileSystems[0], nil - default: - return nil, errors.New("Unexpected condition, multiple filesystems with same ID") - } -} - -func (e *Efs) getMountPointAndSecurityGroupTags(ctx context.Context, id string) (map[string]string, error) { - mts, err := e.getMountTargets(ctx, id) - if err != nil { - return nil, errors.Wrap(err, "Failed to get mount target for the volume") - } - resultTags := make(map[string]string) - for _, mt := range mts { - req := &awsefs.DescribeMountTargetSecurityGroupsInput{} - req.SetMountTargetId(*mt.MountTargetId) - - resp, err := e.DescribeMountTargetSecurityGroupsWithContext(ctx, req) - if err != nil { - return nil, errors.Wrap(err, "Failed to get security group") - } - if mt.SubnetId == nil { - return nil, errors.New("Empty subnet ID in mount target entry") - } - value := mountTargetValue(*mt.SubnetId, resp.SecurityGroups) - if mt.MountTargetId == nil { - return nil, errors.New("Empty ID in mount target entry") - } - key := mountTargetKey(*mt.MountTargetId) - resultTags[key] = value - } - return resultTags, nil -} diff --git a/pkg/blockstorage/awsefs/awsefs_test.go b/pkg/blockstorage/awsefs/awsefs_test.go deleted file mode 100644 index e150f81b02..0000000000 --- a/pkg/blockstorage/awsefs/awsefs_test.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2022 The Kanister 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 awsefs - -import ( - "context" - "os" - - "gopkg.in/check.v1" - - awsconfig "github.com/kanisterio/kanister/pkg/aws" - "github.com/kanisterio/kanister/pkg/testutil" -) - -type AwsEfsSuite struct{} - -var _ = check.Suite(&AwsEfsSuite{}) - -func (a *AwsEfsSuite) TestBackupVaultName(c *check.C) { - testutil.GetEnvOrSkip(c, awsconfig.AccessKeyID) - testutil.GetEnvOrSkip(c, awsconfig.SecretAccessKey) - ctx := context.Background() - - // success - config := map[string]string{ - awsconfig.AccessKeyID: os.Getenv(awsconfig.AccessKeyID), - awsconfig.SecretAccessKey: os.Getenv(awsconfig.SecretAccessKey), - awsconfig.ConfigEFSVaultName: "vault-name", - awsconfig.ConfigRegion: "us-east-2", - } - _, err := NewEFSProvider(ctx, config) - c.Assert(err, check.IsNil) - - // fail because Vault name is expected - config[awsconfig.ConfigEFSVaultName] = "" - _, err = NewEFSProvider(ctx, config) - c.Assert(err, check.NotNil) -} diff --git a/pkg/blockstorage/awsefs/conversion.go b/pkg/blockstorage/awsefs/conversion.go deleted file mode 100644 index b3eca5b3fd..0000000000 --- a/pkg/blockstorage/awsefs/conversion.go +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2019 The Kanister 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 awsefs - -import ( - "strings" - - "github.com/aws/aws-sdk-go/aws" - awsarn "github.com/aws/aws-sdk-go/aws/arn" - "github.com/aws/aws-sdk-go/service/backup" - awsefs "github.com/aws/aws-sdk-go/service/efs" - "github.com/pkg/errors" - - "github.com/kanisterio/kanister/pkg/blockstorage" -) - -const ( - securityGroupSeparator = "+" - mountTargetKeyPrefix = "kasten.io/aws-mount-target/" -) - -// convertFromEFSTags converts AWS EFS tag structure to a flattened map. -func convertFromEFSTags(efsTags []*awsefs.Tag) map[string]string { - tags := make(map[string]string) - for _, t := range efsTags { - tags[*t.Key] = *t.Value - } - return tags -} - -// efsIDFromResourceARN gets EFS filesystem ID from an EFS resource ARN. -func efsIDFromResourceARN(arn string) (string, error) { - resourceARN, err := awsarn.Parse(arn) - if err != nil { - return "", errors.Wrap(err, "Failed to parse ARN") - } - // An example of resourceArn.Resource: - // "file-system/fs-87b1302c" - tokens := strings.Split(resourceARN.Resource, "/") - if len(tokens) != 2 { - return "", errors.New("Bad resource in ARN") - } - if tokens[0] != "file-system" { - return "", errors.New("Bad resource type in ARN") - } - return tokens[1], nil -} - -func snapshotFromRecoveryPoint(rp *backup.DescribeRecoveryPointOutput, volume *blockstorage.Volume, region string) (*blockstorage.Snapshot, error) { - if rp == nil { - return nil, errors.New("Empty recovery point") - } - if rp.CreationDate == nil { - return nil, errors.New("Recovery point has no CreationDate") - } - if rp.BackupSizeInBytes == nil { - return nil, errors.New("Recovery point has no BackupSizeInBytes") - } - if rp.RecoveryPointArn == nil { - return nil, errors.New("Recovery point has no RecoveryPointArn") - } - encrypted := false - if volume != nil { - encrypted = volume.Encrypted - } - return &blockstorage.Snapshot{ - ID: *rp.RecoveryPointArn, - CreationTime: blockstorage.TimeStamp(*rp.CreationDate), - SizeInBytes: *rp.BackupSizeInBytes, - Region: region, - Type: blockstorage.TypeEFS, - Volume: volume, - Encrypted: encrypted, - Tags: nil, - }, nil -} - -func snapshotFromRecoveryPointByVault(rp *backup.RecoveryPointByBackupVault, volume *blockstorage.Volume, tags map[string]string, region string) (*blockstorage.Snapshot, error) { - if rp == nil { - return nil, errors.New("Empty recovery point") - } - if rp.CreationDate == nil { - return nil, errors.New("Recovery point has not CreationDate") - } - if rp.BackupSizeInBytes == nil { - return nil, errors.New("Recovery point has no BackupSizeInBytes") - } - if rp.RecoveryPointArn == nil { - return nil, errors.New("Recovery point has no RecoveryPointArn") - } - encrypted := false - if volume != nil { - encrypted = volume.Encrypted - } - return &blockstorage.Snapshot{ - ID: *rp.RecoveryPointArn, - CreationTime: blockstorage.TimeStamp(*rp.CreationDate), - SizeInBytes: *rp.BackupSizeInBytes, - Region: region, - Type: blockstorage.TypeEFS, - Volume: volume, - Encrypted: encrypted, - Tags: blockstorage.MapToKeyValue(tags), - }, nil -} - -// convertFromBackupTags converts an AWS Backup compliant tag structure to a flattenned map. -func convertFromBackupTags(tags map[string]*string) map[string]string { - result := make(map[string]string) - for k, v := range tags { - result[k] = *v - } - return result -} - -// convertToBackupTags converts a flattened map to AWS Backup compliant tag structure. -func convertToBackupTags(tags map[string]string) map[string]*string { - backupTags := make(map[string]*string) - for k, v := range tags { - vPtr := new(string) - *vPtr = v - backupTags[k] = vPtr - } - return backupTags -} - -// convertToEFSTags converts a flattened map to AWS EFS tag structure. -func convertToEFSTags(tags map[string]string) []*awsefs.Tag { - efsTags := make([]*awsefs.Tag, 0, len(tags)) - for k, v := range tags { - efsTags = append(efsTags, &awsefs.Tag{Key: aws.String(k), Value: aws.String(v)}) - } - return efsTags -} - -// convertListOfStrings converts a flattend list to a list where each -// element is a pointer to original elements. -func convertListOfStrings(strs []string) []*string { - result := make([]*string, 0) - for i := range strs { - result = append(result, &strs[i]) - } - return result -} - -// volumeFromEFSDescription converts an AWS EFS filesystem description to Kanister blockstorage Volume type -// using the information in the description. -// -// ID of the volume is equal to EFS filesystems ID (e.g fs-bdf36586). -// Iops is always set to 0. -// VolumeType and Atrributes set to corresponding empty values. -func volumeFromEFSDescription(description *awsefs.FileSystemDescription, zone string) *blockstorage.Volume { - return &blockstorage.Volume{ - Az: zone, - ID: *description.FileSystemId, - Type: blockstorage.TypeEFS, - Encrypted: *description.Encrypted, - CreationTime: blockstorage.TimeStamp(*description.CreationTime), - SizeInBytes: *description.SizeInBytes.Value, - Tags: blockstorage.MapToKeyValue(convertFromEFSTags(description.Tags)), - Iops: 0, - VolumeType: "", - Attributes: nil, - } -} - -// volumesFromEFSDescriptions returns the list of volumes from the EFS filesystem descriptions. -func volumesFromEFSDescriptions(descriptions []*awsefs.FileSystemDescription, zone string) []*blockstorage.Volume { - volumes := make([]*blockstorage.Volume, 0, len(descriptions)) - for _, desc := range descriptions { - volumes = append(volumes, volumeFromEFSDescription(desc, zone)) - } - return volumes -} - -func mergeSecurityGroups(securityGroups []*string) string { - dereferenced := make([]string, 0, len(securityGroups)) - for _, d := range securityGroups { - dereferenced = append(dereferenced, *d) - } - return strings.Join(dereferenced, securityGroupSeparator) -} - -func mountTargetKey(mountTargetID string) string { - return mountTargetKeyPrefix + mountTargetID -} - -func mountTargetValue(subnetID string, securityGroups []*string) string { - return subnetID + securityGroupSeparator + mergeSecurityGroups(securityGroups) -} diff --git a/pkg/blockstorage/awsefs/conversion_test.go b/pkg/blockstorage/awsefs/conversion_test.go deleted file mode 100644 index f959529e3f..0000000000 --- a/pkg/blockstorage/awsefs/conversion_test.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2019 The Kanister 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 awsefs - -import ( - "testing" - "time" - - "github.com/aws/aws-sdk-go/aws" - awsefs "github.com/aws/aws-sdk-go/service/efs" - "github.com/kanisterio/kanister/pkg/blockstorage" - . "gopkg.in/check.v1" -) - -func Test(t *testing.T) { TestingT(t) } - -type AWSEFSConversionTestSuite struct{} - -var _ = Suite(&AWSEFSConversionTestSuite{}) - -func (s *AWSEFSConversionTestSuite) TestVolumeConversion(c *C) { - az := "us-west-2a" - fsID := "fs-123456" - date := time.Date(2018, 10, 1, 1, 1, 1, 1, time.UTC) - - tcs := []struct { - input *awsefs.FileSystemDescription - expected *blockstorage.Volume - }{ - { - input: &awsefs.FileSystemDescription{ - FileSystemId: aws.String(fsID), - CreationTime: aws.Time(date), - SizeInBytes: &awsefs.FileSystemSize{Value: aws.Int64(1024)}, - Encrypted: aws.Bool(true), - Tags: []*awsefs.Tag{}, - }, - expected: &blockstorage.Volume{ - ID: fsID, - Az: az, - CreationTime: blockstorage.TimeStamp(date), - SizeInBytes: 1024, - Type: blockstorage.TypeEFS, - Encrypted: true, - Tags: blockstorage.VolumeTags{}, - }, - }, - { - input: &awsefs.FileSystemDescription{ - FileSystemId: aws.String(fsID), - CreationTime: aws.Time(date), - SizeInBytes: &awsefs.FileSystemSize{Value: aws.Int64(2 * blockstorage.BytesInGi)}, - Encrypted: aws.Bool(false), - Tags: []*awsefs.Tag{ - {Key: aws.String("key1"), Value: aws.String("value1")}, - {Key: aws.String("key2"), Value: aws.String("value2")}, - }, - }, - expected: &blockstorage.Volume{ - ID: fsID, - Az: az, - CreationTime: blockstorage.TimeStamp(date), - SizeInBytes: 2 * blockstorage.BytesInGi, - Type: blockstorage.TypeEFS, - Encrypted: false, - Tags: blockstorage.VolumeTags( - []*blockstorage.KeyValue{ - {Key: "key1", Value: "value1"}, - {Key: "key2", Value: "value2"}, - }, - ), - }, - }, - } - - for _, tc := range tcs { - vol := volumeFromEFSDescription(tc.input, az) - c.Check(vol.Az, Equals, tc.expected.Az) - c.Check(vol.ID, Equals, tc.expected.ID) - c.Check(vol.CreationTime, Equals, tc.expected.CreationTime) - c.Check(vol.SizeInBytes, Equals, tc.expected.SizeInBytes) - c.Check(vol.Type, Equals, tc.expected.Type) - c.Check(vol.Encrypted, Equals, tc.expected.Encrypted) - c.Check(vol.Tags, HasLen, len(tc.expected.Tags)) - } -} diff --git a/pkg/blockstorage/awsefs/error.go b/pkg/blockstorage/awsefs/error.go deleted file mode 100644 index 523137de5b..0000000000 --- a/pkg/blockstorage/awsefs/error.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2019 The Kanister 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 awsefs - -import ( - "strings" - - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/service/backup" - awsefs "github.com/aws/aws-sdk-go/service/efs" - "github.com/pkg/errors" -) - -func isVolumeNotFound(err error) bool { - switch errV := errors.Cause(err).(type) { - case awserr.Error: - return errV.Code() == awsefs.ErrCodeFileSystemNotFound - default: - return false - } -} - -func isBackupVaultAlreadyExists(err error) bool { - if awsErr, ok := err.(awserr.Error); ok { - return awsErr.Code() == backup.ErrCodeAlreadyExistsException - } - return false -} - -func isResourceNotFoundException(err error) bool { - if awsErr, ok := err.(awserr.Error); ok { - return awsErr.Code() == backup.ErrCodeResourceNotFoundException - } - return false -} - -func isMountTargetNotFound(err error) bool { - if awsErr, ok := err.(awserr.Error); ok { - return awsErr.Code() == awsefs.ErrCodeMountTargetNotFound - } - return false -} - -func isDeleteInProgress(err error) bool { - if awsErr, ok := err.(awserr.Error); ok { - if awsErr.Code() == backup.ErrCodeInvalidRequestException && - strings.Contains(awsErr.Message(), "Recovery point already started the deletion process") { - return true - } - } - return false -} diff --git a/pkg/blockstorage/awsefs/filter.go b/pkg/blockstorage/awsefs/filter.go deleted file mode 100644 index 83a330d7dc..0000000000 --- a/pkg/blockstorage/awsefs/filter.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2019 The Kanister 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 awsefs - -import ( - awsefs "github.com/aws/aws-sdk-go/service/efs" - - kantags "github.com/kanisterio/kanister/pkg/blockstorage/tags" -) - -func filterAvailable(descriptions []*awsefs.FileSystemDescription) []*awsefs.FileSystemDescription { - result := make([]*awsefs.FileSystemDescription, 0) - for _, desc := range descriptions { - if *desc.LifeCycleState == awsefs.LifeCycleStateAvailable { - result = append(result, desc) - } - } - return result -} - -func filterWithTags(descriptions []*awsefs.FileSystemDescription, tags map[string]string) []*awsefs.FileSystemDescription { - result := make([]*awsefs.FileSystemDescription, 0) - for i, desc := range descriptions { - if kantags.IsSubset(convertFromEFSTags(desc.Tags), tags) { - result = append(result, descriptions[i]) - } - } - return result -} diff --git a/pkg/blockstorage/awsefs/wait.go b/pkg/blockstorage/awsefs/wait.go deleted file mode 100644 index 6bf210b376..0000000000 --- a/pkg/blockstorage/awsefs/wait.go +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2019 The Kanister 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 awsefs - -import ( - "context" - - "github.com/aws/aws-sdk-go/service/backup" - awsefs "github.com/aws/aws-sdk-go/service/efs" - "github.com/pkg/errors" - - "github.com/kanisterio/kanister/pkg/poll" -) - -const ( - maxNumErrorRetries = 3 -) - -func (e *Efs) waitUntilFileSystemAvailable(ctx context.Context, id string) error { - return poll.WaitWithRetries(ctx, maxNumErrorRetries, poll.IsAlwaysRetryable, func(ctx context.Context) (bool, error) { - req := &awsefs.DescribeFileSystemsInput{} - req.SetFileSystemId(id) - - desc, err := e.DescribeFileSystemsWithContext(ctx, req) - if err != nil { - return false, err - } - if len(desc.FileSystems) == 0 { - return false, nil - } - state := desc.FileSystems[0].LifeCycleState - if state == nil { - return false, nil - } - return *state == awsefs.LifeCycleStateAvailable, nil - }) -} - -func (e *Efs) waitUntilRecoveryPointCompleted(ctx context.Context, id string) error { - return poll.WaitWithRetries(ctx, maxNumErrorRetries, poll.IsAlwaysRetryable, func(ctx context.Context) (bool, error) { - req := &backup.DescribeRecoveryPointInput{} - req.SetBackupVaultName(e.backupVaultName) - req.SetRecoveryPointArn(id) - - desc, err := e.DescribeRecoveryPointWithContext(ctx, req) - if isResourceNotFoundException(err) { - // Recovery point doesn't appear when the backup jobs finishes. - // Since this case is special, it will be counted as non-error. - return false, nil - } - if err != nil { - return false, err - } - status := desc.Status - if status == nil { - return false, nil - } - return *status == backup.RecoveryPointStatusCompleted, nil - }) -} - -func (e *Efs) waitUntilRecoveryPointVisible(ctx context.Context, id string) error { - return poll.WaitWithRetries(ctx, maxNumErrorRetries, poll.IsAlwaysRetryable, func(ctx context.Context) (bool, error) { - req := &backup.DescribeRecoveryPointInput{} - req.SetBackupVaultName(e.backupVaultName) - req.SetRecoveryPointArn(id) - - _, err := e.DescribeRecoveryPointWithContext(ctx, req) - if isResourceNotFoundException(err) { - // Recovery point doesn't appear when the backup jobs finishes. - // Since this case is special, it will be counted as non-error. - return false, nil - } - if err != nil { - return false, err - } - return true, nil - }) -} - -func (e *Efs) waitUntilMountTargetReady(ctx context.Context, mountTargetID string) error { - return poll.Wait(ctx, func(ctx context.Context) (bool, error) { - req := &awsefs.DescribeMountTargetsInput{} - req.SetMountTargetId(mountTargetID) - - desc, err := e.DescribeMountTargetsWithContext(ctx, req) - if isMountTargetNotFound(err) { - return false, nil - } - if err != nil { - return false, err - } - if len(desc.MountTargets) != 1 { - return false, errors.New("Returned list must have 1 entry") - } - mt := desc.MountTargets[0] - state := mt.LifeCycleState - if state == nil { - return false, nil - } - return *state == awsefs.LifeCycleStateAvailable, nil - }) -} - -func (e *Efs) waitUntilMountTargetIsDeleted(ctx context.Context, mountTargetID string) error { - return poll.Wait(ctx, func(ctx context.Context) (bool, error) { - req := &awsefs.DescribeMountTargetsInput{} - req.SetMountTargetId(mountTargetID) - - _, err := e.DescribeMountTargetsWithContext(ctx, req) - if isMountTargetNotFound(err) { - return true, nil - } - if err != nil { - return false, err - } - return false, nil - }) -} - -func (e *Efs) waitUntilRestoreComplete(ctx context.Context, restoreJobID string) error { - return poll.Wait(ctx, func(ctx context.Context) (bool, error) { - req := &backup.DescribeRestoreJobInput{} - req.SetRestoreJobId(restoreJobID) - - resp, err := e.DescribeRestoreJobWithContext(ctx, req) - if err != nil { - return false, err - } - if resp.Status == nil { - return false, errors.New("Failed to get restore job status") - } - switch *resp.Status { - case backup.RestoreJobStatusCompleted: - return true, nil - case backup.RestoreJobStatusAborted: - return false, errors.Errorf("Restore job aborted (%s)\n", resp.String()) - case backup.RestoreJobStatusFailed: - return false, errors.Errorf("Restore job failed (%s)\n", resp.String()) - default: - return false, nil - } - }) -} diff --git a/pkg/blockstorage/tags/tags.go b/pkg/blockstorage/tags/tags.go index c27a2d4b29..ae99e2eec0 100644 --- a/pkg/blockstorage/tags/tags.go +++ b/pkg/blockstorage/tags/tags.go @@ -102,16 +102,3 @@ func IsSubset(set map[string]string, subset map[string]string) bool { } return true } - -// Union returns union of first and second as a new map. -// second's values have priority if a key from first and second collides. -func Union(first map[string]string, second map[string]string) map[string]string { - result := make(map[string]string) - for k, v := range first { - result[k] = v - } - for k, v := range second { - result[k] = v - } - return result -} diff --git a/pkg/blockstorage/vmware/conversion.go b/pkg/blockstorage/vmware/conversion.go deleted file mode 100644 index 1ccfb9def9..0000000000 --- a/pkg/blockstorage/vmware/conversion.go +++ /dev/null @@ -1,85 +0,0 @@ -package vmware - -import ( - "strings" - - "github.com/pkg/errors" - "github.com/vmware/govmomi/vim25/types" - - "github.com/kanisterio/kanister/pkg/blockstorage" -) - -func convertFromObjectToVolume(vso *types.VStorageObject) (*blockstorage.Volume, error) { - if vso == nil { - return nil, errors.New("Empty object") - } - return &blockstorage.Volume{ - Type: blockstorage.TypeFCD, - ID: vso.Config.Id.Id, - CreationTime: blockstorage.TimeStamp(vso.Config.CreateTime), - SizeInBytes: vso.Config.CapacityInMB * blockstorage.BytesInMi, - Az: "", - Iops: 0, - Encrypted: false, - VolumeType: "", - Tags: blockstorage.VolumeTags{}, - Attributes: map[string]string{}, - }, nil -} - -func convertFromObjectToSnapshot(vso *types.VStorageObjectSnapshotInfoVStorageObjectSnapshot, volID string) (*blockstorage.Snapshot, error) { - if vso == nil { - return nil, errors.New("Empty object") - } - return &blockstorage.Snapshot{ - Type: blockstorage.TypeFCD, - CreationTime: blockstorage.TimeStamp(vso.CreateTime), - ID: SnapshotFullID(volID, vso.Id.Id), - SizeInBytes: 0, - Region: "", - Encrypted: false, - }, nil -} - -// vimID wraps ID string with vim25.ID struct. -func vimID(id string) types.ID { - return types.ID{ - Id: id, - } -} - -// SnapshotFullID create a composite identifier for a volume snapshot. -func SnapshotFullID(volID, snapshotID string) string { - return volID + ":" + snapshotID -} - -// SplitSnapshotFullID splits a volume snapshot composite identifier into its components. -func SplitSnapshotFullID(fullID string) (volID string, snapshotID string, err error) { - split := strings.Split(fullID, ":") - if len(split) != 2 { - return "", "", errors.New("Malformed full ID for snapshot") - } - if len(split[0]) == 0 || len(split[1]) == 0 { - return "", "", errors.New("Malformed volume ID or snapshot ID") - } - return split[0], split[1], nil -} - -func convertKeyValueToTags(kvs []types.KeyValue) []*blockstorage.KeyValue { - tags := make(map[string]string) - for _, kv := range kvs { - tags[kv.Key] = kv.Value - } - return blockstorage.MapToKeyValue(tags) -} - -func convertTagsToKeyValue(tags map[string]string) []types.KeyValue { - result := make([]types.KeyValue, 0) - for k, v := range tags { - var kv types.KeyValue - kv.Key = k - kv.Value = v - result = append(result, kv) - } - return result -} diff --git a/pkg/blockstorage/vmware/conversion_test.go b/pkg/blockstorage/vmware/conversion_test.go deleted file mode 100644 index 77f730f8ec..0000000000 --- a/pkg/blockstorage/vmware/conversion_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package vmware - -import ( - . "gopkg.in/check.v1" -) - -type VMWareConversionSuite struct{} - -var _ = Suite(&VMWareConversionSuite{}) - -func (s *VMWareConversionSuite) TestSnapshotIDConversion(c *C) { - for _, tc := range []struct { - fullID string - errCheck Checker - }{ - { - fullID: "1234-abcd-5678-9213:3413-abcd-1234-1234", - errCheck: IsNil, - }, - { - fullID: "1234-abcd-5678-9213:", - errCheck: NotNil, - }, - { - fullID: ":3413-abcd-1234-1234", - errCheck: NotNil, - }, - { - fullID: "1234-abcd-5678-9213", - errCheck: NotNil, - }, - } { - volID, snapID, err := SplitSnapshotFullID(tc.fullID) - c.Check(err, tc.errCheck) - if tc.errCheck == IsNil { - fullID := SnapshotFullID(volID, snapID) - c.Check(tc.fullID, Equals, fullID) - } - } -} diff --git a/pkg/blockstorage/vmware/vmware.go b/pkg/blockstorage/vmware/vmware.go deleted file mode 100644 index 71ac4c2f4c..0000000000 --- a/pkg/blockstorage/vmware/vmware.go +++ /dev/null @@ -1,870 +0,0 @@ -package vmware - -import ( - "context" - stderrors "errors" - "fmt" - "net/url" - "os" - "regexp" - "strconv" - "strings" - "time" - - "github.com/gofrs/uuid" - "github.com/pkg/errors" - "github.com/vmware/govmomi/cns" - govmomitask "github.com/vmware/govmomi/task" - "github.com/vmware/govmomi/vapi/rest" - vapitags "github.com/vmware/govmomi/vapi/tags" - "github.com/vmware/govmomi/vim25" - "github.com/vmware/govmomi/vim25/methods" - "github.com/vmware/govmomi/vim25/soap" - "github.com/vmware/govmomi/vim25/types" - "github.com/vmware/govmomi/vslm" - vslmtypes "github.com/vmware/govmomi/vslm/types" - "k8s.io/apimachinery/pkg/util/wait" - - "github.com/kanisterio/kanister/pkg/blockstorage" - ktags "github.com/kanisterio/kanister/pkg/blockstorage/tags" - "github.com/kanisterio/kanister/pkg/field" - "github.com/kanisterio/kanister/pkg/log" -) - -var _ blockstorage.Provider = (*FcdProvider)(nil) - -const ( - // VSphereLoginURLKey represents key in config to establish connection. - // It should contain the username and the password. - VSphereLoginURLKey = "VSphereLoginURL" - - // VSphereEndpointKey represents key for the login endpoint. - VSphereEndpointKey = "VSphereEndpoint" - // VSphereUsernameKey represents key for the username. - VSphereUsernameKey = "VSphereUsername" - // VSpherePasswordKey represents key for the password. - VSpherePasswordKey = "VSpherePasswordKey" - - // VSphereIsParaVirtualizedKey is the key for the para-virtualized indicator. - // A value of "true" or "1" implies use of a para-virtualized CSI driver in the - // cluster. When this is true then some operations will fail. - // Note: it is up to the creator of the provider to determine if a para-virtualized environment exists. - VSphereIsParaVirtualizedKey = "VSphereIsParaVirtualizedKey" - - defaultWaitTime = 60 * time.Minute - defaultRetryLimit = 30 * time.Minute - - vmWareTimeoutMinEnv = "VMWARE_GOM_TIMEOUT_MIN" - - // DescriptionTag is the prefix of the tags that should be placed in the snapshot description. - // This constant must be used by clients, so changing this field may make already created snapshots inaccessible. - DescriptionTag = "kanister.fcd.description" - // VolumeIDListTag is the predefined name of the tag which contains volume ids separated by comma - VolumeIDListTag = "kanister.fcd.volume-id" -) - -var ( - vmWareTimeout = time.Duration(getEnvAsIntOrDefault(vmWareTimeoutMinEnv, int(defaultWaitTime/time.Minute))) * time.Minute - - ErrNotSupportedWithParaVirtualizedVolumes = stderrors.New("operation not supported with para-virtualized volumes") -) - -// FcdProvider provides blockstorage.Provider -type FcdProvider struct { - Gom *vslm.GlobalObjectManager - Cns *cns.Client - TagsSvc *vapitags.Manager - tagManager tagManager - categoryID string - isParaVirtualized bool -} - -// NewProvider creates new VMWare FCD provider with the config. -// URL taken from config helps to establish connection. -func NewProvider(config map[string]string) (blockstorage.Provider, error) { - ep, ok := config[VSphereEndpointKey] - if !ok { - return nil, errors.New("Failed to find VSphere endpoint value") - } - username, ok := config[VSphereUsernameKey] - if !ok { - return nil, errors.New("Failed to find VSphere username value") - } - password, ok := config[VSpherePasswordKey] - if !ok { - return nil, errors.New("Failed to find VSphere password value") - } - - u := &url.URL{Scheme: "https", Host: ep, Path: "/sdk"} - soapCli := soap.NewClient(u, true) - ctx := context.Background() - cli, err := vim25.NewClient(ctx, soapCli) - if err != nil { - return nil, errors.Wrap(err, "Failed to create VIM client") - } - req := types.Login{ - This: *cli.ServiceContent.SessionManager, - } - req.UserName = username - req.Password = password - _, err = methods.Login(ctx, cli, &req) - if err != nil { - return nil, errors.Wrap(err, "Failed to login") - } - cnsCli, err := cns.NewClient(ctx, cli) - if err != nil { - return nil, errors.Wrap(err, "Failed to create CNS client") - } - vslmCli, err := vslm.NewClient(ctx, cli) - if err != nil { - return nil, errors.Wrap(err, "Failed to create VSLM client") - } - c := rest.NewClient(cli) - err = c.Login(ctx, url.UserPassword(username, password)) - if err != nil { - return nil, errors.Wrap(err, "Failed to login to VAPI rest client") - } - tm := vapitags.NewManager(c) - if err != nil { - return nil, errors.Wrap(err, "Failed to create tag manager") - } - gom := vslm.NewGlobalObjectManager(vslmCli) - return &FcdProvider{ - Cns: cnsCli, - Gom: gom, - TagsSvc: tm, - tagManager: tm, - isParaVirtualized: configIsParaVirtualized(config), - }, nil -} - -func configIsParaVirtualized(config map[string]string) bool { - if isParaVirtualizedVal, ok := config[VSphereIsParaVirtualizedKey]; ok { - if strings.ToLower(isParaVirtualizedVal) == "true" || isParaVirtualizedVal == "1" { - return true - } - } - return false -} - -// IsParaVirtualized is not part of blockstorage.Provider. -func (p *FcdProvider) IsParaVirtualized() bool { - return p.isParaVirtualized -} - -// Type is part of blockstorage.Provider -func (p *FcdProvider) Type() blockstorage.Type { - return blockstorage.TypeFCD -} - -// Type is part of blockstorage.Provider -func (p *FcdProvider) SetCategoryID(categoryID string) { - p.categoryID = categoryID -} - -// VolumeCreate is part of blockstorage.Provider -func (p *FcdProvider) VolumeCreate(ctx context.Context, volume blockstorage.Volume) (*blockstorage.Volume, error) { - return nil, errors.New("Not implemented") -} - -// VolumeCreateFromSnapshot is part of blockstorage.Provider -func (p *FcdProvider) VolumeCreateFromSnapshot(ctx context.Context, snapshot blockstorage.Snapshot, tags map[string]string) (*blockstorage.Volume, error) { - if p.IsParaVirtualized() { - return nil, errors.WithStack(ErrNotSupportedWithParaVirtualizedVolumes) - } - volID, snapshotID, err := SplitSnapshotFullID(snapshot.ID) - if err != nil { - return nil, errors.Wrap(err, "Failed to split snapshot full ID") - } - log.Debug().Print("CreateDiskFromSnapshot foo", field.M{"VolumeID": volID, "SnapshotID": snapshotID}) - uid, err := uuid.NewV1() - if err != nil { - return nil, errors.Wrap(err, "Failed to create UUID") - } - task, err := p.Gom.CreateDiskFromSnapshot(ctx, vimID(volID), vimID(snapshotID), uid.String(), nil, nil, "") - if err != nil { - return nil, errors.Wrap(err, "Failed to create disk from snapshot") - } - log.Debug().Print("Started CreateDiskFromSnapshot task", field.M{"VolumeID": volID, "SnapshotID": snapshotID}) - res, err := task.Wait(ctx, vmWareTimeout) - if err != nil { - return nil, errors.Wrap(err, "Failed to wait on task") - } - if res == nil { - return nil, errors.Errorf("vSphere task did not complete. TaskRefType: %s, TaskRefValue: %s, VolID: %s, SnapshotID: %s, NewVolID: %s", - task.ManagedObjectReference.Type, task.ManagedObjectReference.Value, volID, snapshotID, uid) - } - log.Debug().Print("CreateDiskFromSnapshot task complete", field.M{"VolumeID": volID, "SnapshotID": snapshotID}) - obj, ok := res.(types.VStorageObject) - if !ok { - return nil, errors.New(fmt.Sprintf("Wrong type returned for vSphere. Type: %T, Value: %v", res, res)) - } - vol, err := p.VolumeGet(ctx, obj.Config.Id.Id, "") - if err != nil { - return nil, errors.Wrap(err, "Failed to get volume") - } - tagsCNS := make(map[string]string) - tagsCNS["cns.tag"] = "1" - tags = ktags.Union(tags, tagsCNS) - if err = p.SetTags(ctx, vol, tags); err != nil { - return nil, errors.Wrap(err, "Failed to set tags") - } - log.Debug().Print("CreateDiskFromSnapshot complete", field.M{"SnapshotID": snapshotID, "NewVolumeID": vol.ID}) - return p.VolumeGet(ctx, vol.ID, "") -} - -// VolumeDelete is part of blockstorage.Provider -func (p *FcdProvider) VolumeDelete(ctx context.Context, volume *blockstorage.Volume) error { - task, err := p.Gom.Delete(ctx, vimID(volume.ID)) - if err != nil { - return errors.Wrap(err, "Failed to delete the disk") - } - _, err = task.Wait(ctx, vmWareTimeout) - return err -} - -// VolumeGet is part of blockstorage.Provider -func (p *FcdProvider) VolumeGet(ctx context.Context, id string, zone string) (*blockstorage.Volume, error) { - obj, err := p.Gom.Retrieve(ctx, vimID(id)) - if err != nil { - return nil, errors.Wrap(err, "Failed to query the disk") - } - kvs, err := p.Gom.RetrieveMetadata(ctx, vimID(id), nil, "") - if err != nil { - return nil, errors.Wrap(err, "Failed to get volume metadata") - } - vol, err := convertFromObjectToVolume(obj) - if err != nil { - return nil, errors.Wrap(err, "Failed to convert object to volume") - } - vol.Tags = convertKeyValueToTags(kvs) - return vol, nil -} - -// SnapshotCopy is part of blockstorage.Provider -func (p *FcdProvider) SnapshotCopy(ctx context.Context, from blockstorage.Snapshot, to blockstorage.Snapshot) (*blockstorage.Snapshot, error) { - return nil, errors.New("Not implemented") -} - -// SnapshotCopyWithArgs is part of blockstorage.Provider -func (p *FcdProvider) SnapshotCopyWithArgs(ctx context.Context, from blockstorage.Snapshot, to blockstorage.Snapshot, args map[string]string) (*blockstorage.Snapshot, error) { - return nil, errors.New("Copy Snapshot with Args not implemented") -} - -var reVslmSyncFaultFatal = regexp.MustCompile("Change tracking invalid or disk in use") - -// SnapshotCreate is part of blockstorage.Provider -func (p *FcdProvider) SnapshotCreate(ctx context.Context, volume blockstorage.Volume, tags map[string]string) (*blockstorage.Snapshot, error) { - var res types.AnyType - description := generateSnapshotDescription(tags) - err := wait.PollUntilContextTimeout(ctx, time.Second, defaultRetryLimit, false, func(context.Context) (bool, error) { - timeOfCreateSnapshotCall := time.Now() - var createErr error - res, createErr = p.createSnapshotAndWaitForCompletion(volume, ctx, description) - if createErr == nil { - return true, nil - } - - // it's possible that snapshot was created despite of SOAP errors, - // we're trying to find snapshot created in the current iteration(using timeOfCreateSnapshotCall) - // so we won't reuse snapshots created in previous runs - foundSnapId := p.getCreatedSnapshotID(ctx, description, volume.ID, timeOfCreateSnapshotCall) - if foundSnapId != nil { - res = *foundSnapId - log.Error().WithError(createErr).Print("snapshot created with errors") - return true, nil - } - - if !soap.IsVimFault(createErr) { - return false, errors.Wrap(createErr, "Failed to wait on task") - } - - // snapshot wasn't created, handle the different SOAP errors then retry - switch t := soap.ToVimFault(createErr).(type) { - case *types.InvalidState: - log.Error().WithError(createErr).Print("There is some operation, other than this CreateSnapshot invocation, on the VM attached still being protected by its VM state. Will retry", field.M{"VolumeID": volume.ID}) - return false, nil - case *vslmtypes.VslmSyncFault: // potentially can leak snapshots - log.Error().Print(fmt.Sprintf("VslmSyncFault: %#v", t)) - if !(govmomiError{createErr}).Matches(reVslmSyncFaultFatal) { - log.Error().Print(fmt.Sprintf("CreateSnapshot failed with VslmSyncFault. Will retry: %s", (govmomiError{createErr}).Format()), field.M{"VolumeID": volume.ID}) - return false, nil - } - return false, errors.Wrap(createErr, "CreateSnapshot failed with VslmSyncFault. A snapshot may have been created by this failed operation") - case *types.NotFound: - log.Error().WithError(createErr).Print("CreateSnapshot failed with NotFound error. Will retry", field.M{"VolumeID": volume.ID}) - return false, nil - default: - return false, errors.Wrap(createErr, "Failed to wait on task") - } - }) - if err != nil { - log.Error().WithError(err).Print(fmt.Sprintf("Failed to create snapshot for FCD %s: %s", volume.ID, govmomiError{err}.Format())) - return nil, errors.Wrap(err, fmt.Sprintf("Failed to create snapshot for FCD %s", volume.ID)) - } - id, ok := res.(types.ID) - if !ok { - return nil, errors.New(fmt.Sprintf("Unexpected type returned for FCD %s", volume.ID)) - } - snap, err := p.SnapshotGet(ctx, SnapshotFullID(volume.ID, id.Id)) - if err != nil { - return nil, errors.Wrap(err, fmt.Sprintf("Failed to get snapshot %s:%s", volume.ID, id.Id)) - } - log.Debug().Print("SnapshotCreate complete", field.M{"VolumeID": volume.ID, "SnapshotID": snap.ID}) - // We don't get size information from `SnapshotGet` - so set this to the volume size for now - if snap.SizeInBytes == 0 { - snap.SizeInBytes = volume.SizeInBytes - } - snap.Volume = &volume - - if err = p.SetTags(ctx, snap, getTagsWithoutDescription(tags)); err != nil { - return nil, errors.Wrap(err, fmt.Sprintf("Failed to set tags for snapshot %s:%s", volume.ID, snap.ID)) - } - - return snap, nil -} - -// SnapshotCreateWaitForCompletion is part of blockstorage.Provider -func (p *FcdProvider) SnapshotCreateWaitForCompletion(ctx context.Context, snapshot *blockstorage.Snapshot) error { - return nil -} - -// SnapshotDelete is part of blockstorage.Provider -func (p *FcdProvider) SnapshotDelete(ctx context.Context, snapshot *blockstorage.Snapshot) error { - volID, snapshotID, err := SplitSnapshotFullID(snapshot.ID) - if err != nil { - return errors.Wrap(err, "Cannot infer volume ID from full snapshot ID") - } - return wait.PollUntilContextTimeout(ctx, time.Second, defaultRetryLimit, false, func(context.Context) (bool, error) { - log.Debug().Print("SnapshotDelete", field.M{"VolumeID": volID, "SnapshotID": snapshotID}) - task, lerr := p.Gom.DeleteSnapshot(ctx, vimID(volID), vimID(snapshotID)) - if lerr != nil { - if soap.IsSoapFault(lerr) { - soapFault := soap.ToSoapFault(lerr) - receivedFault := soapFault.Detail.Fault - _, ok := receivedFault.(types.NotFound) - if ok { - log.Debug().Print("The FCD id was not found in VC during deletion, assuming success", field.M{"err": lerr, "VolumeID": volID, "SnapshotID": snapshotID}) - return true, nil - } - } - return false, errors.Wrap(lerr, "Failed to create a task for the DeleteSnapshot invocation on an IVD Protected Entity") - } - log.Debug().Print("Started SnapshotDelete task", field.M{"VolumeID": volID, "SnapshotID": snapshotID}) - _, lerr = task.Wait(ctx, vmWareTimeout) - if lerr != nil { - // The following error handling was pulled from https://github.com/vmware-tanzu/astrolabe/blob/91eeed4dcf77edd1387a25e984174f159d66fedb/pkg/ivd/ivd_protected_entity.go#L433 - if soap.IsVimFault(lerr) { - switch soap.ToVimFault(lerr).(type) { - case *types.InvalidArgument: - log.Error().WithError(lerr).Print("Disk doesn't have given snapshot due to the snapshot stamp being " + - "removed in the previous DeleteSnapshot operation which failed with an InvalidState fault. It " + - "will be resolved by the next snapshot operation on the same VM. Will NOT retry") - return true, nil - case *types.NotFound: - log.Error().WithError(lerr).Print("There is a temporary catalog mismatch due to a race condition with " + - "one another concurrent DeleteSnapshot operation. It will be resolved by the next " + - "consolidateDisks operation on the same VM. Will NOT retry") - return true, nil - case *types.InvalidState: - log.Error().WithError(lerr).Print("There is some operation, other than this DeleteSnapshot invocation, on the same VM still being protected by its VM state. Will retry") - return false, nil - case *types.TaskInProgress: - log.Error().WithError(lerr).Print("There is some other InProgress operation on the same VM. Will retry") - return false, nil - case *types.FileLocked: - log.Error().WithError(lerr).Print("An error occurred while consolidating disks: Failed to lock the file. Will retry") - return false, nil - } - } - return false, errors.Wrap(lerr, "Failed to wait on task") - } - log.Debug().Print("SnapshotDelete task complete", field.M{"VolumeID": volID, "SnapshotID": snapshotID}) - err = p.deleteSnapshotTags(ctx, snapshot) - if err != nil { - return false, errors.Wrap(err, "Failed to delete snapshot tags") - } - return true, nil - }) -} - -// SnapshotGet is part of blockstorage.Provider -func (p *FcdProvider) SnapshotGet(ctx context.Context, id string) (*blockstorage.Snapshot, error) { - volID, snapshotID, err := SplitSnapshotFullID(id) - if err != nil { - return nil, errors.Wrap(err, "Cannot infer volume ID from full snapshot ID") - } - log.Debug().Print("RetrieveSnapshotInfo:" + volID) - results, err := p.Gom.RetrieveSnapshotInfo(ctx, vimID(volID)) - if err != nil { - return nil, errors.Wrap(err, "Failed to get snapshot info") - } - log.Debug().Print("RetrieveSnapshotInfo done:" + volID) - - for _, result := range results { - if result.Id.Id == snapshotID { - snapshot, err := convertFromObjectToSnapshot(&result, volID) - if err != nil { - return nil, errors.Wrap(err, "Failed to convert object to snapshot") - } - snapID := vimID(snapshotID) - log.Debug().Print("RetrieveMetadata: " + volID + "," + snapshotID) - kvs, err := p.Gom.RetrieveMetadata(ctx, vimID(volID), &snapID, "") - if err != nil { - return nil, errors.Wrap(err, "Failed to get snapshot metadata") - } - log.Debug().Print("RetrieveMetadata done: " + volID + "," + snapshotID) - tags := convertKeyValueToTags(kvs) - additionalTags, err := p.getSnapshotTags(ctx, id) - if err != nil { - return nil, errors.Wrap(err, "Failed to get snapshot tags") - } - tags = append(tags, additionalTags...) - snapshot.Tags = tags - return snapshot, nil - } - } - return nil, errors.New("Failed to find snapshot") -} - -// SetTags is part of blockstorage.Provider -func (p *FcdProvider) SetTags(ctx context.Context, resource interface{}, tags map[string]string) error { - switch r := resource.(type) { - case *blockstorage.Volume: - return p.setTagsVolume(ctx, r, tags) - case *blockstorage.Snapshot: - return p.setSnapshotTags(ctx, r, tags) - default: - return errors.New("Unsupported type for resource") - } -} - -func (p *FcdProvider) setTagsVolume(ctx context.Context, volume *blockstorage.Volume, tags map[string]string) error { - if volume == nil { - return errors.New("Empty volume") - } - task, err := p.Gom.UpdateMetadata(ctx, vimID(volume.ID), convertTagsToKeyValue(tags), nil) - if err != nil { - return errors.Wrap(err, "Failed to update metadata") - } - _, err = task.Wait(ctx, vmWareTimeout) - if err != nil { - return errors.Wrap(err, "Failed to wait on task") - } - return nil -} - -// GetOrCreateCategory takes a category name and attempts to get or create the category -// it returns the category ID. -func (p *FcdProvider) GetOrCreateCategory(ctx context.Context, categoryName string) (string, error) { - id, err := p.GetCategoryID(ctx, categoryName) - if err != nil { - if strings.Contains(err.Error(), "404 Not Found") { - id, err := p.tagManager.CreateCategory(ctx, &vapitags.Category{ - Name: categoryName, - Cardinality: "SINGLE", - }) - if err != nil { - return "", errors.Wrap(err, "Failed to create category") - } - return id, nil - } - return "", err - } - return id, nil -} - -// GetCategoryID takes a category name and returns the category ID if it finds it. -func (p *FcdProvider) GetCategoryID(ctx context.Context, categoryName string) (string, error) { - cat, err := p.tagManager.GetCategory(ctx, categoryName) - if err != nil { - return "", errors.Wrap(err, "Failed to find category") - } - return cat.ID, nil -} - -// SnapshotsList is part of blockstorage.Provider -func (p *FcdProvider) SnapshotsList(ctx context.Context, tags map[string]string) ([]*blockstorage.Snapshot, error) { - if filterStr := generateSnapshotDescription(tags); filterStr != "" { - volumeIDs := strings.Split(tags[VolumeIDListTag], ",") - if len(volumeIDs) == 0 { - return nil, errors.New("vSphere can't list by description without list of volumes. Cannot list snapshots") - } - return p.snapshotsListByDescription(ctx, volumeIDs, filterStr) - } - return p.snapshotsListByTag(ctx, tags) -} - -func (p *FcdProvider) createSnapshotAndWaitForCompletion(volume blockstorage.Volume, ctx context.Context, description string) (types.AnyType, error) { - log.Debug().Print("CreateSnapshot", field.M{"VolumeID": volume.ID}) - task, err := p.Gom.CreateSnapshot(ctx, vimID(volume.ID), description) - if err != nil { - return nil, errors.Wrap(err, "CreateSnapshot task creation failure") - } - log.Debug().Print("Started CreateSnapshot task", field.M{"VolumeID": volume.ID}) - return task.Wait(ctx, vmWareTimeout) -} - -func (p *FcdProvider) getCreatedSnapshotID(ctx context.Context, description string, volID string, notEarlierThan time.Time) *types.ID { - var filteredSns []*blockstorage.Snapshot - sns, err := p.snapshotsListByDescription(ctx, []string{volID}, description) - if err != nil { - log.Error().WithError(err).Print("Failed to list when checking failed creation") - return nil - } - - for _, sn := range sns { - if notEarlierThan.Before((time.Time)(sn.CreationTime)) { - filteredSns = append(filteredSns, sn) - } - } - - if len(filteredSns) == 1 { - _, snapID, err := SplitSnapshotFullID(filteredSns[0].ID) - if err != nil { - log.Error().WithError(err) - return nil - } - return &types.ID{ - Id: snapID, - } - } - - if len(filteredSns) > 1 { - log.Error().Print(fmt.Sprintf("More than one snapshot was found, IDs: %s", strings.Join(getSnapshotsIDs(filteredSns), ","))) - } - return nil -} - -func generateSnapshotDescription(tags map[string]string) string { - var tagsAsStr []string - for name, value := range tags { - if strings.HasPrefix(name, DescriptionTag) { - tagsAsStr = append(tagsAsStr, fmt.Sprintf("%s:%s", name, value)) - } - } - return strings.Join(tagsAsStr, ",") -} - -func getTagsWithoutDescription(tags map[string]string) map[string]string { - result := make(map[string]string, len(tags)) - for name, value := range tags { - if !strings.HasPrefix(name, DescriptionTag) { - result[name] = value - } - } - return result -} - -func getSnapshotsIDs(snapshots []*blockstorage.Snapshot) []string { - result := make([]string, 0, len(snapshots)) - for _, snapshot := range snapshots { - result = append(result, snapshot.ID) - } - return result -} - -// snapshotTag is the struct that will be used to create vmware tags -// the tags are of the form volid:snapid:tag:value -// these tags are assigned to a predefined category that is initialized by the FcdProvider -type snapshotTag struct { - volid string - snapid string - key string - value string -} - -func (t *snapshotTag) String() string { - volid := strings.ReplaceAll(t.volid, ":", "-") - snapid := strings.ReplaceAll(t.snapid, ":", "-") - key := strings.ReplaceAll(t.key, ":", "-") - value := strings.ReplaceAll(t.value, ":", "-") - return fmt.Sprintf("%s:%s:%s:%s", volid, snapid, key, value) -} - -func (t *snapshotTag) Parse(tag string) error { - parts := strings.Split(tag, ":") - if len(parts) != 4 { - return errors.Errorf("Malformed tag (%s)", tag) - } - t.volid, t.snapid, t.key, t.value = parts[0], parts[1], parts[2], parts[3] - return nil -} - -// setSnapshotTags sets tags for a snapshot -func (p *FcdProvider) setSnapshotTags(ctx context.Context, snapshot *blockstorage.Snapshot, tags map[string]string) error { - if p.categoryID == "" { - log.Debug().Print("vSphere snapshot tagging is disabled") - return nil - } - if snapshot == nil { - return errors.New("Empty snapshot") - } - volID, snapID, err := SplitSnapshotFullID(snapshot.ID) - if err != nil { - return errors.Wrap(err, "Cannot infer volumeID and snapshotID from full snapshot ID") - } - - for k, v := range tags { - tag := &snapshotTag{volID, snapID, k, v} - _, err = p.tagManager.CreateTag(ctx, &vapitags.Tag{ - CategoryID: p.categoryID, - Name: tag.String(), - }) - if err != nil && !strings.Contains(err.Error(), "ALREADY_EXISTS") { - return errors.Wrapf(err, "Failed to create tag (%s) for categoryID (%s) ", tag, p.categoryID) - } - } - return nil -} - -func (p *FcdProvider) deleteSnapshotTags(ctx context.Context, snapshot *blockstorage.Snapshot) error { - if p.categoryID == "" { - log.Debug().Print("vSphere snapshot tagging is disabled (categoryID not set). Cannot list snapshots") - return nil - } - if snapshot == nil { - return errors.New("Empty snapshot") - } - volID, snapID, err := SplitSnapshotFullID(snapshot.ID) - if err != nil { - return errors.Wrap(err, "Cannot infer volumeID and snapshotID from full snapshot ID") - } - categoryTags, err := p.tagManager.GetTagsForCategory(ctx, p.categoryID) - if err != nil { - return errors.Wrap(err, "Failed to list tags") - } - for _, tag := range categoryTags { - parsedTag := &snapshotTag{} - err := parsedTag.Parse(tag.Name) - if err != nil { - return errors.Wrapf(err, "Failed to parse tag (%s)", tag.Name) - } - if parsedTag.snapid == snapID && parsedTag.volid == volID { - err := p.tagManager.DeleteTag(ctx, &tag) - if err != nil { - return errors.Wrapf(err, "Failed to delete tag (%s)", tag.Name) - } - } - } - return nil -} - -// VolumesList is part of blockstorage.Provider -func (p *FcdProvider) VolumesList(ctx context.Context, tags map[string]string, zone string) ([]*blockstorage.Volume, error) { - return nil, errors.New("Not implemented") -} - -func (p *FcdProvider) getSnapshotTags(ctx context.Context, fullSnapshotID string) ([]*blockstorage.KeyValue, error) { - if p.categoryID == "" { - log.Debug().Print("vSphere snapshot tagging is disabled (categoryID not set). Cannot get snapshot tags") - return nil, nil - } - categoryTags, err := p.tagManager.GetTagsForCategory(ctx, p.categoryID) - if err != nil { - return nil, errors.Wrap(err, "Failed to list tags") - } - return p.getTagsFromSnapshotID(categoryTags, fullSnapshotID) -} - -func (p *FcdProvider) snapshotsListByDescription(ctx context.Context, volumeIDs []string, filterStr string) ([]*blockstorage.Snapshot, error) { - var result []*blockstorage.Snapshot - for _, volID := range volumeIDs { - snapshots, _ := p.Gom.RetrieveSnapshotInfo(ctx, vimID(volID)) - for _, snapshot := range snapshots { - if snapshot.Description == filterStr { - sn, err := convertFromObjectToSnapshot(&snapshot, volID) - if err != nil { - return nil, errors.Wrap(err, "Failed to convert object to snapshot") - } - result = append(result, sn) - } - } - } - - return result, nil -} - -func (p *FcdProvider) snapshotsListByTag(ctx context.Context, tags map[string]string) ([]*blockstorage.Snapshot, error) { - if p.categoryID == "" { - log.Debug().Print("vSphere snapshot tagging is disabled (categoryID not set). Cannot list snapshots") - return nil, nil - } - - categoryTags, err := p.tagManager.GetTagsForCategory(ctx, p.categoryID) - if err != nil { - return nil, errors.Wrap(err, "Failed to list tags") - } - - snapshotIDs, err := p.getSnapshotIDsFromTags(categoryTags, tags) - if err != nil { - return nil, errors.Wrap(err, "Failed to get snapshotIDs from tags") - } - - var snapshots []*blockstorage.Snapshot - if len(snapshotIDs) > 0 { - for _, snapshotID := range snapshotIDs { - snapshot, err := p.SnapshotGet(ctx, snapshotID) - if err != nil { - return nil, err - } - snapshots = append(snapshots, snapshot) - } - } - return snapshots, nil -} - -func (p *FcdProvider) getTagsFromSnapshotID(categoryTags []vapitags.Tag, fullSnapshotID string) ([]*blockstorage.KeyValue, error) { - tags := map[string]string{} - for _, catTag := range categoryTags { - parsedTag := &snapshotTag{} - if err := parsedTag.Parse(catTag.Name); err != nil { - return nil, errors.Wrapf(err, "Failed to parse tag") - } - snapId := SnapshotFullID(parsedTag.volid, parsedTag.snapid) - if snapId == fullSnapshotID { - tags[parsedTag.key] = parsedTag.value - } - } - return blockstorage.MapToKeyValue(tags), nil -} - -func (p *FcdProvider) getSnapshotIDsFromTags(categoryTags []vapitags.Tag, tags map[string]string) ([]string, error) { - snapshotTagMap := map[string]map[string]string{} - for _, catTag := range categoryTags { - parsedTag := &snapshotTag{} - if err := parsedTag.Parse(catTag.Name); err != nil { - return nil, errors.Wrapf(err, "Failed to parse tag") - } - snapId := SnapshotFullID(parsedTag.volid, parsedTag.snapid) - if _, ok := snapshotTagMap[snapId]; !ok { - snapshotTagMap[snapId] = map[string]string{} - } - snapshotTagMap[snapId][parsedTag.key] = parsedTag.value - } - - snapshotIDs := []string{} - for snapshotID, snapshotTags := range snapshotTagMap { - tagsMatch := true - for k, v := range tags { - if val, ok := snapshotTags[k]; !ok || val != v { - tagsMatch = false - break - } - } - if tagsMatch { - snapshotIDs = append(snapshotIDs, snapshotID) - } - } - return snapshotIDs, nil -} - -func getEnvAsIntOrDefault(envKey string, def int) int { - if v, ok := os.LookupEnv(envKey); ok { - iv, err := strconv.Atoi(v) - if err == nil && iv > 0 { - return iv - } - log.Debug().Print("Using default timeout value for vSphere because of invalid environment variable", field.M{"envVar": v}) - } - - return def -} - -type tagManager interface { - GetCategory(ctx context.Context, id string) (*vapitags.Category, error) - CreateCategory(ctx context.Context, category *vapitags.Category) (string, error) - CreateTag(ctx context.Context, tag *vapitags.Tag) (string, error) - GetTagsForCategory(ctx context.Context, id string) ([]vapitags.Tag, error) - DeleteTag(ctx context.Context, tag *vapitags.Tag) error -} - -// Helper to parse an error code returned by the govmomi repo. -type govmomiError struct { - err error -} - -func (ge govmomiError) Format() string { - msgs := ge.ExtractMessages() - switch len(msgs) { - case 0: - return "" - case 1: - return msgs[0] - } - return fmt.Sprintf("[%s]", strings.Join(msgs, "; ")) -} - -//nolint:gocognit,nestif -func (ge govmomiError) ExtractMessages() []string { - err := ge.err - - if err == nil { - return nil - } - - msgs := []string{} - if reason := err.Error(); reason != "" { - msgs = append(msgs, reason) - } - - // unwrap to a type handled - foundHandledErrorType := false - for err != nil && !foundHandledErrorType { - switch err.(type) { - case govmomitask.Error: - foundHandledErrorType = true - default: - switch { - case soap.IsSoapFault(err): - foundHandledErrorType = true - case soap.IsVimFault(err): - foundHandledErrorType = true - default: - err = errors.Unwrap(err) - } - } - } - - if err != nil { - var faultMsgs []types.LocalizableMessage - switch e := err.(type) { - case govmomitask.Error: - if e.Description != nil { - msgs = append(msgs, e.Description.Message) - } - faultMsgs = e.LocalizedMethodFault.Fault.GetMethodFault().FaultMessage - default: - if soap.IsSoapFault(err) { - detail := soap.ToSoapFault(err).Detail.Fault - if f, ok := detail.(types.BaseMethodFault); ok { - faultMsgs = f.GetMethodFault().FaultMessage - } - } else if soap.IsVimFault(err) { - f := soap.ToVimFault(err) - faultMsgs = f.GetMethodFault().FaultMessage - } - } - - for _, m := range faultMsgs { - if m.Message != "" && !strings.HasPrefix(m.Message, "[context]") { - msgs = append(msgs, fmt.Sprintf("%s (%s)", m.Message, m.Key)) - } - for _, a := range m.Arg { - msgs = append(msgs, fmt.Sprintf("%s", a.Value)) - } - } - } - - return msgs -} - -func (ge govmomiError) Matches(pat *regexp.Regexp) bool { - for _, m := range ge.ExtractMessages() { - if pat.MatchString(m) { - return true - } - } - - return false -} diff --git a/pkg/blockstorage/vmware/vmware_manual_test.go b/pkg/blockstorage/vmware/vmware_manual_test.go deleted file mode 100644 index 1fd20ebba9..0000000000 --- a/pkg/blockstorage/vmware/vmware_manual_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package vmware - -import ( - "context" - - "github.com/gofrs/uuid" - "gopkg.in/check.v1" -) - -type VmwareManSuite struct{} - -var _ = check.Suite(&VmwareManSuite{}) - -func (s *VmwareManSuite) TestCreateAndListSnapshots(c *check.C) { - c.Skip("manual testing") - volumeID := "55c3e39b-95b0-40d1-aaed-ea11be829fa6" - provider, _ := NewProvider(map[string]string{ - VSphereEndpointKey: "", - VSphereUsernameKey: "", - VSpherePasswordKey: "", - }) - ftpProvider := provider.(*FcdProvider) - - ftpProvider.SetCategoryID("K10:0c66728a-dd0d-11ec-9939-ca6a7623d809") - ctx := context.Background() - - guid1, _ := uuid.NewV1() - guid2, _ := uuid.NewV1() - tags := map[string]string{ - DescriptionTag: guid1.String(), - "manifest": guid1.String(), - VolumeIDListTag: volumeID, - } - - volume, _ := provider.VolumeGet(ctx, volumeID, "") - snapshot1, _ := provider.SnapshotCreate(ctx, *volume, map[string]string{"manifest": guid1.String(), DescriptionTag: guid2.String()}) - snapshot2, _ := provider.SnapshotCreate(ctx, *volume, tags) - - foundSnapshotsByID, _ := provider.SnapshotsList(ctx, tags) - foundAllSnapshots, _ := provider.SnapshotsList(ctx, map[string]string{"manifest": guid1.String()}) - - c.Assert(len(foundSnapshotsByID), check.Equals, 1) - c.Assert(len(foundAllSnapshots), check.Equals, 2) - c.Assert(snapshot2.ID, check.Equals, foundSnapshotsByID[0].ID) - - err := provider.SnapshotDelete(ctx, snapshot2) - c.Assert(err, check.IsNil) - err = provider.SnapshotDelete(ctx, snapshot1) - c.Assert(err, check.IsNil) - - foundAllSnapshots, _ = provider.SnapshotsList(ctx, map[string]string{"manifest": guid1.String()}) - c.Assert(len(foundAllSnapshots), check.Equals, 0) -} diff --git a/pkg/blockstorage/vmware/vmware_test.go b/pkg/blockstorage/vmware/vmware_test.go deleted file mode 100644 index af8b9f3439..0000000000 --- a/pkg/blockstorage/vmware/vmware_test.go +++ /dev/null @@ -1,547 +0,0 @@ -package vmware - -import ( - "bytes" - "context" - "fmt" - "os" - "sort" - "testing" - "time" - - "github.com/pkg/errors" - govmomitask "github.com/vmware/govmomi/task" - vapitags "github.com/vmware/govmomi/vapi/tags" - "github.com/vmware/govmomi/vim25/soap" - "github.com/vmware/govmomi/vim25/types" - "github.com/vmware/govmomi/vim25/xml" - vslmtypes "github.com/vmware/govmomi/vslm/types" - . "gopkg.in/check.v1" - - "github.com/kanisterio/kanister/pkg/blockstorage" -) - -func Test(t *testing.T) { TestingT(t) } - -type VMWareSuite struct{} - -var _ = Suite(&VMWareSuite{}) - -func (s *VMWareSuite) TestURLParse(c *C) { - for _, tc := range []struct { - config map[string]string - errCheck Checker - expErrString string - }{ - { - config: map[string]string{}, - errCheck: NotNil, - expErrString: "Failed to find VSphere endpoint value", - }, - { - config: map[string]string{ - VSphereEndpointKey: "ep", - }, - errCheck: NotNil, - expErrString: "Failed to find VSphere username value", - }, - { - config: map[string]string{ - VSphereEndpointKey: "ep", - VSphereUsernameKey: "user", - }, - errCheck: NotNil, - expErrString: "Failed to find VSphere password value", - }, - { // until we can run against a VIM setup this will always fail. - config: map[string]string{ - VSphereEndpointKey: "ep", - VSphereUsernameKey: "user", - VSpherePasswordKey: "pass", - }, - errCheck: NotNil, - expErrString: "Failed to create VIM client", - }, - } { - _, err := NewProvider(tc.config) - c.Check(err, tc.errCheck) - if err != nil { - c.Assert(err, ErrorMatches, ".*"+tc.expErrString+".*") - } - } -} - -func (s *VMWareSuite) TestIsParaVirtualized(c *C) { - // the constructor needs VIM so just check the parsing of the config map. - - config := map[string]string{} - c.Assert(false, Equals, configIsParaVirtualized(config)) - config[VSphereIsParaVirtualizedKey] = "false" - c.Assert(false, Equals, configIsParaVirtualized(config)) - config[VSphereIsParaVirtualizedKey] = "true" - c.Assert(true, Equals, configIsParaVirtualized(config)) - config[VSphereIsParaVirtualizedKey] = "TRUE" - c.Assert(true, Equals, configIsParaVirtualized(config)) - config[VSphereIsParaVirtualizedKey] = "1" - c.Assert(true, Equals, configIsParaVirtualized(config)) - - fcd := &FcdProvider{} - c.Assert(false, Equals, fcd.IsParaVirtualized()) - fcd.isParaVirtualized = true - c.Assert(true, Equals, fcd.IsParaVirtualized()) - - // failed operations - v, err := fcd.VolumeCreateFromSnapshot(context.Background(), blockstorage.Snapshot{}, nil) - c.Assert(true, Equals, errors.Is(err, ErrNotSupportedWithParaVirtualizedVolumes)) - c.Assert(v, IsNil) -} - -func (s *VMWareSuite) TestTimeoutEnvSetting(c *C) { - tempEnv := os.Getenv(vmWareTimeoutMinEnv) - os.Unsetenv(vmWareTimeoutMinEnv) - timeout := time.Duration(getEnvAsIntOrDefault(vmWareTimeoutMinEnv, int(defaultWaitTime/time.Minute))) * time.Minute - c.Assert(timeout, Equals, defaultWaitTime) - - os.Setenv(vmWareTimeoutMinEnv, "7") - timeout = time.Duration(getEnvAsIntOrDefault(vmWareTimeoutMinEnv, int(defaultWaitTime/time.Minute))) * time.Minute - c.Assert(timeout, Equals, 7*time.Minute) - - os.Setenv(vmWareTimeoutMinEnv, "badValue") - timeout = time.Duration(getEnvAsIntOrDefault(vmWareTimeoutMinEnv, int(defaultWaitTime/time.Minute))) * time.Minute - c.Assert(timeout, Equals, defaultWaitTime) - - os.Setenv(vmWareTimeoutMinEnv, "-1") - timeout = time.Duration(getEnvAsIntOrDefault(vmWareTimeoutMinEnv, int(defaultWaitTime/time.Minute))) * time.Minute - c.Assert(timeout, Equals, defaultWaitTime) - - os.Setenv(vmWareTimeoutMinEnv, "0") - timeout = time.Duration(getEnvAsIntOrDefault(vmWareTimeoutMinEnv, int(defaultWaitTime/time.Minute))) * time.Minute - c.Assert(timeout, Equals, defaultWaitTime) - - timeout = time.Duration(getEnvAsIntOrDefault("someotherenv", 5)) * time.Minute - c.Assert(timeout, Equals, 5*time.Minute) - - os.Setenv(vmWareTimeoutMinEnv, tempEnv) -} - -func (s *VMWareSuite) TestGetSnapshotIDsFromTags(c *C) { - for _, tc := range []struct { - catTags []vapitags.Tag - tags map[string]string - errChecker Checker - snapIDs []string - }{ - { - catTags: []vapitags.Tag{ - {Name: "v1:s1:k1:v1"}, - {Name: "v1:s1:k2:v2"}, - {Name: "v1:s2:k1:v1"}, - }, - tags: map[string]string{ - "k1": "v1", - "k2": "v2", - }, - snapIDs: []string{"v1:s1"}, - errChecker: IsNil, - }, - { - catTags: []vapitags.Tag{ - {Name: "v1:s1:k1:v1"}, - {Name: "v1:s1:k2:v2"}, - {Name: "v1:s2:k1:v1"}, - }, - tags: map[string]string{ - "k1": "v1", - }, - snapIDs: []string{"v1:s1", "v1:s2"}, - errChecker: IsNil, - }, - { - catTags: []vapitags.Tag{ - {Name: "v1:s1:k1:v1"}, - {Name: "v1:s1:k2:v2"}, - {Name: "v1:s2:k1:v1"}, - }, - snapIDs: []string{"v1:s1", "v1:s2"}, - errChecker: IsNil, - }, - { - catTags: []vapitags.Tag{ - {Name: "v1:s1k1:v1"}, - }, - tags: map[string]string{ - "k1": "v1", - }, - errChecker: NotNil, - }, - } { - fp := &FcdProvider{} - snapIDs, err := fp.getSnapshotIDsFromTags(tc.catTags, tc.tags) - c.Assert(err, tc.errChecker) - if tc.errChecker == IsNil { - sort.Strings(snapIDs) - sort.Strings(tc.snapIDs) - c.Assert(snapIDs, DeepEquals, tc.snapIDs) - } - } -} - -func (s *VMWareSuite) TestSetTagsSnapshot(c *C) { - ctx := context.Background() - for _, tc := range []struct { - catID string - snapshot *blockstorage.Snapshot - tags map[string]string - errChecker Checker - expNumCreates int - - errCreateTag error - }{ - { // success - catID: "catid", - snapshot: &blockstorage.Snapshot{ID: "volid:snapid"}, - tags: map[string]string{ - "t1": "v1", - "t2": "v2", - }, - expNumCreates: 2, - errChecker: IsNil, - }, - { // idempotent creates - catID: "catid", - snapshot: &blockstorage.Snapshot{ID: "volid:snapid"}, - tags: map[string]string{ - "t1": "v1", - "t2": "v2", - }, - expNumCreates: 2, - errCreateTag: fmt.Errorf("ALREADY_EXISTS"), - errChecker: IsNil, - }, - { // create failure - catID: "catid", - snapshot: &blockstorage.Snapshot{ID: "volid:snapid"}, - tags: map[string]string{ - "t1": "v1", - "t2": "v2", - }, - expNumCreates: 2, - errCreateTag: fmt.Errorf("bad create"), - errChecker: NotNil, - }, - { // malformed id - catID: "catid", - snapshot: &blockstorage.Snapshot{ID: "volidsnapid"}, - errChecker: NotNil, - }, - { // nil snapshot - catID: "catid", - errChecker: NotNil, - }, - { // empty id, No error, not supported - catID: "", - errChecker: IsNil, - }, - } { - ftm := &fakeTagManager{ - errCreateTag: tc.errCreateTag, - } - provider := &FcdProvider{ - categoryID: tc.catID, - tagManager: ftm, - } - err := provider.setSnapshotTags(ctx, tc.snapshot, tc.tags) - c.Assert(err, tc.errChecker) - if tc.errChecker == IsNil { - c.Assert(ftm.numCreates, Equals, tc.expNumCreates) - } - } -} - -func (s *VMWareSuite) TestDeleteTagsSnapshot(c *C) { - ctx := context.Background() - for _, tc := range []struct { - catID string - snapshot *blockstorage.Snapshot - errChecker Checker - expNumDeletes int - - retGetTagsForCategory []vapitags.Tag - errGetTagsForCategory error - errDeleteTag error - }{ - { // success deleting tags - catID: "catid", - snapshot: &blockstorage.Snapshot{ID: "volid:snapid"}, - retGetTagsForCategory: []vapitags.Tag{ - {Name: "volid:snapid:t1:v1"}, - {Name: "volid:snapid:t2:v2"}, - {Name: "volid:snapid2:t1:v1"}, - }, - expNumDeletes: 2, - errChecker: IsNil, - }, - { // error deleting tags - catID: "catid", - snapshot: &blockstorage.Snapshot{ID: "volid:snapid"}, - retGetTagsForCategory: []vapitags.Tag{ - {Name: "volid:snapid:t1:v1"}, - {Name: "volid:snapid:t2:v2"}, - }, - errDeleteTag: fmt.Errorf("Failed to delete tag"), - errChecker: NotNil, - }, - { // error parsing tags - catID: "catid", - snapshot: &blockstorage.Snapshot{ID: "volid:snapid"}, - retGetTagsForCategory: []vapitags.Tag{ - {Name: "volid:snapidt1v1"}, - {Name: "volid:snapid:t2:v2"}, - }, - errChecker: NotNil, - }, - { // error fetching tags - catID: "catid", - snapshot: &blockstorage.Snapshot{ID: "volid:snapid"}, - errGetTagsForCategory: fmt.Errorf("Failed to get tags"), - errChecker: NotNil, - }, - { // malformed id - catID: "catid", - snapshot: &blockstorage.Snapshot{ID: "volidsnapid"}, - errChecker: NotNil, - }, - { // nil snapshot - catID: "catid", - errChecker: NotNil, - }, - { // empty id, No error, not supported - catID: "", - errChecker: IsNil, - }, - } { - ftm := &fakeTagManager{ - retGetTagsForCategory: tc.retGetTagsForCategory, - errGetTagsForCategory: tc.errGetTagsForCategory, - errDeleteTag: tc.errDeleteTag, - } - provider := &FcdProvider{ - categoryID: tc.catID, - tagManager: ftm, - } - err := provider.deleteSnapshotTags(ctx, tc.snapshot) - c.Assert(err, tc.errChecker) - if tc.errChecker == IsNil { - c.Assert(ftm.numDeletes, Equals, tc.expNumDeletes) - } - } -} - -func (s *VMWareSuite) TestGetSnapshotTags(c *C) { - ctx := context.Background() - for _, tc := range []struct { - snapshotID string - catID string - categoryTags []vapitags.Tag - expNumTags int - errGetTags error - errChecker Checker - }{ - { // success - snapshotID: "v1:s1", - categoryTags: []vapitags.Tag{ - {Name: "v1:s1:t1:v1"}, - {Name: "v1:s1:t2:v2"}, - {Name: "v1:s2:t3:v3"}, - {Name: "v3:s2:t4:v4"}, - }, - errChecker: IsNil, - catID: "something", - expNumTags: 2, - }, - { // bad tag - snapshotID: "v1:s1", - categoryTags: []vapitags.Tag{ - {Name: "v1:s1:t1:v1"}, - {Name: "v1:s1:t2:v2"}, - {Name: "v1:s2:t3:v3"}, - {Name: "v3:s2t4:v4"}, - }, - catID: "something", - errChecker: NotNil, - }, - { // bad tag - snapshotID: "v1:s1", - categoryTags: []vapitags.Tag{}, - errGetTags: errors.New("get tags error"), - errChecker: NotNil, - catID: "something", - }, - { // empty cat id - errChecker: IsNil, - catID: "", - expNumTags: 0, - }, - } { - ftm := &fakeTagManager{ - retGetTagsForCategory: tc.categoryTags, - errGetTagsForCategory: tc.errGetTags, - } - provider := &FcdProvider{ - categoryID: tc.catID, - tagManager: ftm, - } - tags, err := provider.getSnapshotTags(ctx, tc.snapshotID) - c.Assert(err, tc.errChecker) - if tc.errChecker == IsNil { - c.Assert(len(tags), Equals, tc.expNumTags) - } - } -} - -// An XML trace from `govc disk.snapshot.ls` with the VslmSyncFault -var ( - vslmSyncFaultReason = "Change tracking invalid or disk in use: api = DiskLib_BlockTrackGetEpoch, path->CValue() = /vmfs/volumes/vsan:52731cd109496ced-173f8e8aec7c6828/dc6d0c61-ec84-381f-2fa3-000c29e75b7f/4e1e7c4619a34919ae1f28fbb53fcd70-000008.vmdk" - - vslmSyncFaultReasonEsc = "Change tracking invalid or disk in use: api = DiskLib_BlockTrackGetEpoch, path->CValue() = /vmfs/volumes/vsan:52731cd109496ced-173f8e8aec7c6828/dc6d0c61-ec84-381f-2fa3-000c29e75b7f/4e1e7c4619a34919ae1f28fbb53fcd70-000008.vmdk" - - vslmSyncFaultString = "A general system error occurred: " + vslmSyncFaultReason - vslmSyncFaultStringEsc = "A general system error occurred: " + vslmSyncFaultReasonEsc - - vslmSyncFaultXML = `<Fault xmlns="http://schemas.xmlsoap.org/soap/envelope/"> - <faultcode>ServerFaultCode</faultcode> - <faultstring>` + vslmSyncFaultStringEsc + `</faultstring> - <detail> - <Fault xmlns:XMLSchema-instance="http://www.w3.org/2001/XMLSchema-instance" XMLSchema-instance:type="SystemError"> - <reason>` + vslmSyncFaultReasonEsc + `</reason> - </Fault> - </detail> - </Fault>` - - vslmSyncFaultXMLEnv = `<?xml version="1.0" encoding="UTF-8"?> - <soapenv:Envelope xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" - xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" - xmlns:xsd="http://www.w3.org/2001/XMLSchema" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <soapenv:Body>` + vslmSyncFaultXML + `</soapenv:Body> - </soapenv:Envelope>` -) - -func (s *VMWareSuite) TestFormatGovmomiError(c *C) { - // basic soap fault - fault := &soap.Fault{ - Code: "soap-fault", - String: "fault string", - } - soapFaultErr := soap.WrapSoapFault(fault) - c.Assert(govmomiError{soapFaultErr}.Format(), Equals, "soap-fault: fault string") - c.Assert(govmomiError{errors.Wrap(soapFaultErr, "outer wrapper")}.Format(), Equals, "outer wrapper: soap-fault: fault string") - - // Experiment with a real fault XML to figure out how to decode an error. - // (adapted from govmomi/vim25/methods/fault_test.go) - type TestBody struct { - Fault *soap.Fault `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault,omitempty"` - } - body := TestBody{} - env := soap.Envelope{Body: &body} - dec := xml.NewDecoder(bytes.NewReader([]byte(vslmSyncFaultXMLEnv))) - dec.TypeFunc = types.TypeFunc() - err := dec.Decode(&env) - c.Assert(err, IsNil) - c.Assert(body.Fault, NotNil) - - err = soap.WrapSoapFault(body.Fault) - c.Assert(soap.IsSoapFault(err), Equals, true) - c.Assert(err.Error(), Equals, "ServerFaultCode: "+vslmSyncFaultString) // details present - - vimFault := &types.VimFault{ - MethodFault: types.MethodFault{ - FaultCause: &types.LocalizedMethodFault{ - LocalizedMessage: err.Error(), - }, - }, - } - err = soap.WrapVimFault(vimFault) - c.Assert(soap.IsVimFault(err), Equals, true) - c.Assert(err.Error(), Equals, "VimFault") // lost the details - - // A vslmFault fault with details such as that returned by gom.SnapshotCreate when - // a volume CTK file is moved. (Note: govc succeeds in this case but list will fail) - vslmFaultValue := "(vmodl.fault.SystemError) {\n faultCause = null,\n faultMessage = null,\n reason = " + vslmSyncFaultReason + "}" - vslmFault := &vslmtypes.VslmSyncFault{ - VslmFault: vslmtypes.VslmFault{ - MethodFault: types.MethodFault{ - FaultMessage: []types.LocalizableMessage{ - { - Key: "com.vmware.pbm.pbmFault.locale", - Arg: []types.KeyAnyValue{ - { - Key: "summary", - Value: vslmFaultValue, - }, - }, - }, - }, - }, - }, - Id: &types.ID{}, - } - c.Assert(vslmFault.GetMethodFault(), NotNil) - c.Assert(vslmFault.GetMethodFault().FaultMessage, DeepEquals, vslmFault.FaultMessage) - - err = soap.WrapVimFault(vslmFault) - c.Assert(err.Error(), Equals, "VslmSyncFault") - c.Assert(govmomiError{err}.Format(), Equals, "["+err.Error()+"; "+vslmFaultValue+"]") - c.Assert(govmomiError{errors.Wrap(err, "outer wrapper")}.Format(), Equals, "[outer wrapper: "+err.Error()+"; "+vslmFaultValue+"]") - - c.Assert(govmomiError{err}.Matches(reVslmSyncFaultFatal), Equals, true) - - // task errors - te := govmomitask.Error{ - LocalizedMethodFault: &types.LocalizedMethodFault{ - Fault: vslmFault, - }, - Description: &types.LocalizableMessage{ - Message: "description message", - }, - } - c.Assert(err.Error(), Equals, "VslmSyncFault") - c.Assert(govmomiError{te}.Format(), Equals, "[description message; "+vslmFaultValue+"]") - c.Assert(govmomiError{errors.Wrap(te, "outer wrapper")}.Format(), Equals, "[outer wrapper: ; description message; "+vslmFaultValue+"]") - - // normal error - testError := errors.New("test-error") - c.Assert(govmomiError{testError}.Format(), Equals, testError.Error()) - - // nil - c.Assert(govmomiError{nil}.Format(), Equals, "") -} - -type fakeTagManager struct { - retGetTagsForCategory []vapitags.Tag - errGetTagsForCategory error - - numDeletes int - errDeleteTag error - - numCreates int - errCreateTag error -} - -func (f *fakeTagManager) GetCategory(ctx context.Context, id string) (*vapitags.Category, error) { - return nil, nil -} -func (f *fakeTagManager) CreateCategory(ctx context.Context, category *vapitags.Category) (string, error) { - return "", nil -} -func (f *fakeTagManager) CreateTag(ctx context.Context, tag *vapitags.Tag) (string, error) { - f.numCreates++ - return "", f.errCreateTag -} -func (f *fakeTagManager) GetTagsForCategory(ctx context.Context, id string) ([]vapitags.Tag, error) { - return f.retGetTagsForCategory, f.errGetTagsForCategory -} -func (f *fakeTagManager) DeleteTag(ctx context.Context, tag *vapitags.Tag) error { - f.numDeletes++ - return f.errDeleteTag -}