Просмотр исходного кода

Merge pull request #50 from chiniforooshan/mods

GCP routers, subnets, buckets, and objects
Nuwan Goonasekera 8 лет назад
Родитель
Сommit
ba82b1a0d8

+ 12 - 1
cloudbridge/cloud/providers/gce/helpers.py

@@ -1,7 +1,7 @@
 # based on http://stackoverflow.com/a/39126754
 # based on http://stackoverflow.com/a/39126754
+from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives import serialization as crypt_serialization
 from cryptography.hazmat.primitives import serialization as crypt_serialization
 from cryptography.hazmat.primitives.asymmetric import rsa
 from cryptography.hazmat.primitives.asymmetric import rsa
-from cryptography.hazmat.backends import default_backend
 
 
 
 
 def generate_key_pair():
 def generate_key_pair():
@@ -17,3 +17,14 @@ def generate_key_pair():
         crypt_serialization.Encoding.OpenSSH,
         crypt_serialization.Encoding.OpenSSH,
         crypt_serialization.PublicFormat.OpenSSH)
         crypt_serialization.PublicFormat.OpenSSH)
     return private_key, public_key
     return private_key, public_key
+
+
+def iter_all(resource, **kwargs):
+    token = None
+    while True:
+        response = resource.list(pageToken=token, **kwargs).execute()
+        for item in response.get('items', []):
+            yield item
+        if 'nextPageToken' not in response:
+            return
+        token = response['nextPageToken']

+ 10 - 14
cloudbridge/cloud/providers/gce/provider.py

@@ -2,18 +2,19 @@
 Provider implementation based on google-api-python-client library
 Provider implementation based on google-api-python-client library
 for GCE.
 for GCE.
 """
 """
-
-
-from cloudbridge.cloud.base import BaseCloudProvider
-import httplib2
 import json
 import json
 import os
 import os
 import re
 import re
-from string import Template
 import time
 import time
+from string import Template
+
+from cloudbridge.cloud.base import BaseCloudProvider
 
 
-from googleapiclient import discovery
 import googleapiclient.http
 import googleapiclient.http
+from googleapiclient import discovery
+
+import httplib2
+
 from oauth2client.client import GoogleCredentials
 from oauth2client.client import GoogleCredentials
 from oauth2client.service_account import ServiceAccountCredentials
 from oauth2client.service_account import ServiceAccountCredentials
 
 
@@ -21,6 +22,7 @@ from .services import GCEBlockStoreService
 from .services import GCEComputeService
 from .services import GCEComputeService
 from .services import GCENetworkService
 from .services import GCENetworkService
 from .services import GCESecurityService
 from .services import GCESecurityService
+from .services import GCSObjectStoreService
 
 
 
 
 class GCPResourceUrl(object):
 class GCPResourceUrl(object):
@@ -164,6 +166,7 @@ class GCECloudProvider(BaseCloudProvider):
         self._security = GCESecurityService(self)
         self._security = GCESecurityService(self)
         self._network = GCENetworkService(self)
         self._network = GCENetworkService(self)
         self._block_store = GCEBlockStoreService(self)
         self._block_store = GCEBlockStoreService(self)
+        self._object_store = GCSObjectStoreService(self)
 
 
         self._compute_resources = GCPResources(self.gce_compute)
         self._compute_resources = GCPResources(self.gce_compute)
         self._storage_resources = GCPResources(self.gcp_storage)
         self._storage_resources = GCPResources(self.gcp_storage)
@@ -186,8 +189,7 @@ class GCECloudProvider(BaseCloudProvider):
 
 
     @property
     @property
     def object_store(self):
     def object_store(self):
-        raise NotImplementedError(
-            "GCECloudProvider does not implement this service")
+        return self._object_store
 
 
     @property
     @property
     def gce_compute(self):
     def gce_compute(self):
@@ -228,12 +230,6 @@ class GCECloudProvider(BaseCloudProvider):
         response = request.execute()
         response = request.execute()
         return response
         return response
 
 
-    def wait_for_global_operation(self, operation):
-        while True:
-            result = self.gce_compute.globalOperations().get(
-                project=self.project_name,
-                operation=operation['name']).execute()
-
     def _connect_gcp_storage(self):
     def _connect_gcp_storage(self):
         return discovery.build('storage', 'v1', credentials=self._credentials)
         return discovery.build('storage', 'v1', credentials=self._credentials)
 
 

+ 499 - 217
cloudbridge/cloud/providers/gce/resources.py

@@ -1,7 +1,17 @@
 """
 """
 DataTypes used by this provider
 DataTypes used by this provider
 """
 """
+import hashlib
+import inspect
+import io
+import json
+import re
+import uuid
+
+import cloudbridge as cb
 from cloudbridge.cloud.base.resources import BaseAttachmentInfo
 from cloudbridge.cloud.base.resources import BaseAttachmentInfo
+from cloudbridge.cloud.base.resources import BaseBucket
+from cloudbridge.cloud.base.resources import BaseBucketObject
 from cloudbridge.cloud.base.resources import BaseFloatingIP
 from cloudbridge.cloud.base.resources import BaseFloatingIP
 from cloudbridge.cloud.base.resources import BaseInstance
 from cloudbridge.cloud.base.resources import BaseInstance
 from cloudbridge.cloud.base.resources import BaseInstanceType
 from cloudbridge.cloud.base.resources import BaseInstanceType
@@ -10,16 +20,21 @@ from cloudbridge.cloud.base.resources import BaseMachineImage
 from cloudbridge.cloud.base.resources import BaseNetwork
 from cloudbridge.cloud.base.resources import BaseNetwork
 from cloudbridge.cloud.base.resources import BasePlacementZone
 from cloudbridge.cloud.base.resources import BasePlacementZone
 from cloudbridge.cloud.base.resources import BaseRegion
 from cloudbridge.cloud.base.resources import BaseRegion
+from cloudbridge.cloud.base.resources import BaseRouter
 from cloudbridge.cloud.base.resources import BaseSecurityGroup
 from cloudbridge.cloud.base.resources import BaseSecurityGroup
 from cloudbridge.cloud.base.resources import BaseSecurityGroupRule
 from cloudbridge.cloud.base.resources import BaseSecurityGroupRule
 from cloudbridge.cloud.base.resources import BaseSnapshot
 from cloudbridge.cloud.base.resources import BaseSnapshot
+from cloudbridge.cloud.base.resources import BaseSubnet
 from cloudbridge.cloud.base.resources import BaseVolume
 from cloudbridge.cloud.base.resources import BaseVolume
+from cloudbridge.cloud.base.resources import ServerPagedResultList
 from cloudbridge.cloud.interfaces.resources import InstanceState
 from cloudbridge.cloud.interfaces.resources import InstanceState
 from cloudbridge.cloud.interfaces.resources import MachineImageState
 from cloudbridge.cloud.interfaces.resources import MachineImageState
+from cloudbridge.cloud.interfaces.resources import RouterState
 from cloudbridge.cloud.interfaces.resources import SnapshotState
 from cloudbridge.cloud.interfaces.resources import SnapshotState
 from cloudbridge.cloud.interfaces.resources import VolumeState
 from cloudbridge.cloud.interfaces.resources import VolumeState
+from cloudbridge.cloud.providers.gce import helpers
 
 
-import cloudbridge as cb
+import googleapiclient
 
 
 # Older versions of Python do not have a built-in set data-structure.
 # Older versions of Python do not have a built-in set data-structure.
 try:
 try:
@@ -27,12 +42,6 @@ try:
 except NameError:
 except NameError:
     from sets import Set as set
     from sets import Set as set
 
 
-import hashlib
-import inspect
-import json
-import re
-import uuid
-
 
 
 class GCEKeyPair(BaseKeyPair):
 class GCEKeyPair(BaseKeyPair):
 
 
@@ -187,8 +196,11 @@ class GCERegion(BaseRegion):
         """
         """
         Accesss information about placement zones within this region.
         Accesss information about placement zones within this region.
         """
         """
