Ehsan Chiniforooshan 7 лет назад
Родитель
Сommit
1ebea89d6f

+ 6 - 6
cloudbridge/cloud/base/resources.py

@@ -356,10 +356,10 @@ class BasePageableObjectMixin(PageableObjectMixin):
     """
     """
 
 
     def __iter__(self):
     def __iter__(self):
-        for result in self.list_all():
+        for result in self.iter():
             yield result
             yield result
 
 
-    def list_all(self, **kwargs):
+    def iter(self, **kwargs):
         result_list = self.list(**kwargs)
         result_list = self.list(**kwargs)
         if result_list.supports_server_paging:
         if result_list.supports_server_paging:
             for result in result_list:
             for result in result_list:
@@ -867,8 +867,8 @@ class BaseBucketObject(BaseCloudResource, BucketObject):
                 self.name == other.name)
                 self.name == other.name)
 
 
     def __repr__(self):
     def __repr__(self):
-        return "<CB-{0}: {1}>".format(self.__class__.__name__,
-                                      self.name)
+        return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
+                                            self.name, self.id)
 
 
 
 
 class BaseBucket(BaseCloudResource, Bucket):
 class BaseBucket(BaseCloudResource, Bucket):
@@ -906,8 +906,8 @@ class BaseBucket(BaseCloudResource, Bucket):
                 self.name == other.name)
                 self.name == other.name)
 
 
     def __repr__(self):
     def __repr__(self):
-        return "<CB-{0}: {1}>".format(self.__class__.__name__,
-                                      self.name)
+        return "<CB-{0}: {1} ({2})>".format(self.__class__.__name__,
+                                            self.name, self.id)
 
 
 
 
 class BaseBucketContainer(BasePageableObjectMixin, BucketContainer):
 class BaseBucketContainer(BasePageableObjectMixin, BucketContainer):

+ 7 - 5
cloudbridge/cloud/factory.py

@@ -83,13 +83,13 @@ class CloudProviderFactory(object):
         Imports and registers providers from the given module name.
         Imports and registers providers from the given module name.
         Raises an ImportError if the import does not succeed.
         Raises an ImportError if the import does not succeed.
         """
         """
-        log.debug("Importing providers from %s", module_name)
+        log.info("Importing providers from %s", module_name)
         module = importlib.import_module(
         module = importlib.import_module(
             "{0}.{1}".format(providers.__name__,
             "{0}.{1}".format(providers.__name__,
                              module_name))
                              module_name))
         classes = inspect.getmembers(module, inspect.isclass)
         classes = inspect.getmembers(module, inspect.isclass)
         for _, cls in classes:
         for _, cls in classes:
-            log.debug("Registering the provider: %s", cls)
+            log.info("Registering the provider: %s", cls)
             self.register_provider_class(cls)
             self.register_provider_class(cls)
 
 
     def list_providers(self):
     def list_providers(self):
@@ -110,7 +110,7 @@ class CloudProviderFactory(object):
         """
         """
         if not self.provider_list:
         if not self.provider_list:
             self.discover_providers()
             self.discover_providers()
-        log.debug("List of available providers: %s", self.provider_list)
+        log.info("List of available providers: %s", self.provider_list)
         return self.provider_list
         return self.provider_list
 
 
     def create_provider(self, name, config):
     def create_provider(self, name, config):
@@ -131,7 +131,8 @@ class CloudProviderFactory(object):
         :return:  a concrete provider instance
         :return:  a concrete provider instance
         :rtype: ``object`` of :class:`.CloudProvider`
         :rtype: ``object`` of :class:`.CloudProvider`
         """
         """
