From d510b4c508c8f9d430955578268ca8951241a347 Mon Sep 17 00:00:00 2001 From: georgiyekkert Date: Tue, 30 Mar 2021 16:24:08 -0700 Subject: [PATCH] tests: add system tests for python-compute (#34) * tests: add system tests for python-compute * tests: add more system tests * tests: fix linter errors * tests: fix lint issue * tests: delete tests/test_system - dummy for mlts --- packages/google-cloud-compute/tests/system.py | 33 ---- .../google-cloud-compute/tests/system/base.py | 69 ++++++++ .../tests/system/test_addresses.py | 64 +++++++ .../tests/system/test_pagination.py | 67 ++++++++ .../tests/system/test_query_params.py | 123 ++++++++++++++ .../tests/system/test_smoke.py | 160 ++++++++++++++++++ 6 files changed, 483 insertions(+), 33 deletions(-) delete mode 100644 packages/google-cloud-compute/tests/system.py create mode 100644 packages/google-cloud-compute/tests/system/base.py create mode 100644 packages/google-cloud-compute/tests/system/test_addresses.py create mode 100644 packages/google-cloud-compute/tests/system/test_pagination.py create mode 100644 packages/google-cloud-compute/tests/system/test_query_params.py create mode 100644 packages/google-cloud-compute/tests/system/test_smoke.py diff --git a/packages/google-cloud-compute/tests/system.py b/packages/google-cloud-compute/tests/system.py deleted file mode 100644 index 2a4191546d78..000000000000 --- a/packages/google-cloud-compute/tests/system.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -# -# 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 -# -# https://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 os -import pytest - -from google.cloud import compute - - -@pytest.fixture(scope="session") -def project_id(): - return os.environ["PROJECT_ID"] - - -def test_list_instances_not_throw_mtls(project_id): - client = compute.InstancesClient() - - # For regular system testing, the following call should never throw. - # For mTLS testing, the call throws if mTLS is not properly configured. - client.list(project=project_id, zone="us-west1-a") diff --git a/packages/google-cloud-compute/tests/system/base.py b/packages/google-cloud-compute/tests/system/base.py new file mode 100644 index 000000000000..8c7e8f93c5b6 --- /dev/null +++ b/packages/google-cloud-compute/tests/system/base.py @@ -0,0 +1,69 @@ +# 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 +# +# https://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 unittest +import uuid +import google.auth +from google.cloud.compute_v1.services.zone_operations.client import ZoneOperationsClient +from google.cloud.compute_v1.services.region_operations.client import ( + RegionOperationsClient, +) +from google.cloud.compute_v1.services.global_operations.client import ( + GlobalOperationsClient, +) + + +class TestBase(unittest.TestCase): + def setUp(self): + _, self.DEFAULT_PROJECT = google.auth.default() + if not self.DEFAULT_PROJECT: + self.skipTest("GCP project was not found, skipping system test") + self.DEFAULT_ZONE = "us-central1-a" + self.DEFAULT_REGION = "us-central1" + self.MACHINE_TYPE = ( + "https://www.googleapis.com/compute/v1/projects/{}/" + "zones/us-central1-a/machineTypes/n1-standard-1".format( + self.DEFAULT_PROJECT + ) + ) + self.DISK_IMAGE = "projects/debian-cloud/global/images/family/debian-10" + + @staticmethod + def get_unique_name(placeholder=""): + return "gapic" + placeholder + uuid.uuid4().hex + + def wait_for_zonal_operation(self, operation): + client = ZoneOperationsClient() + result = client.wait( + operation=operation, zone=self.DEFAULT_ZONE, project=self.DEFAULT_PROJECT + ) + if result.error: + self.fail("Zonal operation {} has errors".format(operation)) + + def wait_for_regional_operation(self, operation): + client = RegionOperationsClient() + result = client.wait( + operation=operation, + region=self.DEFAULT_REGION, + project=self.DEFAULT_PROJECT, + ) + if result.error: + self.fail("Region operation {} has errors".format(operation)) + + def wait_for_global_operation(self, operation): + client = GlobalOperationsClient() + result = client.wait(operation=operation, project=self.DEFAULT_PROJECT) + if result.error: + self.fail("Global operation {} has errors".format(operation)) diff --git a/packages/google-cloud-compute/tests/system/test_addresses.py b/packages/google-cloud-compute/tests/system/test_addresses.py new file mode 100644 index 000000000000..dd606e1209d0 --- /dev/null +++ b/packages/google-cloud-compute/tests/system/test_addresses.py @@ -0,0 +1,64 @@ +# 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 +# +# https://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. + +from google.cloud.compute_v1.types import Address, InsertAddressRequest +from google.cloud.compute_v1.services.addresses.client import AddressesClient +from tests.system.base import TestBase + + +class TestAddresses(TestBase): + def setUp(self) -> None: + super().setUp() + self.client = AddressesClient(transport="rest") + self.name = self.get_unique_name("address") + self.addresses = [] + + def tearDown(self) -> None: + for address in self.addresses: + self.client.delete( + project=self.DEFAULT_PROJECT, + region=self.DEFAULT_REGION, + address=address, + ) + + def insert_address(self): + address_res = Address() + address_res.name = self.name + + request = InsertAddressRequest() + request.project = self.DEFAULT_PROJECT + request.region = self.DEFAULT_REGION + request.address_resource = address_res + operation = self.client.insert(request) + self.wait_for_regional_operation(operation.name) + self.addresses.append(self.name) + + def test_create_read(self): + self.insert_address() + address = self.client.get( + project=self.DEFAULT_PROJECT, region=self.DEFAULT_REGION, address=self.name + ) + self.assertEqual(getattr(address, "name"), self.name) + + def test_list(self): + presented = False + self.insert_address() + result = self.client.list( + project=self.DEFAULT_PROJECT, region=self.DEFAULT_REGION + ) + for item in result: + if getattr(item, "name") == self.name: + presented = True + break + self.assertTrue(presented) diff --git a/packages/google-cloud-compute/tests/system/test_pagination.py b/packages/google-cloud-compute/tests/system/test_pagination.py new file mode 100644 index 000000000000..3f8f4e546a3c --- /dev/null +++ b/packages/google-cloud-compute/tests/system/test_pagination.py @@ -0,0 +1,67 @@ +# 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 +# +# https://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. + + +from google.cloud.compute_v1.services.zones.client import ZonesClient +from google.cloud.compute_v1.types import ListZonesRequest +from tests.system.base import TestBase + + +class TestComputePagination(TestBase): + def setUp(self) -> None: + super().setUp() + self.client = ZonesClient() + + def test_max_results(self): + request = ListZonesRequest() + request.max_results = 1 + request.project = self.DEFAULT_PROJECT + result = self.client.list(request=request) + self.assertEqual(len(getattr(result, "items")), 1) + + def test_next_page_token(self): + request = ListZonesRequest() + request.max_results = 1 + request.project = self.DEFAULT_PROJECT + result = self.client.list(request=request) + + token_request = ListZonesRequest() + token_request.max_results = 1 + token_request.project = self.DEFAULT_PROJECT + token_request.page_token = getattr(result, "next_page_token") + token_result = self.client.list(request=token_request) + self.assertNotEqual(getattr(result, "items"), getattr(token_result, "items")) + + def test_filter(self): + request = ListZonesRequest() + request.project = self.DEFAULT_PROJECT + request.filter = "name = us-central1-a" + result = self.client.list(request=request) + description = getattr(getattr(result, "items")[0], "description") + self.assertEqual(len(getattr(result, "items")), 1) + self.assertEqual(description, "us-central1-a") + + def test_auto_paging(self): + request = ListZonesRequest() + request.max_results = 1 + request.project = self.DEFAULT_PROJECT + request.filter = "name = us-*" + result = self.client.list(request=request) + presented = False + for item in result: + desc = getattr(item, "description") + if desc == self.DEFAULT_ZONE: + presented = True + break + self.assertTrue(presented) diff --git a/packages/google-cloud-compute/tests/system/test_query_params.py b/packages/google-cloud-compute/tests/system/test_query_params.py new file mode 100644 index 000000000000..8f07410ebffc --- /dev/null +++ b/packages/google-cloud-compute/tests/system/test_query_params.py @@ -0,0 +1,123 @@ +# 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 +# +# https://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. + +from google.cloud.compute_v1.services.instances.client import InstancesClient +from google.cloud.compute_v1.services.instance_group_managers.client import ( + InstanceGroupManagersClient, +) +from google.cloud.compute_v1.services.instance_templates.client import ( + InstanceTemplatesClient, +) +from google.cloud.compute_v1.types import ( + InsertInstanceRequest, + Instance, + AttachedDisk, + NetworkInterface, + AttachedDiskInitializeParams, +) +from tests.system.base import TestBase + + +class TestInstanceGroups(TestBase): + def setUp(self): + super().setUp() + self.instances = [] + self.igms = [] + self.templates = [] + self.inst_client = InstancesClient(transport="rest") + self.name = self.get_unique_name("instance") + self.igm_client = InstanceGroupManagersClient() + self.template_client = InstanceTemplatesClient() + + def tearDown(self) -> None: + for igm in self.igms: + op = self.igm_client.delete( + project=self.DEFAULT_PROJECT, + zone=self.DEFAULT_ZONE, + instance_group_manager=igm, + ) + self.wait_for_zonal_operation(op.name) + for instance in self.instances: + op = self.inst_client.delete( + project=self.DEFAULT_PROJECT, zone=self.DEFAULT_ZONE, instance=instance + ) + for template in self.templates: + op = self.template_client.delete( + project=self.DEFAULT_PROJECT, instance_template=template + ) + + """ Resize fails due to + def test_instance_group_resize(self): + template_name = self.get_unique_name('template') + igm_name = self.get_unique_name('igm') + + instance = self.insert_instance().target_link + + template_resource = InstanceTemplate( + name=template_name, + source_instance=instance + ) + operation = self.template_client.insert(project=self.DEFAULT_PROJECT, + instance_template_resource=template_resource) + self.wait_for_global_operation(operation.name) + self.templates.append(template_name) + template = operation.target_link + + igm_resource = InstanceGroupManager( + base_instance_name="gapicinst", + instance_template=template, + name=igm_name, + target_size=1) + operation = self.igm_client.insert(project=self.DEFAULT_PROJECT, zone=self.DEFAULT_ZONE, + instance_group_manager_resource=igm_resource) + self.wait_for_zonal_operation(operation.name) + self.igms.append(igm_name) + + instance_group = self.igm_client.get(project=self.DEFAULT_PROJECT, + zone=self.DEFAULT_ZONE, instance_group_manager=igm_name) + self.assertEqual(instance_group.target_size, 1) + resize_op = self.igm_client.resize(project=self.DEFAULT_PROJECT, + zone=self.DEFAULT_ZONE, size=0, instance_group_manager=igm_name) + self.wait_for_zonal_operation(resize_op.name) + igm = self.igm_client.get(project=self.DEFAULT_PROJECT, zone=self.DEFAULT_ZONE, + instance_group_manager=igm_name) + self.assertEqual(igm.target_size, 0) + """ + + def insert_instance(self): + disk = AttachedDisk() + initialize_params = AttachedDiskInitializeParams() + initialize_params.source_image = self.DISK_IMAGE + disk.initialize_params = initialize_params + disk.auto_delete = True + disk.boot = True + disk.type_ = AttachedDisk.Type.PERSISTENT + + network_interface = NetworkInterface() + network_interface.name = "default" + + instance = Instance() + instance.name = self.name + instance.disks = [disk] + instance.machine_type = self.MACHINE_TYPE + instance.network_interfaces = [network_interface] + + request = InsertInstanceRequest() + request.zone = self.DEFAULT_ZONE + request.project = self.DEFAULT_PROJECT + request.instance_resource = instance + operation = self.inst_client.insert(request=request) + self.wait_for_zonal_operation(operation.name) + self.instances.append(self.name) + return operation diff --git a/packages/google-cloud-compute/tests/system/test_smoke.py b/packages/google-cloud-compute/tests/system/test_smoke.py new file mode 100644 index 000000000000..67d4560539bd --- /dev/null +++ b/packages/google-cloud-compute/tests/system/test_smoke.py @@ -0,0 +1,160 @@ +# 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 +# +# https://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 requests +import time + + +from google.cloud.compute_v1.services.instances.client import InstancesClient +from google.cloud.compute_v1.types import ( + InsertInstanceRequest, + Instance, + AttachedDisk, + NetworkInterface, + AttachedDiskInitializeParams, + ShieldedInstanceConfig, +) +from tests.system.base import TestBase + + +class TestComputeSmoke(TestBase): + def setUp(self) -> None: + super().setUp() + self.client = InstancesClient(transport="rest") + self.name = self.get_unique_name("instance") + self.instances = [] + + def tearDown(self) -> None: + for instance in self.instances: + self.client.delete( + project=self.DEFAULT_PROJECT, zone=self.DEFAULT_ZONE, instance=instance + ) + + def test_insert_instance(self): + self.insert_instance() + self.assert_instance() + + def insert_instance(self): + disk = AttachedDisk() + initialize_params = AttachedDiskInitializeParams() + initialize_params.source_image = self.DISK_IMAGE + disk.initialize_params = initialize_params + disk.auto_delete = True + disk.boot = True + disk.type_ = AttachedDisk.Type.PERSISTENT + + network_interface = NetworkInterface() + network_interface.name = "default" + + instance = Instance() + instance.name = self.name + instance.disks = [disk] + instance.machine_type = self.MACHINE_TYPE + instance.network_interfaces = [network_interface] + + request = InsertInstanceRequest() + request.zone = self.DEFAULT_ZONE + request.project = self.DEFAULT_PROJECT + request.instance_resource = instance + operation = self.client.insert(request=request) + self.wait_for_zonal_operation(operation.name) + self.instances.append(self.name) + + def test_aggregated_list(self): + presented = False + self.insert_instance() + result = self.client.aggregated_list(project=self.DEFAULT_PROJECT) + instances = getattr(result.get("zones/" + self.DEFAULT_ZONE), "instances") + for item in instances: + if getattr(item, "name") == self.name: + presented = True + break + self.assertTrue(presented) + + def test_client_error(self): + with self.assertRaises(expected_exception=requests.exceptions.HTTPError) as ex: + self.client.get(instance=self.name, zone=self.DEFAULT_ZONE) + self.assertIn("Bad Request", str(ex.exception.args)) + + def test_api_error(self): + with self.assertRaises(expected_exception=requests.exceptions.HTTPError) as ex: + self.client.get( + project=self.DEFAULT_PROJECT, + zone=self.DEFAULT_ZONE, + instance="nonexistent9999123412314", + ) + self.assertIn("Not Found", str(ex.exception.args)) + + def test_zero_values(self): + with self.assertRaises(expected_exception=TypeError) as ex: + self.client.get(instance=self.name, zone=0) + self.assertIn( + "0 has type int, but expected one of: bytes, unicode", + str(ex.exception.args), + ) + + def get_instance(self): + return self.client.get( + project=self.DEFAULT_PROJECT, zone=self.DEFAULT_ZONE, instance=self.name + ) + + def assert_instance(self): + instance = self.get_instance() + self.assertEqual(getattr(instance, "name"), self.name) + self.assertEqual(len(getattr(instance, "network_interfaces")), 1) + self.assertEqual(len(getattr(instance, "disks")), 1) + + def test_patch(self): + self.insert_instance() + instance = self.get_instance() + self.assertEqual(instance.shielded_instance_config.enable_secure_boot, False) + op = self.client.stop( + project=self.DEFAULT_PROJECT, zone=self.DEFAULT_ZONE, instance=self.name + ) + self.wait_for_zonal_operation(op.name) + + timeout = time.time() + 120 # it takes some time for instance to stop + while True: + if time.time() > timeout: + self.fail("Instance was not stopped") + instance = self.get_instance() + if instance.status == Instance.Status.TERMINATED: + break + else: + time.sleep(10) + + resource = ShieldedInstanceConfig() + resource.enable_secure_boot = True + op = self.client.update_shielded_instance_config( + project=self.DEFAULT_PROJECT, + zone=self.DEFAULT_ZONE, + instance=self.name, + shielded_instance_config_resource=resource, + ) + self.wait_for_zonal_operation(op.name) + patched_instance = self.get_instance() + self.assertEqual( + patched_instance.shielded_instance_config.enable_secure_boot, True + ) + + def test_list(self): + presented = False + self.insert_instance() + result = self.client.list(project=self.DEFAULT_PROJECT, zone=self.DEFAULT_ZONE) + for item in result: + if getattr(item, "name") == self.name: + presented = True + break + self.assertTrue(presented)