From ec290600d4074faa9ebbcd72a59ad21cc440ac61 Mon Sep 17 00:00:00 2001 From: Pavan Navarathna Devaraj Date: Mon, 10 Dec 2018 16:08:21 -0800 Subject: [PATCH 1/4] CreateVolumeSnapshot region fix (#4544) * WIP: CreateVolumeSnapshot region fix * Error handling --- pkg/blockstorage/awsebs/awsebs.go | 18 ++++++++++++++++++ pkg/function/create_volume_snapshot.go | 12 ++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/pkg/blockstorage/awsebs/awsebs.go b/pkg/blockstorage/awsebs/awsebs.go index 495a60e60a..124a85163c 100644 --- a/pkg/blockstorage/awsebs/awsebs.go +++ b/pkg/blockstorage/awsebs/awsebs.go @@ -10,6 +10,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/ec2metadata" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/ec2" "github.com/jpillora/backoff" @@ -543,3 +544,20 @@ func waitOnSnapshotID(ctx context.Context, ec2Cli *EC2, snapID string) error { return false, nil }) } + +// GetRegionFromEC2Metadata retrieves the region from the EC2 metadata service. +// Only works when the call is performed from inside AWS +func GetRegionFromEC2Metadata() (string, error) { + log.Debug("Retrieving region from metadata") + conf := aws.Config{ + HTTPClient: &http.Client{ + Transport: http.DefaultTransport, + Timeout: 2 * time.Second, + }, + MaxRetries: aws.Int(1), + } + ec2MetaData := ec2metadata.New(session.Must(session.NewSession()), &conf) + + awsRegion, err := ec2MetaData.Region() + return awsRegion, errors.Wrap(err, "Failed to get AWS Region") +} diff --git a/pkg/function/create_volume_snapshot.go b/pkg/function/create_volume_snapshot.go index 5528d19539..3b9e84992e 100644 --- a/pkg/function/create_volume_snapshot.go +++ b/pkg/function/create_volume_snapshot.go @@ -176,7 +176,15 @@ func getPVCInfo(ctx context.Context, kubeCli kubernetes.Interface, namespace str if err = ValidateProfile(tp.Profile); err != nil { return nil, errors.Wrap(err, "Profile validation failed") } - region = tp.Profile.Location.S3Compliant.Region + // Get Region from PV label or EC2 metadata + if pvRegion, ok := pvLabels[kube.PVRegionLabelName]; ok { + region = pvRegion + } else { + region, err = awsebs.GetRegionFromEC2Metadata() + if err != nil { + return nil, err + } + } if pvZone, ok := pvLabels[kube.PVZoneLabelName]; ok { config[awsebs.ConfigRegion] = region config[awsebs.AccessKeyID] = tp.Profile.Credential.KeyPair.ID @@ -189,7 +197,7 @@ func getPVCInfo(ctx context.Context, kubeCli kubernetes.Interface, namespace str } return nil, errors.Errorf("PV zone label is empty, pvName: %s, namespace: %s", pvName, namespace) } - return nil, errors.New("Storage type not supported!") + return nil, errors.New("Storage type not supported") } func getPVCList(tp param.TemplateParams) ([]string, error) { From e7b706e2af6626d014737bf9f48dc8f6d7c775d7 Mon Sep 17 00:00:00 2001 From: Supriya Kharade Date: Mon, 10 Dec 2018 17:21:14 -0800 Subject: [PATCH 2/4] Add Volume Snapshot Functions to kanister docs (#4518) * Add Volume Snapshot Functions to kanister docs * address review comment * address review comment * Address review comments * Address review Comment * Trivial update --- docs/functions.rst | 132 +++++++++++++++++++++++++++++++++++++ docs/spelling_wordlist.txt | 1 + 2 files changed, 133 insertions(+) diff --git a/docs/functions.rst b/docs/functions.rst index 430bc643d8..903e4e243f 100644 --- a/docs/functions.rst +++ b/docs/functions.rst @@ -433,6 +433,138 @@ Example: namespace: "{{ .Deployment.Namespace }}" artifact: s3://bucket/path/artifact +.. _createvolumesnapshot: + +CreateVolumeSnapshot +-------------------- + +This function is used to create snapshots of one or more PVCs +associated with an application. It takes individual snapshot +of each PVC which can be then restored later. It generates an +output that contains the Snapshot info required for restoring PVCs. + +.. note:: + Currently we only support PVC snapshots on AWS EBS. Support for more storage + providers is coming soon! + +Arguments: + +.. csv-table:: + :header: "Argument", "Required", "Type", "Description" + :align: left + :widths: 5,5,5,15 + + `namespace`, Yes, `string`, namespace in which to execute + `pvcs`, No, `[]string`, list of names of PVCs to be backed up + +When no PVCs are specified in the `pvcs` argument above, all PVCs in use by a +Deployment or StatefulSet will be backed up. + +Outputs: + +.. csv-table:: + :header: "Output", "Type", "Description" + :align: left + :widths: 5,5,15 + + `volumeSnapshotInfo`,`string`, Snapshot info required while restoring the PVCs + +Example: + +Consider a scenario where you wish to backup all PVCs of a deployment. The output +of this phase is saved to an Artifact named `backupInfo`, shown below: + +.. code-block:: yaml + :linenos: + + actions: + backup: + type: Deployment + outputArtifacts: + backupInfo: + keyValue: + manifest: "{{ .Phases.backupVolume.Output.volumeSnapshotInfo }}" + phases: + - func: CreateVolumeSnapshot + name: backupVolume + args: + namespace: "{{ .Deployment.Namespace }}" + +CreateVolumeFromSnapshot +------------------------ + +This function is used to restore one or more PVCs of an application from the snapshots +taken using the :ref:`createvolumesnapshot` function. It deletes old PVCs, +if present and creates new PVCs from the snapshots taken earlier. + +Arguments: + +.. csv-table:: + :header: "Argument", "Required", "Type", "Description" + :align: left + :widths: 5,5,5,20 + + `namespace`, Yes, `string`, namespace in which to execute + `snapshots`, Yes, `string`, snapshot info generated as output in CreateVolumeSnapshot function + +Example: + +Consider a scenario where you wish to restore all PVCs of a deployment. +We will first scale down the application, restore PVCs and then scale up. +For this phase, we will make use of the backupInfo Artifact provided by +the :ref:`createvolumesnapshot` function. + +.. code-block:: yaml + :linenos: + + - func: ScaleWorkload + name: shutdownPod + args: + namespace: "{{ .Deployment.Namespace }}" + name: "{{ .Deployment.Name }}" + kind: Deployment + replicas: 0 + - func: CreateVolumeFromSnapshot + name: restoreVolume + args: + namespace: "{{ .Deployment.Namespace }}" + snapshots: "{{ .ArtifactsIn.backupInfo.KeyValue.manifest }}" + - func: ScaleWorkload + name: bringupPod + args: + namespace: "{{ .Deployment.Namespace }}" + name: "{{ .Deployment.Name }}" + kind: Deployment + replicas: 1 + +DeleteVolumeSnapshot +-------------------- + +This function is used to delete snapshots of PVCs taken using the +:ref:`createvolumesnapshot` function. + +Arguments: + +.. csv-table:: + :header: "Argument", "Required", "Type", "Description" + :align: left + :widths: 5,5,5,20 + + `namespace`, Yes, `string`, namespace in which to execute + `snapshots`, Yes, `string`, snapshot info generated as output in CreateVolumeSnapshot function + +Example: + +.. code-block:: yaml + :linenos: + + - func: DeleteVolumeSnapshot + name: deleteVolumeSnapshot + args: + namespace: "{{ .Deployment.Namespace }}" + snapshots: "{{ .ArtifactsIn.backupInfo.KeyValue.manifest }}" + + Registering Functions --------------------- diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 3fec9d4a39..525226a86e 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -29,3 +29,4 @@ PersistentVolumeClaim actionset objectstore Elasticsearch +backupInfo From 977a1522a114619f4e1948537348759471640bdc Mon Sep 17 00:00:00 2001 From: Pavan Navarathna Devaraj Date: Mon, 10 Dec 2018 18:00:32 -0800 Subject: [PATCH 3/4] Add AWS Region to failing kube test (#4548) --- pkg/function/create_volume_snapshot_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/function/create_volume_snapshot_test.go b/pkg/function/create_volume_snapshot_test.go index 74c5ec200c..16fd0b3ec5 100644 --- a/pkg/function/create_volume_snapshot_test.go +++ b/pkg/function/create_volume_snapshot_test.go @@ -54,7 +54,8 @@ func (s *CreateVolumeSnapshotTestSuite) TestGetPVCInfo(c *C) { ObjectMeta: metav1.ObjectMeta{ Name: "pv-test-1", Labels: map[string]string{ - kube.PVZoneLabelName: "us-west-2a", + kube.PVZoneLabelName: "us-west-2a", + kube.PVRegionLabelName: "us-west-2", }, }, Spec: v1.PersistentVolumeSpec{ From 6ba94b9318810a805eb9be65ace298d5bd9fb3dd Mon Sep 17 00:00:00 2001 From: Vaibhav Kamra Date: Sun, 16 Dec 2018 20:56:33 -0800 Subject: [PATCH 4/4] Split SnapshotCreate into Create/Wait methods (#4569) * Split SnapshotCreate into Create/Wait methods This allows the caller to initiate snapshot creation and wait on completion separately * Cleanup * Add `skipWait` optional parameter The `CreateVolumeSnapshot` method will not wait for snapshot completion if this is set * Add a Snapshot Wait function (#4570) Allows the caller to wait for completion of a previously initiated snapshot operation. Also updated the e2e test blueprint --- docs/functions.rst | 16 ++++ pkg/blockstorage/awsebs/awsebs.go | 44 +++++------ pkg/blockstorage/blockstorage.go | 1 + pkg/blockstorage/blockstorage_test.go | 1 + pkg/function/create_volume_snapshot.go | 18 ++++- pkg/function/e2e_volume_snapshot_test.go | 22 +++--- pkg/function/wait_for_snapshot_completion.go | 78 +++++++++++++++++++ .../wait_for_snapshot_completion_test.go | 55 +++++++++++++ .../mockblockstorage/mockblockstorage.go | 5 ++ 9 files changed, 203 insertions(+), 37 deletions(-) create mode 100644 pkg/function/wait_for_snapshot_completion.go create mode 100644 pkg/function/wait_for_snapshot_completion_test.go diff --git a/docs/functions.rst b/docs/functions.rst index 903e4e243f..16e1933c42 100644 --- a/docs/functions.rst +++ b/docs/functions.rst @@ -456,6 +456,7 @@ Arguments: `namespace`, Yes, `string`, namespace in which to execute `pvcs`, No, `[]string`, list of names of PVCs to be backed up + `skipWait`, No, `bool`, initiate but do not wait for the snapshot operation to complete When no PVCs are specified in the `pvcs` argument above, all PVCs in use by a Deployment or StatefulSet will be backed up. @@ -490,6 +491,21 @@ of this phase is saved to an Artifact named `backupInfo`, shown below: args: namespace: "{{ .Deployment.Namespace }}" +WaitForSnapshotCompletion +------------------------- + +This function is used to wait for completion of snapshot operations +initiated using the :ref:`createvolumesnapshot` function. + +Arguments: + +.. csv-table:: + :header: "Argument", "Required", "Type", "Description" + :align: left + :widths: 5,5,5,15 + + `snapshots`, Yes, `string`, snapshot info generated as output in CreateVolumeSnapshot function + CreateVolumeFromSnapshot ------------------------ diff --git a/pkg/blockstorage/awsebs/awsebs.go b/pkg/blockstorage/awsebs/awsebs.go index 124a85163c..9a9d05c811 100644 --- a/pkg/blockstorage/awsebs/awsebs.go +++ b/pkg/blockstorage/awsebs/awsebs.go @@ -273,47 +273,43 @@ func (s *ebsStorage) SnapshotCopy(ctx context.Context, from, to blockstorage.Sna func (s *ebsStorage) SnapshotCreate(ctx context.Context, volume blockstorage.Volume, tags map[string]string) (*blockstorage.Snapshot, error) { // Snapshot the EBS volume csi := (&ec2.CreateSnapshotInput{}).SetVolumeId(volume.ID) - var snapID string - alltags := ktags.GetTags(tags) + csi.SetTagSpecifications([]*ec2.TagSpecification{ + &ec2.TagSpecification{ + ResourceType: aws.String(ec2.ResourceTypeSnapshot), + Tags: mapToEC2Tags(ktags.GetTags(tags)), + }, + }) log.Infof("Snapshotting EBS volume: %s", *csi.VolumeId) csi.SetDryRun(s.ec2Cli.DryRun) snap, err := s.ec2Cli.CreateSnapshotWithContext(ctx, csi) - if isDryRunErr(err) { - snapID = "" - } else { - if err != nil { - return nil, errors.Wrapf(err, "Failed to create snapshot, volume_id: %s", *csi.VolumeId) - } - if err = setResourceTags(ctx, s.ec2Cli, aws.StringValue(snap.SnapshotId), alltags); err != nil { - return nil, err - } - err = waitOnSnapshot(ctx, s.ec2Cli, snap) - if err != nil { - return nil, errors.Wrapf(err, "Waiting on snapshot %v", snap) - } - snapID = aws.StringValue(snap.SnapshotId) - } - - snaps, err := getSnapshots(ctx, s.ec2Cli, []*string{&snapID}) - if err != nil { - return nil, err + if err != nil && !isDryRunErr(err) { + return nil, errors.Wrapf(err, "Failed to create snapshot, volume_id: %s", *csi.VolumeId) } - ebssnap := snaps[0] region, err := availabilityZoneToRegion(ctx, s.ec2Cli, volume.Az) if err != nil { return nil, err } - ms := s.snapshotParse(ctx, ebssnap) + ms := s.snapshotParse(ctx, snap) ms.Region = region - for _, tag := range ebssnap.Tags { + for _, tag := range snap.Tags { ms.Tags = append(ms.Tags, &blockstorage.KeyValue{Key: aws.StringValue(tag.Key), Value: aws.StringValue(tag.Value)}) } ms.Volume = &volume return ms, nil } +func (s *ebsStorage) SnapshotCreateWaitForCompletion(ctx context.Context, snap *blockstorage.Snapshot) error { + if s.ec2Cli.DryRun { + return nil + } + if err := waitOnSnapshotID(ctx, s.ec2Cli, snap.ID); err != nil { + return errors.Wrapf(err, "Waiting on snapshot %v", snap) + } + return nil +} + func (s *ebsStorage) SnapshotDelete(ctx context.Context, snapshot *blockstorage.Snapshot) error { log.Infof("EBS Snapshot ID %s", snapshot.ID) rmsi := &ec2.DeleteSnapshotInput{} diff --git a/pkg/blockstorage/blockstorage.go b/pkg/blockstorage/blockstorage.go index 422536f02e..e69161f26a 100644 --- a/pkg/blockstorage/blockstorage.go +++ b/pkg/blockstorage/blockstorage.go @@ -16,6 +16,7 @@ type Provider interface { // Snapshot operations SnapshotCopy(ctx context.Context, from Snapshot, to Snapshot) (*Snapshot, error) SnapshotCreate(ctx context.Context, volume Volume, tags map[string]string) (*Snapshot, error) + SnapshotCreateWaitForCompletion(context.Context, *Snapshot) error SnapshotDelete(context.Context, *Snapshot) error SnapshotGet(ctx context.Context, id string) (*Snapshot, error) // Others diff --git a/pkg/blockstorage/blockstorage_test.go b/pkg/blockstorage/blockstorage_test.go index 8d9dbf8dab..8026ef4be9 100644 --- a/pkg/blockstorage/blockstorage_test.go +++ b/pkg/blockstorage/blockstorage_test.go @@ -237,6 +237,7 @@ func (s *BlockStorageProviderSuite) createSnapshot(c *C) *blockstorage.Snapshot c.Assert(err, IsNil) s.snapshots = append(s.snapshots, ret) s.checkTagsExist(c, blockstorage.KeyValueToMap(ret.Tags), tags) + c.Assert(s.provider.SnapshotCreateWaitForCompletion(context.Background(), ret), IsNil) return ret } diff --git a/pkg/function/create_volume_snapshot.go b/pkg/function/create_volume_snapshot.go index 3b9e84992e..428beddd09 100644 --- a/pkg/function/create_volume_snapshot.go +++ b/pkg/function/create_volume_snapshot.go @@ -33,6 +33,7 @@ var ( const ( CreateVolumeSnapshotNamespaceArg = "namespace" CreateVolumeSnapshotPVCsArg = "pvcs" + CreateVolumeSnapshotSkipWaitArg = "skipWait" ) type createVolumeSnapshotFunc struct{} @@ -83,7 +84,7 @@ func ValidateProfile(profile *param.Profile) error { return nil } -func createVolumeSnapshot(ctx context.Context, tp param.TemplateParams, cli kubernetes.Interface, namespace string, pvcs []string, getter getter.Getter) (map[string]interface{}, error) { +func createVolumeSnapshot(ctx context.Context, tp param.TemplateParams, cli kubernetes.Interface, namespace string, pvcs []string, getter getter.Getter, skipWait bool) (map[string]interface{}, error) { vols := make([]volumeInfo, 0, len(pvcs)) for _, pvc := range pvcs { volInfo, err := getPVCInfo(ctx, cli, namespace, pvc, tp, getter) @@ -100,7 +101,7 @@ func createVolumeSnapshot(ctx context.Context, tp param.TemplateParams, cli kube wg.Add(1) go func(volInfo volumeInfo) { defer wg.Done() - volSnapInfo, err := snapshotVolume(ctx, volInfo, namespace) + volSnapInfo, err := snapshotVolume(ctx, volInfo, namespace, skipWait) if err != nil { errstrings = append(errstrings, err.Error()) } else { @@ -124,7 +125,7 @@ func createVolumeSnapshot(ctx context.Context, tp param.TemplateParams, cli kube return map[string]interface{}{"volumeSnapshotInfo": string(manifestData)}, nil } -func snapshotVolume(ctx context.Context, volume volumeInfo, namespace string) (*VolumeSnapshotInfo, error) { +func snapshotVolume(ctx context.Context, volume volumeInfo, namespace string, skipWait bool) (*VolumeSnapshotInfo, error) { provider := volume.provider vol, err := provider.VolumeGet(ctx, volume.volumeID, volume.volZone) if err != nil { @@ -145,6 +146,11 @@ func snapshotVolume(ctx context.Context, volume volumeInfo, namespace string) (* if err != nil { return nil, err } + if !skipWait { + if err := provider.SnapshotCreateWaitForCompletion(ctx, snap); err != nil { + return nil, errors.Wrap(err, "Snapshot creation did not complete") + } + } return &VolumeSnapshotInfo{SnapshotID: snap.ID, Type: volume.sType, Region: volume.region, PVCName: volume.pvc, Az: snap.Volume.Az, Tags: snap.Volume.Tags, VolumeType: snap.Volume.VolumeType}, nil } @@ -229,12 +235,16 @@ func (kef *createVolumeSnapshotFunc) Exec(ctx context.Context, tp param.Template } var namespace string var pvcs []string + var skipWait bool if err = Arg(args, CreateVolumeSnapshotNamespaceArg, &namespace); err != nil { return nil, err } if err = OptArg(args, CreateVolumeSnapshotPVCsArg, &pvcs, nil); err != nil { return nil, err } + if err = OptArg(args, CreateVolumeSnapshotSkipWaitArg, &skipWait, nil); err != nil { + return nil, err + } if len(pvcs) == 0 { // Fetch Volumes pvcs, err = getPVCList(tp) @@ -242,7 +252,7 @@ func (kef *createVolumeSnapshotFunc) Exec(ctx context.Context, tp param.Template return nil, err } } - return createVolumeSnapshot(ctx, tp, cli, namespace, pvcs, getter.New()) + return createVolumeSnapshot(ctx, tp, cli, namespace, pvcs, getter.New(), skipWait) } func (*createVolumeSnapshotFunc) RequiredArgs() []string { diff --git a/pkg/function/e2e_volume_snapshot_test.go b/pkg/function/e2e_volume_snapshot_test.go index 437170a8d1..0f8097c59c 100644 --- a/pkg/function/e2e_volume_snapshot_test.go +++ b/pkg/function/e2e_volume_snapshot_test.go @@ -161,6 +161,14 @@ func newVolumeSnapshotBlueprint() *crv1alpha1.Blueprint { Func: "CreateVolumeSnapshot", Args: map[string]interface{}{ CreateVolumeSnapshotNamespaceArg: "{{ .StatefulSet.Namespace }}", + CreateVolumeSnapshotSkipWaitArg: true, + }, + }, + { + Name: "waitOnSnapshots", + Func: "WaitForSnapshotCompletion", + Args: map[string]interface{}{ + WaitForSnapshotCompletionSnapshotsArg: "{{ .Phases.testBackupVolume.Output.volumeSnapshotInfo }}", }, }, }, @@ -288,21 +296,17 @@ func (s *VolumeSnapshotTestSuite) TestVolumeSnapshot(c *C) { phases, err := kanister.GetPhases(*bp, action, *s.tp) c.Assert(err, IsNil) for _, p := range phases { + c.Assert(param.InitPhaseParams(ctx, s.cli, s.tp, p.Name(), p.Objects()), IsNil) output, err := p.Exec(ctx, *bp, action, *s.tp) if err != nil && strings.Contains(err.Error(), skipTestErrorMsg) { c.Skip("Skipping the test since storage type not supported") } c.Assert(err, IsNil) + param.UpdatePhaseParams(ctx, s.tp, p.Name(), output) if action == "backup" { - keyval := make(map[string]string) - c.Assert(output, NotNil) - c.Assert(output[volumeSnapshotInfoKey], NotNil) - keyval[manifestKey] = output[volumeSnapshotInfoKey].(string) - artifact := crv1alpha1.Artifact{ - KeyValue: keyval, - } - s.tp.ArtifactsIn = make(map[string]crv1alpha1.Artifact) - s.tp.ArtifactsIn[backupInfoKey] = artifact + arts, err := param.RenderArtifacts(bp.Actions[action].OutputArtifacts, *s.tp) + c.Assert(err, IsNil) + s.tp.ArtifactsIn = arts } } } diff --git a/pkg/function/wait_for_snapshot_completion.go b/pkg/function/wait_for_snapshot_completion.go new file mode 100644 index 0000000000..c7e276a317 --- /dev/null +++ b/pkg/function/wait_for_snapshot_completion.go @@ -0,0 +1,78 @@ +package function + +import ( + "context" + "encoding/json" + + "github.com/pkg/errors" + + kanister "github.com/kanisterio/kanister/pkg" + "github.com/kanisterio/kanister/pkg/blockstorage" + "github.com/kanisterio/kanister/pkg/blockstorage/awsebs" + "github.com/kanisterio/kanister/pkg/blockstorage/getter" + "github.com/kanisterio/kanister/pkg/param" +) + +func init() { + kanister.Register(&waitForSnapshotCompletionFunc{}) +} + +var ( + _ kanister.Func = (*waitForSnapshotCompletionFunc)(nil) +) + +const ( + WaitForSnapshotCompletionSnapshotsArg = "snapshots" +) + +type waitForSnapshotCompletionFunc struct{} + +func (*waitForSnapshotCompletionFunc) Name() string { + return "WaitForSnapshotCompletion" +} + +func (*waitForSnapshotCompletionFunc) RequiredArgs() []string { + return []string{WaitForSnapshotCompletionSnapshotsArg} +} + +func (kef *waitForSnapshotCompletionFunc) Exec(ctx context.Context, tp param.TemplateParams, args map[string]interface{}) (map[string]interface{}, error) { + var snapshotinfo string + if err := Arg(args, WaitForSnapshotCompletionSnapshotsArg, &snapshotinfo); err != nil { + return nil, err + } + return nil, waitForSnapshotsCompletion(ctx, snapshotinfo, tp.Profile, getter.New()) +} + +func waitForSnapshotsCompletion(ctx context.Context, snapshotinfo string, profile *param.Profile, getter getter.Getter) error { + PVCData := []VolumeSnapshotInfo{} + err := json.Unmarshal([]byte(snapshotinfo), &PVCData) + if err != nil { + return errors.Wrapf(err, "Could not decode JSON data") + } + for _, pvcInfo := range PVCData { + config := make(map[string]string) + switch pvcInfo.Type { + case blockstorage.TypeEBS: + if err = ValidateProfile(profile); err != nil { + return errors.Wrap(err, "Profile validation failed") + } + config[awsebs.ConfigRegion] = pvcInfo.Region + config[awsebs.AccessKeyID] = profile.Credential.KeyPair.ID + config[awsebs.SecretAccessKey] = profile.Credential.KeyPair.Secret + default: + return errors.New("Storage provider not supported " + string(pvcInfo.Type)) + } + provider, err := getter.Get(pvcInfo.Type, config) + if err != nil { + return errors.Wrapf(err, "Could not get storage provider %v", pvcInfo.Type) + } + snapshot, err := provider.SnapshotGet(ctx, pvcInfo.SnapshotID) + if err != nil { + return errors.Wrapf(err, "Failed to get Snapshot from Provider") + } + if err = provider.SnapshotCreateWaitForCompletion(ctx, snapshot); err != nil { + return errors.Wrap(err, "Snapshot creation did not complete "+snapshot.ID) + } + } + return nil +} diff --git a/pkg/function/wait_for_snapshot_completion_test.go b/pkg/function/wait_for_snapshot_completion_test.go new file mode 100644 index 0000000000..9a25de1cdd --- /dev/null +++ b/pkg/function/wait_for_snapshot_completion_test.go @@ -0,0 +1,55 @@ +package function + +import ( + "context" + "encoding/json" + + . "gopkg.in/check.v1" + + crv1alpha1 "github.com/kanisterio/kanister/pkg/apis/cr/v1alpha1" + "github.com/kanisterio/kanister/pkg/blockstorage" + "github.com/kanisterio/kanister/pkg/param" + "github.com/kanisterio/kanister/pkg/testutil/mockblockstorage" +) + +type WaitForSnapshotCompletionTestSuite struct{} + +var _ = Suite(&WaitForSnapshotCompletionTestSuite{}) + +func (s *WaitForSnapshotCompletionTestSuite) TestWait(c *C) { + ctx := context.Background() + mockGetter := mockblockstorage.NewGetter() + profile := ¶m.Profile{ + Location: crv1alpha1.Location{ + Type: crv1alpha1.LocationTypeS3Compliant, + S3Compliant: &crv1alpha1.S3CompliantLocation{ + Region: "us-west-2"}, + }, + Credential: param.Credential{ + Type: param.CredentialTypeKeyPair, + KeyPair: ¶m.KeyPair{ + ID: "foo", + Secret: "bar", + }, + }, + } + pvcData1 := []VolumeSnapshotInfo{ + VolumeSnapshotInfo{SnapshotID: "snap-1", Type: blockstorage.TypeEBS, Region: "us-west-2", PVCName: "pvc-1", Az: "us-west-2a", VolumeType: "ssd"}, + VolumeSnapshotInfo{SnapshotID: "snap-2", Type: blockstorage.TypeEBS, Region: "us-west-2", PVCName: "pvc-2", Az: "us-west-2a", VolumeType: "ssd"}, + } + info, err := json.Marshal(pvcData1) + c.Assert(err, IsNil) + snapinfo := string(info) + for _, tc := range []struct { + snapshotinfo string + check Checker + }{ + { + snapshotinfo: snapinfo, + check: IsNil, + }, + } { + err := waitForSnapshotsCompletion(ctx, tc.snapshotinfo, profile, mockGetter) + c.Assert(err, tc.check) + } +} diff --git a/pkg/testutil/mockblockstorage/mockblockstorage.go b/pkg/testutil/mockblockstorage/mockblockstorage.go index 016bd61f28..10f068339e 100644 --- a/pkg/testutil/mockblockstorage/mockblockstorage.go +++ b/pkg/testutil/mockblockstorage/mockblockstorage.go @@ -139,6 +139,11 @@ func (p *Provider) SnapshotCreate(ctx context.Context, volume blockstorage.Volum return p.MockSnapshot(), nil } +// SnapshotCreateWaitForCompletion mock +func (p *Provider) SnapshotCreateWaitForCompletion(context.Context, *blockstorage.Snapshot) error { + return nil +} + // SnapshotDelete mock func (p *Provider) SnapshotDelete(ctx context.Context, snapshot *blockstorage.Snapshot) error { p.AddDeletedSnapID(snapshot.ID)