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

The floating IP implementation

AWS' floating IP concept is simulated by a combination of GCE's static
regional IPs and forwarding rules.
Ehsan Chiniforooshan 9 лет назад
Родитель
Сommit
2fdd1425cb

+ 19 - 5
cloudbridge/cloud/providers/gce/provider.py

@@ -5,6 +5,7 @@ for GCE.
 
 
 from cloudbridge.cloud.base import BaseCloudProvider
+import cloudbridge as cb
 import json
 import os
 import time
@@ -81,6 +82,10 @@ class GCECloudProvider(BaseCloudProvider):
             self._gce_compute = self._connect_gce_compute()
         return self._gce_compute
 
+    @staticmethod
+    def parse_url(url):
+        return {}
+
     def _connect_gce_compute(self):
         if self.credentials_dict:
             credentials = ServiceAccountCredentials.from_json_keyfile_dict(
@@ -89,15 +94,24 @@ class GCECloudProvider(BaseCloudProvider):
             credentials = GoogleCredentials.get_application_default()
         return discovery.build('compute', 'v1', credentials=credentials)
 
-    def wait_for_global_operation(self, operation):
+    def wait_for_operation(self, operation, region=None, zone=None):
+        args = {'project': self.project_name, 'operation': operation['name']}
+        if not region and not zone:
+            operations = self.gce_compute.globalOperations()
+        elif region:
+            args['region'] = region
+            operations = self.gce_compute.regionOperations()
+        else:
+            args['zone'] = zone
+            operations = self.gce_compute.zoneOperations()
         while True:
-            result = self.gce_compute.globalOperations().get(
-                project=self.project_name,
-                operation=operation['name']).execute()
-
+            result = operations.get(**args).execute()
             if result['status'] == 'DONE':
                 if 'error' in result:
                     raise Exception(result['error'])
                 return result
 
             time.sleep(0.5)
+
+    def get_url(self, url):
+        return {'kind': 'error'}

+ 78 - 3
cloudbridge/cloud/providers/gce/resources.py

@@ -1,6 +1,7 @@
 """
 DataTypes used by this provider
 """
+from cloudbridge.cloud.base.resources import BaseFloatingIP
 from cloudbridge.cloud.base.resources import BaseInstanceType
 from cloudbridge.cloud.base.resources import BaseKeyPair
 from cloudbridge.cloud.base.resources import BaseMachineImage
@@ -11,6 +12,8 @@ from cloudbridge.cloud.base.resources import BaseSecurityGroup
 from cloudbridge.cloud.base.resources import BaseSecurityGroupRule
 from cloudbridge.cloud.interfaces.resources import MachineImageState
 
+import cloudbridge as cb
+
 # Older versions of Python do not have a built-in set data-structure.
 try:
     set
@@ -280,7 +283,7 @@ class GCEFirewallsDelegate(object):
                                       .insert(project=project_name,
                                               body=firewall)
                                       .execute())
-            self._provider.wait_for_global_operation(response)
+            self._provider.wait_for_operation(response)
             # TODO: process the response and handle errors.
             return True
         except:
@@ -376,7 +379,7 @@ class GCEFirewallsDelegate(object):
                                       .delete(project=project_name,
                                               firewall=firewall['name'])
                                       .execute())
-            self._provider.wait_for_global_operation(response)
+            self._provider.wait_for_operation(response)
             # TODO: process the response and handle errors.
             return True
         except:
@@ -726,7 +729,7 @@ class GCENetwork(BaseNetwork):
                     .execute())
             if 'error' in response:
                 return False
-            self._provider.wait_for_global_operation(response)
+            self._provider.wait_for_operation(response)
             return True
         except:
             return False
@@ -739,3 +742,75 @@ class GCENetwork(BaseNetwork):
 
     def refresh(self):
         return self.state
