Hello community,

here is the log from the commit of package python3-img-proof for 
openSUSE:Leap:15.2 checked in at 2020-04-25 19:03:44
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Leap:15.2/python3-img-proof (Old)
 and      /work/SRC/openSUSE:Leap:15.2/.python3-img-proof.new.2738 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python3-img-proof"

Sat Apr 25 19:03:44 2020 rev:2 rq:796635 version:5.0.0

Changes:
--------
--- /work/SRC/openSUSE:Leap:15.2/python3-img-proof/python3-img-proof.changes    
2020-03-31 07:23:05.658416894 +0200
+++ 
/work/SRC/openSUSE:Leap:15.2/.python3-img-proof.new.2738/python3-img-proof.changes
  2020-04-25 19:03:45.415424488 +0200
@@ -1,0 +2,6 @@
+Tue Apr 21 22:00:30 UTC 2020 - Sean Marlow <sean.mar...@suse.com>
+
+- Update to v5.0.0 (2020-04-21)
+  + Migrate GCE to Google API.
+
+-------------------------------------------------------------------

Old:
----
  img-proof-4.8.1.tar.gz

New:
----
  img-proof-5.0.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python3-img-proof.spec ++++++
--- /var/tmp/diff_new_pack.aKc6nk/_old  2020-04-25 19:03:45.711425124 +0200
+++ /var/tmp/diff_new_pack.aKc6nk/_new  2020-04-25 19:03:45.711425124 +0200
@@ -18,7 +18,7 @@
 
 %bcond_without test
 Name:           python3-img-proof
-Version:        4.8.1
+Version:        5.0.0
 Release:        0
 Summary:        Command line and API for testing custom images
 License:        GPL-3.0-or-later
@@ -30,7 +30,6 @@
 BuildRequires:  python3-devel
 BuildRequires:  python3-setuptools
 Requires:       python3-PyYAML
-Requires:       python3-apache-libcloud
 Requires:       python3-azure-common
 Requires:       python3-azure-mgmt-compute
 Requires:       python3-azure-mgmt-network
@@ -39,16 +38,16 @@
 Requires:       python3-certifi
 Requires:       python3-click
 Requires:       python3-cryptography
+Requires:       python3-google-api-python-client
+Requires:       python3-google-auth
 Requires:       python3-oci-sdk
 Requires:       python3-paramiko
 Requires:       python3-pycryptodome
 Requires:       python3-pytest
 Requires:       python3-testinfra
-Obsoletes:      python3-ipa < 4.8.1
 BuildArch:      noarch
 %if %{with test}
 BuildRequires:  python3-PyYAML
-BuildRequires:  python3-apache-libcloud
 BuildRequires:  python3-azure-common
 BuildRequires:  python3-azure-mgmt-compute
 BuildRequires:  python3-azure-mgmt-network
@@ -57,6 +56,8 @@
 BuildRequires:  python3-certifi
 BuildRequires:  python3-coverage
 BuildRequires:  python3-cryptography
+BuildRequires:  python3-google-api-python-client
+BuildRequires:  python3-google-auth
 BuildRequires:  python3-oci-sdk
 BuildRequires:  python3-paramiko
 BuildRequires:  python3-pycryptodome
@@ -64,6 +65,7 @@
 BuildRequires:  python3-pytest-cov
 BuildRequires:  python3-testinfra
 %endif
+Obsoletes:      python3-ipa < 5.0.0
 
 %description
 img-proof provides a command line utility to test images in
@@ -74,7 +76,7 @@
 Group:          Development/Languages/Python
 Requires:       python3-susepubliccloudinfo
 PreReq:         python3-img-proof = %{version}
-Obsoletes:      python3-ipa-tests < 4.8.1
+Obsoletes:      python3-ipa-tests < 5.0.0
 
 %description tests
 Directory of infrastructure tests for testing images.

++++++ img-proof-4.8.1.tar.gz -> img-proof-5.0.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/img-proof-4.8.1/CHANGES.md 
new/img-proof-5.0.0/CHANGES.md
--- old/img-proof-4.8.1/CHANGES.md      2020-03-10 22:30:44.000000000 +0100
+++ new/img-proof-5.0.0/CHANGES.md      2020-04-21 23:50:16.000000000 +0200
@@ -1,3 +1,9 @@
+v5.0.0 (2020-04-21)
+===================
+
+- Migrate GCE to Google API.
+  [\#242](https://github.com/SUSE-Enceladus/ipa/pull/242)
+
 v4.8.1 (2020-03-10)
 ===================
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/img-proof-4.8.1/PKG-INFO new/img-proof-5.0.0/PKG-INFO
--- old/img-proof-4.8.1/PKG-INFO        2020-03-10 22:31:42.000000000 +0100
+++ new/img-proof-5.0.0/PKG-INFO        2020-04-21 23:51:07.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: img-proof
-Version: 4.8.1
+Version: 5.0.0
 Summary: Package for automated testing of cloud images.
 Home-page: https://github.com/SUSE-Enceladus/img-proof
 Author: SUSE
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/img-proof-4.8.1/img_proof/__init__.py 
new/img-proof-5.0.0/img_proof/__init__.py
--- old/img-proof-4.8.1/img_proof/__init__.py   2020-03-10 22:30:44.000000000 
+0100
+++ new/img-proof-5.0.0/img_proof/__init__.py   2020-04-21 23:50:16.000000000 
+0200
@@ -22,4 +22,4 @@
 
 __author__ = """SUSE"""
 __email__ = 'public-cloud-...@susecloud.net'
-__version__ = '4.8.1'
+__version__ = '5.0.0'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/img-proof-4.8.1/img_proof/ipa_azure.py 
new/img-proof-5.0.0/img_proof/ipa_azure.py
--- old/img-proof-4.8.1/img_proof/ipa_azure.py  2020-03-10 22:30:44.000000000 
+0100
+++ new/img-proof-5.0.0/img_proof/ipa_azure.py  2020-04-21 23:50:16.000000000 
+0200
@@ -63,7 +63,9 @@
         timeout=None,
         vnet_name=None,
         vnet_resource_group=None,
-        collect_vm_info=None
+        collect_vm_info=None,
+        enable_secure_boot=None,
+        enable_uefi=None
     ):
         """Initialize Azure Cloud class."""
         super(AzureCloud, self).__init__(
@@ -89,7 +91,9 @@
             collect_vm_info,
             ssh_private_key_file,
             ssh_user,
-            subnet_id
+            subnet_id,
+            enable_secure_boot,
+            enable_uefi
         )
 
         self.vnet_name = vnet_name or self.ipa_config['vnet_name']
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/img-proof-4.8.1/img_proof/ipa_cloud.py 
new/img-proof-5.0.0/img_proof/ipa_cloud.py
--- old/img-proof-4.8.1/img_proof/ipa_cloud.py  2020-03-10 22:30:44.000000000 
+0100
+++ new/img-proof-5.0.0/img_proof/ipa_cloud.py  2020-04-21 23:50:16.000000000 
+0200
@@ -45,9 +45,7 @@
 from img_proof.ipa_exceptions import (
     IpaException,
     IpaCloudException,
-    IpaSSHException,
-    IpaRetryableError,
-    GCECloudRetryableError
+    IpaSSHException
 )
 from img_proof.results_plugin import Report
 
@@ -95,7 +93,9 @@
         collect_vm_info=None,
         ssh_private_key_file=None,
         ssh_user=None,