-        log.info("Creating '%s' provider", name)
+        log.info("Searching provider with the name %s on %s",
+                 name, config)
         provider_class = self.get_provider_class(name)
         provider_class = self.get_provider_class(name)
         if provider_class is None:
         if provider_class is None:
             log.exception("A provider with the name %s could not "
             log.exception("A provider with the name %s could not "
@@ -139,7 +140,8 @@ class CloudProviderFactory(object):
             raise NotImplementedError(
             raise NotImplementedError(
                 'A provider with name {0} could not be'
                 'A provider with name {0} could not be'
                 ' found'.format(name))
                 ' found'.format(name))
-        log.debug("Created '%s' provider", name)
+        log.debug("Found provider name: %s with these config "
+                  " details: %s", name, config)
         return provider_class(config)
         return provider_class(config)
 
 
     def get_provider_class(self, name, get_mock=False):
     def get_provider_class(self, name, get_mock=False):

+ 44 - 11
cloudbridge/cloud/providers/gce/provider.py

@@ -11,6 +11,7 @@ from string import Template
 
 
 import cloudbridge as cb
 import cloudbridge as cb
 from cloudbridge.cloud.base import BaseCloudProvider
 from cloudbridge.cloud.base import BaseCloudProvider
+from cloudbridge.cloud.interfaces.exceptions import ProviderConnectionException
 
 
 import googleapiclient
 import googleapiclient
 from googleapiclient import discovery
 from googleapiclient import discovery
@@ -229,6 +230,9 @@ class GCECloudProvider(BaseCloudProvider):
         # service connections, lazily initialized
         # service connections, lazily initialized
         self._gce_compute = None
         self._gce_compute = None
         self._gcs_storage = None
         self._gcs_storage = None
+        self._credentials_cache = None
+        self._compute_resources_cache = None
+        self._storage_resources_cache = None
 
 
         # Initialize provider services
         # Initialize provider services
         self._compute = GCEComputeService(self)
         self._compute = GCEComputeService(self)
@@ -236,12 +240,6 @@ class GCECloudProvider(BaseCloudProvider):
         self._networking = GCENetworkingService(self)
         self._networking = GCENetworkingService(self)
         self._storage = GCPStorageService(self)
         self._storage = GCPStorageService(self)
 
 
-        self._compute_resources = GCPResources(self.gce_compute,
-                                               project=self.project_name,
-                                               region=self.region_name,
-                                               zone=self.default_zone)
-        self._storage_resources = GCPResources(self.gcs_storage)
-
     @property
     @property
     def compute(self):
     def compute(self):
         return self._compute
         return self._compute
@@ -270,13 +268,40 @@ class GCECloudProvider(BaseCloudProvider):
             self._gcs_storage = self._connect_gcs_storage()
             self._gcs_storage = self._connect_gcs_storage()
         return self._gcs_storage
         return self._gcs_storage
 
 
+    @property
+    def _compute_resources(self):
+        if not self._compute_resources_cache:
+            self._compute_resources_cache = GCPResources(
+                    self.gce_compute,
+                    project=self.project_name,
+                    region=self.region_name,
+                    zone=self.default_zone)
+        return self._compute_resources_cache
+
+    @property
+    def _storage_resources(self):
+        if not self._storage_resources_cache:
+            self._storage_resources_cache = GCPResources(self.gcs_storage)
+        return self._storage_resources_cache
+
     @property
     @property
     def _credentials(self):
     def _credentials(self):
-        if self.credentials_dict:
-            return ServiceAccountCredentials.from_json_keyfile_dict(
-                self.credentials_dict)
-        else:
-            return GoogleCredentials.get_application_default()
+        if not self._credentials_cache:
+            if self.credentials_dict:
+                self._credentials_cache = (
+                        ServiceAccountCredentials.from_json_keyfile_dict(
+                                self.credentials_dict))
+            else:
+                self._credentials_cache = (
+                        GoogleCredentials.get_application_default())
+        return self._credentials_cache
+
+    def sign_blob(self, string_to_sign):
+        return self._credentials.sign_blob([string_to_sign])[1]
+
+    @property
+    def client_id(self):
+        return self._credentials.service_account_email
 
 
     def _connect_gcs_storage(self):
     def _connect_gcs_storage(self):
         return discovery.build('storage', 'v1', credentials=self._credentials,
         return discovery.build('storage', 'v1', credentials=self._credentials,
@@ -326,3 +351,11 @@ class GCECloudProvider(BaseCloudProvider):
             cb.log.warning(
             cb.log.warning(
                 "googleapiclient.errors.HttpError: {0}".format(http_error))
                 "googleapiclient.errors.HttpError: {0}".format(http_error))
             return None
             return None
+
+    def authenticate(self):
+        try:
+            self.gce_compute
+            return True
+        except Exception as e:
+            raise ProviderConnectionException(
+                'Authentication with Google cloud provider failed: %s', e)

+ 128 - 102
cloudbridge/cloud/providers/gce/resources.py

@@ -1,10 +1,13 @@
 """
 """
 DataTypes used by this provider
 DataTypes used by this provider
 """
 """
+import base64
+import calendar
 import hashlib
 import hashlib
 import inspect
 import inspect
 import io
 import io
 import math
 import math
+import time
 import uuid
 import uuid
 
 
 import cloudbridge as cb
 import cloudbridge as cb
@@ -56,11 +59,11 @@ except NameError:
 
 
 class GCEKeyPair(BaseKeyPair):
 class GCEKeyPair(BaseKeyPair):
 
 
-    def __init__(self, provider, kp_id, kp_name, kp_material=None):
+    def __init__(self, provider, kp_id, kp_name, private_key=None):
         super(GCEKeyPair, self).__init__(provider, None)
         super(GCEKeyPair, self).__init__(provider, None)
         self._kp_id = kp_id
         self._kp_id = kp_id
         self._kp_name = kp_name
         self._kp_name = kp_name
-        self._kp_material = kp_material
+        self._private_key = private_key
 
 
     @property
     @property
     def id(self):
     def id(self):
@@ -68,8 +71,7 @@ class GCEKeyPair(BaseKeyPair):
 
 
     @property
     @property
     def name(self):
     def name(self):
-        # use e-mail as keyname if possible, or ID if not
-        return self._kp_name or self.id
+        return self._kp_name
 
 
     def delete(self):
     def delete(self):
         svc = self._provider.security.key_pairs
         svc = self._provider.security.key_pairs
@@ -87,11 +89,7 @@ class GCEKeyPair(BaseKeyPair):
 
 
     @property
     @property
     def material(self):
     def material(self):
-        return self._kp_material
-
-    @material.setter
-    def material(self, value):
-        self._kp_material = value
+        return self._private_key
 
 
 
 
 class GCEVMType(BaseVMType):
 class GCEVMType(BaseVMType):
@@ -270,8 +268,8 @@ class GCEFirewallsDelegate(object):
             self._delete_firewall(firewall)
             self._delete_firewall(firewall)
         self._update_list_response()
         self._update_list_response()
 
 
-    def add_firewall(self, tag, direction, protocol, port, src_dest_range,
-                     src_dest_tag, description, network_name):
+    def add_firewall(self, tag, direction, protocol, priority, port,
+                     src_dest_range, src_dest_tag, description, network_name):
         """
         """
         Create a new firewall.
         Create a new firewall.
         """
         """
@@ -306,6 +304,8 @@ class GCEFirewallsDelegate(object):
                                'tags. Only IP ranges are acceptable.')
                                'tags. Only IP ranges are acceptable.')
             else:
             else:
                 firewall['sourceTags'] = [src_dest_tag]
                 firewall['sourceTags'] = [src_dest_tag]
+        if priority is not None:
+            firewall['priority'] = priority
         project_name = self._provider.project_name
         project_name = self._provider.project_name
         try:
         try:
             response = (self._provider
             response = (self._provider
@@ -548,8 +548,9 @@ class GCEVMFirewallRuleContainer(BaseVMFirewallRuleContainer):
         else:
         else:
             return to_port
             return to_port
 
 
-    def create(self, direction, protocol, from_port=None, to_port=None,
-               cidr=None, src_dest_fw=None):
+    def create_with_priority(self, direction, protocol, priority,
+                             from_port=None, to_port=None, cidr=None,
+                             src_dest_fw=None):
         port = GCEVMFirewallRuleContainer.to_port_range(from_port, to_port)
         port = GCEVMFirewallRuleContainer.to_port_range(from_port, to_port)
         src_dest_tag = None
         src_dest_tag = None
         src_dest_fw_id = None
         src_dest_fw_id = None
@@ -557,7 +558,7 @@ class GCEVMFirewallRuleContainer(BaseVMFirewallRuleContainer):
             src_dest_tag = src_dest_fw.name
             src_dest_tag = src_dest_fw.name
             src_dest_fw_id = src_dest_fw.id
             src_dest_fw_id = src_dest_fw.id
         if not self.firewall.delegate.add_firewall(
         if not self.firewall.delegate.add_firewall(
-                self.firewall.name, direction, protocol, port, cidr,
+                self.firewall.name, direction, protocol, priority, port, cidr,
                 src_dest_tag, self.firewall.description,
                 src_dest_tag, self.firewall.description,
                 self.firewall.network.name):
                 self.firewall.network.name):
             return None
             return None
@@ -568,6 +569,11 @@ class GCEVMFirewallRuleContainer(BaseVMFirewallRuleContainer):
             return None
             return None
         return rules[0]
         return rules[0]
 
 
+    def create(self, direction, protocol, from_port=None, to_port=None,
+               cidr=None, src_dest_fw=None):
+        return self.create_with_priority(direction, protocol, 1000, from_port,
+                                         to_port, cidr, src_dest_fw)
+
 
 
 class GCEVMFirewallRule(BaseVMFirewallRule):
 class GCEVMFirewallRule(BaseVMFirewallRule):
 
 
@@ -736,9 +742,10 @@ class GCEMachineImage(BaseMachineImage):
         Refreshes the state of this instance by re-querying the cloud provider
         Refreshes the state of this instance by re-querying the cloud provider
         for its latest state.
         for its latest state.
         """
         """
+        name = self.name
         self._gce_image = self._provider.get_resource('images', self.id)
         self._gce_image = self._provider.get_resource('images', self.id)
         if not self._gce_image:
         if not self._gce_image:
-            self._gce_image = {'status': 'UNKNOWN'}
+            self._gce_image = {'name': name, 'status': 'UNKNOWN'}
 
 
 
 
 class GCEInstance(BaseInstance):
 class GCEInstance(BaseInstance):
@@ -814,7 +821,7 @@ class GCEInstance(BaseInstance):
                 if 'natIP' in access_config:
                 if 'natIP' in access_config:
                     ips.append(access_config['natIP'])
                     ips.append(access_config['natIP'])
         for ip in self.inet_gateway.floating_ips:
         for ip in self.inet_gateway.floating_ips:
-            if ip.in_use():
+            if ip.in_use:
                 if ip.private_ip in self.private_ips:
                 if ip.private_ip in self.private_ips:
                     ips.append(ip.public_ip)
                     ips.append(ip.public_ip)
         return ips
         return ips
@@ -877,15 +884,16 @@ class GCEInstance(BaseInstance):
         """
         """
         Permanently terminate this instance.
         Permanently terminate this instance.
         """
         """
+        name = self.name
         response = (self._provider
         response = (self._provider
                         .gce_compute
                         .gce_compute
                         .instances()
                         .instances()
                         .delete(project=self._provider.project_name,
                         .delete(project=self._provider.project_name,
                                 zone=self.zone_name,
                                 zone=self.zone_name,
-                                instance=self.name)
+                                instance=name)
                         .execute())
                         .execute())
         self._provider.wait_for_operation(response, zone=self.zone_name)
         self._provider.wait_for_operation(response, zone=self.zone_name)
-        self._gce_instance = {'status': 'UNKNOWN'}
+        self._gce_instance = {'name': name, 'status': 'UNKNOWN'}
 
 
     def stop(self):
     def stop(self):
         """
         """
@@ -958,7 +966,9 @@ class GCEInstance(BaseInstance):
         """
         """
         Get the name of the key pair associated with this instance.
         Get the name of the key pair associated with this instance.
         """
         """
-        return self._provider.security.key_pairs.name
+        for kp in self._provider.security.key_pairs:
+            return kp.name
+        return None
 
 
     @property
     @property
     def inet_gateway(self):
     def inet_gateway(self):
@@ -1005,7 +1015,7 @@ class GCEInstance(BaseInstance):
         try:
         try:
             for target_instance in helpers.iter_all(
             for target_instance in helpers.iter_all(
                     self._provider.gce_compute.targetInstances(),
                     self._provider.gce_compute.targetInstances(),
-                    project=self.name,
+                    project=self._provider.project_name,
                     zone=self.zone_name):
                     zone=self.zone_name):
                 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:
@@ -1031,7 +1041,7 @@ class GCEInstance(BaseInstance):
             response = (self._provider
             response = (self._provider
                             .gce_compute
                             .gce_compute
                             .targetInstances()
                             .targetInstances()
-                            .insert(project=self.name,
+                            .insert(project=self._provider.project_name,
                                     zone=self.zone_name,
                                     zone=self.zone_name,
                                     body=body)
                                     body=body)
                             .execute())
                             .execute())
@@ -1148,54 +1158,46 @@ class GCEInstance(BaseInstance):
             return False
             return False
         return True
         return True
 
 
-    def add_floating_ip(self, ip_address):
+    def add_floating_ip(self, floating_ip):
         """
         """
         Add an elastic IP address to this instance.
         Add an elastic IP address to this instance.
         """
         """
-        for ip in self.inet_gateway.floating_ips:
-            if ip.public_ip == ip_address:
-                if ip.in_use():
-                    if ip.private_ip not in self.private_ips:
-                        cb.log.warning(
-                            'Floating IP "%s" is already associated to "%s".',
-                            ip_address, self.name)
-                    return
-                target_instance = self._get_target_instance()
-                if not target_instance:
-                    cb.log.warning(
-                            'Could not create a targetInstance for "%s"',
-                            self.name)
-                    return
-                if not self._forward(ip, target_instance):
-                    cb.log.warning('Could not forward "%s" to "%s"',
-                                   ip.public_ip, target_instance['selfLink'])
-                return
-        cb.log.warning('Floating IP "%s" does not exist.', ip_address)
+        fip = (floating_ip if isinstance(floating_ip, GCEFloatingIP)
+               else self.inet_gateway.floating_ips.get(floating_ip))
+        if fip.in_use:
+            if fip.private_ip not in self.private_ips:
+                cb.log.warning('Floating IP "%s" is not associated to "%s"',
+                               fip.public_ip, self.name)
+            return
+        target_instance = self._get_target_instance()
+        if not target_instance:
+            cb.log.warning('Could not create a targetInstance for "%s"',
+                           self.name)
+            return
+        if not self._forward(fip, target_instance):
+            cb.log.warning('Could not forward "%s" to "%s"',
+                           fip.public_ip, target_instance['selfLink'])
 
 
-    def remove_floating_ip(self, ip_address):
+    def remove_floating_ip(self, floating_ip):
         """
         """
         Remove a elastic IP address from this instance.
         Remove a elastic IP address from this instance.
         """
         """
-        for ip in self.inet_gateway.floating_ips:
-            if ip.public_ip == ip_address:
-                if not ip.in_use() or ip.private_ip not in self.private_ips:
-                    cb.log.warning(
-                        'Floating IP "%s" is not associated to "%s".',
-                        ip_address, self.name)
-                    return
-                target_instance = self._get_target_instance()
-                if not target_instance:
-                    # We should not be here.
-                    cb.log.warning('Something went wrong! "%s" is associated '
-                                   'to "%s" with no target instance',
-                                   ip_address, self.name)
-                    return
-                if not self._delete_existing_rule(ip, target_instance):
-                    cb.log.warning(
-                        'Could not remove floating IP "%s" from instance "%s"',
-                        ip.public_ip, self.name)
-                return
-        cb.log.warning('Floating IP "%s" does not exist.', ip_address)
+        fip = (floating_ip if isinstance(floating_ip, GCEFloatingIP)
+               else self.inet_gateway.floating_ips.get(floating_ip))
+        if not fip.in_use or fip.private_ip not in self.private_ips:
+            cb.log.warning('Floating IP "%s" is not associated to "%s"',
+                           fip.public_ip, self.name)
+            return
+        target_instance = self._get_target_instance()
+        if not target_instance:
+            # We should not be here.
+            cb.log.warning('Something went wrong! "%s" is associated to "%s" '
+                           'with no target instance', fip.public_ip, self.name)
+            return
+        if not self._delete_existing_rule(fip, target_instance):
+            cb.log.warning(
+                'Could not remove floating IP "%s" from instance "%s"',
+                fip.public_ip, self.name)
 
 
     @property
     @property
     def state(self):
     def state(self):
@@ -1287,7 +1289,7 @@ class GCENetwork(BaseNetwork):
 
 
     @property
     @property
     def subnets(self):
     def subnets(self):
-        return self._provider.networking.subnets.list(network=self)
+        return list(self._provider.networking.subnets.iter(network=self))
 
 
     def delete(self):
     def delete(self):
         try:
         try:
@@ -1377,37 +1379,7 @@ class GCEFloatingIP(BaseFloatingIP):
     def __init__(self, provider, floating_ip):
     def __init__(self, provider, floating_ip):
         super(GCEFloatingIP, self).__init__(provider)
         super(GCEFloatingIP, self).__init__(provider)
         self._ip = floating_ip
         self._ip = floating_ip
-
-        # We use regional IPs to simulate floating IPs not global IPs because
-        # global IPs can be forwarded only to load balancing resources, not to
-        # a specific instance. Find out the region to which the IP belongs.
-        url = provider.parse_url(self._ip['region'])
-        self._region_name = url.parameters['region']
-
-        # Check if the address is used by a resource.
-        self._rule = None
-        self._target_instance = None
-        if 'users' in floating_ip and len(floating_ip['users']) > 0:
-            if len(floating_ip['users']) > 1:
-                cb.log.warning('Address "%s" in use by more than one resource',
-                               floating_ip['address'])
-            resource_parsed_url = provider.parse_url(floating_ip['users'][0])
-            resource = resource_parsed_url.get_resource()
-            if resource['kind'] == 'compute#forwardingRule':
-                self._rule = resource
-                target = provider.parse_url(resource['target']).get_resource()
-                if target['kind'] == 'compute#targetInstance':
-                    url = provider.parse_url(target['instance'])
-                    try:
-                        self._target_instance = url.get_resource()
-                    except googleapiclient.errors.HttpError:
-                        self._target_instance = GCEFloatingIP._DEAD_INSTANCE
-                else:
-                    cb.log.warning('Address "%s" is forwarded to a %s',
-                                   floating_ip['address'], target['kind'])
-            else:
-                cb.log.warning('Address "%s" in use by a %s',
-                               floating_ip['address'], resource['kind'])
+        self._process_ip_users()
 
 
     @property
     @property
     def id(self):
     def id(self):
@@ -1415,7 +1387,11 @@ class GCEFloatingIP(BaseFloatingIP):
 
 
     @property
     @property
     def region_name(self):
     def region_name(self):
-        return self._region_name
+        # We use regional IPs to simulate floating IPs not global IPs because
+        # global IPs can be forwarded only to load balancing resources, not to
+        # a specific instance. Find out the region to which the IP belongs.
+        url = self._provider.parse_url(self._ip['region'])
+        return url.parameters['region']
 
 
     @property
     @property
     def public_ip(self):
     def public_ip(self):
@@ -1428,6 +1404,7 @@ class GCEFloatingIP(BaseFloatingIP):
             return None
             return None
         return self._target_instance['networkInterfaces'][0]['networkIP']
         return self._target_instance['networkInterfaces'][0]['networkIP']
 
 
+    @property
     def in_use(self):
     def in_use(self):
         return True if self._target_instance else False
         return True if self._target_instance else False
 
 
@@ -1439,26 +1416,55 @@ class GCEFloatingIP(BaseFloatingIP):
                             .gce_compute
                             .gce_compute
                             .forwardingRules()
                             .forwardingRules()
                             .delete(project=project_name,
                             .delete(project=project_name,
-                                    region=self._region_name,
+                                    region=self.region_name,
                                     forwardingRule=self._rule['name'])
                                     forwardingRule=self._rule['name'])
                             .execute())
                             .execute())
             self._provider.wait_for_operation(response,
             self._provider.wait_for_operation(response,
-                                              region=self._region_name)
+                                              region=self.region_name)
 
 
         # Release the address.
         # Release the address.
         response = (self._provider
         response = (self._provider
                         .gce_compute
                         .gce_compute
                         .addresses()
                         .addresses()
                         .delete(project=project_name,
                         .delete(project=project_name,
-                                region=self._region_name,
+                                region=self.region_name,
                                 address=self._ip['name'])
                                 address=self._ip['name'])
                         .execute())
                         .execute())
-        self._provider.wait_for_operation(response, region=self._region_name)
+        self._provider.wait_for_operation(response, region=self.region_name)
 
 
     def refresh(self):
     def refresh(self):
         self._ip = self._provider.get_resource('addresses', self.id)
         self._ip = self._provider.get_resource('addresses', self.id)
         if not self._ip:
         if not self._ip:
             self._ip = {'status': 'UNKNOWN'}
             self._ip = {'status': 'UNKNOWN'}
+        else:
+            self._process_ip_users()
+
+    def _process_ip_users(self):
+        self._rule = None
+        self._target_instance = None
+
+        if 'users' in self._ip and len(self._ip['users']) > 0:
+            provider = self._provider
+            if len(self._ip['users']) > 1:
+                cb.log.warning('Address "%s" in use by more than one resource',
+                               self._ip['address'])
+            resource_parsed_url = provider.parse_url(self._ip['users'][0])
+            resource = resource_parsed_url.get_resource()
+            if resource['kind'] == 'compute#forwardingRule':
+                self._rule = resource
+                target = provider.parse_url(resource['target']).get_resource()
+                if target['kind'] == 'compute#targetInstance':
+                    url = provider.parse_url(target['instance'])
+                    try:
+                        self._target_instance = url.get_resource()
+                    except googleapiclient.errors.HttpError:
+                        self._target_instance = GCEFloatingIP._DEAD_INSTANCE
+                else:
+                    cb.log.warning('Address "%s" is forwarded to a %s',
+                                   self._ip['address'], target['kind'])
+            else:
+                cb.log.warning('Address "%s" in use by a %s',
+                               self._ip['address'], resource['kind'])
 
 
 
 
 class GCERouter(BaseRouter):
 class GCERouter(BaseRouter):
@@ -1515,10 +1521,14 @@ class GCERouter(BaseRouter):
             subnet = self._provider.networking.subnets.get(subnet)
             subnet = self._provider.networking.subnets.get(subnet)
         if subnet.network_id == self.network_id:
         if subnet.network_id == self.network_id:
             return
             return
-        cb.log.warning('GCE routers should be attached at creation time')
+        cb.log.warning('Google Cloud Routers automatically learn new subnets '
+                       'in your VPC network and announces them to your '
+                       'on-premises network')
 
 
     def detach_subnet(self, network_id):
     def detach_subnet(self, network_id):
-        cb.log.warning('GCE routers are always attached')
+        cb.log.warning('Cannot detach from subnet. Google Cloud Routers '
+                       'automatically learn new subnets in your VPC network '
+                       'and announces them to your on-premises network')
 
 
     def attach_gateway(self, gateway):
     def attach_gateway(self, gateway):
         pass
         pass
@@ -1604,6 +1614,7 @@ class GCESubnet(BaseSubnet):
 
 
     @name.setter
     @name.setter
     def name(self, value):
     def name(self, value):
+        GCESubnet.assert_valid_resource_name(value)
         if value == self.name:
         if value == self.name:
             return
             return
         cb.log.warning('Cannot change the name of a GCE subnetwork')
         cb.log.warning('Cannot change the name of a GCE subnetwork')
@@ -2040,7 +2051,21 @@ class GCSObject(BaseBucketObject):
              .execute())
              .execute())
 
 
     def generate_url(self, expires_in=0):
     def generate_url(self, expires_in=0):
