baizhang 9 лет назад
Родитель
Сommit
107f2e83d5

+ 23 - 0
cloudbridge/cloud/providers/gce/provider.py

@@ -5,11 +5,13 @@ for GCE.
 
 
 from cloudbridge.cloud.base import BaseCloudProvider
+import httplib2
 import json
 import os
 import time
 
 from googleapiclient import discovery
+import googleapiclient.http
 from oauth2client.client import GoogleCredentials
 from oauth2client.service_account import ServiceAccountCredentials
 
@@ -44,6 +46,8 @@ class GCECloudProvider(BaseCloudProvider):
         self.region_name = self._get_config_value(
             'gce_region_name', 'us-central1')
 
+        # oauth2client.Credentials to be used for authentication
+        self._credentials = None
         # service connections, lazily initialized
         self._gce_compute = None
 
@@ -86,8 +90,27 @@ class GCECloudProvider(BaseCloudProvider):
                 self.credentials_dict)
         else:
             credentials = GoogleCredentials.get_application_default()
+        self._credentials = credentials
         return discovery.build('compute', 'v1', credentials=credentials)
 
+    def get_gce_resource_data(self, uri):
+        """
+        Retrieves GCE resoure data given its resource URI.
+        """
+        http = httplib2.Http()
+        http = self._credentials.authorize(http)
+        def _postproc(*kwargs):
+            if len(kwargs) >= 2:
+                # The first argument is request, and the second is response.
+                resource_dict = json.loads(kwargs[1])
+                return resource_dict
+        request = googleapiclient.http.HttpRequest(http=http,
+                                                   postproc=_postproc,
+                                                   uri=uri)
+        # The response is a dict representing the GCE resource data.
+        response = request.execute()
+        return response
+
     def wait_for_global_operation(self, operation):
         while True:
             result = self.gce_compute.globalOperations().get(

+ 251 - 1
cloudbridge/cloud/providers/gce/resources.py

@@ -1,6 +1,7 @@
 """
 DataTypes used by this provider
 """
+from cloudbridge.cloud.base.resources import BaseInstance
 from cloudbridge.cloud.base.resources import BaseInstanceType
 from cloudbridge.cloud.base.resources import BaseKeyPair
 from cloudbridge.cloud.base.resources import BaseMachineImage
@@ -8,6 +9,8 @@ from cloudbridge.cloud.base.resources import BasePlacementZone
 from cloudbridge.cloud.base.resources import BaseRegion
 from cloudbridge.cloud.base.resources import BaseSecurityGroup
 from cloudbridge.cloud.base.resources import BaseSecurityGroupRule
+from cloudbridge.cloud.interfaces.resources import InstanceState
+from cloudbridge.cloud.interfaces.resources import MachineImageState
 
 # Older versions of Python do not have a built-in set data-structure.
 try:
@@ -65,6 +68,10 @@ class GCEInstanceType(BaseInstanceType):
         super(GCEInstanceType, self).__init__(provider)
         self._inst_dict = instance_dict
 
+    @property
+    def resource_url(self):
+        return self._inst_dict.get('selfLink')
+
     @property
     def id(self):
         return str(self._inst_dict.get('id'))
@@ -614,6 +621,10 @@ class GCEMachineImage(BaseMachineImage):
         else:
             self._gce_image = image
 
+    @property
+    def resource_url(self):
+        return self._gce_image.get('selfLink')
+
     @property
     def id(self):
         """
@@ -680,4 +691,243 @@ class GCEMachineImage(BaseMachineImage):
             # image no longer exists
             cb.log.warning(
                 "googleapiclient.errors.HttpError: {0}".format(http_error))
-            self._gce_image['status'] = "unknown"
+            self._gce_image['status'] = "unknown"
+
+
+class GCEInstance(BaseInstance):
+    # https://cloud.google.com/compute/docs/reference/latest/instances
+    # The status of the instance. One of the following values:
+    # PROVISIONING, STAGING, RUNNING, STOPPING, SUSPENDING, SUSPENDED,
+    # and TERMINATED.
+    INSTANCE_STATE_MAP = {
+        'PROVISIONING': InstanceState.PENDING,
+        'STAGING': InstanceState.PENDING,
+        'RUNNING': InstanceState.RUNNING,
+        'STOPPING': InstanceState.CONFIGURING,
+        'TERMINATED': InstanceState.TERMINATED,
+        'SUSPENDING': InstanceState.CONFIGURING,
+        'SUSPENDED': InstanceState.STOPPED
+    }
+
+    def __init__(self, provider, gce_instance):
+        super(GCEInstance, self).__init__(provider)
+        self._gce_instance = gce_instance
+
+    @property
+    def resource_url(self):
+        return self._gce_instance.get('selfLink')
+
+    @property
+    def id(self):
+        """
+        Get the instance identifier.
+        """
+        return self._gce_instance['id']
+
+    @property
+    def name(self):
+        """
+        Get the instance name.
+        """
+        return self._gce_instance['name']
+
+    @name.setter
+    # pylint:disable=arguments-differ
+    def name(self, value):
+        """
+        Set the instance name.
+        """
+        response = self._provider.gce_compute \
+                                 .instances() \
+                                 .setTags(project=self._provider.project_name,
+                                          zone=self._provider.default_zone,
+                                          instance=self.name,
+                                          body={"items": [value]}) \
+                                 .execute()
+
+    @property
+    def public_ips(self):
+        """
+        Get all the public IP addresses for this instance.
+        """
+        network_interfaces = self._gce_instance.get('networkInterfaces')
+        if network_interfaces is None or len(network_interfaces) == 0:
+            return []
+        access_configs = network_interfaces[0].get('accessConfigs')
+        if access_configs is None or len(access_configs) == 0:
+            return []
+        # 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.
+        access_config = access_configs[0]
+        if 'natIP' in access_config:
+            return [access_config['natIP']]
+        else:
+            return []
+
+    @property
+    def private_ips(self):
+        """
+        Get all the private IP addresses for this instance.
+        """
+        network_interfaces = self._gce_instance.get('networkInterfaces')
+        if network_interfaces is None or len(network_interfaces) == 0:
+            return []
+        if 'networkIP' in network_interfaces[0]:
+            return [network_interfaces[0]['networkIP']]
+        else:
+            return []
+
+    @property
+    def instance_type_id(self):
+        """
+        Get the instance type name.
+        """
+        machine_type_uri = self._gce_instance.get('machineType')
+        if machine_type_uri is None:
+            return None
+        instance_type = self._provider.get_gce_resource_data(machine_type_uri)
+        return instance_type.get('name', None)
+
+    @property
+    def instance_type(self):
+        """
+        Get the instance type.
+        """
+        machine_type_uri = self._gce_instance.get('machineType')
+        if machine_type_uri is None:
+            return None
+        instance_type = self._provider.get_gce_resource_data(machine_type_uri)
+        return GCEInstanceType(self._provider, instance_type)
+
+    def reboot(self):
+        """
+        Reboot this instance.
+        """
+        if self.state == InstanceState.TERMINATED:
+            self._provider.gce_compute \
+                          .instances() \
+                          .start(project=self._provider.project_name,
+                                 zone=self._provider.default_zone,
+                                 instance=self.name) \
+                          .execute()
+        else:
+            self._provider.gce_compute \
+                          .instances() \
+                          .reset(project=self._provider.project_name,
+                                 zone=self._provider.default_zone,
+                                 instance=self.name) \
+                          .execute()
+
+    def terminate(self):
+        """
+        Permanently terminate this instance.
+        """
+        self._provider.gce_compute \
+                      .instances() \
+                      .stop(project=self._provider.project_name,
+                            zone=self._provider.default_zone,
+                            instance=self.name) \
+                      .execute()
+
+    def delete(self):
+        """
+        Permanently delete this instance.
+        """
+        self._provider.gce_compute \
+                      .instances() \
+                      .delete(project=self._provider.project_name,
+                              zone=self._provider.default_zone,
+                              instance=self.name) \
+                      .execute()
+
+    @property
+    def image_id(self):
+        """
+        Get the image ID for this insance.
+        """
+        raise NotImplementedError(
+            'To be implemented after GCEVolumeService.')
+        return None
+
+    @property
+    def zone_id(self):
+        """
+        Get the placement zone id where this instance is running.
+        """
+        zone_uri = self._gce_instance.get('zone')
+        if zone_uri is None:
+            return None
+        zone = self._provider.get_gce_resource_data(zone_uri)
+        return zone.get('name', None)
+
+    @property
+    def security_groups(self):
+        """
+        Get the security groups associated with this instance.
+        """
+        sg_service = self._provider.security.security_groups
+        sg_names = [sg.name for sg in sg_service.list()]
+        tags = self._gce_instance['tags']['items']
+        if len(tags) == 0 or len(sg_names) == 0:
+            return []
+        common_tags = set(sg_names) & set(tags)
+        sgs = []
+        for tag in common_tags:
+            result = sg_service.find(tag)
+            if len(result) > 0:
+                sgs.append(result[0])
+        return sgs
+
+    @property
+    def security_group_ids(self):
+        """
+        Get the security groups IDs associated with this instance.
+        """
+        sg_ids = []
+        for sg in self.security_groups:
+            sg_ids.append(sg.id)
+        return sg_ids
+
+    @property
+    def key_pair_name(self):
+        """
+        Get the name of the key pair associated with this instance.
+        """
+        return self._provider.security.key_pairs.name
+
+    def create_image(self, name):
+        """
+        Create a new image based on this instance.
+        """
+        raise NotImplementedError(
+            'To be implemented after GCEVolumeService.')
+
+    def add_floating_ip(self, ip_address):
+        """
+        Add an elastic IP address to this instance.
+        """
+        raise NotImplementedError(
+            'To be implemented after GCENetworkService.')
+
+    def remove_floating_ip(self, ip_address):
+        """
+        Remove a elastic IP address from this instance.
+        """
+        raise NotImplementedError(
+            'To be implemented after GCENetworkService.')
+
+    @property
+    def state(self):
+        return GCEInstance.INSTANCE_STATE_MAP.get(
+            self._gce_instance['status'], InstanceState.UNKNOWN)
+
+    def refresh(self):
+        """
+        Refreshes the state of this instance by re-querying the cloud provider
+        for its latest state.
+        """
+        self._gce_instance = self._provider.get_gce_resource_data(
+            self._gce_instance.get('selfLink'))

+ 127 - 1
cloudbridge/cloud/providers/gce/services.py

@@ -1,11 +1,13 @@
 from cloudbridge.cloud.base.resources import ClientPagedResultList
 from cloudbridge.cloud.base.services import BaseComputeService
 from cloudbridge.cloud.base.services import BaseImageService
+from cloudbridge.cloud.base.services import BaseInstanceService
 from cloudbridge.cloud.base.services import BaseInstanceTypesService
 from cloudbridge.cloud.base.services import BaseKeyPairService
 from cloudbridge.cloud.base.services import BaseRegionService
 from cloudbridge.cloud.base.services import BaseSecurityGroupService
 from cloudbridge.cloud.base.services import BaseSecurityService
+from cloudbridge.cloud.interfaces.resources import SecurityGroup
 from cloudbridge.cloud.providers.gce import helpers
 import cloudbridge as cb
 
@@ -17,6 +19,7 @@ from retrying import retry
 import sys
 
 from .resources import GCEMachineImage
+from .resources import GCEInstance
 from .resources import GCEInstanceType
 from .resources import GCEKeyPair
 from .resources import GCERegion
@@ -394,10 +397,133 @@ class GCEImageService(BaseImageService):
                                      limit=limit, marker=marker)
 
 
+class GCEInstanceService(BaseInstanceService):
+
+    def __init__(self, provider):
+        super(GCEInstanceService, self).__init__(provider)
+
+    def create(self, name, image, instance_type, zone=None,
+               key_pair=None, security_groups=None, user_data=None,
+               launch_config=None,
+               **kwargs):
+        """
+        Creates a new virtual machine instance.
+        """
+        if zone is None:
+            zone = self.provider.default_zone
+        if launch_config is None:
+            config = {
+                'name': name,
+                'machineType': instance_type.resource_url,
+                'disks': [{'boot': True,
+                           'autoDelete': False,
+                           'initializeParams': {
+                               'sourceImage': image.resource_url,
+                           }
+                       }],
+                'networkInterfaces': [
+                    {'network': 'global/networks/default',
+                     'accessConfigs': [{'type': 'ONE_TO_ONE_NAT',
+                                        'name': 'External NAT'}]
+                 }],
+            }
+            if (security_groups is not None and
+                isinstance(security_groups, list) and
+                len(security_groups) > 0):
+                sg_names = []
+                if isinstance(security_groups[0], SecurityGroup):
+                    sg_namess = [sg.name for sg in security_groups]
+                elif isinstance(security_groups[0], str):
+                    sg_names = security_groups
+                if len(sg_names) > 0:
+                    config['tags'] = {}
+                    config['tags']['items'] = sg_names
+        else:
+            config = launch_config
+        response = self.provider.gce_compute \
+                                .instances() \
+                                .insert(
+                                    project=self.provider.project_name,
+                                    zone=self.provider.default_zone,
+                                    body=config) \
+                                .execute()
+
+    def get(self, instance_name):
+        """
+        Returns an instance given its name. Returns None
+        if the object does not exist.
+
+        GCE client API only supports returning the specified Instance resource
+        by its name (not by its id).
+        """
+        try:
+            response = self.provider.gce_compute\
+                               .instances() \
+                               .get(project=self.provider.project_name,
+                                    zone=self.provider.default_zone,
+                                    instance=instance_name) \
+                               .execute()
+            if response:
+                return GCEInstance(self.provider, response)
+        except googleapiclient.errors.HttpError as http_error:
+            # If the instance is not found, the API will raise
+            # googleapiclient.errors.HttpError.
+            cb.log.warning(
+                "googleapiclient.errors.HttpError: {0}".format(http_error))
+        return None
+
+    def find(self, name, limit=None, marker=None):
+        """
+        Searches for instances by instance name.
+        :return: a list of Instance objects
+        """
+        instances = []
+        for instance in self.list():
+            if instance.name == name:
+                instances.append(instance)
+        if limit and len(instances) > limit:
+            instances = instances[:limit]
+        return instances
+
+    def list(self, limit=None, marker=None):
+        """
+        List all instances.
+        """
+        if limit is None or limit > 500:
+            # For GCE API, Acceptable values are 0 to 500, inclusive.
+            # (Default: 500). If the number of available results is larger
+            # than maxResults, Compute Engine returns a nextPageToken that
+            # can be used to get the next page of results in subsequent
+            # list requests.
+            max_result = 500
+        else:
+            max_result = limit
+        request = self.provider.gce_compute \
+                               .instances() \
+                               .list(project=self.provider.project_name,
+                                     zone=self.provider.default_zone,
+                                     maxResults=max_result)
+        instances = []
+        while request is not None:
+            response = request.execute()
+            instances.extend([GCEInstance(self.provider, inst)
+                              for inst in response['items']])
+            if limit and len(instances) >= limit:
+                break
+            request = self.provider.gce_compute \
+                                   .instances() \
+                                   .list_next(previous_request=request,
+                                              previous_response=response)
+        if limit and len(instances) > limit:
+            instances = instances[:limit]
+        return instances
+
+
 class GCEComputeService(BaseComputeService):
     # TODO: implement GCEComputeService
     def __init__(self, provider):
         super(GCEComputeService, self).__init__(provider)
+        self._instance_svc = GCEInstanceService(self.provider)
         self._instance_type_svc = GCEInstanceTypesService(self.provider)
         self._region_svc = GCERegionService(self.provider)
         self._images_svc = GCEImageService(self.provider)
@@ -412,7 +538,7 @@ class GCEComputeService(BaseComputeService):
 
     @property
     def instances(self):
-        raise NotImplementedError("To be implemented")
+        return self._instance_svc
 
     @property
     def regions(self):