-        subnet_id=None
+        subnet_id=None,
+        enable_secure_boot=None,
+        enable_uefi=None
     ):
         """Initialize base cloud framework class."""
         super(IpaCloud, self).__init__()
@@ -157,6 +157,11 @@
         self.ssh_private_key_file = self.ipa_config['ssh_private_key_file']
         self.ssh_user = self.ipa_config['ssh_user']
         self.subnet_id = self.ipa_config['subnet_id']
+        self.enable_secure_boot = self.ipa_config['enable_secure_boot']
+        self.enable_uefi = self.ipa_config['enable_uefi']
+
+        if self.enable_secure_boot and not self.enable_uefi:
+            self.enable_uefi = True
 
         if self.cloud_config:
             self.cloud_config = os.path.expanduser(self.cloud_config)
@@ -668,19 +673,11 @@
             self.logger.info('Launching new instance')
             try:
                 self._launch_instance()
-            except GCECloudRetryableError as error:
-                with ipa_utils.ignored(Exception):
-                    self._cleanup_instance(1)
-
-                msg = 'Unable to connect to instance: %s' % error
-                self.logger.error(msg)
-                raise IpaRetryableError(msg)
             except Exception as error:
                 with ipa_utils.ignored(Exception):
                     self._cleanup_instance(1)
 
-                msg = 'Unable to connect to instance: %s' % error
-                self.logger.error(msg)
+                self.logger.error(error)
                 raise
 
         if not self.instance_ip:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/img-proof-4.8.1/img_proof/ipa_controller.py 
new/img-proof-5.0.0/img_proof/ipa_controller.py
--- old/img-proof-4.8.1/img_proof/ipa_controller.py     2020-03-10 
22:30:44.000000000 +0100
+++ new/img-proof-5.0.0/img_proof/ipa_controller.py     2020-04-21 
23:50:16.000000000 +0200
@@ -48,6 +48,7 @@
     early_exit=None,
     history_log=None,
     image_id=None,
+    image_project=None,
     inject=None,
     instance_type=None,
     ip_address=None,
@@ -75,7 +76,9 @@
     signing_key_fingerprint=None,
     signing_key_file=None,
     tenancy=None,
-    oci_user_id=None
+    oci_user_id=None,
+    enable_secure_boot=None,
+    enable_uefi=None
 ):
     """Creates a cloud framework instance and initiates testing."""
     kwargs = {
@@ -100,7 +103,9 @@
         'test_dirs': test_dirs,
         'test_files': tests,
         'timeout': timeout,
-        'collect_vm_info': collect_vm_info
+        'collect_vm_info': collect_vm_info,
+        'enable_secure_boot': enable_secure_boot,
+        'enable_uefi': enable_uefi
     }
 
     cloud_name = cloud_name.lower()
@@ -124,6 +129,7 @@
     elif cloud_name == 'gce':
         cloud = GCECloud(
             service_account_file=service_account_file,
+            image_project=image_project,
             **kwargs
         )
     elif cloud_name == 'ssh':
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/img-proof-4.8.1/img_proof/ipa_ec2.py 
new/img-proof-5.0.0/img_proof/ipa_ec2.py
--- old/img-proof-4.8.1/img_proof/ipa_ec2.py    2020-03-10 22:30:44.000000000 
+0100
+++ new/img-proof-5.0.0/img_proof/ipa_ec2.py    2020-04-21 23:50:16.000000000 
+0200
@@ -67,7 +67,9 @@
         test_dirs=None,
         test_files=None,
         timeout=None,
-        collect_vm_info=None
+        collect_vm_info=None,
+        enable_secure_boot=None,
+        enable_uefi=None
     ):
         """Initialize EC2 cloud framework class."""
         super(EC2Cloud, self).__init__(
@@ -93,7 +95,9 @@
             collect_vm_info,
             ssh_private_key_file,
             ssh_user,
-            subnet_id
+            subnet_id,
+            enable_secure_boot,
+            enable_uefi
         )
         # Get command line values that are not None
         cmd_line_values = self._get_non_null_values(locals())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/img-proof-4.8.1/img_proof/ipa_exceptions.py 
new/img-proof-5.0.0/img_proof/ipa_exceptions.py
--- old/img-proof-4.8.1/img_proof/ipa_exceptions.py     2020-03-10 
22:30:44.000000000 +0100
+++ new/img-proof-5.0.0/img_proof/ipa_exceptions.py     2020-04-21 
23:50:16.000000000 +0200
@@ -41,10 +41,6 @@
     """Generic GCE exception."""
 
 
-class GCECloudRetryableError(GCECloudException):
-    """GCE retryable error exception."""
-
-
 class OCICloudException(IpaCloudException):
     """Generic OCI exception."""
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/img-proof-4.8.1/img_proof/ipa_gce.py 
new/img-proof-5.0.0/img_proof/ipa_gce.py
--- old/img-proof-4.8.1/img_proof/ipa_gce.py    2020-03-10 22:30:44.000000000 
+0100
+++ new/img-proof-5.0.0/img_proof/ipa_gce.py    2020-04-21 23:50:16.000000000 
+0200
@@ -2,7 +2,7 @@
 
 """Cloud framework module for testing Google Compute Engine (GCE) images."""
 
-# Copyright (c) 2019 SUSE LLC. All rights reserved.
+# Copyright (c) 2020 SUSE LLC. All rights reserved.
 #
 # This file is part of img_proof. img_proof provides an api and command line
 # utilities for testing images in the Public Cloud.
@@ -22,21 +22,59 @@
 
 import json
 import os
+import time
+
+from contextlib import contextmanager, suppress
 
 from img_proof import ipa_utils
 from img_proof.ipa_constants import (
     GCE_DEFAULT_TYPE,
     GCE_DEFAULT_USER
 )
-from img_proof.ipa_exceptions import GCECloudException
-from img_proof.ipa_exceptions import GCECloudRetryableError
+from img_proof.ipa_exceptions import GCECloudException, IpaRetryableError
 from img_proof.ipa_cloud import IpaCloud
 
-from libcloud.common.google import ResourceNotFoundError
-from libcloud.common.google import GoogleBaseError
-from libcloud.common.google import QuotaExceededError
-from libcloud.compute.types import Provider
-from libcloud.compute.providers import get_driver
+from google.oauth2 import service_account
+from googleapiclient import discovery
+
+
+def get_message_from_http_error(error, resource_name):
+    """
+    Attempt to parse error message from json.
+
+    If there is an error getting the message content
+    use the default of `resource not found`.
+    """
+    with suppress(AttributeError):
+        # In python 3.5 content is bytes
+        error.content = error.content.decode()
+
+    try:
+        message = json.loads(error.content)['error']['message']
+    except (AttributeError, KeyError):
+        message = 'Resource {resource_name} not found.'.format(
+            resource_name=resource_name
+        )
+
+    return message
+
+
+@contextmanager
+def handle_gce_http_errors(type_name, resource_name):
+    """
+    Context manager to handle GCE HTTP Errors.
+    """
+    try:
+        yield
+    except Exception as error:
+        message = get_message_from_http_error(error, resource_name)
+
+        raise GCECloudException(
+            'Unable to retrieve {type_name}: {error}'.format(
+                type_name=type_name,
+                error=message
+            )
+        ) from error
 
 
 class GCECloud(IpaCloud):
@@ -68,7 +106,10 @@
         test_dirs=None,
         test_files=None,
         timeout=None,