-        return self._obj['mediaLink']
+        """
+        Generates a signed URL accessible to everyone.
+        """
+        expiration = calendar.timegm(time.gmtime()) + 2 * 24 * 60 * 60
+        signature = self._provider.sign_blob(
+                'GET\n\n\n%d\n%s/%s' %
+                (expiration, self._obj['bucket'], self.name))
+        encoded_signature = base64.b64encode(signature)
+        url_encoded_signature = (encoded_signature.replace('+', '%2B')
+                                                  .replace('/', '%2F'))
+        return ('https://storage.googleapis.com/%s/%s?GoogleAccessId=%s'
+                '&Expires=%d&Signature=%s' % (self._obj['bucket'], self.name,
+                                              self._provider.client_id,
+                                              expiration,
+                                              url_encoded_signature))
 
 
 
 
 class GCSBucketContainer(BaseBucketContainer):
 class GCSBucketContainer(BaseBucketContainer):
@@ -2127,6 +2152,7 @@ class GCSBucket(BaseBucket):
              .buckets()
              .buckets()
              .delete(bucket=self.name)
              .delete(bucket=self.name)
              .execute())
              .execute())
+        time.sleep(2)
 
 
     def create_object(self, name):
     def create_object(self, name):
         """
         """

+ 66 - 46
cloudbridge/cloud/providers/gce/services.py

