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 = ` - ServerFaultCode - ` + vslmSyncFaultStringEsc + ` - - - ` + vslmSyncFaultReasonEsc + ` - - - ` - - vslmSyncFaultXMLEnv = ` - - ` + vslmSyncFaultXML + ` - ` -) - -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 -}