-        zones_response = self._provider.gce_compute.zones().list(
-            project=self._provider.project_name).execute()
+        zones_response = (self._provider
+                              .gce_compute
+                              .zones()
+                              .list(project=self._provider.project_name)
+                              .execute())
         zones = [zone for zone in zones_response['items']
         zones = [zone for zone in zones_response['items']
                  if zone['region'] == self._gce_region['selfLink']]
                  if zone['region'] == self._gce_region['selfLink']]
         return [GCEPlacementZone(self._provider, zone['name'], self.name)
         return [GCEPlacementZone(self._provider, zone['name'], self.name)
@@ -285,11 +297,12 @@ class GCEFirewallsDelegate(object):
             firewall['sourceTags'] = [source_tag]
             firewall['sourceTags'] = [source_tag]
         project_name = self._provider.project_name
         project_name = self._provider.project_name
         try:
         try:
-            response = (self._provider.gce_compute
-                                      .firewalls()
-                                      .insert(project=project_name,
-                                              body=firewall)
-                                      .execute())
+            response = (self._provider
+                            .gce_compute
+                            .firewalls()
+                            .insert(project=project_name,
+                                    body=firewall)
+                            .execute())
             self._provider.wait_for_operation(response)
             self._provider.wait_for_operation(response)
             # TODO: process the response and handle errors.
             # TODO: process the response and handle errors.
         except:
         except:
@@ -314,7 +327,8 @@ class GCEFirewallsDelegate(object):
             if not self._check_list_in_dict(firewall, 'sourceRanges',
             if not self._check_list_in_dict(firewall, 'sourceRanges',
                                             source_range):
                                             source_range):
                 continue
                 continue
-            if not self._check_list_in_dict(firewall, 'sourceTags', source_tag):
+            if not self._check_list_in_dict(firewall, 'sourceTags',
+                                            source_tag):
                 continue
                 continue
             return firewall['id']
             return firewall['id']
         return None
         return None
@@ -328,7 +342,7 @@ class GCEFirewallsDelegate(object):
             if firewall['id'] != firewall_id:
             if firewall['id'] != firewall_id:
                 continue
                 continue
             if ('sourceRanges' in firewall and
             if ('sourceRanges' in firewall and
-                len(firewall['sourceRanges']) == 1):
+                    len(firewall['sourceRanges']) == 1):
                 info['source_range'] = firewall['sourceRanges'][0]
                 info['source_range'] = firewall['sourceRanges'][0]
             if 'sourceTags' in firewall and len(firewall['sourceTags']) == 1:
             if 'sourceTags' in firewall and len(firewall['sourceTags']) == 1:
                 info['source_tag'] = firewall['sourceTags'][0]
                 info['source_tag'] = firewall['sourceTags'][0]
@@ -337,7 +351,7 @@ class GCEFirewallsDelegate(object):
             if 'IPProtocol' in firewall['allowed'][0]:
             if 'IPProtocol' in firewall['allowed'][0]:
                 info['ip_protocol'] = firewall['allowed'][0]['IPProtocol']
                 info['ip_protocol'] = firewall['allowed'][0]['IPProtocol']
             if ('ports' in firewall['allowed'][0] and
             if ('ports' in firewall['allowed'][0] and
-                len(firewall['allowed'][0]['ports']) == 1):
+                    len(firewall['allowed'][0]['ports']) == 1):
                 info['port'] = firewall['allowed'][0]['ports'][0]
                 info['port'] = firewall['allowed'][0]['ports'][0]
             info['network_name'] = self.network_name(firewall)
             info['network_name'] = self.network_name(firewall)
             return info
             return info
@@ -362,7 +376,8 @@ class GCEFirewallsDelegate(object):
         if 'items' not in self._list_response:
         if 'items' not in self._list_response:
             return
             return
         for firewall in self._list_response['items']:
         for firewall in self._list_response['items']:
-            if 'targetTags' not in firewall or len(firewall['targetTags']) != 1:
+            if ('targetTags' not in firewall or
+                    len(firewall['targetTags']) != 1):
                 continue
                 continue
             if 'allowed' not in firewall or len(firewall['allowed']) != 1:
             if 'allowed' not in firewall or len(firewall['allowed']) != 1:
                 continue
                 continue
@@ -381,11 +396,12 @@ class GCEFirewallsDelegate(object):
         """
         """
         project_name = self._provider.project_name
         project_name = self._provider.project_name
         try:
         try:
-            response = (self._provider.gce_compute
-                                      .firewalls()
-                                      .delete(project=project_name,
-                                              firewall=firewall['name'])
-                                      .execute())
+            response = (self._provider
+                            .gce_compute
+                            .firewalls()
+                            .delete(project=project_name,
+                                    firewall=firewall['name'])
+                            .execute())
             self._provider.wait_for_operation(response)
             self._provider.wait_for_operation(response)
         except:
         except:
             return False
             return False
@@ -396,11 +412,9 @@ class GCEFirewallsDelegate(object):
         """
         """
         Sync the local cache of all firewalls with the server.
         Sync the local cache of all firewalls with the server.
         """
         """
-        self._list_response = (
-                self._provider.gce_compute
-                              .firewalls()
-                              .list(project=self._provider.project_name)
-                              .execute())
+        self._list_response = list(
+                helpers.iter_all(self._provider.gce_compute.firewalls(),
+                                 project=self._provider.project_name))
 
 
     def _check_list_in_dict(self, dictionary, field_name, value):
     def _check_list_in_dict(self, dictionary, field_name, value):
         """
         """
@@ -408,9 +422,8 @@ class GCEFirewallsDelegate(object):
         """
         """
         if field_name not in dictionary:
         if field_name not in dictionary:
             return value is None
             return value is None
-        if (value is None or
-            len(dictionary[field_name]) != 1 or
-            dictionary[field_name][0] != value):
+        if (value is None or len(dictionary[field_name]) != 1 or
+                dictionary[field_name][0] != value):
             return False
             return False
         return True
         return True
 
 
@@ -532,7 +545,7 @@ class GCESecurityGroupRule(BaseSecurityGroupRule):
             return None
             return None
         if 'target_tag' not in info or info['network_name'] is None:
         if 'target_tag' not in info or info['network_name'] is None:
             return None
             return None
-        network = delegate.network.get_by_name(info['network_name'])
+        network = self._delegate.network.get_by_name(info['network_name'])
         if network is None:
         if network is None:
             return None
             return None
         return GCESecurityGroup(self._delegate, info['target_tag'], network)
         return GCESecurityGroup(self._delegate, info['target_tag'], network)
@@ -666,9 +679,12 @@ class GCEMachineImage(BaseMachineImage):
         """
         """
         Delete this image
         Delete this image
         """
         """
-        request = self._provider.gce_compute.images().delete(
-            project=self._provider.project_name, image=self.name)
-        request.execute()
+        (self._provider
+             .gce_compute
+             .images()
+             .delete(project=self._provider.project_name,
+                     image=self.name)
+             .execute())
 
 
     @property
     @property
     def state(self):
     def state(self):
@@ -689,11 +705,11 @@ class GCEMachineImage(BaseMachineImage):
             cb.log.warning("Project name is not found.")
             cb.log.warning("Project name is not found.")
             return
             return
         try:
         try:
-            response = self._provider.gce_compute \
-                                  .images() \
-                                  .get(project=project,
-                                       image=self.name) \
-                                  .execute()
+            response = (self._provider
+                            .gce_compute
+                            .images()
+                            .get(project=project, image=self.name)
+                            .execute())
             if response:
             if response:
                 # pylint:disable=protected-access
                 # pylint:disable=protected-access
                 self._gce_image = response
                 self._gce_image = response
@@ -767,10 +783,10 @@ class GCEInstance(BaseInstance):
             access_configs = network_interfaces[0].get('accessConfigs')
             access_configs = network_interfaces[0].get('accessConfigs')
             if access_configs is not None and len(access_configs) > 0:
             if access_configs is not None and len(access_configs) > 0:
                 # https://cloud.google.com/compute/docs/reference/beta/instances
                 # https://cloud.google.com/compute/docs/reference/beta/instances
-                # An array of configurations for this interface. Currently, only
-                # one access config, ONE_TO_ONE_NAT, is supported. If there are
-                # no accessConfigs specified, then this instance will have no
-                # external internet access.
+                # An array of configurations for this interface. Currently,
+                # only one access config, ONE_TO_ONE_NAT, is supported. If
+                # there are no accessConfigs specified, then this instance will
+                # have no external internet access.
                 access_config = access_configs[0]
                 access_config = access_configs[0]
                 if 'natIP' in access_config:
                 if 'natIP' in access_config:
                     ips.append(access_config['natIP'])
                     ips.append(access_config['natIP'])
@@ -820,41 +836,45 @@ class GCEInstance(BaseInstance):
         Reboot this instance.
         Reboot this instance.
         """
         """
         if self.state == InstanceState.STOPPED:
         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()
+            (self._provider
+                 .gce_compute
+                 .instances()
+                 .start(project=self._provider.project_name,
+                        zone=self._provider.default_zone,
+                        instance=self.name)
+                 .execute())
         else:
         else:
-            self._provider.gce_compute \
-                          .instances() \
-                          .reset(project=self._provider.project_name,
-                                 zone=self._provider.default_zone,
-                                 instance=self.name) \
-                          .execute()
+            (self._provider
+                 .gce_compute
+                 .instances()
+                 .reset(project=self._provider.project_name,
+                        zone=self._provider.default_zone,
+                        instance=self.name)
+                 .execute())
 
 
     def terminate(self):
     def terminate(self):
         """
         """
         Permanently terminate this instance.
         Permanently terminate this instance.
         """
         """
-        self._provider.gce_compute \
-                      .instances() \
-                      .delete(project=self._provider.project_name,
-                              zone=self._provider.default_zone,
-                              instance=self.name) \
-                      .execute()
+        (self._provider
+             .gce_compute
+             .instances()
+             .delete(project=self._provider.project_name,
+                     zone=self._provider.default_zone,
+                     instance=self.name)
+             .execute())
 
 
     def stop(self):
     def stop(self):
         """
         """
         Stop this instance.
         Stop this instance.
         """
         """
-        self._provider.gce_compute \
-                      .instances() \
-                      .stop(project=self._provider.project_name,
-                            zone=self._provider.default_zone,
-                            instance=self.name) \
-                      .execute()
+        (self._provider
+             .gce_compute
+             .instances()
+             .stop(project=self._provider.project_name,
+                   zone=self._provider.default_zone,
+                   instance=self.name)
+             .execute())
 
 
     @property
     @property
     def image_id(self):
     def image_id(self):
@@ -927,15 +947,10 @@ class GCEInstance(BaseInstance):
         """
         """
         self_url = self._provider.parse_url(self._gce_instance['selfLink'])
         self_url = self._provider.parse_url(self._gce_instance['selfLink'])
         try:
         try:
-            response = (self._provider
-                .gce_compute
-                .targetInstances()
-                .list(project=self_url.parameters['project'],
-                      zone=self_url.parameters['zone'])
-                .execute())
-            if 'items' not in response:
-                return None
-            for target_instance in response['items']:
+            for target_instance in helpers.iter_all(
+                    self._provider.gce_compute.targetInstances(),
+                    project=self_url.parameters['project'],
+                    zone=self_url.parameters['zone']):
                 url = self._provider.parse_url(target_instance['instance'])
                 url = self._provider.parse_url(target_instance['instance'])
                 if url.parameters['instance'] == self.name:
                 if url.parameters['instance'] == self.name:
                     return target_instance
                     return target_instance
@@ -959,16 +974,17 @@ class GCEInstance(BaseInstance):
                 'instance': self._gce_instance['selfLink']}
                 'instance': self._gce_instance['selfLink']}
         try:
         try:
             response = (self._provider
             response = (self._provider
-                .gce_compute
-                .targetInstances()
-                .insert(project=self_url.parameters['project'],
-                        zone=self_url.parameters['zone'],
-                        body=body)
-                .execute())
+                            .gce_compute
+                            .targetInstances()
+                            .insert(project=self_url.parameters['project'],
+                                    zone=self_url.parameters['zone'],
+                                    body=body)
+                            .execute())
             self._provider.wait_for_operation(
             self._provider.wait_for_operation(
                 response, zone=self_url.parameters['zone'])
                 response, zone=self_url.parameters['zone'])
         except Exception as e:
         except Exception as e:
-            cb.log.warning('Exception while inserting a target instance: %s', e)
+            cb.log.warning('Exception while inserting a target instance: %s',
+                           e)
             return None
             return None
 
 
         # The following method should find the target instance that we
         # The following method should find the target instance that we
@@ -984,32 +1000,28 @@ class GCEInstance(BaseInstance):
         new_name = target_instance['name']
         new_name = target_instance['name']
         new_url = target_instance['selfLink']
         new_url = target_instance['selfLink']
         try:
         try:
-            response = (self._provider.gce_compute
-                                      .forwardingRules()
-                                      .list(project=self._provider.project_name,
-                                            region=ip.region)
-                                      .execute())
-            if 'items' not in response:
-                return False
-            for rule in response['items']:
-                if rule['IPAddress'] == ip.public_ip:
-                    parsed_target_url = self._provider.parse_url(rule['target'])
-                    old_zone = parsed_target_url.parameters['zone']
-                    old_name = parsed_target_url.parameters['targetInstance']
-                    if old_zone == new_zone and old_name == new_name:
-                        return True
-                    response = (self._provider
-                                    .gce_compute
-                                    .forwardingRules()
-                                    .setTarget(
-                                        project=self._provider.project_name,
-                                        region=ip.region,
-                                        forwardingRule=rule['name'],
-                                        body={'target': new_url})
-                                    .execute())
-                    self._provider.wait_for_operation(response,
-                                                      region=ip.region)
+            for rule in helpers.iter_all(
+                    self._provider.gce_compute.forwardingRules(),
+                    project=self._provider.project_name,
+                    region=ip.region):
+                if rule['IPAddress'] != ip.public_ip:
+                    continue
+                parsed_target_url = self._provider.parse_url(rule['target'])
+                old_zone = parsed_target_url.parameters['zone']
+                old_name = parsed_target_url.parameters['targetInstance']
+                if old_zone == new_zone and old_name == new_name:
                     return True
                     return True
+                response = (self._provider
+                                .gce_compute
+                                .forwardingRules()
+                                .setTarget(
+                                    project=self._provider.project_name,
+                                    region=ip.region,
+                                    forwardingRule=rule['name'],
+                                    body={'target': new_url})
+                                .execute())
+                self._provider.wait_for_operation(response, region=ip.region)
+                return True
         except Exception as e:
         except Exception as e:
             cb.log.warning(
             cb.log.warning(
                 'Exception while listing/changing forwarding rules: %s', e)
                 'Exception while listing/changing forwarding rules: %s', e)
@@ -1028,16 +1040,17 @@ class GCEInstance(BaseInstance):
                 'IPAddress': ip.public_ip,
                 'IPAddress': ip.public_ip,
                 'target': target_instance['selfLink']}
                 'target': target_instance['selfLink']}
         try:
         try:
-            response = (self._provider.gce_compute
-                                      .forwardingRules()
-                                      .insert(
-                                          project=self._provider.project_name,
-                                          region=ip.region,
-                                          body=body)
-                                      .execute())
+            response = (self._provider
+                            .gce_compute
+                            .forwardingRules()
+                            .insert(project=self._provider.project_name,
+                                    region=ip.region,
+                                    body=body)
+                            .execute())
             self._provider.wait_for_operation(response, region=ip.region)
             self._provider.wait_for_operation(response, region=ip.region)
         except Exception as e:
         except Exception as e:
-            cb.log.warning('Exception while inserting a forwarding rule: %s', e)
+            cb.log.warning('Exception while inserting a forwarding rule: %s',
+                           e)
             return False
             return False
         return True
         return True
 
 
@@ -1049,38 +1062,36 @@ class GCEInstance(BaseInstance):
                               .parameters['zone'])
                               .parameters['zone'])
         name = target_instance['name']
         name = target_instance['name']
         try:
         try:
-            response = (self._provider.gce_compute
-                                      .forwardingRules()
-                                      .list(project=self._provider.project_name,
-                                            region=ip.region)
-                                      .execute())
-            if 'items' not in response:
-                return False
-            for rule in response['items']:
-                if rule['IPAddress'] == ip.public_ip:
-                    parsed_target_url = self._provider.parse_url(rule['target'])
-                    temp_zone = parsed_target_url.parameters['zone']
-                    temp_name = parsed_target_url.parameters['targetInstance']
-                    if temp_zone != zone or temp_name != name:
-                        cb.log.warning('"%s" is forwarded to "%s" in zone "%s"',
-                                       ip.public_ip, temp_name, temp_zone)
-                        return False
-                    response = (self._provider
-                                    .gce_compute
-                                    .forwardingRules()
-                                    .delete(
-                                        project=self._provider.project_name,
-                                        region=ip.region,
-                                        forwardingRule=rule['name'])
-                                    .execute())
-                    self._provider.wait_for_operation(response,
-                                                      region=ip.region)
+            for rule in helpers.iter_all(
+                    self._provider.gce_compute.forwardingRules(),
+                    project=self._provider.project_name,
+                    region=ip.region):
+                if rule['IPAddress'] != ip.public_ip:
+                    continue
+                parsed_target_url = self._provider.parse_url(rule['target'])
+                temp_zone = parsed_target_url.parameters['zone']
+                temp_name = parsed_target_url.parameters['targetInstance']
+                if temp_zone != zone or temp_name != name:
+                    cb.log.warning(
+                            '"%s" is forwarded to "%s" in zone "%s"',
+                            ip.public_ip, temp_name, temp_zone)
+                    return False
+                response = (self._provider
+                                .gce_compute
+                                .forwardingRules()
+                                .delete(
+                                    project=self._provider.project_name,
+                                    region=ip.region,
+                                    forwardingRule=rule['name'])
+                                .execute())
+                self._provider.wait_for_operation(response, region=ip.region)
+                return True
         except Exception as e:
         except Exception as e:
             cb.log.warning(
             cb.log.warning(
                 'Exception while listing/deleting forwarding rules: %s', e)
                 'Exception while listing/deleting forwarding rules: %s', e)
             return False
             return False
         return True
         return True
-        
+
     def add_floating_ip(self, ip_address):
     def add_floating_ip(self, ip_address):
         """
         """
         Add an elastic IP address to this instance.
         Add an elastic IP address to this instance.
@@ -1095,8 +1106,9 @@ class GCEInstance(BaseInstance):
                     return
                     return
                 target_instance = self._get_target_instance()
                 target_instance = self._get_target_instance()
                 if not target_instance:
                 if not target_instance:
-                    cb.log.warning('Could not create a targetInstance for "%s"',
-                                   self.name)
+                    cb.log.warning(
+                            'Could not create a targetInstance for "%s"',
+                            self.name)
                     return
                     return
                 if not self._forward(ip, target_instance):
                 if not self._forward(ip, target_instance):
                     cb.log.warning('Could not forward "%s" to "%s"',
                     cb.log.warning('Could not forward "%s" to "%s"',
@@ -1113,7 +1125,7 @@ class GCEInstance(BaseInstance):
                 if not ip.in_use() or ip.private_ip not in self.private_ips:
                 if not ip.in_use() or ip.private_ip not in self.private_ips:
                     cb.log.warning(
                     cb.log.warning(
                         'Floating IP "%s" is not associated to "%s".',
                         'Floating IP "%s" is not associated to "%s".',
-                         ip_address, self.name)
+                        ip_address, self.name)
                     return
                     return
                 target_instance = self._get_target_instance()
                 target_instance = self._get_target_instance()
                 if not target_instance:
                 if not target_instance:
@@ -1142,6 +1154,7 @@ class GCEInstance(BaseInstance):
         self._gce_instance = self._provider.get_gce_resource_data(
         self._gce_instance = self._provider.get_gce_resource_data(
             self._gce_instance.get('selfLink'))
             self._gce_instance.get('selfLink'))
 
 
+
 class GCENetwork(BaseNetwork):
 class GCENetwork(BaseNetwork):
 
 
     def __init__(self, provider, network):
     def __init__(self, provider, network):
@@ -1175,11 +1188,11 @@ class GCENetwork(BaseNetwork):
     def delete(self):
     def delete(self):
         try:
         try:
             response = (self._provider
             response = (self._provider
-                    .gce_compute
-                    .networks()
-                    .delete(project=self._provider.project_name,
-                            network=self.name)
-                    .execute())
+                            .gce_compute
+                            .networks()
+                            .delete(project=self._provider.project_name,
+                                    network=self.name)
+                            .execute())
             if 'error' in response:
             if 'error' in response:
                 return False
                 return False
             self._provider.wait_for_operation(response)
             self._provider.wait_for_operation(response)
@@ -1188,14 +1201,15 @@ class GCENetwork(BaseNetwork):
         return True
         return True
 
 
     def subnets(self):
     def subnets(self):
-        raise NotImplementedError("To be implemented")
+        return self._provider.network.subnets.list()
 
 
     def create_subnet(self, cidr_block, name=None):
     def create_subnet(self, cidr_block, name=None):
-        raise NotImplementedError("To be implemented")
+        return self._provider.network.subnets.create(self, cidr_block, name)
 
 
     def refresh(self):
     def refresh(self):
         return self.state
         return self.state
 
 
+
 class GCEFloatingIP(BaseFloatingIP):
 class GCEFloatingIP(BaseFloatingIP):
     _DEAD_INSTANCE = 'dead instance'
     _DEAD_INSTANCE = 'dead instance'
 
 
@@ -1223,9 +1237,9 @@ class GCEFloatingIP(BaseFloatingIP):
                 if target['kind'] == 'compute#targetInstance':
                 if target['kind'] == 'compute#targetInstance':
                     url = provider.parse_url(target['instance'])
                     url = provider.parse_url(target['instance'])
                     try:
                     try:
-                      self._target_instance = url.get()
+                        self._target_instance = url.get()
                     except:
                     except:
-                      self._target_instance = GCEFloatingIP._DEAD_INSTANCE
+                        self._target_instance = GCEFloatingIP._DEAD_INSTANCE
                 else:
                 else:
                     cb.log.warning('Address "%s" is forwarded to a %s',
                     cb.log.warning('Address "%s" is forwarded to a %s',
                                    floating_ip['address'], target['kind'])
                                    floating_ip['address'], target['kind'])
@@ -1248,7 +1262,7 @@ class GCEFloatingIP(BaseFloatingIP):
     @property
     @property
     def private_ip(self):
     def private_ip(self):
         if (not self._target_instance or
         if (not self._target_instance or
-            self._target_instance == GCEFloatingIP._DEAD_INSTANCE):
+                self._target_instance == GCEFloatingIP._DEAD_INSTANCE):
             return None
             return None
         return self._target_instance['networkInterfaces'][0]['networkIP']
         return self._target_instance['networkInterfaces'][0]['networkIP']
 
 
@@ -1256,25 +1270,125 @@ class GCEFloatingIP(BaseFloatingIP):
         return True if self._target_instance else False
         return True if self._target_instance else False
 
 
     def delete(self):
     def delete(self):
-       project_name = self._provider.project_name
-       # First, delete the forwarding rule, if there is any.
-       if self._rule:
-           response = (self._provider.gce_compute
-                                     .forwardingRules()
-                                     .delete(project=project_name,
-                                             region=self._region,
-                                             forwardingRule=self._rule['name'])
-                                     .execute())
-           self._provider.wait_for_operation(response, region=self._region)
-
-       # Release the address.
-       response = (self._provider.gce_compute
-                                 .addresses()
-                                 .delete(project=project_name,
-                                         region=self._region,
-                                         address=self._ip['name'])
-                                 .execute())
-       self._provider.wait_for_operation(response, region=self._region)
+        project_name = self._provider.project_name
+        # First, delete the forwarding rule, if there is any.
+        if self._rule:
+            response = (self._provider
+                            .gce_compute
+                            .forwardingRules()
+                            .delete(project=project_name,
+                                    region=self._region,
+                                    forwardingRule=self._rule['name'])
+                            .execute())
+            self._provider.wait_for_operation(response, region=self._region)
+
+        # Release the address.
+        response = (self._provider
+                        .gce_compute
+                        .addresses()
+                        .delete(project=project_name,
+                                region=self._region,
+                                address=self._ip['name'])
+                        .execute())
+        self._provider.wait_for_operation(response, region=self._region)
+
+
+class GCERouter(BaseRouter):
+
+    def __init__(self, provider, router):
+        super(GCERouter, self).__init__(provider)
+        self._router = router
+
+    @property
+    def id(self):
+        return self._router['id']
+
+    @property
+    def name(self):
+        return self._router['name']
+
+    def refresh(self):
+        self._router = self._provider.parse_url(self._router['selfLink']).get()
+
+    @property
+    def state(self):
+        # GCE routers are always attached to a network.
+        return RouterState.ATTACHED
+
+    @property
+    def network_id(self):
+        network = self._provider.parse_url(self._router['network']).get()
+        return network['id']
+
+    def delete(self):
+        response = (self._provider
+                        .gce_compute
+                        .routers()
+                        .delete(project=self._provider.project_name,
+                                region=self._router['region'],
+                                router=self._router['name'])
+                        .execute())
+        self._provider.wait_for_operation(response,
+                                          region=self._router['region'])
+
+    def attach_network(self, network_id):
+        if network_id == self.network_id:
+            return
+        cb.log.warning('GCE routers should be attached at creation time')
+
+    def detach_network(self, network_id):
+        cb.log.warning('GCE routers are always attached')
+
+    def add_route(self, subnet_id):
+        cb.log.warning('Not implemented')
+
+    def remove_route(self, subnet_id):
+        cb.log.warning('Not implemented')
+
+
+class GCESubnet(BaseSubnet):
+
+    def __init__(self, provider, subnet):
+        super(GCESubnet, self).__init__(provider)
+        self._subnet = subnet
+
+    @property
+    def id(self):
+        return self._subnet['id']
+
+    @property
+    def name(self):
+        return self._subnet['name']
+
+    @name.setter
+    def name(self, value):
+        if value == self.name:
+            return
+        cb.log.warning('Cannot change the name of a GCE subnetwork')
+
+    @property
+    def cidr_block(self):
+        return self._subnet['ipCidrRange']
+
+    @property
+    def network_url(self):
+        return self._subnet['network']
+
+    @property
+    def network_id(self):
+        return self._provider.parse_url(self.network_url).get()['id']
+
+    @property
+    def region(self):
+        return self._subnet['region']
+
+    @property
+    def zone(self):
+        raise NotImplementedError('To be implemented')
+
+    @property
+    def delete(self):
+        return self._provider.network.subnets.delete(self)
 
 
 
 
 class GCEVolume(BaseVolume):
 class GCEVolume(BaseVolume):
@@ -1319,21 +1433,22 @@ class GCEVolume(BaseVolume):
     @description.setter
     @description.setter
     def description(self, value):
     def description(self, value):
         request_body = {
         request_body = {
-            'labels': {'description': value.replace(' ', '_').lower(),},
+            'labels': {'description': value.replace(' ', '_').lower()},
             'labelFingerprint': self._volume.get('labelFingerprint'),
             'labelFingerprint': self._volume.get('labelFingerprint'),
         }
         }
         try:
         try:
-            response = (self._provider.gce_compute
-                        .disks()
-                        .setLabels(
-                            project=self._provider.project_name,
+            (self._provider
+                 .gce_compute
+                 .disks()
+                 .setLabels(project=self._provider.project_name,
                             zone=self._provider.default_zone,
                             zone=self._provider.default_zone,
                             resource=self.name,
                             resource=self.name,
-                            body=request_body).execute())
+                            body=request_body)
+                 .execute())
         except Exception as e:
         except Exception as e:
-            cb.log.warning('Exception while setting volume description: %s.'
-                           'Check for invalid characters in description. Should'
-                           'confirm to RFC1035.', e)
+            cb.log.warning('Exception while setting volume description: %s. '
+                           'Check for invalid characters in description. '
+                           'Should confirm to RFC1035.', e)
             raise e
             raise e
         self.refresh()
         self.refresh()
 
 
@@ -1361,7 +1476,7 @@ class GCEVolume(BaseVolume):
         # the first user of a disk.
         # the first user of a disk.
         if 'users' in self._volume and len(self._volume) > 0:
         if 'users' in self._volume and len(self._volume) > 0:
             if len(self._volume) > 1:
             if len(self._volume) > 1:
-                cb.log.warning("This volume is attached to multiple instances.")
+                cb.log.warning("This volume is attached to multiple instances")
             return BaseAttachmentInfo(self,
             return BaseAttachmentInfo(self,
                                       self._volume.get('users')[0],
                                       self._volume.get('users')[0],
                                       None)
                                       None)
@@ -1386,13 +1501,14 @@ class GCEVolume(BaseVolume):
         instance_name = instance.name if isinstance(
         instance_name = instance.name if isinstance(
             instance,
             instance,
             GCEInstance) else instance
             GCEInstance) else instance
-        response = (self._provider.gce_compute
-                        .instances()
-                        .attachDisk(
-                            project=self._provider.project_name,
-                            zone=self._provider.default_zone,
-                            instance=instance_name,
-                            body=attach_disk_body).execute())
+        (self._provider
+             .gce_compute
+             .instances()
+             .attachDisk(project=self._provider.project_name,
+                         zone=self._provider.default_zone,
+                         instance=instance_name,
+                         body=attach_disk_body)
+             .execute())
 
 
     def detach(self, force=False):
     def detach(self, force=False):
         """
         """
@@ -1409,17 +1525,18 @@ class GCEVolume(BaseVolume):
         device_name = None
         device_name = None
         for disk in instance_data['disks']:
         for disk in instance_data['disks']:
             if ('source' in disk and 'deviceName' in disk and
             if ('source' in disk and 'deviceName' in disk and
-                disk['source'] == self.id):
+                    disk['source'] == self.id):
                 device_name = disk['deviceName']
                 device_name = disk['deviceName']
         if not device_name:
         if not device_name:
             return
             return
-        response = (self._provider.gce_compute
-                        .instances()
-                        .detachDisk(
-                            project=self._provider.project_name,
-                            zone=self._provider.default_zone,
-                            instance=instance_data.get('name'),
-                            deviceName=device_name).execute())
+        (self._provider
+             .gce_compute
+             .instances()
+             .detachDisk(project=self._provider.project_name,
+                         zone=self._provider.default_zone,
+                         instance=instance_data.get('name'),
+                         deviceName=device_name)
+             .execute())
 
 
     def create_snapshot(self, name, description=None):
     def create_snapshot(self, name, description=None):
         """
         """
@@ -1432,12 +1549,13 @@ class GCEVolume(BaseVolume):
         """
         """
         Delete this volume.
         Delete this volume.
         """
         """
-        response = (self._provider.gce_compute
-                        .disks()
-                        .delete(
-                            project=self._provider.project_name,
-                            zone=self._provider.default_zone,
-                            disk=self.name).execute())
+        (self._provider
+             .gce_compute
+             .disks()
+             .delete(project=self._provider.project_name,
+                     zone=self._provider.default_zone,
+                     disk=self.name)
+             .execute())
 
 
     @property
     @property
     def state(self):
     def state(self):
@@ -1520,11 +1638,12 @@ class GCESnapshot(BaseSnapshot):
         """
         """
         Delete this snapshot.
         Delete this snapshot.
         """
         """
-        response = (self._provider.gce_compute
-                        .snapshots()
-                        .delete(
-                            project=self._provider.project_name,
-                            snapshot=self.name).execute())
+        (self._provider
+             .gce_compute
+             .snapshots()
+             .delete(project=self._provider.project_name,
+                     snapshot=self.name)
+             .execute())
 
 
     def create_volume(self, placement, size=None, volume_type=None, iops=None):
     def create_volume(self, placement, size=None, volume_type=None, iops=None):
         """
         """
@@ -1548,11 +1667,174 @@ class GCESnapshot(BaseSnapshot):
             'type': vol_type,
             'type': vol_type,
             'sourceSnapshot': self.id
             'sourceSnapshot': self.id
         }
         }