-        collect_vm_info=None
+        collect_vm_info=None,
+        image_project=None,
+        enable_secure_boot=None,
+        enable_uefi=None
     ):
         super(GCECloud, self).__init__(
             'gce',
@@ -93,7 +134,9 @@
             collect_vm_info,
             ssh_private_key_file,
             ssh_user,
-            subnet_id
+            subnet_id,
+            enable_secure_boot,
+            enable_uefi
         )
 
         self.service_account_file = (
@@ -116,14 +159,15 @@
 
         self.ssh_user = self.ssh_user or GCE_DEFAULT_USER
         self.ssh_public_key = self._get_ssh_public_key()
+        self.image_project = image_project
 
-        self._get_service_account_info()
+        self.credentials = self._get_credentials()
         self.compute_driver = self._get_driver()
 
         self._validate_region()
 
-    def _get_service_account_info(self):
-        """Retrieve json dict from service account file."""
+    def _get_credentials(self):
+        """Retrieve credentials object using service account file."""
         with open(self.service_account_file, 'r') as f:
             info = json.load(f)
 
@@ -143,28 +187,27 @@
                 'docs for information on GCE configuration.'
             )
 
+        return service_account.Credentials.from_service_account_file(
+            self.service_account_file
+        )
+
     def _get_driver(self):
         """Get authenticated GCE driver."""
-        ComputeEngine = get_driver(Provider.GCE)
-        return ComputeEngine(
-            self.service_account_email,
-            self.service_account_file,
-            project=self.service_account_project
+        return discovery.build(
+            'compute',
+            'v1',
+            credentials=self.credentials,
+            cache_discovery=False
         )
 
     def _get_instance(self):
         """Retrieve instance matching instance_id."""
-        try:
-            instance = self.compute_driver.ex_get_node(
-                self.running_instance_id,
-                zone=self.region
-            )
-        except ResourceNotFoundError as e:
-            raise GCECloudException(
-                'Instance with id: {id} cannot be found: {error}'.format(
-                    id=self.running_instance_id, error=e
-                )
-            )
+        with handle_gce_http_errors('instance', self.running_instance_id):
+            instance = self.compute_driver.instances().get(
+                project=self.service_account_project,
+                zone=self.region,
+                instance=self.running_instance_id
+            ).execute()
 
         return instance
 
@@ -176,92 +219,258 @@
             key=key.decode()
         )
 
+    def _get_network(self, network_id):
+        """
+        Return the network by network id (name).
+
+        If network not found GCE will raise a 404 error.
+        """
+        with handle_gce_http_errors('network', network_id):
+            network = self.compute_driver.networks().get(
+                project=self.service_account_project,
+                network=network_id
+            ).execute()
+
+        return network
+
     def _get_subnet(self, subnet_id):
-        subnet = None
-        try:
+        """
+        Return the subnet by subnet id (name).
+
+        If subnet not found GCE will raise a 404 error.
+        """
+        with handle_gce_http_errors('subnet', subnet_id):
             # Subnet lives in a region whereas self.region
             # is a specific zone (us-west1-a).
             region = '-'.join(self.region.split('-')[:-1])
-            subnet = self.compute_driver.ex_get_subnetwork(
-                subnet_id, region=region
-            )
-        except Exception:
-            raise GCECloudException(
-                'GCE subnet: {subnet_id} not found.'.format(
-                    subnet_id=subnet_id
-                )
-            )
+            subnet = self.compute_driver.subnetworks().get(
+                project=self.service_account_project,
+                region=region,
+                subnetwork=subnet_id
+            ).execute()
 
         return subnet
 
+    def _get_instance_type(self, type_name):
+        """
+        Return the instance type by name.
+
+        If type not found GCE will raise a 404 error.
+        """
+        with handle_gce_http_errors('instance type', type_name):
+            machine_type = self.compute_driver.machineTypes().get(
+                project=self.service_account_project,
+                zone=self.region,
+                machineType=type_name
+            ).execute()
+
+        return machine_type
+
+    def _get_image(self, image_name):
+        """
+        Return the image by image name.
+
+        If image is not found GCE will raise a 404 error.
+        """
+        with handle_gce_http_errors('image', image_name):
+            image = self.compute_driver.images().get(
+                project=self.image_project or self.service_account_project,
+                image=image_name
+            ).execute()
+
+        return image
+
+    def _get_disk(self, disk_name):
+        """
+        Return the disk by name.
+
+        If disk is not found GCE will raise a 404 error.
+        """
+        with handle_gce_http_errors('disk', disk_name):
+            disk = self.compute_driver.disks().get(
+                project=self.service_account_project,
+                zone=self.region,
+                disk=disk_name
+            ).execute()
+
+        return disk
+
+    def _get_network_config(self, subnet_id):
+        """
+        Return the network config.
+
+        If a subnet_id is provided use the subnet and
+        network. Otherwise use the default network.
+        """
+        interface = {
+            'accessConfigs': [{
+                'name': 'External NAT',
+                'type': 'ONE_TO_ONE_NAT'
+            }]
+        }
+
+        if subnet_id:
+            subnet = self._get_subnet(subnet_id)
+            interface['subnetwork'] = subnet['selfLink']
+            interface['network'] = subnet['network']
+        else:
+            interface['network'] = self._get_network('default')['selfLink']
+
+        return interface
+
+    @staticmethod
+    def get_shielded_instance_config(
+        enable_secure_boot=False,
+        enable_vtpm=True,
+        enable_integrity_monitoring=True
+    ):
+        """
+        Return shielded instance config object.
+
+        Return with default values unless overridden by args.
+        """
+        shielded_instance_config = {
+            'enableSecureBoot': enable_secure_boot,
+            'enableVtpm': enable_vtpm,
+            'enableIntegrityMonitoring': enable_integrity_monitoring
+        }
+
+        return shielded_instance_config
+
+    @staticmethod
+    def get_instance_config(
+        instance_name,
+        machine_type,
+        network_interfaces,
+        service_account_email,
+        source_image,
+        ssh_key,
+        auto_delete=True,
+        boot_disk=True,
+        disk_type='PERSISTENT',
+        disk_mode='READ_WRITE',
+        shielded_instance_config=None,
+    ):
+        """Return an instance config for launching a new instance."""
+        config = {
+            'metadata': {
+                'items': [{'key': 'ssh-keys', 'value': ssh_key}]
+            },
+            'service_accounts': [{
+                'email': service_account_email,
+                'scopes': ['storage-ro']
+            }],
+            'machineType': machine_type,
+            'disks': [{
+                'autoDelete': auto_delete,
+                'boot': boot_disk,
+                'type': disk_type,
+                'mode': disk_mode,
+                'deviceName': instance_name,
+                'initializeParams': {
+                    'diskName': instance_name,
+                    'sourceImage': source_image
+                }
+            }],
+            'networkInterfaces': network_interfaces,
+            'name': instance_name
+        }
+
+        if shielded_instance_config:
+            config['shieldedInstanceConfig'] = shielded_instance_config
+            config['disks'][0]['guestOsFeatures'] = [{
+                'type': 'UEFI_COMPATIBLE'
+            }]
+
+        return config
+
     def _launch_instance(self):
         """Launch an instance of the given image."""
-        metadata = {'key': 'ssh-keys', 'value': self.ssh_public_key}
         self.running_instance_id = ipa_utils.generate_instance_name(
             'gce-img-proof-test'
         )
         self.logger.debug('ID of instance: %s' % self.running_instance_id)
 
