From ae601cf8a2fbc3afbdb02951665b77a245d2c56b Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Tue, 14 Dec 2021 11:40:01 +0100 Subject: [PATCH 01/11] chore(deps): update dependency google-cloud-compute to v0.9.0 (#171) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): update dependency google-cloud-compute to v0.9.0 * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot --- samples/snippets/requirements.txt | 2 +- samples/snippets/sample_start_stop.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index 3d8cb4e6c..e2693e6f2 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1 +1 @@ -google-cloud-compute==0.8.0 \ No newline at end of file +google-cloud-compute==0.9.0 \ No newline at end of file diff --git a/samples/snippets/sample_start_stop.py b/samples/snippets/sample_start_stop.py index 80eb95f8e..a73f05ba5 100644 --- a/samples/snippets/sample_start_stop.py +++ b/samples/snippets/sample_start_stop.py @@ -43,7 +43,9 @@ def start_instance(project_id: str, zone: str, instance_name: str): instance_client = compute_v1.InstancesClient() op_client = compute_v1.ZoneOperationsClient() - op = instance_client.start_unary(project=project_id, zone=zone, instance=instance_name) + op = instance_client.start_unary( + project=project_id, zone=zone, instance=instance_name + ) while op.status != compute_v1.Operation.Status.DONE: op = op_client.wait(operation=op.name, zone=zone, project=project_id) From 76a1d29909ab1346a7de55c15198aa057a4a50e1 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Tue, 14 Dec 2021 17:54:52 +0100 Subject: [PATCH 02/11] docs(samples): Samples for custom vm types (#155) * chore(docs): Adding samples for Custom VM Types. Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- samples/snippets/quickstart.py | 7 +- samples/snippets/sample_create_vm.py | 8 +- samples/snippets/sample_custom_types.py | 531 +++++++++++++++++++ samples/snippets/test_quickstart.py | 2 +- samples/snippets/test_sample_custom_types.py | 194 +++++++ 5 files changed, 736 insertions(+), 6 deletions(-) create mode 100644 samples/snippets/sample_custom_types.py create mode 100644 samples/snippets/test_sample_custom_types.py diff --git a/samples/snippets/quickstart.py b/samples/snippets/quickstart.py index bdbdf4fc1..3303cc317 100644 --- a/samples/snippets/quickstart.py +++ b/samples/snippets/quickstart.py @@ -21,6 +21,7 @@ # [START compute_instances_create] # [START compute_instances_delete] +import re import sys # [START compute_instances_list] @@ -149,8 +150,10 @@ def create_instance( instance = compute_v1.Instance() instance.name = instance_name instance.disks = [disk] - full_machine_type_name = f"zones/{zone}/machineTypes/{machine_type}" - instance.machine_type = full_machine_type_name + if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type): + instance.machine_type = machine_type + else: + instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" instance.network_interfaces = [network_interface] # Prepare the request to insert an instance. diff --git a/samples/snippets/sample_create_vm.py b/samples/snippets/sample_create_vm.py index e1ba3907a..5b4377d98 100644 --- a/samples/snippets/sample_create_vm.py +++ b/samples/snippets/sample_create_vm.py @@ -11,7 +11,7 @@ # 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. - +import re import sys from typing import List @@ -194,8 +194,10 @@ def create_with_disks( instance = compute_v1.Instance() instance.name = instance_name instance.disks = disks - full_machine_type_name = f"zones/{zone}/machineTypes/{machine_type}" - instance.machine_type = full_machine_type_name + if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type): + instance.machine_type = machine_type + else: + instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}" instance.network_interfaces = [network_interface] # Shielded Instance settings diff --git a/samples/snippets/sample_custom_types.py b/samples/snippets/sample_custom_types.py new file mode 100644 index 000000000..38ad64b81 --- /dev/null +++ b/samples/snippets/sample_custom_types.py @@ -0,0 +1,531 @@ +# Copyright 2021 Google LLC +# +# 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. +# [START compute_custom_machine_type_create ] +from collections import namedtuple +from enum import Enum, unique +import sys +import time +from typing import Union + +from google.cloud import compute_v1 + + +# [END compute_custom_machine_type_create ] + + +# [START compute_custom_machine_type_helper_class ] +def gb_to_mb(value: int) -> int: + return value << 10 + + +class CustomMachineType: + """ + Allows to create custom machine types to be used with the VM instances. + """ + + @unique + class CPUSeries(Enum): + N1 = "custom" + N2 = "n2-custom" + N2D = "n2d-custom" + E2 = "e2-custom" + E2_MICRO = "e2-custom-micro" + E2_SMALL = "e2-custom-small" + E2_MEDIUM = "e2-custom-medium" + + TypeLimits = namedtuple( + "TypeLimits", + [ + "allowed_cores", + "min_mem_per_core", + "max_mem_per_core", + "allow_extra_memory", + "extra_memory_limit", + ], + ) + + LIMITS = { + CPUSeries.E2: TypeLimits(frozenset(range(2, 33, 2)), 512, 8192, False, 0), + CPUSeries.E2_MICRO: TypeLimits(frozenset(), 1024, 2048, False, 0), + CPUSeries.E2_SMALL: TypeLimits(frozenset(), 2048, 4096, False, 0), + CPUSeries.E2_MEDIUM: TypeLimits(frozenset(), 4096, 8192, False, 0), + CPUSeries.N2: TypeLimits( + frozenset(range(2, 33, 2)).union(set(range(36, 129, 4))), + 512, + 8192, + True, + gb_to_mb(624), + ), + CPUSeries.N2D: TypeLimits( + frozenset({2, 4, 8, 16, 32, 48, 64, 80, 96}), 512, 8192, True, gb_to_mb(768) + ), + CPUSeries.N1: TypeLimits( + frozenset({1}.union(range(2, 97, 2))), 922, 6656, True, gb_to_mb(624) + ), + } + + def __init__( + self, zone: str, cpu_series: CPUSeries, memory_mb: int, core_count: int = 0 + ): + self.zone = zone + self.cpu_series = cpu_series + self.limits = self.LIMITS[self.cpu_series] + self.core_count = 2 if self.is_shared() else core_count + self.memory_mb = memory_mb + + self._check() + self.extra_memory_used = self._check_extra_memory() + + def is_shared(self): + return self.cpu_series in ( + CustomMachineType.CPUSeries.E2_SMALL, + CustomMachineType.CPUSeries.E2_MICRO, + CustomMachineType.CPUSeries.E2_MEDIUM, + ) + + def _check_extra_memory(self) -> bool: + # Assuming this runs after _check() and the total memory requested is correct + return self.memory_mb > self.core_count * self.limits.max_mem_per_core + + def _check(self): + """ + Check whether the requested parameters are allowed. Find more information about limitations of custom machine + types at: https://cloud.google.com/compute/docs/general-purpose-machines#custom_machine_types + """ + # Check the number of cores + if ( + self.limits.allowed_cores + and self.core_count not in self.limits.allowed_cores + ): + raise RuntimeError( + f"Invalid number of cores requested. Allowed number of cores for {self.cpu_series.name} is: {sorted(self.limits.allowed_cores)}" + ) + + # Memory must be a multiple of 256 MB + if self.memory_mb % 256 != 0: + raise RuntimeError("Requested memory must be a multiple of 256 MB.") + + # Check if the requested memory isn't too little + if self.memory_mb < self.core_count * self.limits.min_mem_per_core: + raise RuntimeError( + f"Requested memory is too low. Minimal memory for {self.cpu_series.name} is {self.limits.min_mem_per_core} MB per core." + ) + + # Check if the requested memory isn't too much + if self.memory_mb > self.core_count * self.limits.max_mem_per_core: + if self.limits.allow_extra_memory: + if self.memory_mb > self.limits.extra_memory_limit: + raise RuntimeError( + f"Requested memory is too large.. Maximum memory allowed for {self.cpu_series.name} is {self.limits.extra_memory_limit} MB." + ) + else: + raise RuntimeError( + f"Requested memory is too large.. Maximum memory allowed for {self.cpu_series.name} is {self.limits.max_mem_per_core} MB per core." + ) + + def __str__(self) -> str: + """ + Return the custom machine type in form of a string acceptable by Compute Engine API. + """ + if self.cpu_series in { + self.CPUSeries.E2_SMALL, + self.CPUSeries.E2_MICRO, + self.CPUSeries.E2_MEDIUM, + }: + return f"zones/{self.zone}/machineTypes/{self.cpu_series.value}-{self.memory_mb}" + + if self.extra_memory_used: + return f"zones/{self.zone}/machineTypes/{self.cpu_series.value}-{self.core_count}-{self.memory_mb}-ext" + + return f"zones/{self.zone}/machineTypes/{self.cpu_series.value}-{self.core_count}-{self.memory_mb}" + + def short_type_str(self) -> str: + """ + Return machine type in a format without the zone. For example, n2-custom-0-10240. + This format is used to create instance templates. + """ + return str(self).rsplit("/", maxsplit=1)[1] + + @classmethod + def from_str(cls, machine_type: str): + """ + Construct a new object from a string. The string needs to be a valid custom machine type like: + - https://www.googleapis.com/compute/v1/projects/diregapic-mestiv/zones/us-central1-b/machineTypes/e2-custom-4-8192 + - zones/us-central1-b/machineTypes/e2-custom-4-8192 + - e2-custom-4-8192 (in this case, the zone parameter will not be set) + """ + zone = None + if machine_type.startswith("http"): + machine_type = machine_type[machine_type.find("zones/") :] + + if machine_type.startswith("zones/"): + _, zone, _, machine_type = machine_type.split("/") + + extra_mem = machine_type.endswith("-ext") + + if machine_type.startswith("custom"): + cpu = cls.CPUSeries.N1 + _, cores, memory = machine_type.rsplit("-", maxsplit=2) + else: + if extra_mem: + cpu_series, _, cores, memory, _ = machine_type.split("-") + else: + cpu_series, _, cores, memory = machine_type.split("-") + if cpu_series == "n2": + cpu = cls.CPUSeries.N2 + elif cpu_series == "n2d": + cpu = cls.CPUSeries.N2D + elif cpu_series == "e2": + cpu = cls.CPUSeries.E2 + if cores == "micro": + cpu = cls.CPUSeries.E2_MICRO + cores = 2 + elif cores == "small": + cpu = cls.CPUSeries.E2_SMALL + cores = 2 + elif cores == "medium": + cpu = cls.CPUSeries.E2_MEDIUM + cores = 2 + else: + raise RuntimeError("Unknown CPU series.") + + cores = int(cores) + memory = int(memory) + + return cls(zone, cpu, memory, cores) + + +# [END compute_custom_machine_type_helper_class ] + + +# [START compute_custom_machine_type_create ] +def create_instance( + project_id: str, + zone: str, + instance_name: str, + machine_type: Union[str, "CustomMachineType"], +): + """ + Send an instance creation request to the Compute Engine API and wait for it to complete. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + machine_type: machine type of the VM being created. This value uses the + following format: "zones/{zone}/machineTypes/{type_name}". + For example: "zones/europe-west3-c/machineTypes/f1-micro" + OR + It can be a CustomMachineType object, describing a custom type + you want to use. + + Return: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + operation_client = compute_v1.ZoneOperationsClient() + + # Describe the size and source image of the boot disk to attach to the instance. + disk = compute_v1.AttachedDisk() + initialize_params = compute_v1.AttachedDiskInitializeParams() + initialize_params.source_image = ( + "projects/debian-cloud/global/images/family/debian-10" + ) + initialize_params.disk_size_gb = 10 + disk.initialize_params = initialize_params + disk.auto_delete = True + disk.boot = True + disk.type_ = compute_v1.AttachedDisk.Type.PERSISTENT.name + + # Use the network interface provided in the network_name argument. + network_interface = compute_v1.NetworkInterface() + network_interface.name = "global/networks/default" + + # Collect information into the Instance object. + instance = compute_v1.Instance() + instance.name = instance_name + instance.disks = [disk] + instance.machine_type = str(machine_type) + instance.network_interfaces = [network_interface] + + # Prepare the request to insert an instance. + request = compute_v1.InsertInstanceRequest() + request.zone = zone + request.project = project_id + request.instance_resource = instance + + # Wait for the create operation to complete. + print( + f"Creating the {instance_name} instance of type {instance.machine_type} in {zone}..." + ) + operation = instance_client.insert_unary(request=request) + while operation.status != compute_v1.Operation.Status.DONE: + operation = operation_client.wait( + operation=operation.name, zone=zone, project=project_id + ) + if operation.error: + print("Error during creation:", operation.error, file=sys.stderr) + if operation.warnings: + print("Warning during creation:", operation.warnings, file=sys.stderr) + print(f"Instance {instance_name} created.") + return instance_client.get(project=project_id, zone=zone, instance=instance.name) + + +# [END compute_custom_machine_type_create ] + + +# [START compute_custom_machine_type_create_with_helper ] +def create_custom_instance( + project_id: str, + zone: str, + instance_name: str, + cpu_series: CustomMachineType.CPUSeries, + core_count: int, + memory: int, +) -> compute_v1.Instance: + """ + Create a new VM instance with a custom machine type. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + cpu_series: the type of CPU you want to use. Select one value from the CustomMachineType.CPUSeries enum. + For example: CustomMachineType.CPUSeries.N2 + core_count: number of CPU cores you want to use. + memory: the amount of memory for the VM instance, in megabytes. + + Return: + Instance object. + """ + assert cpu_series in ( + CustomMachineType.CPUSeries.E2, + CustomMachineType.CPUSeries.N1, + CustomMachineType.CPUSeries.N2, + CustomMachineType.CPUSeries.N2D, + ) + custom_type = CustomMachineType(zone, cpu_series, memory, core_count) + return create_instance(project_id, zone, instance_name, custom_type) + + +# [END compute_custom_machine_type_create_with_helper ] + + +# [START compute_custom_machine_type_create_shared_with_helper ] +def create_custom_shared_core_instance( + project_id: str, + zone: str, + instance_name: str, + cpu_series: CustomMachineType.CPUSeries, + memory: int, +): + """ + Create a new VM instance with a custom type using shared CPUs. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + cpu_series: the type of CPU you want to use. Pick one value from the CustomMachineType.CPUSeries enum. + For example: CustomMachineType.CPUSeries.E2_MICRO + memory: the amount of memory for the VM instance, in megabytes. + + Return: + Instance object. + """ + assert cpu_series in ( + CustomMachineType.CPUSeries.E2_MICRO, + CustomMachineType.CPUSeries.E2_SMALL, + CustomMachineType.CPUSeries.E2_MEDIUM, + ) + custom_type = CustomMachineType(zone, cpu_series, memory) + return create_instance(project_id, zone, instance_name, custom_type) + + +# [END compute_custom_machine_type_create_shared_with_helper ] + + +# [START compute_custom_machine_type_create_without_helper ] +def create_custom_instances_no_helper( + project_id: str, zone: str, instance_name: str, core_count: int, memory: int +): + """ + Create new VM instances without using a CustomMachineType helper function. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + core_count: number of CPU cores you want to use. + memory: the amount of memory for the VM instance, in megabytes. + + Returns: + List of Instance objects. + """ + # The core_count and memory values are not validated anywhere and can be rejected by the API. + instances = [ + create_instance( + project_id, + zone, + f"{instance_name}_n1", + f"zones/{zone}/machineTypes/custom-{core_count}-{memory}", + ), + create_instance( + project_id, + zone, + f"{instance_name}_n2", + f"zones/{zone}/machineTypes/n2-custom-{core_count}-{memory}", + ), + create_instance( + project_id, + zone, + f"{instance_name}_n2d", + f"zones/{zone}/machineTypes/n2d-custom-{core_count}-{memory}", + ), + create_instance( + project_id, + zone, + f"{instance_name}_e2", + f"zones/{zone}/machineTypes/e2-custom-{core_count}-{memory}", + ), + create_instance( + project_id, + zone, + f"{instance_name}_e2_micro", + f"zones/{zone}/machineTypes/e2-custom-micro-{memory}", + ), + create_instance( + project_id, + zone, + f"{instance_name}_e2_small", + f"zones/{zone}/machineTypes/e2-custom-small-{memory}", + ), + create_instance( + project_id, + zone, + f"{instance_name}_e2_medium", + f"zones/{zone}/machineTypes/e2-custom-medium-{memory}", + ), + ] + return instances + + +# [END compute_custom_machine_type_create_without_helper ] + + +# [START compute_custom_machine_type_extra_mem_no_helper ] +def create_custom_instances_extra_mem_no_helper( + project_id: str, zone: str, instance_name: str, core_count: int, memory: int +): + """ + Create new VM instances with extra memory without using a CustomMachineType helper class. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + core_count: number of CPU cores you want to use. + memory: the amount of memory for the VM instance, in megabytes. + + Returns: + List of Instance objects. + """ + # The core_count and memory values are not validated anywhere and can be rejected by the API. + instances = [ + create_instance( + project_id, + zone, + f"{instance_name}_n1_extra_mem", + f"zones/{zone}/machineTypes/custom-{core_count}-{memory}-ext", + ), + create_instance( + project_id, + zone, + f"{instance_name}_n2_extra_mem", + f"zones/{zone}/machineTypes/n2-custom-{core_count}-{memory}-ext", + ), + create_instance( + project_id, + zone, + f"{instance_name}_n2d_extra_mem", + f"zones/{zone}/machineTypes/n2d-custom-{core_count}-{memory}-ext", + ), + ] + return instances + + +# [END compute_custom_machine_type_extra_mem_no_helper ] + + +# [START compute_custom_machine_type_update_memory ] +def add_extended_memory_to_instance( + project_id: str, zone: str, instance_name: str, new_memory: int +): + """ + Modify an existing VM to use extended memory. + + Args: + project_id: project ID or project number of the Cloud project you want to use. + zone: name of the zone to create the instance in. For example: "us-west3-b" + instance_name: name of the new virtual machine (VM) instance. + new_memory: the amount of memory for the VM instance, in megabytes. + + Returns: + Instance object. + """ + instance_client = compute_v1.InstancesClient() + operation_client = compute_v1.ZoneOperationsClient() + instance = instance_client.get( + project=project_id, zone=zone, instance=instance_name + ) + + # Make sure that the machine is turned off + if instance.status not in ( + instance.Status.TERMINATED.name, + instance.Status.STOPPED.name, + ): + op = instance_client.stop_unary( + project=project_id, zone=zone, instance=instance_name + ) + operation_client.wait(project=project_id, zone=zone, operation=op.name) + while instance.status not in ( + instance.Status.TERMINATED.name, + instance.Status.STOPPED.name, + ): + # Waiting for the instance to be turned off. + instance = instance_client.get( + project=project_id, zone=zone, instance=instance_name + ) + time.sleep(2) + + # Modify the machine definition, remember that extended memory is available only for N1, N2 and N2D CPUs + start, end = instance.machine_type.rsplit("-", maxsplit=1) + instance.machine_type = start + f"-{new_memory}-ext" + # Using CustomMachineType helper + # cmt = CustomMachineType.from_str(instance.machine_type) + # cmt.memory_mb = new_memory + # cmt.extra_memory_used = True + # instance.machine_type = str(cmt) + op = instance_client.update_unary( + project=project_id, + zone=zone, + instance=instance_name, + instance_resource=instance, + ) + operation_client.wait(project=project_id, zone=zone, operation=op.name) + + return instance_client.get(project=project_id, zone=zone, instance=instance_name) + + +# [END compute_custom_machine_type_update_memory ] diff --git a/samples/snippets/test_quickstart.py b/samples/snippets/test_quickstart.py index 91a2d3642..705707656 100644 --- a/samples/snippets/test_quickstart.py +++ b/samples/snippets/test_quickstart.py @@ -18,7 +18,7 @@ import google.auth -from samples.snippets.quickstart import main +from quickstart import main PROJECT = google.auth.default()[1] INSTANCE_NAME = "i" + uuid.uuid4().hex[:10] diff --git a/samples/snippets/test_sample_custom_types.py b/samples/snippets/test_sample_custom_types.py new file mode 100644 index 000000000..812b04b50 --- /dev/null +++ b/samples/snippets/test_sample_custom_types.py @@ -0,0 +1,194 @@ +# Copyright 2021 Google LLC +# +# 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. +import uuid + +import google.auth +import pytest + +from quickstart import create_instance, delete_instance +from sample_custom_types import ( + add_extended_memory_to_instance, + create_custom_instance, + create_custom_shared_core_instance, + CustomMachineType, +) + +PROJECT = google.auth.default()[1] +REGION = "us-central1" +INSTANCE_ZONE = "us-central1-b" + + +@pytest.fixture +def auto_delete_instance_name(): + instance_name = "test-instance-" + uuid.uuid4().hex[:10] + yield instance_name + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) + + +@pytest.fixture +def instance(): + instance_name = "test-instance-" + uuid.uuid4().hex[:10] + instance = create_instance( + PROJECT, INSTANCE_ZONE, instance_name, "n2-custom-8-10240" + ) + yield instance + delete_instance(PROJECT, INSTANCE_ZONE, instance_name) + + +def test_custom_instance_creation(auto_delete_instance_name): + instance = create_custom_instance( + PROJECT, + INSTANCE_ZONE, + auto_delete_instance_name, + CustomMachineType.CPUSeries.E2, + 4, + 8192, + ) + + assert instance.name == auto_delete_instance_name + assert instance.machine_type.endswith( + f"zones/{INSTANCE_ZONE}/machineTypes/e2-custom-4-8192" + ) + + +def test_custom_shared_instance_creation(auto_delete_instance_name): + instance = create_custom_shared_core_instance( + PROJECT, + INSTANCE_ZONE, + auto_delete_instance_name, + CustomMachineType.CPUSeries.E2_MICRO, + 2048, + ) + + assert instance.name == auto_delete_instance_name + assert instance.machine_type.endswith( + f"zones/{INSTANCE_ZONE}/machineTypes/e2-custom-micro-2048" + ) + + +def test_custom_machine_type_good(): + # N1 + cmt = CustomMachineType(INSTANCE_ZONE, CustomMachineType.CPUSeries.N1, 8192, 8) + assert str(cmt) == f"zones/{INSTANCE_ZONE}/machineTypes/custom-8-8192" + assert cmt.short_type_str() == "custom-8-8192" + # N2 + cmt = CustomMachineType(INSTANCE_ZONE, CustomMachineType.CPUSeries.N2, 4096, 4) + assert str(cmt) == f"zones/{INSTANCE_ZONE}/machineTypes/n2-custom-4-4096" + assert cmt.short_type_str() == "n2-custom-4-4096" + # N2D + cmt = CustomMachineType(INSTANCE_ZONE, CustomMachineType.CPUSeries.N2D, 8192, 4) + assert str(cmt) == f"zones/{INSTANCE_ZONE}/machineTypes/n2d-custom-4-8192" + assert cmt.short_type_str() == "n2d-custom-4-8192" + # E2 + cmt = CustomMachineType(INSTANCE_ZONE, CustomMachineType.CPUSeries.E2, 8192, 8) + assert str(cmt) == f"zones/{INSTANCE_ZONE}/machineTypes/e2-custom-8-8192" + assert cmt.short_type_str() == "e2-custom-8-8192" + # E2 SMALL + cmt = CustomMachineType(INSTANCE_ZONE, CustomMachineType.CPUSeries.E2_SMALL, 4096) + assert str(cmt) == f"zones/{INSTANCE_ZONE}/machineTypes/e2-custom-small-4096" + assert cmt.short_type_str() == "e2-custom-small-4096" + # E2 MICRO + cmt = CustomMachineType(INSTANCE_ZONE, CustomMachineType.CPUSeries.E2_MICRO, 2048) + assert str(cmt) == f"zones/{INSTANCE_ZONE}/machineTypes/e2-custom-micro-2048" + assert cmt.short_type_str() == "e2-custom-micro-2048" + # E2 MEDIUM + cmt = CustomMachineType(INSTANCE_ZONE, CustomMachineType.CPUSeries.E2_MEDIUM, 8192) + assert str(cmt) == f"zones/{INSTANCE_ZONE}/machineTypes/e2-custom-medium-8192" + assert cmt.short_type_str() == "e2-custom-medium-8192" + + +def test_custom_machine_type_bad_memory_256(): + try: + CustomMachineType(INSTANCE_ZONE, CustomMachineType.CPUSeries.N1, 8194, 8) + except RuntimeError as err: + assert err.args[0] == "Requested memory must be a multiple of 256 MB." + else: + assert not "This test should have raised an exception!" + + +def test_custom_machine_type_ext_memory(): + cmt = CustomMachineType(INSTANCE_ZONE, CustomMachineType.CPUSeries.N2, 638720, 8) + assert str(cmt) == f"zones/{INSTANCE_ZONE}/machineTypes/n2-custom-8-638720-ext" + + +def test_custom_machine_type_bad_cpu_count(): + try: + CustomMachineType(INSTANCE_ZONE, CustomMachineType.CPUSeries.N2, 8194, 66) + except RuntimeError as err: + assert err.args[0].startswith( + "Invalid number of cores requested. Allowed number of cores for" + ) + else: + assert not "This test should have raised an exception!" + + +def test_add_extended_memory_to_instance(instance): + instance = add_extended_memory_to_instance( + PROJECT, INSTANCE_ZONE, instance.name, 819200 + ) + assert instance.machine_type.endswith("819200-ext") + + +def test_from_str_creation(): + cmt = CustomMachineType.from_str( + "https://www.googleapis.com/compute/v1/projects/diregapic-mestiv/zones/us-central1-b/machineTypes/e2-custom-4-8192" + ) + assert cmt.zone == "us-central1-b" + assert cmt.memory_mb == 8192 + assert cmt.extra_memory_used is False + assert cmt.cpu_series is CustomMachineType.CPUSeries.E2 + assert cmt.core_count == 4 + + cmt = CustomMachineType.from_str( + "zones/europe-west4-b/machineTypes/n2-custom-8-81920-ext" + ) + assert cmt.zone == "europe-west4-b" + assert cmt.memory_mb == 81920 + assert cmt.extra_memory_used is True + assert cmt.cpu_series is CustomMachineType.CPUSeries.N2 + assert cmt.core_count == 8 + + cmt = CustomMachineType.from_str( + "zones/europe-west4-b/machineTypes/e2-custom-small-4096" + ) + assert cmt.zone == "europe-west4-b" + assert cmt.memory_mb == 4096 + assert cmt.extra_memory_used is False + assert cmt.cpu_series == CustomMachineType.CPUSeries.E2_SMALL + assert cmt.core_count == 2 + + cmt = CustomMachineType.from_str( + "zones/europe-central2-b/machineTypes/custom-2-2048" + ) + assert cmt.zone == "europe-central2-b" + assert cmt.memory_mb == 2048 + assert cmt.extra_memory_used is False + assert cmt.cpu_series is CustomMachineType.CPUSeries.N1 + assert cmt.core_count == 2 + + try: + CustomMachineType.from_str( + "zones/europe-central2-b/machineTypes/n8-custom-2-1024" + ) + except RuntimeError as err: + assert err.args[0] == "Unknown CPU series." + else: + assert not "This was supposed to raise a RuntimeError." + + cmt = CustomMachineType.from_str("n2d-custom-8-81920-ext") + assert cmt.zone is None + assert cmt.memory_mb == 81920 + assert cmt.extra_memory_used is True + assert cmt.cpu_series is CustomMachineType.CPUSeries.N2D + assert cmt.core_count == 8 From 0682bacb10c9b5de3209588ccac39e4fcfc268f6 Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Tue, 28 Dec 2021 13:33:49 -0500 Subject: [PATCH 03/11] chore: update .repo-metadata.json (#176) --- .repo-metadata.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.repo-metadata.json b/.repo-metadata.json index c928d112d..7e78b0027 100644 --- a/.repo-metadata.json +++ b/.repo-metadata.json @@ -4,12 +4,13 @@ "product_documentation": "https://cloud.google.com/compute/", "client_documentation": "https://cloud.google.com/python/docs/reference/compute/latest", "issue_tracker": "https://issuetracker.google.com/issues/new?component=187134&template=0", - "release_level": "alpha", + "release_level": "preview", "language": "python", "library_type": "GAPIC_AUTO", "repo": "googleapis/python-compute", "distribution_name": "google-cloud-compute", "api_id": "compute.googleapis.com", "codeowner_team": "@googleapis/api-compute", - "default_version": "v1" + "default_version": "v1", + "api_shortname": "compute" } From f89ace016360b46e5b55b2796998454a3fb319f2 Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Wed, 5 Jan 2022 13:56:10 +0100 Subject: [PATCH 04/11] chore(samples): Fixing a small issue with sample region tags (#180) --- samples/snippets/sample_create_vm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/snippets/sample_create_vm.py b/samples/snippets/sample_create_vm.py index 5b4377d98..1ea6ba2d2 100644 --- a/samples/snippets/sample_create_vm.py +++ b/samples/snippets/sample_create_vm.py @@ -20,11 +20,11 @@ # [START compute_instances_create_from_snapshot] # [START compute_instances_create_from_image_plus_empty_disk] # [START compute_instances_create_from_custom_image] -# [START compute_instances_create_from_image ] +# [START compute_instances_create_from_image] from google.cloud import compute_v1 -# [END compute_instances_create_from_image ] +# [END compute_instances_create_from_image] # [END compute_instances_create_from_custom_image] # [END compute_instances_create_from_image_plus_empty_disk] # [END compute_instances_create_from_snapshot] From e9bc241d20ee1af4374e3ef50b3aed10e4e4c03f Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Thu, 6 Jan 2022 10:51:26 -0700 Subject: [PATCH 05/11] chore: use python-samples-reviewers (#183) Source-Link: https://github.com/googleapis/synthtool/commit/da9308710160980198d85a4bcddac1d6f6f1a5bc Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:899d5d7cc340fa8ef9d8ae1a8cfba362c6898584f779e156f25ee828ba824610 Co-authored-by: Owl Bot --- .github/.OwlBot.lock.yaml | 2 +- .github/CODEOWNERS | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index a67698f8f..f33299ddb 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -1,3 +1,3 @@ docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:74124fe59b8859f30143dcdea7b78300046d97de816dc53c0e381308a5f4f8bc + digest: sha256:899d5d7cc340fa8ef9d8ae1a8cfba362c6898584f779e156f25ee828ba824610 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c7eaf450d..fc461db0a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,5 +8,5 @@ # @googleapis/yoshi-python @googleapis/api-compute are the default owners for changes in this repo * @googleapis/yoshi-python @googleapis/api-compute -# @googleapis/python-samples-owners @googleapis/api-compute are the default owners for samples changes -/samples/ @googleapis/python-samples-owners @googleapis/api-compute +# @googleapis/python-samples-reviewers @googleapis/api-compute are the default owners for samples changes +/samples/ @googleapis/python-samples-reviewers @googleapis/api-compute From efa02df18b348840f91c0df59d88f7d68432b3cf Mon Sep 17 00:00:00 2001 From: Maciej Strzelczyk Date: Mon, 10 Jan 2022 20:09:19 +0100 Subject: [PATCH 06/11] chore(samples): Removing some leaked instances. (#179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(samples): Removing some leaked instances. * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * chore(samples): Removing more leaked instances. * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * chore(samples): Removing cleanup code, leaving only the firewall test fix. * chore(samples): Restructuring firewall test. Co-authored-by: Owl Bot --- samples/snippets/test_sample_firewall.py | 28 ++++++++++++++++++------ 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/samples/snippets/test_sample_firewall.py b/samples/snippets/test_sample_firewall.py index 1bb63cfd8..517174395 100644 --- a/samples/snippets/test_sample_firewall.py +++ b/samples/snippets/test_sample_firewall.py @@ -24,7 +24,6 @@ create_firewall_rule, delete_firewall_rule, get_firewall_rule, - list_firewall_rules, patch_firewall_priority, ) @@ -64,14 +63,29 @@ def firewall_rule(): raise err -def test_create_delete(): +@pytest.fixture +def autodelete_firewall_name(): + """ + Provide a name for a firewall rule and then delete the rule. + """ rule_name = "firewall-sample-test-" + uuid.uuid4().hex[:10] - create_firewall_rule(PROJECT, rule_name) - rule = get_firewall_rule(PROJECT, rule_name) - assert rule.name == rule_name + yield rule_name + try: + delete_firewall_rule(PROJECT, rule_name) + except google.api_core.exceptions.BadRequest as err: + if err.code == 400 and "is not ready" in err.message: + # We can ignore this, this is most likely GCE Enforcer removing the rule before us. + pass + else: + # Something else went wrong, let's escalate it. + raise err + + +def test_create(autodelete_firewall_name): + create_firewall_rule(PROJECT, autodelete_firewall_name) + rule = get_firewall_rule(PROJECT, autodelete_firewall_name) + assert rule.name == autodelete_firewall_name assert "web" in rule.target_tags - delete_firewall_rule(PROJECT, rule_name) - assert all(rule.name != rule_name for rule in list_firewall_rules(PROJECT)) def test_patch_rule(firewall_rule): From b634df2a02b427693a527170dbba9f18c667ab92 Mon Sep 17 00:00:00 2001 From: WhiteSource Renovate Date: Tue, 11 Jan 2022 16:15:50 +0100 Subject: [PATCH 07/11] chore(deps): update dependency google-cloud-storage to v1.44.0 (#182) Co-authored-by: Anthonios Partheniou --- samples/snippets/requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/snippets/requirements-test.txt b/samples/snippets/requirements-test.txt index 6c0e2549b..3d7f0afb7 100644 --- a/samples/snippets/requirements-test.txt +++ b/samples/snippets/requirements-test.txt @@ -1,3 +1,3 @@ pytest==6.2.5 flaky==3.7.0 -google-cloud-storage==1.43.0 \ No newline at end of file +google-cloud-storage==1.44.0 \ No newline at end of file From 915995eca179c9032aabe4c23f139fa6477bcaef Mon Sep 17 00:00:00 2001 From: "gcf-owl-bot[bot]" <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Tue, 11 Jan 2022 11:19:35 -0500 Subject: [PATCH 08/11] chore(samples): Add check for tests in directory (#186) Source-Link: https://github.com/googleapis/synthtool/commit/52aef91f8d25223d9dbdb4aebd94ba8eea2101f3 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-python:latest@sha256:36a95b8f494e4674dc9eee9af98961293b51b86b3649942aac800ae6c1f796d4 Co-authored-by: Owl Bot Co-authored-by: Anthonios Partheniou --- .github/.OwlBot.lock.yaml | 2 +- samples/snippets/noxfile.py | 70 +++++++++++++++++++++---------------- 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index f33299ddb..6b8a73b31 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -1,3 +1,3 @@ docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:899d5d7cc340fa8ef9d8ae1a8cfba362c6898584f779e156f25ee828ba824610 + digest: sha256:36a95b8f494e4674dc9eee9af98961293b51b86b3649942aac800ae6c1f796d4 diff --git a/samples/snippets/noxfile.py b/samples/snippets/noxfile.py index 93a9122cc..3bbef5d54 100644 --- a/samples/snippets/noxfile.py +++ b/samples/snippets/noxfile.py @@ -14,6 +14,7 @@ from __future__ import print_function +import glob import os from pathlib import Path import sys @@ -184,37 +185,44 @@ def blacken(session: nox.sessions.Session) -> None: def _session_tests( session: nox.sessions.Session, post_install: Callable = None ) -> None: - if TEST_CONFIG["pip_version_override"]: - pip_version = TEST_CONFIG["pip_version_override"] - session.install(f"pip=={pip_version}") - """Runs py.test for a particular project.""" - if os.path.exists("requirements.txt"): - if os.path.exists("constraints.txt"): - session.install("-r", "requirements.txt", "-c", "constraints.txt") - else: - session.install("-r", "requirements.txt") - - if os.path.exists("requirements-test.txt"): - if os.path.exists("constraints-test.txt"): - session.install("-r", "requirements-test.txt", "-c", "constraints-test.txt") - else: - session.install("-r", "requirements-test.txt") - - if INSTALL_LIBRARY_FROM_SOURCE: - session.install("-e", _get_repo_root()) - - if post_install: - post_install(session) - - session.run( - "pytest", - *(PYTEST_COMMON_ARGS + session.posargs), - # Pytest will return 5 when no tests are collected. This can happen - # on travis where slow and flaky tests are excluded. - # See http://doc.pytest.org/en/latest/_modules/_pytest/main.html - success_codes=[0, 5], - env=get_pytest_env_vars(), - ) + # check for presence of tests + test_list = glob.glob("*_test.py") + glob.glob("test_*.py") + if len(test_list) == 0: + print("No tests found, skipping directory.") + else: + if TEST_CONFIG["pip_version_override"]: + pip_version = TEST_CONFIG["pip_version_override"] + session.install(f"pip=={pip_version}") + """Runs py.test for a particular project.""" + if os.path.exists("requirements.txt"): + if os.path.exists("constraints.txt"): + session.install("-r", "requirements.txt", "-c", "constraints.txt") + else: + session.install("-r", "requirements.txt") + + if os.path.exists("requirements-test.txt"): + if os.path.exists("constraints-test.txt"): + session.install( + "-r", "requirements-test.txt", "-c", "constraints-test.txt" + ) + else: + session.install("-r", "requirements-test.txt") + + if INSTALL_LIBRARY_FROM_SOURCE: + session.install("-e", _get_repo_root()) + + if post_install: + post_install(session) + + session.run( + "pytest", + *(PYTEST_COMMON_ARGS + session.posargs), + # Pytest will return 5 when no tests are collected. This can happen + # on travis where slow and flaky tests are excluded. + # See http://doc.pytest.org/en/latest/_modules/_pytest/main.html + success_codes=[0, 5], + env=get_pytest_env_vars(), + ) @nox.session(python=ALL_VERSIONS) From cdb45d5d809cd7ba5a0283a0b4f4374f8a3ee995 Mon Sep 17 00:00:00 2001 From: georgiyekkert Date: Tue, 11 Jan 2022 11:40:30 -0800 Subject: [PATCH 09/11] tests: remove int64 field test (#177) Co-authored-by: Anthonios Partheniou --- tests/system/test_smoke.py | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/tests/system/test_smoke.py b/tests/system/test_smoke.py index ba8572fc8..05526bc24 100644 --- a/tests/system/test_smoke.py +++ b/tests/system/test_smoke.py @@ -17,13 +17,10 @@ import google.api_core.exceptions from google.cloud.compute_v1.services.firewalls.client import FirewallsClient -from google.cloud.compute_v1.services.images.client import ImagesClient from google.cloud.compute_v1.services.instances.client import InstancesClient from google.cloud.compute_v1.types import ( Allowed, Firewall, - Image, - InsertImageRequest, InsertInstanceRequest, Instance, AttachedDisk, @@ -195,32 +192,6 @@ def insert_instance(self): self.instances.append(self.name) -class TestComputeImages(TestBase): - def setUp(self) -> None: - super().setUp() - - def test_int64(self): - # we want to test a field with format:int64 - name = self.get_unique_name("image") - license_codes = [5543610867827062957] - image = Image( - name=name, - license_codes=license_codes, - source_image="projects/debian-cloud/global/images/debian-10-buster-v20210721", - ) - images_client = ImagesClient(transport="rest") - request = InsertImageRequest(project=self.DEFAULT_PROJECT, image_resource=image) - op = images_client.insert_unary(request) - try: - self.wait_for_global_operation(op.name) - - fetched = images_client.get(project=self.DEFAULT_PROJECT, image=name) - self.assertEqual(fetched.license_codes, license_codes) - self.assertEqual(fetched.name, name) - finally: - images_client.delete_unary(project=self.DEFAULT_PROJECT, image=name) - - class TestComputeFirewalls(TestBase): def setUp(self): super().setUp() From d5a543867ab390d136eef153d0704a654aacb0db Mon Sep 17 00:00:00 2001 From: Anthonios Partheniou Date: Thu, 13 Jan 2022 13:02:16 -0500 Subject: [PATCH 10/11] feat: bump release level to production/stable (#189) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #53 🦕 Googlers see internal bug b/209082587 Release-As: 1.0.0 --- .repo-metadata.json | 2 +- README.rst | 6 +++--- setup.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.repo-metadata.json b/.repo-metadata.json index 7e78b0027..8e79e1b54 100644 --- a/.repo-metadata.json +++ b/.repo-metadata.json @@ -4,7 +4,7 @@ "product_documentation": "https://cloud.google.com/compute/", "client_documentation": "https://cloud.google.com/python/docs/reference/compute/latest", "issue_tracker": "https://issuetracker.google.com/issues/new?component=187134&template=0", - "release_level": "preview", + "release_level": "stable", "language": "python", "library_type": "GAPIC_AUTO", "repo": "googleapis/python-compute", diff --git a/README.rst b/README.rst index 5de8e227d..919ed5678 100644 --- a/README.rst +++ b/README.rst @@ -1,15 +1,15 @@ Python Client for Compute Engine ================================================= -|alpha| |pypi| |versions| +|stable| |pypi| |versions| `Compute Engine API`_: Create and runs virtual machines on Google Cloud Platform. - `Client Library Documentation`_ - `Product Documentation`_ -.. |alpha| image:: https://img.shields.io/badge/support-alpha-orange.svg - :target: https://github.com/googleapis/google-cloud-python/blob/main/README.rst#alpha-support +.. |stable| image:: https://img.shields.io/badge/support-stable-gold.svg + :target: https://github.com/googleapis/google-cloud-python/blob/main/README.rst#general-availability .. |pypi| image:: https://img.shields.io/pypi/v/google-cloud-compute.svg :target: https://pypi.org/project/google-cloud-compute/ .. |versions| image:: https://img.shields.io/pypi/pyversions/google-cloud-compute.svg diff --git a/setup.py b/setup.py index 8bec31737..c764082ed 100644 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ ), python_requires=">=3.6", classifiers=[ - "Development Status :: 3 - Alpha", + "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Operating System :: OS Independent", "Programming Language :: Python", From f0b5f329f15f29b162ff97168428197f0fb9671f Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 13 Jan 2022 19:04:14 +0000 Subject: [PATCH 11/11] chore(main): release 1.0.0 (#190) :robot: I have created a release *beep* *boop* --- ## [1.0.0](https://github.com/googleapis/python-compute/compare/v0.9.0...v1.0.0) (2022-01-13) ### Features * bump release level to production/stable ([#189](https://github.com/googleapis/python-compute/issues/189)) ([d5a5438](https://github.com/googleapis/python-compute/commit/d5a543867ab390d136eef153d0704a654aacb0db)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --- CHANGELOG.md | 7 +++++++ setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 450bd0e2f..de0bfe97d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.0.0](https://github.com/googleapis/python-compute/compare/v0.9.0...v1.0.0) (2022-01-13) + + +### Features + +* bump release level to production/stable ([#189](https://github.com/googleapis/python-compute/issues/189)) ([d5a5438](https://github.com/googleapis/python-compute/commit/d5a543867ab390d136eef153d0704a654aacb0db)) + ## [0.9.0](https://www.github.com/googleapis/python-compute/compare/v0.8.0...v0.9.0) (2021-12-13) diff --git a/setup.py b/setup.py index c764082ed..0ea720815 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ import os import setuptools # type: ignore -version = "0.9.0" +version = "1.0.0" package_root = os.path.abspath(os.path.dirname(__file__))