+
+class GCEFloatingIP(BaseFloatingIP):
+
+    def __init__(self, provider, floating_ip):
+        super(GCEFloatingIP, self).__init__(provider)
+        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.
+        parsed_url = GCECloudProvider.parse_url(self._ip['region'])
+        if 'regions' in parsed_url:
+            self._region = parsed_url['regions']
+        else:
+            self._region = provider.region_name
+
+        # 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('IP is in user by more than one resource')
+            resource = provider.get_url(floating_ip['users'][0])
+            if resource['kind'] == 'compute#instance':
+                self._target_instance = resource
+            elif resource['kind'] == 'compute#forwardingRule':
+                self._rule = resource
+                target = provider.get_url(resource['target'])
+                if target['kind'] == 'compute#targetInstance':
+                    self._target_instance = provider.get_url(target['instance'])
+                else:
+                    cb.log.warning('IP is forwarded to a %s' % target['kind'])
+            else:
+                cb.log.warning('IP in use by a %s' % resource['kind'])
+
+    @property
+    def id(self):
+        return self._ip['id']
+
+    @property
+    def public_ip(self):
+        return self._ip['address']
+
+    @property
+    def private_ip(self):
+        if not self._target_instance:
+            return None
+        return self._target_instance['networkInterfaces'][0]['networkIP']
+
+    def in_use(self):
+        return True if self._target_instance else False
+
+    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
+                                     .forwarding_rules()
+                                     .delete(project=project_name,
+                                             region=self._region,
+                                             forwardingRule=self._rule['name'])
+                                     .execute())
+           self._provider.wait_forion(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)

+ 43 - 6
cloudbridge/cloud/providers/gce/services.py

@@ -17,7 +17,10 @@ import googleapiclient
 from retrying import retry
 import sys
 
+import uuid
+
 from .resources import GCEFirewallsDelegate
+from .resources import GCEFloatingIP
 from .resources import GCEMachineImage
 from .resources import GCENetwork
 from .resources import GCEInstanceType
@@ -138,7 +141,7 @@ class GCEKeyPairService(BaseKeyPairService):
             # common_metadata will have the current fingerprint at this point
             operation = self.gce_projects.setCommonInstanceMetadata(
                 project=self.provider.project_name, body=metadata).execute()
-            self.provider.wait_for_global_operation(operation)
+            self.provider.wait_for_operation(operation)
 
         # Retry a few times if the fingerprints conflict
         retry_decorator = retry(stop_max_attempt_number=5)
@@ -479,7 +482,7 @@ class GCENetworkService(BaseNetworkService):
                                      .execute())
             if 'error' in response:
                 return None
-            self.provider.wait_for_global_operation(response)
+            self.provider.wait_for_operation(response)
             networks = self.list(filter='name eq %s' % name)
             return None if len(networks) == 0 else networks[0]
         except:
@@ -489,11 +492,45 @@ class GCENetworkService(BaseNetworkService):
     def subnets(self):
         raise NotImplementedError('To be implemented')
 
-    def floating_ips(self, network_id=None):
-        raise NotImplementedError('To be implemented')
+    def floating_ips(self, network_id=None, region=None):
+        if not region:
+            region = self.provider.region_name
+        try:
+            response = (self.provider.gce_compute
+                                     .addresses()
+                                     .list(project=self.provider.project_name,
+                                           region=region)
+                                     .execute())
+            ips = []
+            if 'items' in response:
+                for ip in response['items']:
+                    ips.append(GCEFloatingIP(self.provider, ip))
+            # TODO: if network_id is given, filter out IPs that are assigned to
+            # resources in a different network.
+            return ips
+        except:
+            return []
 
-    def create_floating_ip(self):
-        raise NotImplementedError('To be implemented')
+    def create_floating_ip(self, region=None):
+        if not region:
+            region = self.provider.region_name
+        ip_name = 'ip-{0}'.format(uuid.uuid4())
+        try:
+            response = (self.provider.gce_compute
+                                     .addresses()
+                                     .insert(project=self.provider.project_name,
+                                             region=region,
+                                             body={'name': ip_name})
+                                     .execute())
+            if 'error' in response:
+                return None
+            self.provider.wait_for_operation(response, region=region)
+            ips = self.floating_ips()
+            for ip in ips:
+                if ip.id == response["targetId"]:
+                    return ip
+        except:
+            return None
 
     def routers(self):
         raise NotImplementedError('To be implemented')