-        operation = (self._provider.gce_compute
+        operation = (self._provider
+                         .gce_compute
                          .disks()
                          .disks()
-                         .insert(
-                             project=self._provider.project_name,
-                             zone=placement,
-                             body=disk_body).execute())
+                         .insert(project=self._provider.project_name,
+                                 zone=placement,
+                                 body=disk_body)
+                         .execute())
         return self._provider.block_store.volumes.get(
         return self._provider.block_store.volumes.get(
             operation.get('targetLink'))
             operation.get('targetLink'))
+
+
+class GCSObject(BaseBucketObject):
+
+    def __init__(self, provider, bucket, obj):
+        super(GCSObject, self).__init__(provider)
+        self._bucket = bucket
+        self._obj = obj
+
+    @property
+    def id(self):
+        return self._obj['id']
+
+    @property
+    def name(self):
+        return self._obj['name']
+
+    @property
+    def size(self):
+        return self._obj['size']
+
+    @property
+    def last_modified(self):
+        return self._obj['updated']
+
+    def iter_content(self):
+        return io.BytesIO(self._provider
+                              .gcp_storage
+                              .objects()
+                              .get_media(bucket=self._obj['bucket'],
+                                         object=self.name)
+                              .execute())
+
+    def upload(self, data):
+        """
+        Set the contents of this object to the given text.
+        """
+        media_body = googleapiclient.http.MediaIoBaseUpload(
+                io.BytesIO(data), mimetype='plain/text')
+        response = self._bucket.create_object_with_media_body(self.name,
+                                                              media_body)
+        if response:
+            self._obj = response
+
+    def upload_from_file(self, path):
+        """
+        Upload a binary file.
+        """
+        with open(path, 'rb') as f:
+            media_body = googleapiclient.http.MediaIoBaseUpload(
+                    f, 'application/octet-stream')
+            response = self._bucket.create_object_with_media_body(self.name,
+                                                                  media_body)
+            if response:
+                self._obj = response
+
+    def delete(self):
+        (self._provider
+             .gcp_storage
+             .objects()
+             .delete(bucket=self._obj['bucket'], object=self.name)
+             .execute())
+
+    def generate_url(self, expires_in=0):
+        return self._obj['mediaLink']
+
+
+class GCSBucket(BaseBucket):
+
+    def __init__(self, provider, bucket):
+        super(GCSBucket, self).__init__(provider)
+        self._bucket = bucket
+
+    @property
+    def id(self):
+        return self._bucket['id']
+
+    @property
+    def name(self):
+        """
+        Get this bucket's name.
+        """
+        return self._bucket['name']
+
+    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:
+            return None
+
+    def list(self, limit=None, marker=None, prefix=None):
+        """
+        List all objects within this bucket.
+        """
+        max_result = limit if limit is not None and limit < 500 else 500
+        try:
+            response = (self._provider
+                            .gcp_storage
+                            .objects()
+                            .list(bucket=self.name,
+                                  prefix=prefix if prefix else '',
+                                  maxResults=max_result,
+                                  pageToken=marker)
+                            .execute())
+            if 'error' in response:
+                return ServerPagedResultList(False, None, False, data=[])
+            objects = []
+            for obj in response.get('items', []):
+                objects.append(GCSObject(self._provider, self, obj))
+            if len(objects) > max_result:
+                cb.log.warning('Expected at most %d results; got %d',
+                               max_result, len(objects))
+            return ServerPagedResultList('nextPageToken' in response,
+                                         response.get('nextPageToken'),
+                                         False, data=objects)
+        except:
+            return ServerPagedResultList(False, None, False, data=[])
+
+    def delete(self, delete_contents=False):
+        """
+        Delete this bucket.
+        """
+        (self._provider
+             .gcp_storage
+             .buckets()
+             .delete(bucket=self.name)
+             .execute())
+
+    def create_object(self, name):
+        """
+        Create an empty plain text object.
+        """
+        response = self.create_object_with_media_body(
+            name,
+            googleapiclient.http.MediaIoBaseUpload(
+                    io.BytesIO(''), mimetype='plain/text'))
+        return GCSObject(self._provider, self, response) if response else None
+
+    def create_object_with_media_body(self, name, media_body):
+        try:
+            response = (self._provider
+                            .gcp_storage
+                            .objects()
+                            .insert(bucket=self.name,
+                                    body={'name': name},
+                                    media_body=media_body)
+                            .execute())
+            if 'error' in response:
+                return None
+            return response
+        except:
+            return None

+ 359 - 111
cloudbridge/cloud/providers/gce/services.py

@@ -1,3 +1,8 @@
+import hashlib
+import uuid
+from collections import namedtuple
+
+import cloudbridge as cb
 from cloudbridge.cloud.base.resources import ClientPagedResultList
 from cloudbridge.cloud.base.resources import ClientPagedResultList
 from cloudbridge.cloud.base.resources import ServerPagedResultList
 from cloudbridge.cloud.base.resources import ServerPagedResultList
 from cloudbridge.cloud.base.services import BaseBlockStoreService
 from cloudbridge.cloud.base.services import BaseBlockStoreService
@@ -7,24 +12,20 @@ from cloudbridge.cloud.base.services import BaseInstanceService
 from cloudbridge.cloud.base.services import BaseInstanceTypesService
 from cloudbridge.cloud.base.services import BaseInstanceTypesService
 from cloudbridge.cloud.base.services import BaseKeyPairService
 from cloudbridge.cloud.base.services import BaseKeyPairService
 from cloudbridge.cloud.base.services import BaseNetworkService
 from cloudbridge.cloud.base.services import BaseNetworkService
+from cloudbridge.cloud.base.services import BaseObjectStoreService
 from cloudbridge.cloud.base.services import BaseRegionService
 from cloudbridge.cloud.base.services import BaseRegionService
 from cloudbridge.cloud.base.services import BaseSecurityGroupService
 from cloudbridge.cloud.base.services import BaseSecurityGroupService
 from cloudbridge.cloud.base.services import BaseSecurityService
 from cloudbridge.cloud.base.services import BaseSecurityService
 from cloudbridge.cloud.base.services import BaseSnapshotService
 from cloudbridge.cloud.base.services import BaseSnapshotService
+from cloudbridge.cloud.base.services import BaseSubnetService
 from cloudbridge.cloud.base.services import BaseVolumeService
 from cloudbridge.cloud.base.services import BaseVolumeService
 from cloudbridge.cloud.interfaces.resources import PlacementZone
 from cloudbridge.cloud.interfaces.resources import PlacementZone
 from cloudbridge.cloud.interfaces.resources import SecurityGroup
 from cloudbridge.cloud.interfaces.resources import SecurityGroup
 from cloudbridge.cloud.providers.gce import helpers
 from cloudbridge.cloud.providers.gce import helpers
-import cloudbridge as cb
 
 
-from collections import namedtuple
-import hashlib
 import googleapiclient
 import googleapiclient
 
 
 from retrying import retry
 from retrying import retry
-import sys
-
-import uuid
 
 
 from .resources import GCEFirewallsDelegate
 from .resources import GCEFirewallsDelegate
 from .resources import GCEFloatingIP
 from .resources import GCEFloatingIP
