Repository: google/cloud-forensics-utils

Issue:
Add disk type option for CreateVolumeCopy in AWS
Allow users to choose the volume type when making a volume copy, and default to the source volume type if none choses.

Discussion:
codecov-commenter: # [Codecov](https://codecov.io/gh/google/cloud-forensics-utils/pull/221?src=pr&el=h1) Report
> Merging [#221](https://codecov.io/gh/google/cloud-forensics-utils/pull/221?src=pr&el=desc) into [master](https://codecov.io/gh/google/cloud-forensics-utils/commit/e4448c60002eee03c344ec2803276ff5d91936be&el=desc) will **increase** coverage by `2.69%`.
> The diff coverage is `71.24%`.

[![Impacted file tree graph](https://codecov.io/gh/google/cloud-forensics-utils/pull/221/graphs/tree.svg?width=650&height=150&src=pr&token=zZkPno3ah0)](https://codecov.io/gh/google/cloud-forensics-utils/pull/221?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #221      +/-   ##
==========================================
+ Coverage   65.39%   68.08%   +2.69%     
==========================================
  Files          14       22       +8     
  Lines         968     1711     +743     
==========================================
+ Hits          633     1165     +532     
- Misses        335      546     +211     
```

| Flag | Coverage Δ | |
|---|---|---|
| #nosetests | `68.08% <71.24%> (+2.69%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/google/cloud-forensics-utils/pull/221?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [libcloudforensics/providers/aws/forensics.py](https://codecov.io/gh/google/cloud-forensics-utils/pull/221/diff?src=pr&el=tree#diff-bGliY2xvdWRmb3JlbnNpY3MvcHJvdmlkZXJzL2F3cy9mb3JlbnNpY3MucHk=) | `56.94% <48.57%> (-12.83%)` | :arrow_down: |
| [libcloudforensics/providers/gcp/forensics.py](https://codecov.io/gh/google/cloud-forensics-utils/pull/221/diff?src=pr&el=tree#diff-bGliY2xvdWRmb3JlbnNpY3MvcHJvdmlkZXJzL2djcC9mb3JlbnNpY3MucHk=) | `53.62% <48.83%> (-11.38%)` | :arrow_down: |
| [...ibcloudforensics/providers/gcp/internal/compute.py](https://codecov.io/gh/google/cloud-forensics-utils/pull/221/diff?src=pr&el=tree#diff-bGliY2xvdWRmb3JlbnNpY3MvcHJvdmlkZXJzL2djcC9pbnRlcm5hbC9jb21wdXRlLnB5) | `56.78% <52.38%> (-5.58%)` | :arrow_down: |
| [...cs/providers/gcp/internal/compute\_base\_resource.py](https://codecov.io/gh/google/cloud-forensics-utils/pull/221/diff?src=pr&el=tree#diff-bGliY2xvdWRmb3JlbnNpY3MvcHJvdmlkZXJzL2djcC9pbnRlcm5hbC9jb21wdXRlX2Jhc2VfcmVzb3VyY2UucHk=) | `37.33% <52.94%> (+0.82%)` | :arrow_up: |
| [libcloudforensics/providers/aws/internal/log.py](https://codecov.io/gh/google/cloud-forensics-utils/pull/221/diff?src=pr&el=tree#diff-bGliY2xvdWRmb3JlbnNpY3MvcHJvdmlkZXJzL2F3cy9pbnRlcm5hbC9sb2cucHk=) | `73.07% <55.55%> (-4.20%)` | :arrow_down: |
| [...cloudforensics/providers/azure/internal/compute.py](https://codecov.io/gh/google/cloud-forensics-utils/pull/221/diff?src=pr&el=tree#diff-bGliY2xvdWRmb3JlbnNpY3MvcHJvdmlkZXJzL2F6dXJlL2ludGVybmFsL2NvbXB1dGUucHk=) | `55.68% <55.68%> (ø)` | |
| [...ibcloudforensics/providers/gcp/internal/project.py](https://codecov.io/gh/google/cloud-forensics-utils/pull/221/diff?src=pr&el=tree#diff-bGliY2xvdWRmb3JlbnNpY3MvcHJvdmlkZXJzL2djcC9pbnRlcm5hbC9wcm9qZWN0LnB5) | `61.90% <66.66%> (-3.81%)` | :arrow_down: |
| [...bcloudforensics/providers/azure/internal/common.py](https://codecov.io/gh/google/cloud-forensics-utils/pull/221/diff?src=pr&el=tree#diff-bGliY2xvdWRmb3JlbnNpY3MvcHJvdmlkZXJzL2F6dXJlL2ludGVybmFsL2NvbW1vbi5weQ==) | `72.05% <72.05%> (ø)` | |
| [libcloudforensics/providers/gcp/internal/gke.py](https://codecov.io/gh/google/cloud-forensics-utils/pull/221/diff?src=pr&el=tree#diff-bGliY2xvdWRmb3JlbnNpY3MvcHJvdmlkZXJzL2djcC9pbnRlcm5hbC9na2UucHk=) | `73.68% <73.68%> (ø)` | |
| [libcloudforensics/providers/aws/internal/ebs.py](https://codecov.io/gh/google/cloud-forensics-utils/pull/221/diff?src=pr&el=tree#diff-bGliY2xvdWRmb3JlbnNpY3MvcHJvdmlkZXJzL2F3cy9pbnRlcm5hbC9lYnMucHk=) | `72.36% <75.00%> (-3.00%)` | :arrow_down: |
| ... and [21 more](https://codecov.io/gh/google/cloud-forensics-utils/pull/221/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/google/cloud-forensics-utils/pull/221?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/google/cloud-forensics-utils/pull/221?src=pr&el=footer). Last update [cc650e1...3bcc530](https://codecov.io/gh/google/cloud-forensics-utils/pull/221?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/libcloudforensics/providers/aws/forensics.py b/libcloudforensics/providers/aws/forensics.py
index 4bc6f4e..1f80d04 100644
--- a/libcloudforensics/providers/aws/forensics.py
+++ b/libcloudforensics/providers/aws/forensics.py
@@ -30,6 +30,7 @@ def CreateVolumeCopy(zone: str,
                      dst_zone: Optional[str] = None,
                      instance_id: Optional[str] = None,
                      volume_id: Optional[str] = None,
+                     volume_type: Optional[str] = None,
                      src_profile: Optional[str] = None,
                      dst_profile: Optional[str] = None,
                      tags: Optional[Dict[str, str]] = None) -> 'ebs.AWSVolume':
@@ -81,6 +82,9 @@ def CreateVolumeCopy(zone: str,
         that volume_id will be copied.
     volume_id (str): Optional. ID of the volume to copy. If not set,
         then instance_id needs to be set and the boot volume will be copied.
+    volume_type (str): Optional. The volume type for the volume to be
+        created. Can be one of 'standard'|'io1'|'gp2'|'sc1'|'st1'. The default
+        behavior is to use the same volume type as the source volume.
     src_profile (str): Optional. If the AWS account containing the volume
         that needs to be copied is different from the default account
         specified in the AWS credentials file then you can specify a
@@ -115,6 +119,9 @@ def CreateVolumeCopy(zone: str,
       instance = source_account.GetInstanceById(instance_id)
       volume_to_copy = instance.GetBootVolume()
 
+    if not volume_type:
+      volume_type = volume_to_copy.GetVolumeType()
+
     logger.info('Volume copy of {0:s} started...'.format(
         volume_to_copy.volume_id))
     snapshot = volume_to_copy.Snapshot()
@@ -152,10 +159,16 @@ def CreateVolumeCopy(zone: str,
 
     if tags and tags.get('Name'):
       new_volume = destination_account.CreateVolumeFromSnapshot(
-          snapshot, volume_name=tags['Name'], tags=tags)
+          snapshot,
+          volume_type=volume_type,
+          volume_name=tags['Name'],
+          tags=tags)
     else:
       new_volume = destination_account.CreateVolumeFromSnapshot(
-          snapshot, volume_name_prefix='evidence', tags=tags)
+          snapshot,
+          volume_type=volume_type,
+          volume_name_prefix='evidence',
+          tags=tags)
 
     logger.info('Volume {0:s} successfully copied to {1:s}'.format(
         volume_to_copy.volume_id, new_volume.volume_id))
@@ -177,6 +190,7 @@ def StartAnalysisVm(
     vm_name: str,
     default_availability_zone: str,
     boot_volume_size: int,
+    boot_volume_type: str = 'gp2',
     ami: Optional[str] = None,
     cpu_cores: int = 4,
     attach_volumes: Optional[List[Tuple[str, str]]] = None,
@@ -194,6 +208,9 @@ def StartAnalysisVm(
     default_availability_zone (str): Default zone within the region to create
         new resources in.
     boot_volume_size (int): The size of the analysis VM boot volume (in GB).
+    boot_volume_type (str): Optional. The volume type for the boot volume
+          of the VM. Can be one of 'standard'|'io1'|'gp2'|'sc1'|'st1'. The
+          default is 'gp2'.
     ami (str): Optional. The Amazon Machine Image ID to use to create the VM.
         Default is a version of Ubuntu 18.04.
     cpu_cores (int): Optional. The number of CPU cores to create the machine
@@ -248,6 +265,7 @@ def StartAnalysisVm(
       boot_volume_size,
       ami,
       cpu_cores,
+      boot_volume_type=boot_volume_type,
       ssh_key_name=ssh_key_name,
       tags=tags)
   logger.info('VM started.')
diff --git a/libcloudforensics/providers/aws/internal/account.py b/libcloudforensics/providers/aws/internal/account.py
index e295508..595a695 100644
--- a/libcloudforensics/providers/aws/internal/account.py
+++ b/libcloudforensics/providers/aws/internal/account.py
@@ -420,6 +420,7 @@ class AWSAccount:
       snapshot: ebs.AWSSnapshot,
       volume_name: Optional[str] = None,
       volume_name_prefix: Optional[str] = None,
+      volume_type: str = 'gp2',
       kms_key_id: Optional[str] = None,
       tags: Optional[Dict[str, str]] = None) -> ebs.AWSVolume:
     """Create a new volume based on a snapshot.
@@ -428,6 +429,9 @@ class AWSAccount:
       snapshot (AWSSnapshot): Snapshot to use.
       volume_name (str): Optional. String to use as new volume name.
       volume_name_prefix (str): Optional. String to prefix the volume name with.
+      volume_type (str): Optional. The volume type for the volume to create.
+          Can be one of 'standard'|'io1'|'gp2'|'sc1'|'st1'. The default is
+          'gp2'.
       kms_key_id (str): Optional. A KMS key id to encrypt the volume with.
       tags (Dict[str, str]): Optional. A dictionary of tags to add to the
           volume, for example {'TicketID': 'xxx'}. An entry for the volume
@@ -437,10 +441,15 @@ class AWSAccount:
       AWSVolume: An AWS EBS Volume.
 
     Raises:
-      ValueError: If the volume name does not comply with the RegEx.
+      ValueError: If the volume name does not comply with the RegEx,
+          or if the volume type is invalid.
       RuntimeError: If the volume could not be created.
     """
 
+    if volume_type not in ['standard', 'io1', 'gp2', 'sc1', 'st1']:
+      raise ValueError('Volume type must be one of [standard, io1, gp2, sc1, '
+                       'st1]. Got: {0:s}'.format(volume_type))
+
     if not volume_name:
       volume_name = self._GenerateVolumeName(
           snapshot, volume_name_prefix=volume_name_prefix)
@@ -457,11 +466,19 @@ class AWSAccount:
     create_volume_args = {
         'AvailabilityZone': snapshot.availability_zone,
         'SnapshotId': snapshot.snapshot_id,
-        'TagSpecifications': [common.CreateTags(common.VOLUME, tags)]
+        'TagSpecifications': [common.CreateTags(common.VOLUME, tags)],
+        'VolumeType': volume_type
     }
     if kms_key_id:
       create_volume_args['Encrypted'] = True
       create_volume_args['KmsKeyId'] = kms_key_id
+    if volume_type == 'io1':
+      # If using the io1 volume type, we must specify Iops, see
+      # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/
+      # services/ec2.html#EC2.Client.create_volume. io1 volumes allow for a
+      # ratio of 50 IOPS per 1 GiB.
+      create_volume_args['Iops'] = self.ResourceApi(
+          common.EC2_SERVICE).Snapshot(snapshot.snapshot_id).volume_size * 50
     try:
       volume = client.create_volume(**create_volume_args)
       volume_id = volume['VolumeId']
@@ -489,6 +506,7 @@ class AWSAccount:
       boot_volume_size: int,
       ami: str,
       cpu_cores: int,
+      boot_volume_type: str = 'gp2',
       packages: Optional[List[str]] = None,
       ssh_key_name: Optional[str] = None,
       tags: Optional[Dict[str, str]] = None) -> Tuple[ec2.AWSInstance, bool]:
@@ -499,6 +517,9 @@ class AWSAccount:
       boot_volume_size (int): The size of the analysis VM boot volume (in GB).
       ami (str): The Amazon Machine Image ID to use to create the VM.
       cpu_cores (int): Number of CPU cores for the analysis VM.
+      boot_volume_type (str): Optional. The volume type for the boot volume
+          of the VM. Can be one of 'standard'|'io1'|'gp2'|'sc1'|'st1'. The
+          default is 'gp2'.
       packages (List[str]): Optional. List of packages to install in the VM.
       ssh_key_name (str): Optional. A SSH key pair name linked to the AWS
           account to associate with the VM. If none provided, the VM can only
@@ -547,7 +568,8 @@ class AWSAccount:
     client = self.ClientApi(common.EC2_SERVICE)
     vm_args = {
         'BlockDeviceMappings':
-            [self._GetBootVolumeConfigByAmi(ami, boot_volume_size)],
+            [self._GetBootVolumeConfigByAmi(
+                ami, boot_volume_size, boot_volume_type)],
         'ImageId': ami,
         'MinCount': 1,
         'MaxCount': 1,
@@ -735,12 +757,14 @@ class AWSAccount:
 
   def _GetBootVolumeConfigByAmi(self,
                                 ami: str,
-                                boot_volume_size: int) -> Dict[str, Any]:
+                                boot_volume_size: int,
+                                boot_volume_type: str) -> Dict[str, Any]:
     """Return a boot volume configuration for a given AMI and boot volume size.
 
     Args:
       ami (str): The Amazon Machine Image ID.
       boot_volume_size (int): Size of the boot volume, in GB.
+      boot_volume_type (str): Type of the boot volume.
 
     Returns:
       Dict[str, str|Dict]]: A BlockDeviceMappings configuration for the
@@ -765,6 +789,13 @@ class AWSAccount:
     block_device_mapping = image['Images'][0]['BlockDeviceMappings'][0]  # type: Dict[str, Any]
     # pylint: enable=line-too-long
     block_device_mapping['Ebs']['VolumeSize'] = boot_volume_size
+    block_device_mapping['Ebs']['VolumeType'] = boot_volume_type
+    if boot_volume_type == 'io1':
+      # If using the io1 volume type, we must specify Iops, see
+      # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/
+      # services/ec2.html#EC2.Client.create_volume. io1 volumes allow for a
+      # ratio of 50 IOPS per 1 GiB.
+      block_device_mapping['Ebs']['Iops'] = boot_volume_size * 50
     return block_device_mapping
 
   def ListImages(
diff --git a/libcloudforensics/providers/aws/internal/ebs.py b/libcloudforensics/providers/aws/internal/ebs.py
index 5b6e197..5a4e40b 100644
--- a/libcloudforensics/providers/aws/internal/ebs.py
+++ b/libcloudforensics/providers/aws/internal/ebs.py
@@ -162,6 +162,16 @@ class AWSVolume(AWSElasticBlockStore):
       raise RuntimeError('Could not delete volume {0:s}: {1:s}'.format(
           self.volume_id, str(exception)))
 
+  def GetVolumeType(self) -> str:
+    """Return the volume type.
+
+    Returns:
+      str: The volume type.
+    """
+    client = self.aws_account.ResourceApi(common.EC2_SERVICE)
+    volume_type = client.Volume(self.volume_id).volume_type  # type: str
+    return volume_type
+
 
 class AWSSnapshot(AWSElasticBlockStore):
   """Class representing an AWS EBS snapshot.
diff --git a/tools/aws_cli.py b/tools/aws_cli.py
index 95b5faf..c64aa65 100644
--- a/tools/aws_cli.py
+++ b/tools/aws_cli.py
@@ -81,6 +81,7 @@ def CreateVolumeCopy(args: 'argparse.Namespace') -> None:
                                            dst_zone=args.dst_zone,
                                            instance_id=args.instance_id,
                                            volume_id=args.volume_id,
+                                           volume_type=args.volume_type,
                                            src_profile=args.src_profile,
                                            dst_profile=args.dst_profile,
                                            tags=tags)
@@ -157,6 +158,7 @@ def StartAnalysisVm(args: 'argparse.Namespace') -> None:
   vm = forensics.StartAnalysisVm(vm_name=args.instance_name,
                                  default_availability_zone=args.zone,
                                  boot_volume_size=int(args.boot_volume_size),
+                                 boot_volume_type=args.boot_volume_type,
                                  cpu_cores=int(args.cpu_cores),
                                  ami=args.ami,
                                  ssh_key_name=key_name,
diff --git a/tools/cli.py b/tools/cli.py
index 32d717f..a800ce8 100644
--- a/tools/cli.py
+++ b/tools/cli.py
@@ -129,6 +129,10 @@ def Main() -> None:
                                 'copy. If none specified, then --instance_id '
                                 'must be specified and the boot volume of the '
                                 'AWS instance will be copied.', None),
+                ('--volume_type', 'The volume type for the volume copy. '
+                                  'Can be standard, io1, gp2, sc1, st1. The '
+                                  'default behavior is to use the same volume '
+                                  'type as the source volume.', None),
                 ('--src_profile', 'The name of the profile for the source '
                                   'account, as defined in the AWS credentials '
                                   'file.', None),
@@ -150,6 +154,9 @@ def Main() -> None:
                  ''),
                 ('--boot_volume_size', 'Size of instance boot volume in GB.',
                  '50'),
+                ('--boot_volume_type', 'The boot volume type for the VM. '
+                                       'Can be standard, io1, gp2, sc1, st1. '
+                                       'Default is gp2', 'gp2'),
                 ('--cpu_cores', 'Instance CPU core count.', '4'),
                 ('--ami', 'AMI ID to use as base image. Will search '
                           'Ubuntu 18.04 LTS server x86_64 for chosen region '


Test patch:
diff --git a/tests/providers/aws/aws_test.py b/tests/providers/aws/aws_test.py
index d58462f..d3988e4 100644
--- a/tests/providers/aws/aws_test.py
+++ b/tests/providers/aws/aws_test.py
@@ -163,7 +163,8 @@ MOCK_DESCRIBE_AMI = {
     'Images': [{
         'BlockDeviceMappings': [{
             'Ebs': {
-                'VolumeSize': None
+                'VolumeSize': None,
+                'VolumeType': None
             }
         }]
     }]
@@ -369,6 +370,12 @@ class AWSAccountTest(unittest.TestCase):
     self.assertEqual(
         'prefix-fake-snapshot-d69d57c3-copy', volume_from_snapshot.name)
 
+    with self.assertRaises(ValueError) as error:
+      FAKE_AWS_ACCOUNT.CreateVolumeFromSnapshot(
+          FAKE_SNAPSHOT, volume_type='invalid-volume-type')
+    self.assertEqual('Volume type must be one of [standard, io1, gp2, sc1, '
+                     'st1]. Got: invalid-volume-type', str(error.exception))
+
   @typing.no_type_check
   @mock.patch('libcloudforensics.scripts.utils.ReadStartupScript')
   @mock.patch('libcloudforensics.providers.aws.internal.account.AWSAccount.GetInstancesByName')
@@ -427,12 +434,16 @@ class AWSAccountTest(unittest.TestCase):
   def testGetBootVolumeConfigByAmi(self, mock_ec2_api):
     """Test that the boot volume configuration is correctly created."""
     mock_ec2_api.return_value.describe_images.return_value = MOCK_DESCRIBE_AMI
+    # pylint: disable=protected-access,line-too-long
     self.assertIsNone(
-        MOCK_DESCRIBE_AMI['Images'][0]['BlockDeviceMappings'][0]['Ebs']['VolumeSize'])  # pylint: disable=line-too-long
-    # pylint: disable=protected-access
-    config = FAKE_AWS_ACCOUNT._GetBootVolumeConfigByAmi('ami-id', 50)
-    # pylint: enable=protected-access
+        MOCK_DESCRIBE_AMI['Images'][0]['BlockDeviceMappings'][0]['Ebs']['VolumeSize'])
+    self.assertIsNone(
+        MOCK_DESCRIBE_AMI['Images'][0]['BlockDeviceMappings'][0]['Ebs']['VolumeType'])
+    config = FAKE_AWS_ACCOUNT._GetBootVolumeConfigByAmi(
+        'ami-id', 50, 'fake-volume-type')
+    # pylint: enable=protected-access,line-too-long
     self.assertEqual(50, config['Ebs']['VolumeSize'])
+    self.assertEqual('fake-volume-type', config['Ebs']['VolumeType'])
 
   @typing.no_type_check
   @mock.patch('libcloudforensics.providers.aws.internal.account.AWSAccount.ClientApi')
@@ -559,6 +570,7 @@ class AWSTest(unittest.TestCase):
 
   @typing.no_type_check
   @mock.patch('boto3.session.Session._setup_loader')
+  @mock.patch('libcloudforensics.providers.aws.internal.ebs.AWSVolume.GetVolumeType')
   @mock.patch('libcloudforensics.providers.aws.internal.ebs.AWSVolume.Snapshot')
   @mock.patch('libcloudforensics.providers.aws.internal.account.AWSAccount.GetVolumeById')
   @mock.patch('libcloudforensics.providers.aws.internal.account.AWSAccount.GetAccountInformation')
@@ -568,6 +580,7 @@ class AWSTest(unittest.TestCase):
                             mock_account,
                             mock_get_volume,
                             mock_snapshot,
+                            mock_volume_type,
                             mock_loader):
     """Test that a volume is correctly cloned."""
     FAKE_SNAPSHOT.name = FAKE_VOLUME.volume_id
@@ -575,6 +588,7 @@ class AWSTest(unittest.TestCase):
     mock_account.return_value = 'fake-account-id'
     mock_get_volume.return_value = FAKE_VOLUME
     mock_snapshot.return_value = FAKE_SNAPSHOT
+    mock_volume_type.return_value = 'standard'
     mock_loader.return_value = None
 
     # CreateVolumeCopy(zone, volume_id='fake-volume-id'). This should grab
@@ -589,6 +603,7 @@ class AWSTest(unittest.TestCase):
 
   @typing.no_type_check
   @mock.patch('boto3.session.Session._setup_loader')
+  @mock.patch('libcloudforensics.providers.aws.internal.ebs.AWSVolume.GetVolumeType')
   @mock.patch('libcloudforensics.providers.aws.internal.ebs.AWSVolume.Snapshot')
   @mock.patch('libcloudforensics.providers.aws.internal.ec2.AWSInstance.GetBootVolume')
   @mock.patch('libcloudforensics.providers.aws.internal.account.AWSAccount.GetInstanceById')
@@ -600,6 +615,7 @@ class AWSTest(unittest.TestCase):
                             mock_get_instance,
                             mock_get_volume,
                             mock_snapshot,
+                            mock_volume_type,
                             mock_loader):
     """Test that a volume is correctly cloned."""
     FAKE_SNAPSHOT.name = FAKE_BOOT_VOLUME.volume_id
@@ -608,6 +624,7 @@ class AWSTest(unittest.TestCase):
     mock_get_instance.return_value = FAKE_INSTANCE
     mock_get_volume.return_value = FAKE_BOOT_VOLUME
     mock_snapshot.return_value = FAKE_SNAPSHOT
+    mock_volume_type.return_value = 'standard'
     mock_loader.return_value = None
 
     # CreateVolumeCopy(zone, instance='fake-instance-id'). This should grab