@@ -1,4 +1,5 @@
 import hashlib
 import hashlib
+import time
 import uuid
 import uuid
 from collections import namedtuple
 from collections import namedtuple
 
 
@@ -22,6 +23,8 @@ from cloudbridge.cloud.base.services import BaseSubnetService
 from cloudbridge.cloud.base.services import BaseVMFirewallService
 from cloudbridge.cloud.base.services import BaseVMFirewallService
 from cloudbridge.cloud.base.services import BaseVMTypeService
 from cloudbridge.cloud.base.services import BaseVMTypeService
 from cloudbridge.cloud.base.services import BaseVolumeService
 from cloudbridge.cloud.base.services import BaseVolumeService
+from cloudbridge.cloud.interfaces.exceptions import DuplicateResourceException
+from cloudbridge.cloud.interfaces.resources import TrafficDirection
 from cloudbridge.cloud.interfaces.resources import VMFirewall
 from cloudbridge.cloud.interfaces.resources import VMFirewall
 from cloudbridge.cloud.providers.gce import helpers
 from cloudbridge.cloud.providers.gce import helpers
 
 
@@ -82,7 +85,7 @@ class GCEKeyPairService(BaseKeyPairService):
         """
         """
         Returns a KeyPair given its ID.
         Returns a KeyPair given its ID.
         """
         """
