Przeglądaj źródła

Merge pull request #116 from chiniforooshan/17-12-15-gce-sync

Fixing some bugs
Nuwan Goonasekera 8 lat temu
rodzic
commit
e2294004f0

+ 17 - 17
cloudbridge/cloud/providers/gce/provider.py

@@ -60,13 +60,9 @@ class GCPResourceUrl(object):
 
 class GCPResources(object):
 
-    def __init__(self, connection, project_name, region_name, default_zone):
+    def __init__(self, connection, **kwargs):
         self._connection = connection
-        self._parameter_defaults = {
-            'project': project_name,
-            'region': region_name,
-            'zone': default_zone,
-        }
+        self._parameter_defaults = kwargs
 
         # Resource descriptions are already pulled into the internal
         # _resourceDesc field of the connection.
@@ -167,7 +163,7 @@ class GCPResources(object):
                 out.parameters[parameter] = m.group(index + 1)
             return out
 
-    def get_resource_url_with_default(self, resource, url_or_name):
+    def get_resource_url_with_default(self, resource, url_or_name, **kwargs):
         """
         Build a GCPResourceUrl from a service's name and resource url or name.
         If the url_or_name is a valid GCP resource URL, then we build the
@@ -180,10 +176,15 @@ class GCPResources(object):
             return self.parse_url(url_or_name)
         # Otherwise, construct resource URL with default values.
         if resource not in self._resources:
+            cb.log.warning('Unknown resource: %s', resource)
             return None
+
+        parameter_defaults = self._parameter_defaults.copy()
+        parameter_defaults.update(kwargs)
+
         parsed_url = GCPResourceUrl(resource, self._connection)
         for key in self._resources[resource]['parameters']:
-            parsed_url.parameters[key] = self._parameter_defaults.get(
+            parsed_url.parameters[key] = parameter_defaults.get(
                 key, url_or_name)
         return parsed_url
 
@@ -235,12 +236,11 @@ class GCECloudProvider(BaseCloudProvider):
         self._networking = GCENetworkingService(self)
         self._storage = GCPStorageService(self)
 
-        self._compute_resources = GCPResources(
-            self.gce_compute, self.project_name, self.region_name,
-            self.default_zone)
-        self._storage_resources = GCPResources(
-            self.gcs_storage, self.project_name, self.region_name,
-            self.default_zone)
+        self._compute_resources = GCPResources(self.gce_compute,
+                                               project=self.project_name,
+                                               region=self.region_name,
+                                               zone=self.default_zone)
+        self._storage_resources = GCPResources(self.gcs_storage)
 
     @property
     def compute(self):
@@ -308,12 +308,12 @@ class GCECloudProvider(BaseCloudProvider):
         out = self._compute_resources.parse_url(url)
         return out if out else self._storage_resources.parse_url(url)
 
-    def get_resource(self, resource, url_or_name):
+    def get_resource(self, resource, url_or_name, **kwargs):
         resource_url = (
             self._compute_resources.get_resource_url_with_default(
-                resource, url_or_name) or
+                resource, url_or_name, **kwargs) or
             self._storage_resources.get_resource_url_with_default(
-                resource, url_or_name))
+                resource, url_or_name, **kwargs))
         if resource_url is None:
             return None
         try:

+ 242 - 194
cloudbridge/cloud/providers/gce/resources.py

@@ -6,12 +6,13 @@ import inspect
 import io
 import json
 import math
-import re
 import uuid
 
 import cloudbridge as cb
+import cloudbridge.cloud.base.helpers as cb_helpers
 from cloudbridge.cloud.base.resources import BaseAttachmentInfo
 from cloudbridge.cloud.base.resources import BaseBucket
+from cloudbridge.cloud.base.resources import BaseBucketContainer
 from cloudbridge.cloud.base.resources import BaseBucketObject
 from cloudbridge.cloud.base.resources import BaseFloatingIP
 from cloudbridge.cloud.base.resources import BaseFloatingIPContainer
@@ -143,15 +144,9 @@ class GCEVMType(BaseVMType):
 
 class GCEPlacementZone(BasePlacementZone):
 
-    def __init__(self, provider, zone, region):
+    def __init__(self, provider, zone):
         super(GCEPlacementZone, self).__init__(provider)
-        if isinstance(zone, GCEPlacementZone):
-            # pylint:disable=protected-access
-            self._gce_zone = zone._gce_zone
-            self._gce_region = zone._gce_region
-        else:
-            self._gce_zone = zone
-            self._gce_region = region
+        self._zone = zone
 
     @property
     def id(self):
@@ -160,7 +155,7 @@ class GCEPlacementZone(BasePlacementZone):
         :rtype: ``str``
         :return: ID for this zone as returned by the cloud middleware.
         """
-        return self._gce_zone.get('selfLink')
+        return self._zone['selfLink']
 
     @property
     def name(self):
@@ -169,7 +164,7 @@ class GCEPlacementZone(BasePlacementZone):
         :rtype: ``str``
         :return: Name for this zone as returned by the cloud middleware.
         """
-        return self._gce_zone.get('name')
+        return self._zone['name']
 
     @property
     def region_name(self):
@@ -178,7 +173,8 @@ class GCEPlacementZone(BasePlacementZone):
         :rtype: ``str``
         :return: Name of this zone's region as returned by the cloud middleware
         """
-        return self._gce_region
+        parsed_region_url = self._provider.parse_url(self._zone['region'])
+        return parsed_region_url.parameters['region']
 
 
 class GCERegion(BaseRegion):
@@ -207,8 +203,7 @@ class GCERegion(BaseRegion):
                               .execute())
         zones = [zone for zone in zones_response['items']
                  if zone['region'] == self._gce_region['selfLink']]
-        return [GCEPlacementZone(self._provider, zone, self.name)
-                for zone in zones]
+        return [GCEPlacementZone(self._provider, zone) for zone in zones]
 
 
 class GCEFirewallsDelegate(object):