@@ -33,11 +34,14 @@ from .resources import GCEInstanceType
 from .resources import GCEKeyPair
 from .resources import GCEKeyPair
 from .resources import GCEMachineImage
 from .resources import GCEMachineImage
 from .resources import GCENetwork
 from .resources import GCENetwork
+from .resources import GCEPlacementZone
 from .resources import GCERegion
 from .resources import GCERegion
+from .resources import GCERouter
 from .resources import GCESecurityGroup
 from .resources import GCESecurityGroup
-from .resources import GCESecurityGroupRule
 from .resources import GCESnapshot
 from .resources import GCESnapshot
+from .resources import GCESubnet
 from .resources import GCEVolume
 from .resources import GCEVolume
+from .resources import GCSBucket
 
 
 
 
 class GCESecurityService(BaseSecurityService):
 class GCESecurityService(BaseSecurityService):
@@ -125,8 +129,8 @@ class GCEKeyPairService(BaseKeyPairService):
             # elems should be "ssh-rsa <public_key> <email>"
             # elems should be "ssh-rsa <public_key> <email>"
             elems = key.split(" ")
             elems = key.split(" ")
             if elems and elems[0]:  # ignore blank lines
             if elems and elems[0]:  # ignore blank lines
-                yield GCEKeyPairService.GCEKeyInfo(elems[0], elems[1].encode('ascii'),
-                                                   elems[2])
+                yield GCEKeyPairService.GCEKeyInfo(
+                        elems[0], elems[1].encode('ascii'), elems[2])
 
 
     def gce_metadata_save_op(self, callback):
     def gce_metadata_save_op(self, callback):
         """
         """
@@ -276,11 +280,12 @@ class GCEInstanceTypesService(BaseInstanceTypesService):
 
 
     @property
     @property
     def instance_data(self):
     def instance_data(self):
-        response = self.provider.gce_compute \
-                                .machineTypes() \
-                                .list(project=self.provider.project_name,
-                                      zone=self.provider.default_zone) \
-                                .execute()
+        response = (self.provider
+                        .gce_compute
+                        .machineTypes()
+                        .list(project=self.provider.project_name,
+                              zone=self.provider.default_zone)
+                        .execute())
         return response['items']
         return response['items']
 
 
     def get(self, instance_type_id):
     def get(self, instance_type_id):
@@ -318,11 +323,12 @@ class GCERegionService(BaseRegionService):
 
 
     def get(self, region_id):
     def get(self, region_id):
         try:
         try:
-            region = self.provider.gce_compute \
-                                  .regions() \
-                                  .get(project=self.provider.project_name,
-                                       region=region_id) \
-                                  .execute()
+            region = (self.provider
+                          .gce_compute
+                          .regions()
+                          .get(project=self.provider.project_name,
+                               region=region_id)
+                          .execute())
         # Handle the case when region_id is not valid
         # Handle the case when region_id is not valid
         except googleapiclient.errors.HttpError:
         except googleapiclient.errors.HttpError:
             return None
             return None
@@ -332,12 +338,22 @@ class GCERegionService(BaseRegionService):
             return None
             return None
 
 
     def list(self, limit=None, marker=None):
     def list(self, limit=None, marker=None):
-        regions_response = self.provider.gce_compute.regions().list(
-            project=self.provider.project_name).execute()
+        max_result = limit if limit is not None and limit < 500 else 500
+        regions_response = (self.provider
+                                .gce_compute
+                                .regions()
+                                .list(project=self.provider.project_name,
+                                      maxResults=max_result,
+                                      pageToken=marker)
+                                .execute())
         regions = [GCERegion(self.provider, region)
         regions = [GCERegion(self.provider, region)
                    for region in regions_response['items']]
                    for region in regions_response['items']]
-        return ClientPagedResultList(self.provider, regions,
-                                     limit=limit, marker=marker)
+        if len(regions) > max_result:
+            cb.log.warning('Expected at most %d results; got %d',
+                           max_result, len(regions))
+        return ServerPagedResultList('nextPageToken' in regions_response,
+                                     regions_response.get('nextPageToken'),
+                                     False, data=regions)
 
 
     @property
     @property
     def current(self):
     def current(self):
@@ -351,36 +367,33 @@ class GCEImageService(BaseImageService):
         self._public_images = None
         self._public_images = None
 
 
     _PUBLIC_IMAGE_PROJECTS = ['centos-cloud', 'coreos-cloud', 'debian-cloud',
     _PUBLIC_IMAGE_PROJECTS = ['centos-cloud', 'coreos-cloud', 'debian-cloud',
-                             'opensuse-cloud', 'ubuntu-os-cloud']
+                              'opensuse-cloud', 'ubuntu-os-cloud']
 
 
     def _retrieve_public_images(self):
     def _retrieve_public_images(self):
         if self._public_images is not None:
         if self._public_images is not None:
             return
             return
         self._public_images = []
         self._public_images = []
-        for project in GCEImageService._PUBLIC_IMAGE_PROJECTS:
-            try:
-                response = self.provider.gce_compute \
-                                        .images() \
-                                        .list(project=project) \
-                                        .execute()
-            except googleapiclient.errors.HttpError as http_error:
+        try:
+            for project in GCEImageService._PUBLIC_IMAGE_PROJECTS:
+                for image in helpers.iter_all(
+                        self.provider.gce_compute.images(), project=project):
+                    self._public_iamges.append(
+                        GCEMachineImage(self.provider, image))
+        except googleapiclient.errors.HttpError as http_error:
                 cb.log.warning("googleapiclient.errors.HttpError: {0}".format(
                 cb.log.warning("googleapiclient.errors.HttpError: {0}".format(
                     http_error))
                     http_error))
-            if 'items' in response:
-                self._public_images.extend(
-                    [GCEMachineImage(self.provider, image) for image
-                     in response['items']])
 
 
     def get(self, image_id):
     def get(self, image_id):
         """
         """
         Returns an Image given its id
         Returns an Image given its id
         """
         """
         try:
         try:
-            image = self.provider.gce_compute \
-                                  .images() \
-                                  .get(project=self.provider.project_name,
-                                       image=image_id) \
-                                  .execute()
+            image = (self.provider
+                         .gce_compute
+                         .images()
+                         .get(project=self.provider.project_name,
+                              image=image_id)
+                         .execute())
             if image:
             if image:
                 return GCEMachineImage(self.provider, image)
                 return GCEMachineImage(self.provider, image)
         except TypeError as type_error:
         except TypeError as type_error:
@@ -415,16 +428,12 @@ class GCEImageService(BaseImageService):
         self._retrieve_public_images()
         self._retrieve_public_images()
         images = []
         images = []
         if (self.provider.project_name not in
         if (self.provider.project_name not in
-            GCEImageService._PUBLIC_IMAGE_PROJECTS):
+                GCEImageService._PUBLIC_IMAGE_PROJECTS):
             try:
             try:
-                response = self.provider \
-                               .gce_compute \
-                               .images() \
-                               .list(project=self.provider.project_name) \
-                               .execute()
-                if 'items' in response:
-                    images = [GCEMachineImage(self.provider, image) for image
-                              in response['items']]
+                for image in helpers.iter_all(
+                        self.provider.gce_compute.images(),
+                        project=self.provider.project_name):
+                    images.append(GCEMachineImage(self.provider, image))
             except googleapiclient.errors.HttpError as http_error:
             except googleapiclient.errors.HttpError as http_error:
                 cb.log.warning(
                 cb.log.warning(
                     "googleapiclient.errors.HttpError: {0}".format(http_error))
                     "googleapiclient.errors.HttpError: {0}".format(http_error))
@@ -481,11 +490,11 @@ class GCEInstanceService(BaseInstanceService):
                     config['tags']['items'] = sg_names
                     config['tags']['items'] = sg_names
         else:
         else:
             config = launch_config
             config = launch_config
-        operation = (self.provider.gce_compute.instances()
-                         .insert(
-                             project=self.provider.project_name,
-                             zone=self.provider.default_zone,
-                             body=config)
+        operation = (self.provider
+                         .gce_compute.instances()
+                         .insert(project=self.provider.project_name,
+                                 zone=self.provider.default_zone,
+                                 body=config)
                          .execute())
                          .execute())
         if 'zone' not in operation:
         if 'zone' not in operation:
             return None
             return None
@@ -531,17 +540,24 @@ class GCEInstanceService(BaseInstanceService):
         # For GCE API, Acceptable values are 0 to 500, inclusive.
         # For GCE API, Acceptable values are 0 to 500, inclusive.
         # (Default: 500).
         # (Default: 500).
         max_result = limit if limit is not None and limit < 500 else 500
         max_result = limit if limit is not None and limit < 500 else 500
-        response = self.provider.gce_compute.instances().list(
-            project=self.provider.project_name,
-            zone=self.provider.default_zone,
-            maxResults=max_result,
-            pageToken=marker).execute()
+        response = (self.provider
+                        .gce_compute
+                        .instances()
+                        .list(project=self.provider.project_name,
+                              zone=self.provider.default_zone,
+                              maxResults=max_result,
+                              pageToken=marker)
+                        .execute())
         instances = [GCEInstance(self.provider, inst)
         instances = [GCEInstance(self.provider, inst)
                      for inst in response['items']]
                      for inst in response['items']]
-        return ServerPagedResultList(len(instances) > max_result,
+        if len(instances) > max_result:
+            cb.log.warning('Expected at most %d results; got %d',
+                           max_result, len(instances))
+        return ServerPagedResultList('nextPageToken' in response,
                                      response.get('nextPageToken'),
                                      response.get('nextPageToken'),
                                      False, data=instances)
                                      False, data=instances)
 
 