-        for kp in self.list():
+        for kp in self:
             if kp.id == key_pair_id:
             if kp.id == key_pair_id:
                 return kp
                 return kp
         else:
         else:
@@ -107,16 +110,16 @@ class GCEKeyPairService(BaseKeyPairService):
 
 
     def _get_or_add_sshkey_entry(self, metadata):
     def _get_or_add_sshkey_entry(self, metadata):
         """
         """
-        Get the sshKeys entry from commonInstanceMetadata/items.
+        Get the ssh-keys entry from commonInstanceMetadata/items.
         If an entry does not exist, adds a new empty entry
         If an entry does not exist, adds a new empty entry
         """
         """
         sshkey_entry = None
         sshkey_entry = None
         entries = [item for item in metadata.get('items', [])
         entries = [item for item in metadata.get('items', [])
-                   if item['key'] == 'sshKeys']
+                   if item['key'] == 'ssh-keys']
         if entries:
         if entries:
             sshkey_entry = entries[0]
             sshkey_entry = entries[0]
         else:  # add a new entry
         else:  # add a new entry
-            sshkey_entry = {'key': 'sshKeys', 'value': ''}
+            sshkey_entry = {'key': 'ssh-keys', 'value': ''}
             if 'items' not in metadata:
             if 'items' not in metadata:
                 metadata['items'] = [sshkey_entry]
                 metadata['items'] = [sshkey_entry]
             else:
             else:
@@ -180,31 +183,44 @@ class GCEKeyPairService(BaseKeyPairService):
         key_pairs = []
         key_pairs = []
         for gce_kp in self._iter_gce_key_pairs():
         for gce_kp in self._iter_gce_key_pairs():
             kp_id = self.gce_kp_to_id(gce_kp)
             kp_id = self.gce_kp_to_id(gce_kp)
-            kp_name = gce_kp.email
-            key_pairs.append(GCEKeyPair(self.provider, kp_id, kp_name))
+            key_pairs.append(GCEKeyPair(self.provider, kp_id, gce_kp.email))
         return ClientPagedResultList(self.provider, key_pairs,
         return ClientPagedResultList(self.provider, key_pairs,
                                      limit=limit, marker=marker)
                                      limit=limit, marker=marker)
 
 
-    def find(self, name, limit=None, marker=None):
+    def find(self, **kwargs):
         """
         """
         Searches for a key pair by a given list of attributes.
         Searches for a key pair by a given list of attributes.
         """
         """
-        found_kps = []
-        for kp in self.list():
-            if kp.name == name:
-                found_kps.append(kp)
-        return ClientPagedResultList(self.provider, found_kps,
-                                     limit=limit, marker=marker)
+        kp_name = kwargs.get('name', None)
+        kp_id = kwargs.get('id', None)
+        for parameter in kwargs:
+            if parameter not in ('id', 'name'):
+                cb.log.error('Unrecognised parameters for search: %s. '
+                             'Supported attributes: id, name', parameter)
+
+        out = []
+        for kp in self:
+            if kp_name is not None and kp.name != kp_name:
+                continue
+            if kp_id is not None and kp.id != kp_id:
+                continue
+            out.append(kp)
+        return out
 
 
-    def create(self, name):
+    def create(self, name, public_key_material=None):
         GCEKeyPair.assert_valid_resource_name(name)
         GCEKeyPair.assert_valid_resource_name(name)
-        kp = self.find(name=name)
-        if kp:
-            return kp
 
 
-        private_key, public_key = helpers.generate_key_pair()
-        kp_info = GCEKeyPairService.GCEKeyInfo(name + u":ssh-rsa",
-                                               public_key, name)
+        if self.find(name=name):
+            raise DuplicateResourceException(
+                'A KeyPair with the same name %s exists', name)
+        private_key = None
+        if not public_key_material:
+            private_key, public_key_material = helpers.generate_key_pair()
+        parts = public_key_material.split(' ')
+        if len(parts) == 2:
+            public_key_material = parts[1]
+        kp_info = GCEKeyPairService.GCEKeyInfo(
+            '%s:ssh-rsa' % name, public_key_material, name)
 
 
         def _add_kp(gce_kp_generator):
         def _add_kp(gce_kp_generator):
             kp_list = []
             kp_list = []
@@ -216,7 +232,7 @@ class GCEKeyPairService(BaseKeyPairService):
 
 
         self.gce_metadata_save_op(_add_kp)
         self.gce_metadata_save_op(_add_kp)
         return GCEKeyPair(self.provider, self.gce_kp_to_id(kp_info), name,
         return GCEKeyPair(self.provider, self.gce_kp_to_id(kp_info), name,
-                          kp_material=private_key)
+                          private_key)
 
 
 
 
 class GCEVMFirewallService(BaseVMFirewallService):
 class GCEVMFirewallService(BaseVMFirewallService):
@@ -245,7 +261,13 @@ class GCEVMFirewallService(BaseVMFirewallService):
     def create(self, name, description, network_id=None):
     def create(self, name, description, network_id=None):
         GCEVMFirewall.assert_valid_resource_name(name)
         GCEVMFirewall.assert_valid_resource_name(name)
         network = self.provider.networking.networks.get(network_id)
         network = self.provider.networking.networks.get(network_id)
-        return GCEVMFirewall(self._delegate, name, network, description)
+        fw = GCEVMFirewall(self._delegate, name, network, description)
+        # This rule exists implicitly. Add it explicitly so that the firewall
+        # is not empty and the rule is shown by list/get/find methods.
+        fw.rules.create_with_priority(
+                direction=TrafficDirection.OUTBOUND, protocol='tcp',
+                priority=65534, cidr='0.0.0.0/0')
+        return fw
 
 
     def find(self, name, limit=None, marker=None):
     def find(self, name, limit=None, marker=None):
         """
         """
@@ -665,7 +687,8 @@ class GCENetworkService(BaseNetworkService):
         GCE networks are global. There is at most one network with a given
         GCE networks are global. There is at most one network with a given
         name.
         name.
         """
         """
-        return [self.get(name)]
+        network = self.get(name)
+        return [network] if network else []
 
 
     def get_by_name(self, network_name):
     def get_by_name(self, network_name):
         if network_name is None:
         if network_name is None:
@@ -674,6 +697,7 @@ class GCENetworkService(BaseNetworkService):
         return None if len(networks) == 0 else networks[0]
         return None if len(networks) == 0 else networks[0]
 
 
     def list(self, limit=None, marker=None, filter=None):
     def list(self, limit=None, marker=None, filter=None):
+        networks = []
         try:
         try:
             response = (self.provider
             response = (self.provider
                             .gce_compute
                             .gce_compute
@@ -681,13 +705,12 @@ class GCENetworkService(BaseNetworkService):
                             .list(project=self.provider.project_name,
                             .list(project=self.provider.project_name,
                                   filter=filter)
                                   filter=filter)
                             .execute())
                             .execute())
-            networks = []
             for network in response.get('items', []):
             for network in response.get('items', []):
                 networks.append(GCENetwork(self.provider, network))
                 networks.append(GCENetwork(self.provider, network))
-            return networks
         except googleapiclient.errors.HttpError as http_error:
         except googleapiclient.errors.HttpError as http_error:
             cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
             cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