@@ -711,28 +706,9 @@ class GCEMachineImage(BaseMachineImage):
         Refreshes the state of this instance by re-querying the cloud provider
         for its latest state.
         """
-        resource_link = self._gce_image['selfLink']
-        project_pattern = 'projects/(.*?)/'
-        match = re.search(project_pattern, resource_link)
-        if match:
-            project = match.group(1)
-        else:
-            cb.log.warning("Project name is not found.")
-            return
-        try:
-            response = (self._provider
-                            .gce_compute
-                            .images()
-                            .get(project=project, image=self.name)
-                            .execute())
-            if response:
-                # pylint:disable=protected-access
-                self._gce_image = response
-        except googleapiclient.errors.HttpError as http_error:
-            # image no longer exists
-            cb.log.warning(
-                "googleapiclient.errors.HttpError: {0}".format(http_error))
-            self._gce_image['status'] = "unknown"
+        self._gce_image = self._provider.get_resource('images', self.id)
+        if not self._gce_image:
+            self._gce_image = {'status': 'UNKNOWN'}
 
 
 class GCEInstance(BaseInstance):
@@ -753,6 +729,7 @@ class GCEInstance(BaseInstance):
     def __init__(self, provider, gce_instance):
         super(GCEInstance, self).__init__(provider)
         self._gce_instance = gce_instance
+        self._inet_gateway = None
 
     @property
     def resource_url(self):
@@ -805,7 +782,7 @@ class GCEInstance(BaseInstance):
                 access_config = access_configs[0]
                 if 'natIP' in access_config:
                     ips.append(access_config['natIP'])
-        for ip in self._provider.networking.floating_ips:
+        for ip in self.inet_gateway.floating_ips:
             if ip.in_use():
                 if ip.private_ip in self.private_ips:
                     ips.append(ip.public_ip)
@@ -846,46 +823,50 @@ class GCEInstance(BaseInstance):
         """
         Reboot this instance.
         """
+        response = None
         if self.state == InstanceState.STOPPED:
-            (self._provider
-                 .gce_compute
-                 .instances()
-                 .start(project=self._provider.project_name,
-                        zone=self._provider.default_zone,
-                        instance=self.name)
-                 .execute())
+            response = (self._provider
+                            .gce_compute
+                            .instances()
+                            .start(project=self._provider.project_name,
+                                   zone=self.zone_name,
+                                   instance=self.name)
+                            .execute())
         else:
-            (self._provider
-                 .gce_compute
-                 .instances()
-                 .reset(project=self._provider.project_name,
-                        zone=self._provider.default_zone,
-                        instance=self.name)
-                 .execute())
+            response = (self._provider
+                            .gce_compute
+                            .instances()
+                            .reset(project=self._provider.project_name,
+                                   zone=self.zone_name,
+                                   instance=self.name)
+                            .execute())
+        self._provider.wait_for_operation(response, zone=self.zone_name)
 
     def delete(self):
         """
         Permanently terminate this instance.
         """
-        (self._provider
-             .gce_compute
-             .instances()
-             .delete(project=self._provider.project_name,
-                     zone=self._provider.default_zone,
-                     instance=self.name)
-             .execute())
+        response = (self._provider
+                        .gce_compute
+                        .instances()
+                        .delete(project=self._provider.project_name,
+                                zone=self.zone_name,
+                                instance=self.name)
+                        .execute())
+        self._provider.wait_for_operation(response, zone=self.zone_name)
 
     def stop(self):
         """
         Stop this instance.
         """
-        (self._provider
-             .gce_compute
-             .instances()
-             .stop(project=self._provider.project_name,
-                   zone=self._provider.default_zone,
-                   instance=self.name)
-             .execute())
+        response = (self._provider
+                        .gce_compute
+                        .instances()
+                        .stop(project=self._provider.project_name,
+                              zone=self.zone_name,
+                              instance=self.name)
+                        .execute())
+        self._provider.wait_for_operation(response, zone=self.zone_name)
 
     @property
     def image_id(self):
@@ -907,6 +888,10 @@ class GCEInstance(BaseInstance):
         """
         return self._gce_instance.get('zone')
 
+    @property
+    def zone_name(self):
+        return self._provider.parse_url(self.zone_id).parameters['zone']
+
     @property
     def vm_firewalls(self):
         """
@@ -943,6 +928,16 @@ class GCEInstance(BaseInstance):
         """
         return self._provider.security.key_pairs.name
 
+    @property
+    def inet_gateway(self):
+        if self._inet_gateway:
+            return self._inet_gateway
+        network_url = self._gce_instance.get('networkInterfaces')[0].get(
+            'network')
+        network = self._provider.networking.networks.get(network_url)
+        self._inet_gateway = network.gateways.get_or_create_inet_gateway()
+        return self._inet_gateway
+
     def create_image(self, name):
         """
         Create a new image based on this instance.
@@ -961,10 +956,12 @@ class GCEInstance(BaseInstance):
                              .gce_compute
                              .images()
                              .insert(project=self._provider.project_name,
-                                     body=image_body)
+                                     body=image_body,
+                                     forceCreate=True)
                              .execute())
                 self._provider.wait_for_operation(operation)
-                return
+                img = self._provider.get_resource('images', name)
+                return GCEMachineImage(self._provider, img) if img else None
         cb.log.error('Failed to create image: no boot disk found.')
 
     def _get_existing_target_instance(self):
@@ -973,12 +970,11 @@ class GCEInstance(BaseInstance):
 
         If there is no target instance for this instance, return None.
         """
-        self_url = self._provider.parse_url(self._gce_instance['selfLink'])
         try:
             for target_instance in helpers.iter_all(
                     self._provider.gce_compute.targetInstances(),
-                    project=self_url.parameters['project'],
-                    zone=self_url.parameters['zone']):
+                    project=self.name,
+                    zone=self.zone_name):
                 url = self._provider.parse_url(target_instance['instance'])
                 if url.parameters['instance'] == self.name:
                     return target_instance
@@ -997,19 +993,17 @@ class GCEInstance(BaseInstance):
             return existing_target_instance
 
         # No targetInstance exists for this instance. Create one.
-        self_url = self._provider.parse_url(self._gce_instance['selfLink'])
         body = {'name': 'target-instance-{0}'.format(uuid.uuid4()),
                 'instance': self._gce_instance['selfLink']}
         try:
             response = (self._provider
                             .gce_compute
                             .targetInstances()
-                            .insert(project=self_url.parameters['project'],
-                                    zone=self_url.parameters['zone'],
+                            .insert(project=self.name,
+                                    zone=self.zone_name,
                                     body=body)
                             .execute())
-            self._provider.wait_for_operation(
-                response, zone=self_url.parameters['zone'])
+            self._provider.wait_for_operation(response, zone=self.zone_name)
         except Exception as e:
             cb.log.warning('Exception while inserting a target instance: %s',
                            e)
@@ -1031,7 +1025,7 @@ class GCEInstance(BaseInstance):
             for rule in helpers.iter_all(
                     self._provider.gce_compute.forwardingRules(),
                     project=self._provider.project_name,
-                    region=ip.region):
+                    region=ip.region_name):
                 if rule['IPAddress'] != ip.public_ip:
                     continue
                 parsed_target_url = self._provider.parse_url(rule['target'])