+        machine_type = self._get_instance_type(
+            self.instance_type or GCE_DEFAULT_TYPE
+        )['selfLink']
+        source_image = self._get_image(self.image_id)['selfLink']
+        network_interfaces = [self._get_network_config(self.subnet_id)]
+
         kwargs = {
-            'location': self.region,
-            'ex_metadata': metadata,
-            'ex_service_accounts': [{
-                'email': self.service_account_email,
-                'scopes': ['storage-ro']
-            }]
+            'instance_name': self.running_instance_id,
+            'machine_type': machine_type,
+            'service_account_email': self.service_account_email,
+            'source_image': source_image,
+            'ssh_key': self.ssh_public_key,
+            'network_interfaces': network_interfaces
         }
 
-        if self.subnet_id:
-            kwargs['ex_subnetwork'] = self._get_subnet(self.subnet_id)
-            kwargs['ex_network'] = kwargs['ex_subnetwork'].network
+        if self.enable_uefi:
+            kwargs['shielded_instance_config'] = \
+                self.get_shielded_instance_config(
+                    enable_secure_boot=self.enable_secure_boot
+                )
 
         try:
-            instance = self.compute_driver.create_node(
-                self.running_instance_id,
-                self.instance_type or GCE_DEFAULT_TYPE,
-                self.image_id,
-                **kwargs
-            )
-        except ResourceNotFoundError as error:
+            response = self.compute_driver.instances().insert(
+                project=self.service_account_project,
+                zone=self.region,
+                body=self.get_instance_config(**kwargs)
+            ).execute()
+        except Exception as error:
+            with suppress(AttributeError):
+                # In python 3.5 content is bytes
+                error.content = error.content.decode()
+
+            error_obj = json.loads(error.content)['error']
+
             try:
-                message = error.value['message']
-            except TypeError:
-                message = error
+                message = error_obj['message']
+            except (AttributeError, KeyError):
+                message = 'Unknown exception.'
+
+            if error_obj['code'] == 412:
+                # 412 is conditionNotmet
+                error_class = IpaRetryableError
+            else:
+                error_class = GCECloudException
 
