Repository: google/cloud-forensics-utils

Issue:
Sanitize CPU number in GCP VM creation
Currently users can specify an arbitrary number of CPUs, but not all of them are valid. It would be good to check this before attempting to create the VM.

Discussion:
codecov-commenter: # [Codecov](https://codecov.io/gh/google/cloud-forensics-utils/pull/330?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
> :exclamation: No coverage uploaded for pull request base (`main@8e0302a`). [Click here to learn what that means](https://docs.codecov.io/docs/error-reference?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#section-missing-base-commit).
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/google/cloud-forensics-utils/pull/330/graphs/tree.svg?width=650&height=150&src=pr&token=zZkPno3ah0&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)](https://codecov.io/gh/google/cloud-forensics-utils/pull/330?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)

```diff
@@           Coverage Diff           @@
##             main     #330   +/-   ##
=======================================
  Coverage        ?   63.99%           
=======================================
  Files           ?       33           
  Lines           ?     2386           
  Branches        ?        0           
=======================================
  Hits            ?     1527           
  Misses          ?      859           
  Partials        ?        0           
```

| Flag | Coverage Δ | |
|---|---|---|
| nosetests | `63.99% <0.00%> (?)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#carryforward-flags-in-the-pull-request-comment) to find out more.


------

[Continue to review full report at Codecov](https://codecov.io/gh/google/cloud-forensics-utils/pull/330?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/google/cloud-forensics-utils/pull/330?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google). Last update [8e0302a...68c175d](https://codecov.io/gh/google/cloud-forensics-utils/pull/330?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).


Patch:
diff --git a/libcloudforensics/providers/gcp/internal/compute.py b/libcloudforensics/providers/gcp/internal/compute.py
index e7c9926..7dcb244 100644
--- a/libcloudforensics/providers/gcp/internal/compute.py
+++ b/libcloudforensics/providers/gcp/internal/compute.py
@@ -34,6 +34,13 @@ if TYPE_CHECKING:
 logging_utils.SetUpLogger(__name__)
 logger = logging_utils.GetLogger(__name__)
 
+# Default general purpose machine type used for the forensics VM
+DEFAULT_MACHINE_TYPE = 'e2-standard'
+
+# Supported number of cores for the default machine type
+# https://cloud.google.com/compute/docs/general-purpose-machines#e2-standard
+E2_STANDARD_CPU_CORES = [2, 4, 8, 16, 32]
+
 
 class GoogleCloudCompute(common.GoogleCloudComputeClient):
   """Class representing all Google Cloud Compute objects in a project.
@@ -276,6 +283,8 @@ class GoogleCloudCompute(common.GoogleCloudComputeClient):
 
     Raises:
       RuntimeError: If virtual machine cannot be created.
+      ValueError: If the requested number of CPU cores is not available for the
+          machine type.
     """
 
     # Re-use instance if it already exists, or create a new one.
@@ -286,8 +295,13 @@ class GoogleCloudCompute(common.GoogleCloudComputeClient):
     except errors.ResourceNotFoundError:
       pass
 
-    machine_type = 'zones/{0:s}/machineTypes/e2-standard-{1:d}'.format(
-        self.default_zone, cpu_cores)
+    if cpu_cores not in E2_STANDARD_CPU_CORES:
+      raise ValueError(
+          'Number of requested CPU cores ({0:d}) not available for machine type'
+          ' {1:s}'.format(cpu_cores, DEFAULT_MACHINE_TYPE))
+
+    machine_type = 'zones/{0:s}/machineTypes/{1:s}-{2:d}'.format(
+        self.default_zone, DEFAULT_MACHINE_TYPE, cpu_cores)
     ubuntu_image = self.GceApi().images().getFromFamily(
         project=image_project, family=image_family).execute()
     source_disk_image = ubuntu_image['selfLink']


Test patch:
diff --git a/tests/providers/gcp/internal/test_compute.py b/tests/providers/gcp/internal/test_compute.py
index 7eeb947..9260d56 100644
--- a/tests/providers/gcp/internal/test_compute.py
+++ b/tests/providers/gcp/internal/test_compute.py
@@ -192,6 +192,12 @@ class GoogleCloudComputeTest(unittest.TestCase):
     self.assertEqual('non-existent-analysis-vm', vm.name)
     self.assertTrue(created)
 
+    # If specifying a number of CPU cores that's not available, should throw a
+    # ValueError
+    with self.assertRaises(ValueError):
+      _, created = gcp_mocks.FAKE_ANALYSIS_PROJECT.compute.GetOrCreateAnalysisVm(
+          'non-existent-analysis-vm', boot_disk_size=1, cpu_cores=5)
+
   @typing.no_type_check
   @mock.patch('libcloudforensics.providers.gcp.internal.compute.GoogleCloudCompute.ListInstanceByLabels')
   @mock.patch('libcloudforensics.providers.gcp.internal.common.GoogleCloudComputeClient.GceApi')