@@ -1044,11 +1038,12 @@ class GCEInstance(BaseInstance):
                                 .forwardingRules()
                                 .setTarget(
                                     project=self._provider.project_name,
-                                    region=ip.region,
+                                    region=ip.region_name,
                                     forwardingRule=rule['name'],
                                     body={'target': new_url})
                                 .execute())
-                self._provider.wait_for_operation(response, region=ip.region)
+                self._provider.wait_for_operation(response,
+                                                  region=ip.region_name)
                 return True
         except Exception as e:
             cb.log.warning(
@@ -1072,10 +1067,10 @@ class GCEInstance(BaseInstance):
                             .gce_compute
                             .forwardingRules()
                             .insert(project=self._provider.project_name,
-                                    region=ip.region,
+                                    region=ip.region_name,
                                     body=body)
                             .execute())
-            self._provider.wait_for_operation(response, region=ip.region)
+            self._provider.wait_for_operation(response, region=ip.region_name)
         except Exception as e:
             cb.log.warning('Exception while inserting a forwarding rule: %s',
                            e)
@@ -1093,7 +1088,7 @@ class GCEInstance(BaseInstance):
             for rule in helpers.iter_all(
                     self._provider.gce_compute.forwardingRules(),
                     project=self._provider.project_name,
-                    region=ip.region):
+                    region=ip.region_name):
                 if rule['IPAddress'] != ip.public_ip:
                     continue
                 parsed_target_url = self._provider.parse_url(rule['target'])
@@ -1109,10 +1104,11 @@ class GCEInstance(BaseInstance):
                                 .forwardingRules()
                                 .delete(
                                     project=self._provider.project_name,
-                                    region=ip.region,
+                                    region=ip.region_name,
                                     forwardingRule=rule['name'])
                                 .execute())