+
 class GCEComputeService(BaseComputeService):
 class GCEComputeService(BaseComputeService):
     # TODO: implement GCEComputeService
     # TODO: implement GCEComputeService
     def __init__(self, provider):
     def __init__(self, provider):
@@ -572,14 +588,14 @@ class GCENetworkService(BaseNetworkService):
 
 
     def __init__(self, provider):
     def __init__(self, provider):
         super(GCENetworkService, self).__init__(provider)
         super(GCENetworkService, self).__init__(provider)
+        self._subnet_svc = GCESubnetService(self.provider)
 
 
     def get(self, network_id):
     def get(self, network_id):
         if network_id is None:
         if network_id is None:
             return None
             return None
-        # networks = self.list(filter='id eq %s' % network_id) would be better.
-        # But, there is a GCE API bug that causes an error if the network_id
-        # has more than 19 digits. So, we list all networks and filter
-        # ourselves.
+        # Note: networks = self.list(filter='id eq %s' % network_id) does not
+        # work due to a GCE API bug that causes an error if the network_id has
+        # has more than 19 digits.
         networks = self.list()
         networks = self.list()
         for network in networks:
         for network in networks:
             if network.id == network_id:
             if network.id == network_id:
@@ -594,11 +610,12 @@ class GCENetworkService(BaseNetworkService):
 
 
     def list(self, limit=None, marker=None, filter=None):
     def list(self, limit=None, marker=None, filter=None):
         try:
         try:
-            response = (self.provider.gce_compute
-                                     .networks()
-                                     .list(project=self.provider.project_name,
-                                           filter=filter)
-                                     .execute())
+            response = (self.provider
+                            .gce_compute
+                            .networks()
+                            .list(project=self.provider.project_name,
+                                  filter=filter)
+                            .execute())
             networks = []
             networks = []
             if 'items' in response:
             if 'items' in response:
                 for network in response['items']:
                 for network in response['items']:
@@ -608,16 +625,28 @@ class GCENetworkService(BaseNetworkService):
             return []
             return []
 
 
     def create(self, name):
     def create(self, name):
+        """
+        Creates a custom mode VPC network.
+        """
         try:
         try:
             networks = self.list(filter='name eq %s' % name)
             networks = self.list(filter='name eq %s' % name)
             if len(networks) > 0:
             if len(networks) > 0:
                 return networks[0]
                 return networks[0]
 
 
-            response = (self.provider.gce_compute
-                                     .networks()
-                                     .insert(project=self.provider.project_name,
-                                             body={'name': name})
-                                     .execute())
+            # Possible values for 'autoCreateSubnetworks' are:
+            #
+            # None: For creating a legacy (non-subnetted) network.
+            # True: For creating an auto mode VPC network. This also creates a
+            #       subnetwork in every region.
+            # False: For creating a custom mode VPC network. Subnetworks should
+            #        be created manually.
+            response = (self.provider
+                            .gce_compute
+                            .networks()
+                            .insert(project=self.provider.project_name,
+                                    body={'name': name,
+                                          'autoCreateSubnetworks': False})
+                            .execute())
             if 'error' in response:
             if 'error' in response:
                 return None
                 return None
             self.provider.wait_for_operation(response)
             self.provider.wait_for_operation(response)
@@ -628,21 +657,17 @@ class GCENetworkService(BaseNetworkService):
 
 
     @property
     @property
     def subnets(self):
     def subnets(self):
-        raise NotImplementedError('To be implemented')
+        return self._subnet_svc
 
 
     def floating_ips(self, network_id=None, region=None):
     def floating_ips(self, network_id=None, region=None):
         if not region:
         if not region:
             region = self.provider.region_name
             region = self.provider.region_name
         try:
         try:
-            response = (self.provider.gce_compute
-                                     .addresses()
-                                     .list(project=self.provider.project_name,
-                                           region=region)
-                                     .execute())
             ips = []
             ips = []
-            if 'items' in response:
-                for ip in response['items']:
-                    ips.append(GCEFloatingIP(self.provider, ip))
+            for ip in helpers.iter_all(self.provider.gce_compute.addresses(),
+                                       project=self.provider.project_name,
+                                       region=region):
+                ips.append(GCEFloatingIP(self.provider, ip))
             # TODO: if network_id is given, filter out IPs that are assigned to
             # TODO: if network_id is given, filter out IPs that are assigned to
             # resources in a different network.
             # resources in a different network.
             return ips
             return ips
@@ -654,28 +679,141 @@ class GCENetworkService(BaseNetworkService):
             region = self.provider.region_name
             region = self.provider.region_name
         ip_name = 'ip-{0}'.format(uuid.uuid4())
         ip_name = 'ip-{0}'.format(uuid.uuid4())
         try:
         try:
-            response = (self.provider.gce_compute
-                                     .addresses()
-                                     .insert(project=self.provider.project_name,
-                                             region=region,
-                                             body={'name': ip_name})
-                                     .execute())
+            response = (self.provider
+                            .gce_compute
+                            .addresses()
+                            .insert(project=self.provider.project_name,
+                                    region=region,
+                                    body={'name': ip_name})
+                            .execute())
             if 'error' in response:
             if 'error' in response:
                 return None
                 return None
             self.provider.wait_for_operation(response, region=region)
             self.provider.wait_for_operation(response, region=region)
             ips = self.floating_ips()
             ips = self.floating_ips()
             for ip in ips:
             for ip in ips:
-                if ip.id == response["targetId"]:
+                if ip.id == response['targetId']:
                     return ip
                     return ip
         except:
         except:
             return None
             return None
 
 
-    def routers(self):
-        raise NotImplementedError('To be implemented')
+    def routers(self, region=None):
+        if not region:
+            region = self.provider.region_name
+        try:
+            routers = []
+            for router in helpers.iter_all(self.provider.gce_compute.routers(),
+                                           project=self.provider.project_name,
+                                           region=region):
+                routers.append(GCERouter(self.provider, router))
+            return routers
+        except:
+            return []
+
+    def create_router(self, name=None, network=None, region=None):
+        network_url = 'global/networks/default'
+        if isinstance(network, GCENetwork):
+            network_url = network.resource_url
+        if not region:
+            region = self.provider.region_name
+        try:
+            response = (self.provider
+                            .gce_compute
+                            .routers()
+                            .insert(project=self.provider.project_name,
+                                    region=region,
+                                    body={'name': name,
+                                          'network': network_url})
+                            .execute())
+            if 'error' in response:
+                return None
+            self.provider.wait_for_operation(response, region=region)
+            routers = self.routers()
+            for router in routers:
+                if router.id == response['targetId']:
+                    return router
+        except:
+            return None
+
+
+class GCESubnetService(BaseSubnetService):
+
+    def __init__(self, provider):
+        super(GCESubnetService, self).__init__(provider)
 
 
-    def create_router(self, name=None):
+    def get(self, subnet_id):
+        for subnet in self.list():
+            if subnet.id == subnet_id:
+                return subnet
+        return None
+
+    def list(self, network=None, region=None, limit=None, marker=None):
+        if not region:
+            region = self.provider.region_name
+        filter = None
+        if network is not None:
+            filter = 'network eq %s' % network.resource_url
+        max_result = limit if limit is not None and limit < 500 else 500
+        response = (self.provider
+                        .gce_compute
+                        .subnetworks()
+                        .list(project=self.provider.project_name,
+                              region=region,
+                              filter=filter,
+                              maxResults=max_result,
+                              pageToken=marker)
+                        .execute())
+        subnets = []
+        for subnet in response.get('items', []):
+            subnets.append(GCESubnet(self.provider, subnet))
+        if len(subnets) > max_result:
+            cb.log.warning('Expected at most %d results; got %d',
+                           max_result, len(subnets))
+        return ServerPagedResultList('nextPageToken' in response,
+                                     response.get('nextPageToken'),
+                                     False, data=subnets)
+
+    def create(self, network, cidr_block, name=None, zone=None):
+        if not name:
+            name = 'subnet-{0}'.format(uuid.uuid4())
+        region = self.provider.region_name
+        if isinstance(zone, GCEPlacementZone):
+            region = zone.region_name
+        body = {'ipCidrRange': cidr_block,
+                'name': name,
+                'network': network.resource_url,
+                'region': region}
+        try:
+            response = (self.provider
+                            .gce_compute
+                            .subnetworks()
+                            .insert(project=self.provider.project_name,
+                                    region=region,
+                                    body=body)
+                            .execute())
+            self.provider.wait_for_operation(response, region=region)
+            if 'error' in response:
+                return None
+            subnets = self.list(network, region)
+            for subnet in subnets:
+                cb.log.warning('subnet ID: %s', subnet.id)
+                if subnet.id == response['targetId']:
+                    return subnet
+        except:
+            return None
+
+    def get_or_create_default(self, zone=None):
         raise NotImplementedError('To be implemented')
         raise NotImplementedError('To be implemented')
 
 
+    def delete(self, subnet):
+        response = (self.provider
+                        .gce_compute
+                        .subnetworks()
+                        .delete(project=self.provider.project_name,
+                                region=subnet.region,
+                                router=subnet.name)
+                        .execute())
+        self._provider.wait_for_operation(response, region=subnet.region)
+
 
 
 class GCEBlockStoreService(BaseBlockStoreService):
 class GCEBlockStoreService(BaseBlockStoreService):
 
 