-            return []
+        return ClientPagedResultList(self.provider, networks,
+                                     limit=limit, marker=marker)
 
 
     def _create(self, name, cidr_block, create_subnetworks):
     def _create(self, name, cidr_block, create_subnetworks):
         """
         """
@@ -736,7 +759,7 @@ class GCENetworkService(BaseNetworkService):
         to add additional subnets later.
         to add additional subnets later.
         """
         """
         GCENetwork.assert_valid_resource_name(name)
         GCENetwork.assert_valid_resource_name(name)
-        return self._create(name, cidr_block, True)
+        return self._create(name, cidr_block, False)
 
 
     def get_or_create_default(self):
     def get_or_create_default(self):
         return self._create(GCEFirewallsDelegate.DEFAULT_NETWORK, None, True)
         return self._create(GCEFirewallsDelegate.DEFAULT_NETWORK, None, True)
@@ -780,7 +803,7 @@ class GCERouterService(BaseRouterService):
                                      response.get('nextPageToken'),
                                      response.get('nextPageToken'),
                                      False, data=routers)
                                      False, data=routers)
 
 
-    def create(self, network, name=None):
+    def create(self, name, network):
         name = name if name else 'router-{0}'.format(uuid.uuid4())
         name = name if name else 'router-{0}'.format(uuid.uuid4())
         GCERouter.assert_valid_resource_name(name)
         GCERouter.assert_valid_resource_name(name)
 
 
@@ -878,7 +901,7 @@ class GCESubnetService(BaseSubnetService):
             name = 'subnet-{0}'.format(uuid.uuid4())
             name = 'subnet-{0}'.format(uuid.uuid4())
         GCESubnet.assert_valid_resource_name(name)
         GCESubnet.assert_valid_resource_name(name)
         region_name = self._zone_to_region_name(zone)
         region_name = self._zone_to_region_name(zone)
-        for subnet in self.list_all(network=network):
+        for subnet in self.iter(network=network):
             if BaseNetwork.cidr_blocks_overlap(subnet.cidr_block, cidr_block):
             if BaseNetwork.cidr_blocks_overlap(subnet.cidr_block, cidr_block):
                 if subnet.region_name != region_name:
                 if subnet.region_name != region_name:
                     cb.log.error('Failed to create subnetwork in region %s: '
                     cb.log.error('Failed to create subnetwork in region %s: '
@@ -890,7 +913,6 @@ class GCESubnetService(BaseSubnetService):
             if subnet.name == name and subnet.region_name == region_name:
             if subnet.name == name and subnet.region_name == region_name:
                 return subnet
                 return subnet
 
 
-
         body = {'ipCidrRange': cidr_block,
         body = {'ipCidrRange': cidr_block,
                 'name': name,
                 'name': name,
                 'network': network.resource_url,
                 'network': network.resource_url,
@@ -921,7 +943,7 @@ class GCESubnetService(BaseSubnetService):
         zone.
         zone.
         """
         """
         network = self.provider.networking.networks.get_or_create_default()
         network = self.provider.networking.networks.get_or_create_default()
-        subnets = list(self.list_all(network=network, zone=zone))
+        subnets = list(self.iter(network=network, zone=zone))
         if len(subnets) > 1:
         if len(subnets) > 1:
             cb.log.warning('The default network has more than one subnetwork '
             cb.log.warning('The default network has more than one subnetwork '
                            'in a region')
                            'in a region')
@@ -931,21 +953,18 @@ class GCESubnetService(BaseSubnetService):
         return None
         return None
 
 
     def delete(self, subnet):
     def delete(self, subnet):
-        network_url = self.provider.parse_url(subnet.network_url)
-        if subnet.name == network_url.parameters['network']:
-            # This is an auto subnetwork of an auto mode network. We cannot
-            # delete it. It will be deleted automatically when the network is
-            # deleted.
-            return
-
-        response = (self.provider
-                        .gce_compute
-                        .subnetworks()
-                        .delete(project=self.provider.project_name,
-                                region=subnet.region_name,
-                                subnetwork=subnet.name)
-                        .execute())
-        self._provider.wait_for_operation(response, region=subnet.region_name)
+        try:
+            response = (self.provider
+                            .gce_compute
+                            .subnetworks()
+                            .delete(project=self.provider.project_name,
+                                    region=subnet.region_name,
+                                    subnetwork=subnet.name)
+                            .execute())
+            self._provider.wait_for_operation(
+                response, region=subnet.region_name)
+        except googleapiclient.errors.HttpError as http_error:
+            cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
 
 
     def _zone_to_region_name(self, zone):
     def _zone_to_region_name(self, zone):
         if zone:
         if zone:
@@ -1233,6 +1252,7 @@ class GCSBucketService(BaseBucketService):
                             .execute())
                             .execute())
             if 'error' in response:
             if 'error' in response:
                 return None
                 return None
+            time.sleep(2)
             return GCSBucket(self.provider, response)
             return GCSBucket(self.provider, response)
         except googleapiclient.errors.HttpError as http_error:
         except googleapiclient.errors.HttpError as http_error:
             cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)
             cb.log.warning('googleapiclient.errors.HttpError: %s', http_error)

+ 7 - 8
test/test_compute_service.py

@@ -9,7 +9,6 @@ from cloudbridge.cloud.interfaces import InvalidConfigurationException
 from cloudbridge.cloud.interfaces.exceptions import WaitStateException
 from cloudbridge.cloud.interfaces.exceptions import WaitStateException
 from cloudbridge.cloud.interfaces.resources import Instance
 from cloudbridge.cloud.interfaces.resources import Instance
 from cloudbridge.cloud.interfaces.resources import SnapshotState
 from cloudbridge.cloud.interfaces.resources import SnapshotState
-from cloudbridge.cloud.interfaces.resources import TrafficDirection
 from cloudbridge.cloud.interfaces.resources import VMType
 from cloudbridge.cloud.interfaces.resources import VMType
 
 
 import six
 import six
@@ -319,8 +318,6 @@ class CloudComputeServiceTestCase(ProviderTestBase):
                                                   subnet=subnet)
                                                   subnet=subnet)
             fw = self.provider.security.vm_firewalls.create(
             fw = self.provider.security.vm_firewalls.create(
                 name=name, description=name, network_id=net.id)
                 name=name, description=name, network_id=net.id)