-            raise GCECloudException(
-                'An error occurred launching instance: {message}.'.format(
+            raise error_class(
+                'Failed to launch instance: {message}'.format(
                     message=message
                 )
-            )
-        except QuotaExceededError as error:
-            raise GCECloudRetryableError(
-                'An error occurred launching instance: {message}.'.format(
-                    message=error.value['message']
-                )
-            )
-        except GoogleBaseError as error:
-            if error.value['reason'] in ['quotaExceeded', 'conditionNotMet']:
-                raise GCECloudRetryableError(
-                    'An error occurred launching instance: {message}.'.format(
-                        message=error.value['message']
-                    )
-                )
+            ) from error
+
+        operation = self._wait_on_operation(response['name'])
+
+        if 'error' in operation and operation['error'].get('errors'):
+            error = operation['error']['errors'][0]
+
+            if error['code'] in ('QUOTA_EXCEEDED', 'PRECONDITION_FAILED'):
+                error_class = IpaRetryableError
             else:
-                raise GCECloudException(
-                    'An error occurred launching instance: {message}.'.format(
-                        message=error.value['message']
-                    )
+                error_class = GCECloudException
+
+            raise error_class(
+                'Failed to launch instance: {message}'.format(
+                    message=error['message']
                 )
+            )
 
-        self.compute_driver.wait_until_running(
-            [instance],
+        self._wait_on_instance(
+            'RUNNING',
             timeout=self.timeout
         )
 
     def _set_image_id(self):
-        """If existing image used get image id."""
+        """Set the image_id instance variable based on boot disk."""
         instance = self._get_instance()
-        self.image_id = instance.image
+        disk = self._get_disk(instance['disks'][0]['deviceName'])
+
+        # Example sourceImage format:
+        # projects/debian-cloud/global/images/opensuse-leap-15.0-YYYYMMDD
+        self.image_id = disk['sourceImage'].rsplit('/', maxsplit=1)[-1]
 
     def _validate_region(self):
         """Validate region was passed in and is a valid GCE zone."""
@@ -272,7 +481,10 @@
             )
 
         try:
-            zone = self.compute_driver.ex_get_zone(self.region)
+            zone = self.compute_driver.zones().get(
+                project=self.service_account_project,
+                zone=self.region
+            ).execute()
         except Exception:
             zone = None
 
@@ -287,50 +499,87 @@
     def _get_instance_state(self):
         """Attempt to retrieve the state of the instance."""
         instance = self._get_instance()
-        return instance.state
+        return instance['status']
 
     def _is_instance_running(self):
         """Return True if instance is in running state."""
-        return self._get_instance_state() == 'running'
+        return self._get_instance_state() == 'RUNNING'
 
     def _set_instance_ip(self):
         """Retrieve and set the instance ip address."""
         instance = self._get_instance()
 
-        if instance.public_ips:
-            self.instance_ip = instance.public_ips[0]
-        elif instance.private_ips:
-            self.instance_ip = instance.private_ips[0]
-        else:
-            raise GCECloudException(
-                'IP address for instance: %s cannot be found.'
-                % self.running_instance_id
-            )
+        interface = instance['networkInterfaces'][0]
+        try:
+            self.instance_ip = interface['accessConfigs'][0]['natIP']
+        except (KeyError, IndexError):
+            try:
+                self.instance_ip = interface['networkIP']
+            except KeyError:
+                raise GCECloudException(
+                    'IP address for instance: %s cannot be found.'
+                    % self.running_instance_id
+                )
 
     def _start_instance(self):
         """Start the instance."""
-        instance = self._get_instance()
-        self.compute_driver.ex_start_node(instance)
-        self.compute_driver.wait_until_running(
-            [instance],
+        self.compute_driver.instances().start(
+            project=self.service_account_project,
+            zone=self.region,
+            instance=self.running_instance_id
+        ).execute()
+
+        self._wait_on_instance(
+            'RUNNING',
             timeout=self.timeout
         )
 
     def _stop_instance(self):
         """Stop the instance."""
-        instance = self._get_instance()
-        self.compute_driver.ex_stop_node(instance)
-        self._wait_on_instance('stopped', timeout=self.timeout)
+        self.compute_driver.instances().stop(
+            project=self.service_account_project,
+            zone=self.region,
+            instance=self.running_instance_id
+        ).execute()
+
+        # In GCE an instance that is stopped has a state of TERMINATED:
+        # https://cloud.google.com/compute/docs/instances/instance-life-cycle
+        self._wait_on_instance(
+            'TERMINATED',
+            timeout=self.timeout
+        )
 
     def _terminate_instance(self):
         """Terminate the instance."""
-        instance = self._get_instance()
-        instance.destroy()
+        self.compute_driver.instances().delete(
+            project=self.service_account_project,
+            zone=self.region,
+            instance=self.running_instance_id
+        ).execute()
 
     def get_console_log(self):
         """
         Return console log output if it is available.
         """
-        instance = self._get_instance()
-        output = self.compute_driver.ex_get_serial_output(instance)
-        return output
+        output = self.compute_driver.instances().getSerialPortOutput(
+            project=self.service_account_project,
+            zone=self.region,
+            instance=self.running_instance_id
+        ).execute()
+        return output.get('contents', '')
+
+    def _wait_on_operation(self, operation_name, timeout=600, wait_period=10):
+        start = time.time()
+        end = start + timeout
+
+        while time.time() < end:
+            time.sleep(wait_period)
+
+            operation = self.compute_driver.zoneOperations().get(
+                project=self.service_account_project,
+                zone=self.region,
+                operation=operation_name
+            ).execute()
+
+            if operation['status'] == 'DONE':
+                return operation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/img-proof-4.8.1/img_proof/ipa_oci.py 
new/img-proof-5.0.0/img_proof/ipa_oci.py
--- old/img-proof-4.8.1/img_proof/ipa_oci.py    2020-03-10 22:30:44.000000000 
+0100
+++ new/img-proof-5.0.0/img_proof/ipa_oci.py    2020-04-21 23:50:16.000000000 
+0200
@@ -63,7 +63,9 @@
         signing_key_fingerprint=None,
         signing_key_file=None,
         tenancy=None,
-        oci_user_id=None
+        oci_user_id=None,
+        enable_secure_boot=None,
+        enable_uefi=None
     ):
         """Initialize OCI cloud framework class."""
         super(OCICloud, self).__init__(
@@ -89,7 +91,9 @@
             collect_vm_info,
             ssh_private_key_file,
             ssh_user,
-            subnet_id
+            subnet_id,
+            enable_secure_boot,
+            enable_uefi
         )
 
         self.availability_domain = (
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/img-proof-4.8.1/img_proof/scripts/cli.py 
new/img-proof-5.0.0/img_proof/scripts/cli.py
--- old/img-proof-4.8.1/img_proof/scripts/cli.py        2020-03-10 
22:30:44.000000000 +0100
+++ new/img-proof-5.0.0/img_proof/scripts/cli.py        2020-04-21 
23:50:16.000000000 +0200
@@ -137,6 +137,12 @@
     help='The ID of the image used for instance.'
 )
 @click.option(
+    '--image-project',
+    help='The image project where the image exists. This is required if '
+         'testing an image in a project different than the service account '
+         'project.'
+)
+@click.option(
     '--inject',
     help='Path to an injection yaml config file.',
     type=click.Path(exists=True)
@@ -273,6 +279,18 @@
     '--oci-user-id',
     help='The ID for the OCI user.'
 )
+@click.option(
+    '--enable-secure-boot',
+    is_flag=True,
+    help='Enable secure boot for the instance. Secure boot requires '
+         'UEFI boot firmware.'
+)
+@click.option(
+    '--enable-uefi',
+    is_flag=True,
+    help='Enable boot firmware for the instance. By default secure boot '
+         'is disabled.'
+)
 @click.argument('tests', nargs=-1)
 @click.pass_context
 def test(context,
@@ -286,6 +304,7 @@
          early_exit,
          history_log,
          image_id,
+         image_project,
          inject,
          instance_type,
          ip_address,
@@ -314,6 +333,8 @@
          signing_key_file,
          tenancy,
          oci_user_id,
+         enable_secure_boot,
+         enable_uefi,
          tests):
     """Test image in the given framework using the supplied test files."""
     no_color = context.obj['no_color']
@@ -330,6 +351,7 @@
             early_exit,
             history_log,
             image_id,
+            image_project,
             inject,
             instance_type,
             ip_address,
@@ -358,6 +380,8 @@
             signing_key_file,
             tenancy,
             oci_user_id,
+            enable_secure_boot,
+            enable_uefi
         )
         echo_results(results, no_color)
         sys.exit(status)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/img-proof-4.8.1/img_proof.egg-info/PKG-INFO 
new/img-proof-5.0.0/img_proof.egg-info/PKG-INFO
--- old/img-proof-4.8.1/img_proof.egg-info/PKG-INFO     2020-03-10 
22:31:42.000000000 +0100
+++ new/img-proof-5.0.0/img_proof.egg-info/PKG-INFO     2020-04-21 
23:51:07.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: img-proof
-Version: 4.8.1
+Version: 5.0.0
 Summary: Package for automated testing of cloud images.
 Home-page: https://github.com/SUSE-Enceladus/img-proof
 Author: SUSE
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/img-proof-4.8.1/img_proof.egg-info/requires.txt 
new/img-proof-5.0.0/img_proof.egg-info/requires.txt
--- old/img-proof-4.8.1/img_proof.egg-info/requires.txt 2020-03-10 
22:31:42.000000000 +0100
+++ new/img-proof-5.0.0/img_proof.egg-info/requires.txt 2020-04-21 
23:51:07.000000000 +0200
@@ -1,5 +1,4 @@
 boto3
-apache-libcloud
 azure-common
 azure-mgmt-compute
 azure-mgmt-network
@@ -13,6 +12,8 @@
 PyYAML
 testinfra
 oci
+google-auth
+google-api-python-client
 
 [dev]
 coverage
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/img-proof-4.8.1/package/python3-img-proof.spec 
new/img-proof-5.0.0/package/python3-img-proof.spec
--- old/img-proof-4.8.1/package/python3-img-proof.spec  2020-03-10 
22:30:44.000000000 +0100
+++ new/img-proof-5.0.0/package/python3-img-proof.spec  2020-04-21 
23:50:16.000000000 +0200
@@ -18,7 +18,7 @@
 
 %bcond_without test
 Name:           python3-img-proof
-Version:        4.8.1
+Version:        5.0.0
 Release:        0
 Summary:        Command line and API for testing custom images
 License:        GPL-3.0-or-later
@@ -31,7 +31,8 @@
 BuildRequires:  python3-click
 Requires:       python3-PyYAML
 Requires:       python3-boto3
-Requires:       python3-apache-libcloud
+Requires:       python3-google-auth
+Requires:       python3-google-api-python-client
 Requires:       python3-azure-common
 Requires:       python3-azure-mgmt-compute
 Requires:       python3-azure-mgmt-network
@@ -48,7 +49,8 @@
 %if %{with test}
 BuildRequires:  python3-PyYAML
 BuildRequires:  python3-boto3
-BuildRequires:  python3-apache-libcloud
+BuildRequires:  python3-google-auth
+BuildRequires:  python3-google-api-python-client
 BuildRequires:  python3-azure-common
 BuildRequires:  python3-azure-mgmt-compute
 BuildRequires:  python3-azure-mgmt-network
@@ -63,7 +65,7 @@
 BuildRequires:  python3-testinfra
 BuildRequires:  python3-oci-sdk
 %endif
-Obsoletes:      python3-ipa < 4.8.1
+Obsoletes:      python3-ipa < 5.0.0
 
 %description
 img-proof provides a command line utility to test images in
@@ -74,7 +76,7 @@
 Group:          Development/Languages/Python
 Requires:       python3-susepubliccloudinfo
 PreReq:         python3-img-proof = %{version}
-Obsoletes:      python3-ipa-tests < 4.8.1
+Obsoletes:      python3-ipa-tests < 5.0.0
 
 %description tests
 Directory of infrastructure tests for testing images.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/img-proof-4.8.1/requirements.txt 
new/img-proof-5.0.0/requirements.txt
--- old/img-proof-4.8.1/requirements.txt        2020-03-10 22:30:44.000000000 
+0100
+++ new/img-proof-5.0.0/requirements.txt        2020-04-21 23:50:16.000000000 
+0200
@@ -1,5 +1,4 @@
 boto3
-apache-libcloud
 azure-common
 azure-mgmt-compute
 azure-mgmt-network
@@ -13,3 +12,5 @@
 PyYAML
 testinfra
 oci
+google-auth
+google-api-python-client
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/img-proof-4.8.1/setup.cfg 
new/img-proof-5.0.0/setup.cfg
--- old/img-proof-4.8.1/setup.cfg       2020-03-10 22:31:42.000000000 +0100
+++ new/img-proof-5.0.0/setup.cfg       2020-04-21 23:51:07.000000000 +0200
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 4.8.1
+current_version = 5.0.0
 commit = True
 tag = False
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/img-proof-4.8.1/setup.py new/img-proof-5.0.0/setup.py
--- old/img-proof-4.8.1/setup.py        2020-03-10 22:30:44.000000000 +0100
+++ new/img-proof-5.0.0/setup.py        2020-04-21 23:50:16.000000000 +0200
@@ -43,7 +43,7 @@
 
 setup(
     name='img-proof',
-    version='4.8.1',
+    version='5.0.0',
     description="Package for automated testing of cloud images.",
     long_description=readme,
     long_description_content_type="text/markdown",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/img-proof-4.8.1/tests/test_ipa_gce.py 
new/img-proof-5.0.0/tests/test_ipa_gce.py
--- old/img-proof-4.8.1/tests/test_ipa_gce.py   2020-03-10 22:30:44.000000000 
+0100
+++ new/img-proof-5.0.0/tests/test_ipa_gce.py   2020-04-21 23:50:16.000000000 
+0200
@@ -21,25 +21,42 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import json
 import pytest
 
 from img_proof.ipa_gce import GCECloud
-from img_proof.ipa_exceptions import GCECloudException
+from img_proof.ipa_exceptions import GCECloudException, IpaRetryableError
 
 from unittest.mock import MagicMock, patch
 
-from libcloud.common.google import ResourceNotFoundError
+from googleapiclient.errors import HttpError
+
+
+def get_http_error(msg, status='404'):
+    resp = MagicMock()
+    resp.status = status
+
+    content = {
+        'error': {
+            'code': int(status),
+            'message': msg
+        }
+    }
+
+    return HttpError(resp, json.dumps(content).encode())
 
 
 class TestGCECloud(object):
     """Test GCE cloud class."""
 
+    @patch('img_proof.ipa_gce.service_account')
     @patch.object(GCECloud, '_validate_region')
-    @patch('libcloud.compute.drivers.gce.GCENodeDriver')
+    @patch('img_proof.ipa_gce.discovery')
     def setup(
         self,
-        mock_node_driver,
-        mock_validate_region
+        mock_discovery,
+        mock_validate_region,
+        mock_service_account
     ):
         """Set up kwargs dict."""
         self.kwargs = {
@@ -53,7 +70,11 @@
         }
 
         driver = MagicMock()
-        mock_node_driver.return_value = driver
+        mock_discovery.build.return_value = driver
+
+        service_account = MagicMock()
+        mock_service_account.Credentials.\
+            from_service_account_file.return_value = service_account
 
         self.cloud = GCECloud(**self.kwargs)
 
@@ -80,21 +101,14 @@
 
         self.kwargs['ssh_private_key_file'] = 'tests/data/ida_test'
 
-    def test_gce_get_service_account_info(self):
-        """Test get service account info method."""
-        self.cloud._get_service_account_info()
-
-        assert self.cloud.service_account_email == \
-            't...@test.iam.gserviceaccount.com'
-        assert self.cloud.service_account_project == 'test'
-
-    def test_gce_get_service_account_info_invalid(self):
-        """Test get service account info method."""
+    @patch('img_proof.ipa_gce.service_account')
+    def test_gce_get_service_account_info_invalid(self, mock_service_account):
+        """Test get credentials method with invalid service account."""
         self.cloud.service_account_file = \
             'tests/gce/invalid-service-account.json'
 
         with pytest.raises(GCECloudException) as error:
-            self.cloud._get_service_account_info()
+            self.cloud._get_credentials()
 
         msg = 'Service account JSON file is invalid for GCE. ' \
             'client_email key is expected. See getting started ' \
@@ -104,143 +118,312 @@
     def test_gce_get_instance(self):
         """Test gce get instance method."""
         instance = MagicMock()
-        self.cloud.compute_driver.ex_get_node.return_value = instance
+        instances_obj = MagicMock()
+        operation = MagicMock()
+        operation.execute.return_value = instance
+        instances_obj.get.return_value = operation
+        self.cloud.compute_driver.instances.return_value = instances_obj
 
         val = self.cloud._get_instance()
 
         assert val == instance
 
         self.cloud.running_instance_id = 'test-instance'
-        self.cloud.compute_driver.ex_get_node.side_effect = \
-            ResourceNotFoundError(
-                'Broken',
-                'test',
-                'test'
-            )
+        instances_obj.get.side_effect = get_http_error(
+            'test-instance cannot be found.'
+        )
 
         with pytest.raises(GCECloudException) as error:
             self.cloud._get_instance()
 
-        assert str(error.value) == "Instance with id: test-instance cannot" \
-            " be found: 'Broken'"
+        exc = "Unable to retrieve instance: test-instance cannot be found."
+        assert str(error.value) == exc
+
+    def test_gce_get_network(self):
+        """Test GCE get network method."""
+        network = MagicMock()
+        networks_obj = MagicMock()
+        operation = MagicMock()
+        operation.execute.return_value = network
+        networks_obj.get.return_value = operation
+        self.cloud.compute_driver.networks.return_value = networks_obj
+
+        result = self.cloud._get_network('test-network')
+
+        assert result == network
+
+        networks_obj.get.side_effect = get_http_error(
+            'Resource test-network not found.'
+        )
+
+        msg = 'Unable to retrieve network: Resource test-network not found.'
+        with pytest.raises(GCECloudException) as error:
+            self.cloud._get_network('test-network')
+
+        assert msg == str(error.value)
 
     def test_gce_get_subnet(self):
         """Test GCE get subnetwork method."""
         subnetwork = MagicMock()
-        self.cloud.compute_driver.ex_get_subnetwork.return_value = subnetwork
+        subnet_obj = MagicMock()
+        operation = MagicMock()
+        operation.execute.return_value = subnetwork
+        subnet_obj.get.return_value = operation
+        self.cloud.compute_driver.subnetworks.return_value = subnet_obj
 
         self.cloud.region = 'us-west-1a'
         result = self.cloud._get_subnet('test-subnet')
 
         assert result == subnetwork
 
-    def test_gce_get_subnet_exception(self):
-        """Test GCE get subnetwork method."""
-        self.cloud.compute_driver.ex_get_subnetwork.side_effect = Exception(
-            'Cannot find subnet!'
+        subnet_obj.get.side_effect = get_http_error(
+            'Resource test-subnet not found.'
         )
 
-        self.cloud.region = 'us-west-1a'
-
-        msg = 'GCE subnet: test-subnet not found.'
+        msg = 'Unable to retrieve subnet: Resource test-subnet not found.'
         with pytest.raises(GCECloudException) as error:
             self.cloud._get_subnet('test-subnet')
 
         assert msg == str(error.value)
 
+    def test_gce_get_instance_type(self):
+        """Test GCE get instance type method."""
+        machine_type = MagicMock()
+        machine_type_obj = MagicMock()
+        operation = MagicMock()
+        operation.execute.return_value = machine_type
+        machine_type_obj.get.return_value = operation
+        self.cloud.compute_driver.machineTypes.return_value = machine_type_obj
+
+        result = self.cloud._get_instance_type('n1-standard-1')
+        assert result == machine_type
+
+        machine_type_obj.get.side_effect = get_http_error(
+            'Resource n1-standard-1 not found.'
+        )
+
+        msg = 'Unable to retrieve instance type: ' \
+              'Resource n1-standard-1 not found.'
+        with pytest.raises(GCECloudException) as error:
+            self.cloud._get_instance_type('n1-standard-1')
+
+        assert msg == str(error.value)
+
+    def test_gce_get_image(self):
+        """Test GCE get image method."""
+        image = MagicMock()
+        image_obj = MagicMock()
+        operation = MagicMock()
+        operation.execute.return_value = image
+        image_obj.get.return_value = operation
+        self.cloud.compute_driver.images.return_value = image_obj
+
+        result = self.cloud._get_image('fake-image-20200202')
+        assert result == image
+
+        image_obj.get.side_effect = get_http_error(
+            'Resource fake-image-20200202 not found.'
+        )
+
+        msg = 'Unable to retrieve image: ' \
+              'Resource fake-image-20200202 not found.'
+        with pytest.raises(GCECloudException) as error:
+            self.cloud._get_image('fake-image-20200202')
+
+        assert msg == str(error.value)
+
+    def test_gce_get_disk(self):
+        """Test GCE get image method."""
+        disk = MagicMock()
+        disk_obj = MagicMock()
+        operation = MagicMock()
+        operation.execute.return_value = disk
+        disk_obj.get.return_value = operation
+        self.cloud.compute_driver.disks.return_value = disk_obj
+
+        result = self.cloud._get_disk('disk12')
+        assert result == disk
+
+        disk_obj.get.side_effect = get_http_error(
+            'Resource disk12 not found.'
+        )
+
+        msg = 'Unable to retrieve disk: ' \
+              'Resource disk12 not found.'
+        with pytest.raises(GCECloudException) as error:
+            self.cloud._get_disk('disk12')
+
+        assert msg == str(error.value)
+
     @patch.object(GCECloud, '_get_subnet')
+    def test_get_network_config(self, mock_get_subnet):
+        subnet = 'projects/test/regions/us-west1/subnetworks/sub-123'
+        net = 'projects/test/global/networks/network'
+
+        mock_get_subnet.return_value = {
+            'selfLink': subnet,
+            'network': net
+        }
+
+        subnet_config = self.cloud._get_network_config('sub-123')
+
+        assert subnet_config['network'] == net
+        assert subnet_config['subnetwork'] == subnet
+
+    def test_get_shielded_instance_config(self):
+        si_config = self.cloud.get_shielded_instance_config()
+
+        assert si_config['enableSecureBoot'] is False
+        assert si_config['enableVtpm']
+        assert si_config['enableIntegrityMonitoring']
+
+    def test_get_instance_config(self):
+        config = self.cloud.get_instance_config(
+            'instance123',
+            'n1-standard-1',
+            [{}],
+            'service-account-...@email.com',
+            'image123',
+            'secretkey',
+            shielded_instance_config={'shielded': 'config'}
+        )
+
+        assert 'metadata' in config
+        assert 'service_accounts' in config
+        assert 'machineType' in config
+        assert 'disks' in config
+        assert 'networkInterfaces' in config
+        assert 'name' in config
+        assert 'shieldedInstanceConfig' in config
+
+    @patch.object(GCECloud, '_wait_on_instance')
+    @patch.object(GCECloud, '_wait_on_operation')
+    @patch.object(GCECloud, '_get_network')
+    @patch.object(GCECloud, '_get_image')
+    @patch.object(GCECloud, '_get_instance_type')
     @patch('img_proof.ipa_utils.generate_instance_name')
     def test_gce_launch_instance(
         self,
         mock_generate_instance_name,
-        mock_get_subnet
+        mock_get_instance_type,
+        mock_get_image,
+        mock_get_network,
+        mock_wait_on_operation,
+        mock_wait_on_instance
     ):
         """Test GCE launch instance method."""
-        instance = MagicMock()
-        self.cloud.compute_driver.create_node.return_value = instance
-        self.cloud.compute_driver.wait_until_running.return_value = None
         mock_generate_instance_name.return_value = 'test-instance'
+        mock_get_network.return_value = {
+            'selfLink': 'projects/test/global/networks/net1'
+        }
+        mock_get_image.return_value = {
+            'selfLink': 'projects/test/global/images/img-123'
+        }
+        mock_get_instance_type.return_value = {
+            'selfLink': 'zones/us-west1-a/machineTypes/n1-standard-1'
+        }
+        mock_wait_on_operation.return_value = {}
 
-        self.cloud.region = 'us-west1-a'
-        self.cloud.subnet_id = 'test-subnet'
+        instances_obj = MagicMock()
+        operation = MagicMock()
+        operation.execute.return_value = {'name': 'operation123'}
+        instances_obj.insert.return_value = operation
+        self.cloud.compute_driver.instances.return_value = instances_obj
 
-        subnet = MagicMock()
-        network = MagicMock()
-        subnet.network = network
-        mock_get_subnet.return_value = subnet
+        self.cloud.region = 'us-west1-a'
 
         self.cloud._launch_instance()
 
         assert self.cloud.running_instance_id == 'test-instance'
+        assert mock_wait_on_instance.call_count == 1
+
+        # Exception on operation
+
+        mock_wait_on_operation.return_value = {
+            'error': {
+                'errors': [{
+                    'code': 'QUOTA_EXCEEDED',
+                    'message': 'Too many cpus.'
+                }]
+            }
+        }
+
+        with pytest.raises(IpaRetryableError) as error:
+            self.cloud._launch_instance()
+
+        assert 'Failed to launch instance: Too many cpus.' == str(error.value)
+
+        # Exception on API call
 
+        mock_wait_on_operation.return_value = {}
+        instances_obj.insert.side_effect = get_http_error(
+            'Invalid instance type.',
+            '412'
+        )
+
+        with pytest.raises(IpaRetryableError) as error:
+            self.cloud._launch_instance()
+
+        msg = 'Failed to launch instance: Invalid instance type.'
+        assert msg == str(error.value)
+
+    @patch.object(GCECloud, '_get_disk')
     @patch.object(GCECloud, '_get_instance')
-    def test_gce_set_image_id(self, mock_get_instance):
+    def test_gce_set_image_id(self, mock_get_instance, mock_get_disk):
         """Test gce cloud set image id method."""
-        instance = MagicMock()
-        instance.image = 'test-image'
+        instance = {
+            'disks': [{'deviceName': 'disk123'}]
+        }
+        disk = {
+            'sourceImage': 'projects/suse/global/images/opensuse-leap-15.0'
+        }
         mock_get_instance.return_value = instance
+        mock_get_disk.return_value = disk
 
         self.cloud._set_image_id()
 
-        assert self.cloud.image_id == instance.image
+        assert self.cloud.image_id == 'opensuse-leap-15.0'
         assert mock_get_instance.call_count == 1
+        assert mock_get_disk.call_count == 1
 
-    @patch.object(GCECloud, '_get_driver')
-    def test_gce_validate_region(self, mock_get_driver):
+    def test_gce_validate_region(self):
         """Test gce cloud set image id method."""
-        driver = MagicMock()
-        driver.ex_get_zone.return_value = None
-        mock_get_driver.return_value = driver
+        zones_obj = MagicMock()
+        operation = MagicMock()
+        operation.execute.return_value = None
+        zones_obj.get.return_value = operation
+        self.cloud.compute_driver.zones.return_value = zones_obj
 
         with pytest.raises(GCECloudException) as error:
-            GCECloud(**self.kwargs)
+            self.cloud._validate_region()
 
         assert str(error.value) == \
             'Zone is required for GCE cloud framework: Example: us-west1-a'
 
-        self.kwargs['region'] = 'fake'
+        self.cloud.region = 'fake'
 
         with pytest.raises(GCECloudException) as error:
-            GCECloud(**self.kwargs)
-
-        driver.ex_get_zone.assert_called_once_with('fake')
+            self.cloud._validate_region()
 
         assert str(error.value) == \
             'fake is not a valid GCE zone. Example: us-west1-a'
 
     @patch.object(GCECloud, '_get_instance')
-    def test_gce_get_instance_state(self, mock_get_instance):
-        """Test gce get instance method."""
-        instance = MagicMock()
-        instance.state = 'running'
-        mock_get_instance.return_value = instance
-
-        val = self.cloud._get_instance_state()
-
-        assert val == 'running'
-        assert mock_get_instance.call_count == 1
-
-    @patch.object(GCECloud, '_get_instance_state')
-    def test_gce_is_instance_running(self, mock_get_instance_state):
+    def test_gce_is_instance_running(self, mock_get_instance):
         """Test gce cloud is instance runnning method."""
-        mock_get_instance_state.return_value = 'running'
-
+        mock_get_instance.return_value = {'status': 'RUNNING'}
         assert self.cloud._is_instance_running()
-        assert mock_get_instance_state.call_count == 1
-
-        mock_get_instance_state.return_value = 'stopped'
-        mock_get_instance_state.reset_mock()
+        assert mock_get_instance.call_count == 1
 
+        mock_get_instance.return_value = {'status': 'TERMINATED'}
         assert not self.cloud._is_instance_running()
-        assert mock_get_instance_state.call_count == 1
 
     @patch.object(GCECloud, '_get_instance')
     def test_gce_set_instance_ip(self, mock_get_instance):
         """Test gce cloud set instance ip method."""
-        instance = MagicMock()
-        instance.public_ips = []
-        instance.private_ips = []
-        mock_get_instance.return_value = instance
+        mock_get_instance.return_value = {
+            'networkInterfaces': [{'some': 'data'}]
+        }
 
         self.cloud.running_instance_id = 'test'
 
@@ -251,63 +434,79 @@
             'IP address for instance: test cannot be found.'
         assert mock_get_instance.call_count == 1
 
-        mock_get_instance.reset_mock()
-
-        instance.public_ips = ['127.0.0.1']
+        mock_get_instance.return_value = {
+            'networkInterfaces': [{'networkIP': '10.0.0.0'}]
+        }
         self.cloud._set_instance_ip()
 
-        assert self.cloud.instance_ip == '127.0.0.1'
-        assert mock_get_instance.call_count == 1
+        assert self.cloud.instance_ip == '10.0.0.0'
 
-    @patch.object(GCECloud, '_get_instance')
-    def test_gce_start_instance(self, mock_get_instance):
+    @patch.object(GCECloud, '_wait_on_instance')
+    def test_gce_start_instance(self, mock_wait_on_instance):
         """Test gce start instance method."""
-        instance = MagicMock()
-        mock_get_instance.return_value = instance
-        self.cloud.compute_driver.ex_start_node.return_value = None
-        self.cloud.compute_driver.wait_until_running.return_value = None
+        mock_wait_on_instance.return_value = None
+
+        instances_obj = MagicMock()
+        operation = MagicMock()
+        operation.execute.return_value = None
+        instances_obj.start.return_value = operation
+        self.cloud.compute_driver.instances.return_value = instances_obj
 
         self.cloud._start_instance()
 
-        assert mock_get_instance.call_count == 1
-        assert self.cloud.compute_driver.ex_start_node.call_count == 1
-        assert self.cloud.compute_driver.wait_until_running.call_count == 1
+        assert instances_obj.start.call_count == 1
 
     @patch.object(GCECloud, '_wait_on_instance')
-    @patch.object(GCECloud, '_get_instance')
-    def test_gce_stop_instance(
-        self,
-        mock_get_instance,
-        mock_wait_on_instance
-    ):
+    def test_gce_stop_instance(self, mock_wait_on_instance):
         """Test gce stop instance method."""
-        instance = MagicMock()
-        mock_get_instance.return_value = instance
         mock_wait_on_instance.return_value = None
-        self.cloud.compute_driver.ex_stop_node.return_value = None
+
+        instances_obj = MagicMock()
+        operation = MagicMock()
+        operation.execute.return_value = None
+        instances_obj.stop.return_value = operation
+        self.cloud.compute_driver.instances.return_value = instances_obj
 
         self.cloud._stop_instance()
 
-        assert mock_get_instance.call_count == 1
-        assert self.cloud.compute_driver.ex_stop_node.call_count == 1
+        assert instances_obj.stop.call_count == 1
 
-    @patch.object(GCECloud, '_get_instance')
-    def test_gce_terminate_instance(self, mock_get_instance):
+    def test_gce_terminate_instance(self):
         """Test gce terminate instance method."""
-        instance = MagicMock()
-        instance.destroy.return_value = None
-        mock_get_instance.return_value = instance
+        instances_obj = MagicMock()
+        operation = MagicMock()
+        operation.execute.return_value = None
+        instances_obj.delete.return_value = operation
+        self.cloud.compute_driver.instances.return_value = instances_obj
 
         self.cloud._terminate_instance()
-        assert instance.destroy.call_count == 1
+        assert instances_obj.delete.call_count == 1
 
-    @patch.object(GCECloud, '_get_instance')
-    def test_gce_get_console_log(self, mock_get_instance):
+    def test_gce_get_console_log(self):
         """Test gce get console log method."""
-        instance = MagicMock()
-        mock_get_instance.return_value = instance
-        self.cloud.compute_driver.ex_get_serial_output.return_value = 'output'
+        instances_obj = MagicMock()
+        operation = MagicMock()
+        operation.execute.return_value = {'content': 'some output'}
+        instances_obj.getSerialPortOutput.return_value = operation
+        self.cloud.compute_driver.instances.return_value = instances_obj
+
+        self.cloud.get_console_log()
+        assert instances_obj.getSerialPortOutput.call_count == 1
+
+    @patch('img_proof.ipa_gce.time')
+    def test_wait_on_operation(self, mock_time):
+        self.cloud.service_account_project = 'test_project'
+        self.cloud.region = 'us-west1-a'
+
+        mock_time.sleep.return_value = None
+        mock_time.time.return_value = 10
 
-        output = self.cloud.get_console_log()
-        assert output == 'output'
-        assert self.cloud.compute_driver.ex_get_serial_output.call_count == 1
+        zone_ops_obj = MagicMock()
+        operation = MagicMock()
+        operation.execute.return_value = {'status': 'DONE'}
+        zone_ops_obj.get.return_value = operation
+        self.cloud.compute_driver.zoneOperations.return_value = zone_ops_obj
+
+        result = self.cloud._wait_on_operation('operation213')
+        assert result['status'] == 'DONE'
+        assert zone_ops_obj.get.call_count == 1


Reply via email to