-                self._provider.wait_for_operation(response, region=ip.region)
+                self._provider.wait_for_operation(response,
+                                                  region=ip.region_name)
                 return True
         except Exception as e:
             cb.log.warning(
@@ -1124,7 +1120,7 @@ class GCEInstance(BaseInstance):
         """
         Add an elastic IP address to this instance.
         """
-        for ip in self._provider.networking.floating_ips:
+        for ip in self.inet_gateway.floating_ips:
             if ip.public_ip == ip_address:
                 if ip.in_use():
                     if ip.private_ip not in self.private_ips:
@@ -1148,7 +1144,7 @@ class GCEInstance(BaseInstance):
         """
         Remove a elastic IP address from this instance.
         """
-        for ip in self._provider.networking.floating_ips:
+        for ip in self.inet_gateway.floating_ips:
             if ip.public_ip == ip_address:
                 if not ip.in_use() or ip.private_ip not in self.private_ips:
                     cb.log.warning(
@@ -1179,14 +1175,37 @@ class GCEInstance(BaseInstance):
         Refreshes the state of this instance by re-querying the cloud provider
         for its latest state.
         """
-        self_link = self._gce_instance.get('selfLink')
-        self._gce_instance = self._provider.parse_url(self_link).get_resource()
+        self._gce_instance = self._provider.get_resource('instances', self.id)
+        if not self._gce_instance:
+            self._gce_instance = {'status': 'UNKNOWN'}
 
     def add_vm_firewall(self, sg):
-        raise NotImplementedError('To be implemented.')
+        tag = sg.name if isinstance(sg, GCEVMFirewall) else sg
+        tags = self._gce_instance.get('tags', {}).get('items', [])
+        tags.append(tag)
+        self._set_tags(tags)
 
     def remove_vm_firewall(self, sg):
-        raise NotImplementedError('To be implemented.')
+        tag = sg.name if isinstance(sg, GCEVMFirewall) else sg
+        tags = self._gce_instance.get('tags', {}).get('items', [])
+        if tag in tags:
+            tags.remove(tag)
+            self._set_tags(tags)
+
+    def _set_tags(self, tags):
+        # Refresh to make sure we are using the most recent tags fingerprint.
+        self.refresh()
+        fingerprint = self._gce_instance.get('tags', {}).get('fingerprint', '')
+        response = (self._provider
+                        .gce_compute
+                        .instances()
+                        .setTags(project=self._provider.project_name,
+                                 zone=self.zone_name,
+                                 instance=self.name,
+                                 body={'items': tags,
+                                       'fingerprint': fingerprint})
+                        .execute())
+        self._provider.wait_for_operation(response, zone=self.zone_name)
 
 
 class GCENetwork(BaseNetwork):
@@ -1222,6 +1241,8 @@ class GCENetwork(BaseNetwork):
         When a GCP network created by the CloudBridge API, we wait until the
         network is ready.
         """
+        if 'status' in self._network and self._network['status'] == 'UNKNOWN':
+            return NetworkState.UNKNOWN
         return NetworkState.AVAILABLE
 
     @property
@@ -1256,8 +1277,9 @@ class GCENetwork(BaseNetwork):
             self, cidr_block, name, zone)
 
     def refresh(self):
-        self_link = self._network.get('selfLink')
-        self._network = self._provider.parse_url(self_link).get_resource()
+        self._network = self._provider.get_resource('networks', self.id)
+        if not self._network:
+            self._network = {'status': 'UNKNOWN'}
 
     @property
     def gateways(self):
@@ -1270,30 +1292,21 @@ class GCEFloatingIPContainer(BaseFloatingIPContainer):
         super(GCEFloatingIPContainer, self).__init__(provider, gateway)
 
     def get(self, floating_ip_id):
-        try:
-            response = (self.provider
-                            .gce_compute
-                            .addresses()
-                            .get(project=self.provider.project_name,
-                                 region=self.provider.region_name)
-                            .execute())
-            return GCEFloatingIP(self.provider, response)
-        except googleapiclient.errors.HttpError as http_error:
-            cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
-            return None
+        fip = self._provider.get_resource('addresses', floating_ip_id)
+        return GCEFloatingIP(self._provider, fip) if fip else None
 
     def list(self, limit=None, marker=None):
         max_result = limit if limit is not None and limit < 500 else 500
         try:
-            response = (self.provider
+            response = (self._provider
                             .gce_compute
                             .addresses()
-                            .list(project=self.provider.project_name,
-                                  region=self.provider.region_name,
+                            .list(project=self._provider.project_name,
+                                  region=self._provider.region_name,
                                   maxResults=max_result,
                                   pageToken=marker)
                             .execute())
-            ips = [GCEFloatingIP(self.provider, ip)
+            ips = [GCEFloatingIP(self._provider, ip)
                    for ip in response.get('items', [])]
             if len(ips) > max_result:
                 cb.log.warning('Expected at most %d results; got %d',
@@ -1306,19 +1319,19 @@ class GCEFloatingIPContainer(BaseFloatingIPContainer):
             return None
 
     def create(self):
-        region = self.provider.region_name
+        region_name = self._provider.region_name
         ip_name = 'ip-{0}'.format(uuid.uuid4())
         try:
-            response = (self.provider
+            response = (self._provider
                             .gce_compute
                             .addresses()
-                            .insert(project=self.provider.project_name,
-                                    region=region,
+                            .insert(project=self._provider.project_name,
+                                    region=region_name,
                                     body={'name': ip_name})
                             .execute())
             if 'error' in response:
                 return None
-            self.provider.wait_for_operation(response, region=region)
+            self._provider.wait_for_operation(response, region=region_name)
             return self.get(ip_name)
         except googleapiclient.errors.HttpError as http_error:
             cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
@@ -1336,7 +1349,7 @@ class GCEFloatingIP(BaseFloatingIP):
         # global IPs can be forwarded only to load balancing resources, not to
         # a specific instance. Find out the region to which the IP belongs.
         url = provider.parse_url(self._ip['region'])
-        self._region = url.parameters['region']
+        self._region_name = url.parameters['region']
 
         # Check if the address is used by a resource.
         self._rule = None
@@ -1368,8 +1381,8 @@ class GCEFloatingIP(BaseFloatingIP):
         return self._ip['selfLink']
 
     @property
-    def region(self):
-        return self._region
+    def region_name(self):
+        return self._region_name
 
     @property
     def public_ip(self):
@@ -1393,23 +1406,26 @@ class GCEFloatingIP(BaseFloatingIP):
                             .gce_compute
                             .forwardingRules()
                             .delete(project=project_name,
-                                    region=self._region,
+                                    region=self._region_name,
                                     forwardingRule=self._rule['name'])
                             .execute())
-            self._provider.wait_for_operation(response, region=self._region)
+            self._provider.wait_for_operation(response,
+                                              region=self._region_name)
 
         # Release the address.
         response = (self._provider
                         .gce_compute
                         .addresses()
                         .delete(project=project_name,
-                                region=self._region,
+                                region=self._region_name,
                                 address=self._ip['name'])
                         .execute())
-        self._provider.wait_for_operation(response, region=self._region)
+        self._provider.wait_for_operation(response, region=self._region_name)
 
     def refresh(self):
-        pass
+        self._ip = self._provider.get_resource('addresses', self.id)
+        if not self._ip:
+            self._ip = {'status': 'UNKNOWN'}
 
 
 class GCERouter(BaseRouter):
@@ -1426,12 +1442,22 @@ class GCERouter(BaseRouter):
     def name(self):
         return self._router['name']
 
+    @property
+    def region_name(self):
+        parsed_url = self._provider.parse_url(self.id)
+        return parsed_url.parameters['region']
+
     def refresh(self):
-        parsed_url = self._provider.parse_url(self._router['selfLink'])
-        self._router = parsed_url.get_resource()
+        self._router = self._provider.get_resource('routers', self.id)
+        if not self._router:
+            self._router = {'status': 'UNKNOWN'}
 
     @property
     def state(self):
+        # If the router info is refreshed after it is deleted, its status will
+        # be UNKNOWN.
+        if 'status' in self._router and self._router['status'] == 'UNKNOWN':
+            return RouterState.UNKNOWN
         # GCE routers are always attached to a network.
         return RouterState.ATTACHED
 
@@ -1446,25 +1472,26 @@ class GCERouter(BaseRouter):
                         .gce_compute
                         .routers()
                         .delete(project=self._provider.project_name,
-                                region=self._router['region'],
-                                router=self._router['name'])
+                                region=self.region_name,
+                                router=self.name)
                         .execute())
-        self._provider.wait_for_operation(response,
-                                          region=self._router['region'])
+        self._provider.wait_for_operation(response, region=self.region_name)
 
-    def attach_network(self, network_id):
-        if network_id == self.network_id:
+    def attach_subnet(self, subnet):
+        if not isinstance(subnet, GCESubnet):
+            subnet = self._provider.networking.subnets.get(subnet)
+        if subnet.network_id == self.network_id:
             return
         cb.log.warning('GCE routers should be attached at creation time')
 
-    def detach_network(self, network_id):
+    def detach_subnet(self, network_id):
         cb.log.warning('GCE routers are always attached')
 
-    def add_route(self, subnet_id):
-        cb.log.warning('Not implemented')
+    def attach_gateway(self, gateway):
+        pass
 
-    def remove_route(self, subnet_id):
-        cb.log.warning('Not implemented')
+    def detach_gateway(self, gateway):
+        pass
 
 
 class GCEGatewayContainer(BaseGatewayContainer):
@@ -1479,8 +1506,7 @@ class GCEGatewayContainer(BaseGatewayContainer):
                     GCEGatewayContainer._DEFAULT_GATEWAY_NAME),
              'name': GCEGatewayContainer._DEFAULT_GATEWAY_NAME})
 
-    def get_or_create_inet_gateway(self, name):
-        GCEInternetGateway.assert_valid_resource_name(name)
+    def get_or_create_inet_gateway(self, name=None):
         return self._default_internet_gateway
 
     def delete(self, gateway):
@@ -1526,7 +1552,7 @@ class GCEInternetGateway(BaseInternetGateway):
 
     @property
     def floating_ips(self):
-        return self._fips_container
+        return self._fip_container
 
 
 class GCESubnet(BaseSubnet):
@@ -1559,38 +1585,43 @@ class GCESubnet(BaseSubnet):
 
     @property
     def network_id(self):
-        return self._provider.parse_url(
-            self.network_url).get_resource()['selfLink']
+        return self.network_url
 
     @property
     def region(self):
         return self._subnet['region']
 
+    @property
+    def region_name(self):
+        parsed_url = self.provider.parse_url(self.id)
+        return parsed_url.parameters['region']
+
     @property
     def zone(self):
         return None
 
-    @property
     def delete(self):
         return self._provider.networking.subnets.delete(self)
 
     @property
     def state(self):
-        return SubnetState.AVAILABEL
+        if 'status' in self._subnet and self._subnet['status'] == 'UNKNOWN':
+            return SubnetState.UNKNOWN
+        return SubnetState.AVAILABLE
 
     def refresh(self):
-        self_link = self._subnet.get('selfLink')
-        self._subnet = self._provider.parse_url(self_link).get_resource()
+        self._subnet = self._provider.get_resource('subnetworks', self.id)
+        if not self._subnet:
+            self._subnet = {'status': 'UNKNOWN'}
 
 
 class GCEVolume(BaseVolume):
 
     VOLUME_STATE_MAP = {
-        'RESTORING': VolumeState.CONFIGURING,
-        'PENDING': VolumeState.CONFIGURING,
+        'CREATING': VolumeState.CONFIGURING,
+        'FAILED': VolumeState.ERROR,
         'READY': VolumeState.AVAILABLE,
-        'DONE': VolumeState.AVAILABLE,
-        'RUNNING': VolumeState.IN_USE,
+        'RESTORING': VolumeState.CONFIGURING,
     }
 
     def __init__(self, provider, volume):
@@ -1639,7 +1670,7 @@ class GCEVolume(BaseVolume):
 
     @property
     def size(self):
-        return self._volume.get('sizeGb')
+        return int(self._volume.get('sizeGb'))
 
     @property
     def create_time(self):
@@ -1691,17 +1722,16 @@ class GCEVolume(BaseVolume):
         """
         attach_disk_body = {
             "source": self.id,
-            "deviceName": device,
+            "deviceName": device.split('/')[-1],
         }
-        instance_name = instance.name if isinstance(
-            instance,
-            GCEInstance) else instance
+        if not isinstance(instance, GCEInstance):
+            instance = self._provider.get_resource('instances', instance)
         (self._provider
              .gce_compute
              .instances()
              .attachDisk(project=self._provider.project_name,
-                         zone=self._provider.default_zone,
-                         instance=instance_name,
+                         zone=instance.zone_name,
+                         instance=instance.name,
                          body=attach_disk_body)
              .execute())
 
@@ -1754,6 +1784,8 @@ class GCEVolume(BaseVolume):
 
     @property
     def state(self):
+        if len(self._volume.get('users', [])) > 0:
+            return VolumeState.IN_USE
         return GCEVolume.VOLUME_STATE_MAP.get(
             self._volume.get('status'), VolumeState.UNKNOWN)
 
@@ -1762,8 +1794,9 @@ class GCEVolume(BaseVolume):
         Refreshes the state of this volume by re-querying the cloud provider
         for its latest state.
         """
-        self_link = self._volume.get('selfLink')
-        self._volume = self._provider.parse_url(self_link).get_resource()
+        self._volume = self._provider.get_resource('disks', self.id)
+        if not self._volume:
+            self._volume = {'status': 'UNKNOWN'}
 
 
 class GCESnapshot(BaseSnapshot):
@@ -1798,7 +1831,7 @@ class GCESnapshot(BaseSnapshot):
 
     @property
     def size(self):
-        return self._snapshot.get('diskSizeGb')
+        return int(self._snapshot.get('diskSizeGb'))
 
     @property
     def volume_id(self):
@@ -1818,8 +1851,9 @@ class GCESnapshot(BaseSnapshot):
         Refreshes the state of this snapshot by re-querying the cloud provider
         for its latest state.
         """
-        self_link = self._snapshot.get('selfLink')
-        self._snapshot = self._provider.parse_url(self_link).get_resource()
+        self._snapshot = self._provider.get_resource('snapshots', self.id)
+        if not self._snapshot:
+            self._snapshot = {'status': 'UNKNOWN'}
 
     def delete(self):
         """
@@ -1844,8 +1878,11 @@ class GCESnapshot(BaseSnapshot):
                 'pd-ssd'.
             iops: Not supported by GCE.
         """
+        zone_name = placement
+        if isinstance(placement, GCEPlacementZone):
+            zone_name = placement.name
         vol_type = 'zones/{0}/diskTypes/{1}'.format(
-            placement,
+            zone_name,
             'pd-standard' if (volume_type != 'pd-standard' or
                               volume_type != 'pd-ssd') else volume_type)
         disk_body = {
@@ -1858,7 +1895,7 @@ class GCESnapshot(BaseSnapshot):
                          .gce_compute
                          .disks()
                          .insert(project=self._provider.project_name,
-                                 zone=placement,
+                                 zone=zone_name,
                                  body=disk_body)
                          .execute())
         return self._provider.storage.volumes.get(
@@ -1882,7 +1919,7 @@ class GCSObject(BaseBucketObject):
 
     @property
     def size(self):
-        return self._obj['size']
+        return int(self._obj['size'])
 
     @property
     def last_modified(self):
@@ -1890,7 +1927,7 @@ class GCSObject(BaseBucketObject):
 
     def iter_content(self):
         return io.BytesIO(self._provider
-                              .gcp_storage
+                              .gcs_storage
                               .objects()
                               .get_media(bucket=self._obj['bucket'],
                                          object=self.name)
@@ -1921,7 +1958,7 @@ class GCSObject(BaseBucketObject):
 
     def delete(self):
         (self._provider
-             .gcp_storage
+             .gcs_storage
              .objects()
              .delete(bucket=self._obj['bucket'], object=self.name)
              .execute())
@@ -1930,39 +1967,18 @@ class GCSObject(BaseBucketObject):
         return self._obj['mediaLink']
 
 
-class GCSBucket(BaseBucket):
+class GCSBucketContainer(BaseBucketContainer):
 
     def __init__(self, provider, bucket):
-        super(GCSBucket, self).__init__(provider)
-        self._bucket = bucket
-
-    @property
-    def id(self):
-        return self._bucket['selfLink']
-
-    @property
-    def name(self):
-        """
-        Get this bucket's name.
-        """
-        return self._bucket['name']
+        super(GCSBucketContainer, self).__init__(provider, bucket)
 
     def get(self, name):
         """
         Retrieve a given object from this bucket.
         """
-        try:
-            response = (self._provider
-                            .gcp_storage
-                            .objects()
-                            .get(bucket=self.name, object=name)
-                            .execute())
-            if 'error' in response:
-                return None
-            return GCSObject(self._provider, self, response)
-        except googleapiclient.errors.HttpError as http_error:
-            cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
-            return None
+        obj = self._provider.get_resource('objects', name,
+                                          bucket=self.bucket.name)
+        return GCSObject(self._provider, self.bucket, obj) if obj else None
 
     def list(self, limit=None, marker=None, prefix=None):
         """
@@ -1971,9 +1987,9 @@ class GCSBucket(BaseBucket):
         max_result = limit if limit is not None and limit < 500 else 500
         try:
             response = (self._provider
-                            .gcp_storage
+                            .gcs_storage
                             .objects()
-                            .list(bucket=self.name,
+                            .list(bucket=self.bucket.name,
                                   prefix=prefix if prefix else '',
                                   maxResults=max_result,
                                   pageToken=marker)
@@ -1982,7 +1998,7 @@ class GCSBucket(BaseBucket):
                 return ServerPagedResultList(False, None, False, data=[])
             objects = []
             for obj in response.get('items', []):
-                objects.append(GCSObject(self._provider, self, obj))
+                objects.append(GCSObject(self._provider, self.bucket, obj))
             if len(objects) > max_result:
                 cb.log.warning('Expected at most %d results; got %d',
                                max_result, len(objects))
@@ -1993,12 +2009,45 @@ class GCSBucket(BaseBucket):
             cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
             return ServerPagedResultList(False, None, False, data=[])
 
+    def find(self, **kwargs):
+        obj_list = self.list()
+        filters = ['name']
+        matches = cb_helpers.generic_find(filters, kwargs, obj_list)
+        return ClientPagedResultList(self._provider, list(matches),
+                                     limit=None, marker=None)
+
+    def create(self, name):
+        return self.bucket.create_object(name)
+
+
+class GCSBucket(BaseBucket):
+
+    def __init__(self, provider, bucket):
+        super(GCSBucket, self).__init__(provider)
+        self._bucket = bucket
+        self._object_container = GCSBucketContainer(provider, self)
+
+    @property
+    def id(self):
+        return self._bucket['selfLink']
+
+    @property
+    def name(self):
+        """
+        Get this bucket's name.
+        """
+        return self._bucket['name']
+
+    @property
+    def objects(self):
+        return self._object_container
+
     def delete(self, delete_contents=False):
         """
         Delete this bucket.
         """
         (self._provider
-             .gcp_storage
+             .gcs_storage
              .buckets()
              .delete(bucket=self.name)
              .execute())
@@ -2014,10 +2063,9 @@ class GCSBucket(BaseBucket):
         return GCSObject(self._provider, self, response) if response else None
 
     def create_object_with_media_body(self, name, media_body):
-        self.assert_valid_resource_name(name)
         try:
             response = (self._provider
-                            .gcp_storage
+                            .gcs_storage
                             .objects()
                             .insert(bucket=self.name,
                                     body={'name': name},

+ 151 - 97
cloudbridge/cloud/providers/gce/services.py

@@ -22,7 +22,6 @@ from cloudbridge.cloud.base.services import BaseSubnetService
 from cloudbridge.cloud.base.services import BaseVMFirewallService
 from cloudbridge.cloud.base.services import BaseVMTypeService
 from cloudbridge.cloud.base.services import BaseVolumeService
-from cloudbridge.cloud.interfaces.resources import PlacementZone
 from cloudbridge.cloud.interfaces.resources import VMFirewall
 from cloudbridge.cloud.providers.gce import helpers
 
@@ -329,7 +328,8 @@ class GCERegionService(BaseRegionService):
         super(GCERegionService, self).__init__(provider)
 
     def get(self, region_id):
-        region = self.provider.get_resource('regions', region_id)
+        region = self.provider.get_resource('regions', region_id,
+                                            region=region_id)
         return GCERegion(self.provider, region) if region else None
 
     def list(self, limit=None, marker=None):
@@ -434,53 +434,114 @@ class GCEInstanceService(BaseInstanceService):
         Creates a new virtual machine instance.
         """
         GCEInstance.assert_valid_resource_name(name)
-        if not zone:
-            zone = self.provider.default_zone
-        if not isinstance(image, GCEMachineImage):
-            image = self.provider.compute.images.get(image)
+        zone_name = self.provider.default_zone
+        if zone:
+            if not isinstance(zone, GCEPlacementZone):
+                zone = GCEPlacementZone(
+                    self.provider,
+                    self.provider.get_resource('zones', zone, zone=zone))
+            zone_name = zone.name
         if not isinstance(vm_type, GCEVMType):
             vm_type = self.provider.compute.vm_types.get(vm_type)
-        if not launch_config:
-            if subnet:
-                network = self.provider.networking.networks.get(
-                        subnet.network_id)
-                network_url = (network.resource_url
-                               if isinstance(network, GCENetwork) else network)
-            else:
-                network_url = 'global/networks/default'
-            config = {
-                'name': name,
-                'machineType': vm_type.resource_url,
-                'disks': [{'boot': True,
-                           'autoDelete': True,
-                           'initializeParams': {
-                               'sourceImage': image.resource_url,
-                           }
-                           }],
-                'networkInterfaces': [
-                    {
-                        # TODO: Should replace network below with subnetwork
-                        'network': network_url,
-                        'accessConfigs': [{'type': 'ONE_TO_ONE_NAT',
-                                           'name': 'External NAT'}]
-                    }],
-            }
-            if vm_firewalls and isinstance(vm_firewalls, list):
-                vm_firewall_names = []
-                if isinstance(vm_firewalls[0], VMFirewall):
-                    vm_firewall_names = [f.name for f in vm_firewalls]
-                elif isinstance(vm_firewalls[0], str):
-                    vm_firewall_names = vm_firewalls
-                if len(vm_firewall_names) > 0:
-                    config['tags'] = {}
-                    config['tags']['items'] = vm_firewall_names
+
+        network_interface = {'accessConfigs': [{'type': 'ONE_TO_ONE_NAT',
+                                                'name': 'External NAT'}]}
+        if subnet:
+            network_interface['subnetwork'] = subnet.id
         else:
-            config = launch_config
+            network_interface['network'] = 'global/networks/default'
+
+        num_roots = 0
+        disks = []
+        boot_disk = None
+        if isinstance(launch_config, GCELaunchConfig):
+            for disk in launch_config.block_devices:
+                if not disk.source:
+                    volume_name = 'disk-{0}'.format(uuid.uuid4())
+                    volume_size = disk.size if disk.size else 1
+                    volume = self.provider.storage.volumes.create(
+                        volume_name, volume_size, zone)
+                    volume.wait_till_ready()
+                    source_field = 'source'
+                    source_value = volume.id
+                elif isinstance(disk.source, GCEMachineImage):
+                    source_field = 'initializeParams'
+                    # Explicitly set diskName; otherwise, instance name will be
+                    # used by default which may collide with existing disks.
+                    source_value = {
+                        'sourceImage': disk.source.id,
+                        'diskName': 'image-disk-{0}'.format(uuid.uuid4())}
+                elif isinstance(disk.source, GCEVolume):
+                    source_field = 'source'
+                    source_value = disk.source.id
+                elif isinstance(disk.source, GCESnapshot):
+                    volume = disk.source.create_volume(zone, size=disk.size)
+                    volume.wait_till_ready()
+                    source_field = 'source'
+                    source_value = volume.id
+                else:
+                    cb.log.warning('Unknown disk source')
+                    continue
+                autoDelete = True
+                if disk.delete_on_terminate is not None:
+                    autoDelete = disk.delete_on_terminate
+                num_roots += 1 if disk.is_root else 0
+                if disk.is_root and not boot_disk:
+                    boot_disk = {'boot': True,
+                                 'autoDelete': autoDelete,
+                                 source_field: source_value}
+                else:
+                    disks.append({'boot': False,
+                                  'autoDelete': autoDelete,
+                                  source_field: source_value})
+
+        if num_roots > 1:
+            cb.log.warning('The launch config contains %d boot disks. Will '
+                           'use the first one', num_roots)
+        if image:
+            if boot_disk:
+                cb.log.warning('A boot image is given while the launch config '
+                               'contains a boot disk, too. The launch config '
+                               'will be used')
+            else:
+                if not isinstance(image, GCEMachineImage):
+                    image = self.provider.compute.images.get(image)
+                # Explicitly set diskName; otherwise, instance name will be
+                # used by default which may conflict with existing disks.
+                boot_disk = {
+                    'boot': True,
+                    'autoDelete': True,
+                    'initializeParams': {
+                        'sourceImage': image.id,
+                        'diskName': 'image-disk-{0}'.format(uuid.uuid4())}}
+
+        if not boot_disk:
+            cb.log.warning('No boot disk is given')
+            return None
+        # The boot disk must be the first disk attached to the instance.
+        disks.insert(0, boot_disk)
+
+        config = {
+            'name': name,
+            'machineType': vm_type.resource_url,
+            'disks': disks,
+            'networkInterfaces': [network_interface]
+        }
+
+        if vm_firewalls and isinstance(vm_firewalls, list):
+            vm_firewall_names = []
+            if isinstance(vm_firewalls[0], VMFirewall):
+                vm_firewall_names = [f.name for f in vm_firewalls]
+            elif isinstance(vm_firewalls[0], str):
+                vm_firewall_names = vm_firewalls
+            if len(vm_firewall_names) > 0:
+                config['tags'] = {}
+                config['tags']['items'] = vm_firewall_names
         try:
             operation = (self.provider
                              .gce_compute.instances()
                              .insert(project=self.provider.project_name,
-                                     zone=zone,
+                                     zone=zone_name,
                                      body=config)
                              .execute())
         except googleapiclient.errors.HttpError as http_error:
@@ -489,10 +550,8 @@ class GCEInstanceService(BaseInstanceService):
             cb.log.warning(
                 "googleapiclient.errors.HttpError: {0}".format(http_error))
             return None
-        zone_url = self.provider.parse_url(operation['zone'])
         instance_id = operation.get('targetLink')
-        self.provider.wait_for_operation(operation,
-                                         zone=zone_url.parameters['zone'])
+        self.provider.wait_for_operation(operation, zone=zone_name)
         return self.get(instance_id)
 
     def get(self, instance_id):
@@ -591,10 +650,6 @@ class GCENetworkingService(BaseNetworkingService):
     def routers(self):
         return self._router_service
 
-    @property
-    def gateways(self):
-        return self._gateway_servcie
-
 
 class GCENetworkService(BaseNetworkService):
 
@@ -698,7 +753,9 @@ class GCERouterService(BaseRouterService):
     def find(self, name, limit=None, marker=None):
         routers = []
         for region in self.provider.compute.regions.list():
-            routers.append(self._get_in_region(name, region.name))
+            router = self._get_in_region(name, region.name)
+            if router:
+                routers.append(router)
         return ClientPagedResultList(self.provider, routers, limit=limit,
                                      marker=marker)
 
@@ -730,50 +787,45 @@ class GCERouterService(BaseRouterService):
         if not isinstance(network, GCENetwork):
             network = self.provider.networking.networks.get(network)
         network_url = network.resource_url
-        region = self.provider.region_name
+        region_name = self.provider.region_name
         try:
             response = (self.provider
                             .gce_compute
                             .routers()
                             .insert(project=self.provider.project_name,
-                                    region=region,
+                                    region=region_name,
                                     body={'name': name,
                                           'network': network_url})
                             .execute())
             if 'error' in response:
                 return None
-            self.provider.wait_for_operation(response, region=region)
-            return self._get_in_region(name, region)
+            self.provider.wait_for_operation(response, region=region_name)
+            return self._get_in_region(name, region_name)
         except googleapiclient.errors.HttpError as http_error:
             cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
             return None
 
     def delete(self, router):
-        region = self.provider.region_name
+        region_name = self.provider.region_name
         name = router.name if isinstance(router, GCERouter) else router
         response = (self.provider
                         .gce_compute
                         .routers()
                         .delete(project=self.provider.project_name,
-                                region=region,
+                                region=region_name,
                                 router=name)
                         .execute())
-        self._provider.wait_for_operation(response, region=region)
+        self._provider.wait_for_operation(response, region=region_name)
 
     def _get_in_region(self, router_id, region=None):
-        region = region if region else self.provider.region_name
-        try:
-            response = (self.provider
-                            .gce_compute
-                            .routers()
-                            .get(project=self.provider.project_name,
-                                 region=region,
-                                 router=router_id)
-                            .execute())
-            return GCERouter(self.provider, response)
-        except googleapiclient.errors.HttpError as http_error:
-            cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
-            return None
+        region_name = self.provider.region_name
+        if region:
+            if not isinstance(region, GCERegion):
+                region = self.provider.compute.regions.get(region)
+            region_name = region.name
+        router = self.provider.get_resource(
+            'routers', router_id, region=region_name)
+        return GCERouter(self.provider, router) if router else None
 
 
 class GCESubnetService(BaseSubnetService):
@@ -792,17 +844,19 @@ class GCESubnetService(BaseSubnetService):
         filter = None
         if network is not None:
             filter = 'network eq %s' % network.resource_url
+        region_names = []
         if zone:
-            regions = [zone.region_name]
+            region_names.append(self._zone_to_region_name(zone))
         else:
-            regions = [r.name for r in self.provider.compute.regions.list()]
+            for r in self.provider.compute.regions.list():
+                region_names.append(r.name)
         subnets = []
-        for region in regions:
+        for region_name in region_names:
             response = (self.provider
                             .gce_compute
                             .subnetworks()
                             .list(project=self.provider.project_name,
-                                  region=region,
+                                  region=region_name,
                                   filter=filter)
                             .execute())
             for subnet in response.get('items', []):
@@ -828,26 +882,25 @@ class GCESubnetService(BaseSubnetService):
 
         if not name:
             name = 'subnet-{0}'.format(uuid.uuid4())
-        region = self._zone_to_region(zone)
+        region_name = self._zone_to_region_name(zone)
         body = {'ipCidrRange': cidr_block,
                 'name': name,
                 'network': network.resource_url,
-                'region': region}
+                'region': region_name}
         try:
             response = (self.provider
                             .gce_compute
                             .subnetworks()
                             .insert(project=self.provider.project_name,
-                                    region=region,
+                                    region=region_name,
                                     body=body)
                             .execute())
-            self.provider.wait_for_operation(response, region=region)
             if 'error' in response:
+                cb.log.warning('Error while creating a subnet: %s',
+                               response['error'])
                 return None
-            subnets = self.list(network, region)
-            for subnet in subnets:
-                if subnet.id == response['targetId']:
-                    return subnet
+            self.provider.wait_for_operation(response, region=region_name)
+            return self.get(name)
         except googleapiclient.errors.HttpError as http_error:
             cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
             return None
@@ -877,24 +930,22 @@ class GCESubnetService(BaseSubnetService):
             # deleted.
             return
 
-        region_url = self.provider.parse_url(subnet.region)
         response = (self.provider
                         .gce_compute
                         .subnetworks()
                         .delete(project=self.provider.project_name,
-                                region=region_url.parameters['region'],
+                                region=subnet.region_name,
                                 subnetwork=subnet.name)
                         .execute())
-        self._provider.wait_for_operation(response, region=subnet.region)
+        self._provider.wait_for_operation(response, region=subnet.region_name)
 
-    def _zone_to_region(self, zone):
-        if isinstance(zone, GCEPlacementZone):
+    def _zone_to_region_name(self, zone):
+        if zone:
+            if not isinstance(zone, GCEPlacementZone):
+                zone = GCEPlacementZone(
+                    self.provider,
+                    self.provider.get_resource('zones', zone, zone=zone))
             return zone.region_name
-        elif zone:
-            for r in self.provider.compute.regions.list():
-                for z in r.zones:
-                    if zone == z.name:
-                        return z.region_name
         return self.provider.region_name
 
 
@@ -997,7 +1048,11 @@ class GCEVolumeService(BaseVolumeService):
         cannot be a dash.
         """
         GCEVolume.assert_valid_resource_name(name)
-        zone_name = zone.name if isinstance(zone, PlacementZone) else zone
+        if not isinstance(zone, GCEPlacementZone):
+            zone = GCEPlacementZone(
+                self.provider,
+                self.provider.get_resource('zones', zone, zone=zone))
+        zone_name = zone.name
         snapshot_id = snapshot.id if isinstance(
             snapshot, GCESnapshot) and snapshot else snapshot
         disk_body = {
@@ -1094,9 +1149,8 @@ class GCESnapshotService(BaseSnapshotService):
                          .execute())
         if 'zone' not in operation:
             return None
-        zone_url = self.provider.parse_url(operation['zone'])
         self.provider.wait_for_operation(operation,
-                                         zone=zone_url.parameters['zone'])
+                                         zone=self.provider.default_zone)
         snapshots = self.provider.storage.snapshots.find(name=name)
         if snapshots:
             return snapshots[0]
@@ -1133,7 +1187,7 @@ class GCSBucketService(BaseBucketService):
         max_result = limit if limit is not None and limit < 500 else 500
         try:
             response = (self.provider
-                            .gcp_storage
+                            .gcs_storage
                             .buckets()
                             .list(project=self.provider.project_name,
                                   maxResults=max_result,
@@ -1164,7 +1218,7 @@ class GCSBucketService(BaseBucketService):
             body['location'] = location
         try:
             response = (self.provider
-                            .gcp_storage
+                            .gcs_storage
                             .buckets()
                             .insert(project=self.provider.project_name,
                                     body=body)

+ 1 - 1
setup.cfg

@@ -8,7 +8,7 @@ omit =
 with-coverage=1
 cover-branches=1
 cover-package=cloudbridge
-processes=4
+processes=2
 process-timeout=2700
 match=^[Tt]est 
 # Don't capture stdout - print immediately

+ 3 - 1
test/helpers/__init__.py

@@ -91,7 +91,9 @@ TEST_DATA_CONFIG = {
         "placement": os.environ.get('CB_PLACEMENT_OS', 'zone-r1'),
     },
     'GCECloudProvider': {
-        'image': 'ubuntu-1710-artful-v20180126',
+        'image': ('https://www.googleapis.com/compute/v1/'
+                  'projects/ubuntu-os-cloud/global/images/'
+                  'ubuntu-1710-artful-v20180126'),
         'vm_type': 'n1-standard-1',
         'placement': os.environ.get('GCE_DEFAULT_ZONE', 'us-central1-a'),
     },

+ 6 - 2
test/test_compute_service.py

@@ -272,14 +272,18 @@ class CloudComputeServiceTestCase(ProviderTestBase):
                     "vm_type")
                 vm_type = self.provider.compute.vm_types.find(
                     name=vm_type_name)[0]
-                for _ in range(vm_type.num_ephemeral_disks):
+                # Some providers, e.g. GCP, has a limit on total number of
+                # attached disks; it does not matter how many of them are
+                # ephemeral or persistent. So, wee keep in mind that we have
+                # attached 4 disks already, and add ephemeral disks accordingly
+                # to not exceed the limit.
+                for _ in range(vm_type.num_ephemeral_disks - 4):
                     lc.add_ephemeral_device()
 
                 net, subnet = helpers.create_test_network(self.provider, name)
 
                 with helpers.cleanup_action(lambda:
                                             helpers.delete_test_network(net)):
-
                     inst = helpers.create_test_instance(
                         self.provider,
                         name,

+ 1 - 3
test/test_network_service.py

@@ -33,9 +33,7 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
         subnet_name = 'cb-propsubnet-{0}'.format(helpers.get_uuid())
         net = self.provider.networking.networks.create(
             name=name, cidr_block='10.0.0.0/16')
-        with helpers.cleanup_action(
-            lambda: net.delete()
-        ):
+        with helpers.cleanup_action(lambda: net.delete()):
             net.wait_till_ready()
             self.assertEqual(
                 net.state, 'available',

+ 1 - 1
test/test_security_service.py

@@ -97,7 +97,7 @@ class CloudSecurityServiceTestCase(ProviderTestBase):
 
     @helpers.skipIfNoService(['security.vm_firewalls'])
     def test_crud_vm_firewall_rules(self):
-        name = 'cb-crudfw_rules-{0}'.format(helpers.get_uuid())
+        name = 'cb-crudfw-rules-{0}'.format(helpers.get_uuid())
 
         # Declare these variables and late binding will allow
         # the cleanup method access to the most current values