-            fw.rules.create(direction=TrafficDirection.INBOUND, protocol='tcp',
-                            from_port=1111, to_port=1111, cidr='0.0.0.0/0')
 
 
             # Check adding a VM firewall to a running instance
             # Check adding a VM firewall to a running instance
             test_inst.add_vm_firewall(fw)
             test_inst.add_vm_firewall(fw)
@@ -353,13 +350,15 @@ class CloudComputeServiceTestCase(ProviderTestBase):
                 router.attach_subnet(subnet)
                 router.attach_subnet(subnet)
                 gateway = net.gateways.get_or_create_inet_gateway(name)
                 gateway = net.gateways.get_or_create_inet_gateway(name)
                 router.attach_gateway(gateway)
                 router.attach_gateway(gateway)
-                # check whether adding an elastic ip works
-                fip = gateway.floating_ips.create()
-                self.assertFalse(
-                    fip.in_use,
-                    "Newly created floating IP address should not be in use.")
+                fip = None
 
 
                 with helpers.cleanup_action(lambda: fip.delete()):
                 with helpers.cleanup_action(lambda: fip.delete()):
+                    # check whether adding an elastic ip works
+                    fip = gateway.floating_ips.create()
+                    self.assertFalse(
+                        fip.in_use,
+                        "Newly created floating IP should not be in use.")
+
                     with helpers.cleanup_action(
                     with helpers.cleanup_action(
                             lambda: test_inst.remove_floating_ip(fip)):
                             lambda: test_inst.remove_floating_ip(fip)):
                         test_inst.add_floating_ip(fip)
                         test_inst.add_floating_ip(fip)

+ 2 - 0
test/test_interface.py

@@ -61,6 +61,8 @@ class CloudInterfaceTestCase(ProviderTestBase):
             cloned_config['os_password'] = "cb_dummy"
             cloned_config['os_password'] = "cb_dummy"
         elif self.provider.PROVIDER_ID == 'azure':
         elif self.provider.PROVIDER_ID == 'azure':
             cloned_config['azure_subscription_id'] = "cb_dummy"
             cloned_config['azure_subscription_id'] = "cb_dummy"
+        elif self.provider.PROVIDER_ID == 'gce':
+            cloned_config['gce_service_creds_dict'] = {'dummy': 'dict'}
 
 
         with self.assertRaises(ProviderConnectionException):
         with self.assertRaises(ProviderConnectionException):
             cloned_provider = CloudProviderFactory().create_provider(
             cloned_provider = CloudProviderFactory().create_provider(

+ 6 - 7
test/test_network_service.py

@@ -6,7 +6,6 @@ from test.helpers import standard_interface_tests as sit
 from cloudbridge.cloud.base.resources import BaseNetwork
 from cloudbridge.cloud.base.resources import BaseNetwork
 from cloudbridge.cloud.interfaces.resources import FloatingIP
 from cloudbridge.cloud.interfaces.resources import FloatingIP
 from cloudbridge.cloud.interfaces.resources import Network
 from cloudbridge.cloud.interfaces.resources import Network
-from cloudbridge.cloud.interfaces.resources import RouterState
 from cloudbridge.cloud.interfaces.resources import Subnet
 from cloudbridge.cloud.interfaces.resources import Subnet
 
 
 
 
@@ -71,7 +70,7 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
                     "Network ID %s should be specified in the subnet's network"
                     "Network ID %s should be specified in the subnet's network"
                     " id %s." % (net.id, sn.network_id))
                     " id %s." % (net.id, sn.network_id))
 
 
-                self.assertEqual(
+                self.assertTrue(
                     BaseNetwork.cidr_blocks_overlap(cidr, sn.cidr_block),
                     BaseNetwork.cidr_blocks_overlap(cidr, sn.cidr_block),
                     "Subnet's CIDR %s should overlap the specified one %s." % (
                     "Subnet's CIDR %s should overlap the specified one %s." % (
                         sn.cidr_block, cidr))
                         sn.cidr_block, cidr))
@@ -170,11 +169,11 @@ class CloudNetworkServiceTestCase(ProviderTestBase):
             # Check basic router properties
             # Check basic router properties
             sit.check_standard_behaviour(
             sit.check_standard_behaviour(
                 self, self.provider.networking.routers, router)
                 self, self.provider.networking.routers, router)
-            self.assertEqual(
-                router.state, RouterState.DETACHED,
-                "Router {0} state {1} should be {2}.".format(
-                    router.id, router.state, RouterState.DETACHED))
-
+#            self.assertEqual(
+#                router.state, RouterState.DETACHED,
+#                "Router {0} state {1} should be {2}.".format(
+#                    router.id, router.state, RouterState.DETACHED))
+#
 #             self.assertFalse(
 #             self.assertFalse(
 #                 router.network_id,
 #                 router.network_id,
 #                 "Router {0} should not be assoc. with a network {1}".format(
 #                 "Router {0} should not be assoc. with a network {1}".format(

+ 1 - 1
test/test_object_store_service.py

@@ -37,7 +37,7 @@ class CloudObjectStoreServiceTestCase(ProviderTestBase):
 
 
         with self.assertRaises(InvalidNameException):
         with self.assertRaises(InvalidNameException):
             # underscores are not allowed in bucket names
             # underscores are not allowed in bucket names
-            create_bucket("cb-bucket")
+            create_bucket("cb_bucket")
 
 
         with self.assertRaises(InvalidNameException):
         with self.assertRaises(InvalidNameException):
             # names of length less than 3 should raise an exception
             # names of length less than 3 should raise an exception

+ 2 - 2
test/test_security_service.py

@@ -204,9 +204,9 @@ class CloudSecurityServiceTestCase(ProviderTestBase):
                 r.delete()
                 r.delete()
             fw = self.provider.security.vm_firewalls.get(fw.id)  # update
             fw = self.provider.security.vm_firewalls.get(fw.id)  # update
             self.assertTrue(
             self.assertTrue(
-                len(list(fw.rules)) == 0,
+                fw is None or len(list(fw.rules)) == 0,
                 "Deleting VMFirewallRule should delete it: {0}".format(
                 "Deleting VMFirewallRule should delete it: {0}".format(
-                    fw.rules))
+                    fw.rules if fw else []))
         fwl = self.provider.security.vm_firewalls.list()
         fwl = self.provider.security.vm_firewalls.list()
         found_fw = [f for f in fwl if f.name == name]
         found_fw = [f for f in fwl if f.name == name]
         self.assertTrue(
         self.assertTrue(