@@ -722,17 +860,22 @@ class GCEVolumeService(BaseVolumeService):
         filtr = 'name eq ' + name
         filtr = 'name eq ' + name
         max_result = limit if limit is not None and limit < 500 else 500
         max_result = limit if limit is not None and limit < 500 else 500
         response = (self.provider
         response = (self.provider
-                        .gce_compute.disks()
+                        .gce_compute
+                        .disks()
                         .list(project=self.provider.project_name,
                         .list(project=self.provider.project_name,
                               zone=self.provider.default_zone,
                               zone=self.provider.default_zone,
                               filter=filtr,
                               filter=filtr,
                               maxResults=max_result,
                               maxResults=max_result,
-                              pageToken=marker).execute())
+                              pageToken=marker)
+                        .execute())
         if 'items' not in response:
         if 'items' not in response:
             return []
             return []
         gce_vols = [GCEVolume(self.provider, vol)
         gce_vols = [GCEVolume(self.provider, vol)
                     for vol in response['items']]
                     for vol in response['items']]
-        return ServerPagedResultList(len(gce_vols) > max_result,
+        if len(gce_vols) > max_result:
+            cb.log.warning('Expected at most %d results; got %d',
+                           max_result, len(gce_vols))
+        return ServerPagedResultList('nextPageToken' in response,
                                      response.get('nextPageToken'),
                                      response.get('nextPageToken'),
                                      False, data=gce_vols)
                                      False, data=gce_vols)
 
 
@@ -748,16 +891,21 @@ class GCEVolumeService(BaseVolumeService):
         # (Default: 500).
         # (Default: 500).
         max_result = limit if limit is not None and limit < 500 else 500
         max_result = limit if limit is not None and limit < 500 else 500
         response = (self.provider
         response = (self.provider
-                        .gce_compute.disks()
+                        .gce_compute
+                        .disks()
                         .list(project=self.provider.project_name,
                         .list(project=self.provider.project_name,
                               zone=self.provider.default_zone,
                               zone=self.provider.default_zone,
                               maxResults=max_result,
                               maxResults=max_result,
-                              pageToken=marker).execute())
+                              pageToken=marker)
+                        .execute())
         if 'items' not in response:
         if 'items' not in response:
             return []
             return []
         gce_vols = [GCEVolume(self.provider, vol)
         gce_vols = [GCEVolume(self.provider, vol)
                     for vol in response['items']]
                     for vol in response['items']]
-        return ServerPagedResultList(len(gce_vols) > max_result,
+        if len(gce_vols) > max_result:
+            cb.log.warning('Expected at most %d results; got %d',
+                           max_result, len(gce_vols))
+        return ServerPagedResultList('nextPageToken' in response,
                                      response.get('nextPageToken'),
                                      response.get('nextPageToken'),
                                      False, data=gce_vols)
                                      False, data=gce_vols)
 
 
@@ -782,11 +930,14 @@ class GCEVolumeService(BaseVolumeService):
             'sourceSnapshot': snapshot_id,
             'sourceSnapshot': snapshot_id,
             'description': description,
             'description': description,
         }
         }
-        operation = (self.provider.gce_compute.disks()
+        operation = (self.provider
+                         .gce_compute
+                         .disks()
                          .insert(
                          .insert(
                              project=self._provider.project_name,
                              project=self._provider.project_name,
                              zone=zone_name,
                              zone=zone_name,
-                             body=disk_body).execute())
+                             body=disk_body)
+                         .execute())
         return self.get(operation.get('targetLink'))
         return self.get(operation.get('targetLink'))
 
 
 
 
@@ -817,16 +968,21 @@ class GCESnapshotService(BaseSnapshotService):
         filtr = 'name eq ' + name
         filtr = 'name eq ' + name
         max_result = limit if limit is not None and limit < 500 else 500
         max_result = limit if limit is not None and limit < 500 else 500
         response = (self.provider
         response = (self.provider
-                        .gce_compute.snapshots()
+                        .gce_compute
+                        .snapshots()
                         .list(project=self.provider.project_name,
                         .list(project=self.provider.project_name,
                               filter=filtr,
                               filter=filtr,
                               maxResults=max_result,
                               maxResults=max_result,
-                              pageToken=marker).execute())
+                              pageToken=marker)
+                        .execute())
         if 'items' not in response:
         if 'items' not in response:
             return []
             return []
         snapshots = [GCESnapshot(self.provider, snapshot)
         snapshots = [GCESnapshot(self.provider, snapshot)
-                    for snapshot in response['items']]
-        return ServerPagedResultList(len(snapshots) > max_result,
+                     for snapshot in response['items']]
+        if len(snapshots) > max_result:
+            cb.log.warning('Expected at most %d results; got %d',
+                           max_result, len(snapshots))
+        return ServerPagedResultList('nextPageToken' in response,
                                      response.get('nextPageToken'),
                                      response.get('nextPageToken'),
                                      False, data=snapshots)
                                      False, data=snapshots)
 
 
@@ -836,15 +992,20 @@ class GCESnapshotService(BaseSnapshotService):
         """
         """
         max_result = limit if limit is not None and limit < 500 else 500
         max_result = limit if limit is not None and limit < 500 else 500
         response = (self.provider
         response = (self.provider
-                        .gce_compute.snapshots()
+                        .gce_compute
+                        .snapshots()
                         .list(project=self.provider.project_name,
                         .list(project=self.provider.project_name,
                               maxResults=max_result,
                               maxResults=max_result,
-                              pageToken=marker).execute())
+                              pageToken=marker)
+                        .execute())
         if 'items' not in response:
         if 'items' not in response:
             return []
             return []
         snapshots = [GCESnapshot(self.provider, snapshot)
         snapshots = [GCESnapshot(self.provider, snapshot)
-                    for snapshot in response['items']]
-        return ServerPagedResultList(len(snapshots) > max_result,
+                     for snapshot in response['items']]
+        if len(snapshots) > max_result:
+            cb.log.warning('Expected at most %d results; got %d',
+                           max_result, len(snapshots))
+        return ServerPagedResultList('nextPageToken' in response,
                                      response.get('nextPageToken'),
                                      response.get('nextPageToken'),
                                      False, data=snapshots)
                                      False, data=snapshots)
 
 
@@ -858,11 +1019,13 @@ class GCESnapshotService(BaseSnapshotService):
             "description": description
             "description": description
         }
         }
         operation = (self.provider
         operation = (self.provider
-                         .gce_compute.disks()
+                         .gce_compute
+                         .disks()
                          .createSnapshot(
                          .createSnapshot(
                              project=self.provider.project_name,
                              project=self.provider.project_name,
                              zone=self.provider.default_zone,
                              zone=self.provider.default_zone,
-                             disk=volume_name, body=snapshot_body).execute())
+                             disk=volume_name, body=snapshot_body)
+                         .execute())
         if 'zone' not in operation:
         if 'zone' not in operation:
             return None
             return None
         gce_zone = self.provider.get_gce_resource_data(operation['zone'])
         gce_zone = self.provider.get_gce_resource_data(operation['zone'])
@@ -872,3 +1035,88 @@ class GCESnapshotService(BaseSnapshotService):
             return snapshots[0]
             return snapshots[0]
         else:
         else:
             return None
             return None
+
+
+class GCSObjectStoreService(BaseObjectStoreService):
+
+    def __init__(self, provider):
+        super(GCSObjectStoreService, self).__init__(provider)
+
+    def get(self, bucket_id):
+        """
+        Returns a bucket given its ID. Returns ``None`` if the bucket
+        does not exist or if the user does not have permission to access the
+        bucket.
+        """
+        try:
+            response = (self.provider
+                            .gcp_storage
+                            .buckets()
+                            .get(bucket=bucket_id)
+                            .execute())
+            if 'error' in response:
+                # response['error']['code'] is 404 if the bucket does not exist
+                # and 403 if the user does not have permission to access it.
+                if response['error']['code'] not in (403, 404):
+                    cb.log.warning('Unexpected error code (%d) when accessing '
+                                   'bucket %s', response['error']['code'],
+                                   bucket_id)
+                return None
+            return GCSBucket(self.provider, response)
+        except:
+            return None
+
+    def find(self, name, limit=None, marker=None):
+        """
+        Searches in bucket names for a substring.
+        """
+        buckets = [bucket for bucket in self if name in bucket.name]
+        return ClientPagedResultList(self.provider, buckets, limit=limit,
+                                     marker=marker)
+
+    def list(self, limit=None, marker=None):
+        """
+        List all containers.
+        """
+        max_result = limit if limit is not None and limit < 500 else 500
+        try:
+            response = (self.provider
+                            .gcp_storage
+                            .buckets()
+                            .list(project=self.provider.project_name,
+                                  maxResults=max_result,
+                                  pageToken=marker)
+                            .execute())
+            if 'error' in response:
+                return ServerPagedResultList(False, None, False, data=[])
+            buckets = []
+            for bucket in response.get('items', []):
+                buckets.append(GCSBucket(self.provider, bucket))
+            if len(buckets) > max_result:
+                cb.log.warning('Expected at most %d results; got %d',
+                               max_result, len(buckets))
+            return ServerPagedResultList('nextPageToken' in response,
+                                         response.get('nextPageToken'),
+                                         False, data=buckets)
+        except:
+            return ServerPagedResultList(False, None, False, data=[])
+
+    def create(self, name, location=None):
+        """
+        Create a new bucket and returns it. Returns None if creation fails.
+        """
+        body = {'name': name}
+        if location:
+            body['location'] = location
+        try:
+            response = (self.provider
+                            .gcp_storage
+                            .buckets()
+                            .insert(project=self.provider.project_name,
+                                    body=body)
+                            .execute())
+            if 'error' in response:
+                return None
+            return GCSBucket(self.provider, response)
+        except:
+            return None