| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 |
- 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 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.providers.gce import helpers
- from collections import namedtuple
- import hashlib
- import googleapiclient
- from retrying import retry
- from .resources import GCEMachineImage
- from .resources import GCEInstanceType
- from .resources import GCEKeyPair
- from .resources import GCERegion
- class GCESecurityService(BaseSecurityService):
- def __init__(self, provider):
- super(GCESecurityService, self).__init__(provider)
- # Initialize provider services
- self._key_pairs = GCEKeyPairService(provider)
- @property
- def key_pairs(self):
- return self._key_pairs
- @property
- def security_groups(self):
- raise NotImplementedError(
- "GCECloudProvider does not implement this service")
- class GCEKeyPairService(BaseKeyPairService):
- GCEKeyInfo = namedtuple('GCEKeyInfo', 'format public_key email')
- def __init__(self, provider):
- super(GCEKeyPairService, self).__init__(provider)
- self._gce_projects = None
- @property
- def gce_projects(self):
- if not self._gce_projects:
- self._gce_projects = self.provider.gce_compute.projects()
- return self._gce_projects
- def get(self, key_pair_id):
- """
- Returns a KeyPair given its ID.
- """
- for kp in self.list():
- if kp.id == key_pair_id:
- return kp
- else:
- return None
- def _iter_gce_key_pairs(self):
- """
- Iterates through the project's metadata, yielding a GCEKeyInfo object
- for each entry in commonInstanceMetaData/items
- """
- metadata = self._get_common_metadata()
- for kpinfo in self._iter_gce_ssh_keys(metadata):
- yield kpinfo
- def _get_common_metadata(self):
- """
- Get a project's commonInstanceMetadata entry
- """
- metadata = self.gce_projects.get(
- project=self.provider.project_name).execute()
- return metadata["commonInstanceMetadata"]
- def _get_or_add_sshkey_entry(self, metadata):
- """
- Get the sshKeys entry from commonInstanceMetadata/items.
- If an entry does not exist, adds a new empty entry
- """
- sshkey_entry = None
- entries = [item for item in metadata["items"]
- if item["key"] == "sshKeys"]
- if entries:
- sshkey_entry = entries[0]
- else: # add a new entry
- sshkey_entry = {"key": "sshKeys", "value": ""}
- metadata["items"].append(sshkey_entry)
- return sshkey_entry
- def _iter_gce_ssh_keys(self, metadata):
- """
- Iterates through the ssh keys given a commonInstanceMetadata dict,
- yielding a GCEKeyInfo object for each entry in
- commonInstanceMetaData/items
- """
- sshkeys = self._get_or_add_sshkey_entry(metadata)["value"]
- for key in sshkeys.split("\n"):
- # elems should be "ssh-rsa <public_key> <email>"
- elems = key.split(" ")
- if elems and elems[0]: # ignore blank lines
- yield GCEKeyPairService.GCEKeyInfo(elems[0], elems[1],
- elems[2])
- def gce_metadata_save_op(self, callback):
- """
- Carries out a metadata save operation. In GCE, a fingerprint based
- locking mechanism is used to prevent lost updates. A new fingerprint
- is returned each time metadata is retrieved. Therefore, this method
- retrieves the metadata, invokes the provided callback with that
- metadata, and saves the metadata using the original fingerprint
- immediately afterwards, ensuring that update conflicts can be detected.
- """
- def _save_common_metadata():
- metadata = self._get_common_metadata()
- # add a new entry if one doesn'te xist
- sshkey_entry = self._get_or_add_sshkey_entry(metadata)
- gce_kp_list = callback(self._iter_gce_ssh_keys(metadata))
- entry = ""
- for gce_kp in gce_kp_list:
- entry = entry + u"{0} {1} {2}\n".format(gce_kp.format,
- gce_kp.public_key,
- gce_kp.email)
- sshkey_entry["value"] = entry.rstrip()
- # 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)
- # Retry a few times if the fingerprints conflict
- retry_decorator = retry(stop_max_attempt_number=5)
- retry_decorator(_save_common_metadata)()
- def gce_kp_to_id(self, gce_kp):
- """
- Accept a GCEKeyInfo object and return a unique
- ID for it
- """
- md5 = hashlib.md5()
- md5.update(gce_kp.public_key)
- return md5.hexdigest()
- def list(self, limit=None, marker=None):
- key_pairs = []
- for gce_kp in self._iter_gce_key_pairs():
- kp_id = self.gce_kp_to_id(gce_kp)
- kp_name = gce_kp.email
- key_pairs.append(GCEKeyPair(self.provider, kp_id, kp_name))
- return ClientPagedResultList(self.provider, key_pairs,
- limit=limit, marker=marker)
- def find(self, name, limit=None, marker=None):
- """
- 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)
- def create(self, 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)
- def _add_kp(gce_kp_generator):
- kp_list = []
- # Add the new key pair
- kp_list.append(kp_info)
- for gce_kp in gce_kp_generator:
- kp_list.append(gce_kp)
- return kp_list
- self.gce_metadata_save_op(_add_kp)
- return GCEKeyPair(self.provider, self.gce_kp_to_id(kp_info), name,
- kp_material=private_key)
- class GCESecurityGroupService(BaseSecurityGroupService):
- def __init__(self, provider):
- super(GCESecurityGroupService, self).__init__(provider)
- class GCEInstanceTypesService(BaseInstanceTypesService):
- def __init__(self, provider):
- super(GCEInstanceTypesService, self).__init__(provider)
- @property
- def instance_data(self):
- response = self.provider.gce_compute \
- .machineTypes() \
- .list(project=self.provider.project_name,
- zone=self.provider.default_zone) \
- .execute()
- return response['items']
- def get(self, instance_type_id):
- for inst_type in self.instance_data:
- if inst_type.get('id') == instance_type_id:
- return GCEInstanceType(self.provider, inst_type)
- return None
- def find(self, **kwargs):
- matched_inst_types = []
- for inst_type in self.instance_data:
- is_match = True
- for key, value in kwargs.iteritems():
- if key not in inst_type:
- raise TypeError("The attribute key is not valid.")
- if inst_type.get(key) != value:
- is_match = False
- break
- if is_match:
- matched_inst_types.append(
- GCEInstanceType(self.provider, inst_type))
- return matched_inst_types
- def list(self, limit=None, marker=None):
- inst_types = [GCEInstanceType(self.provider, inst_type)
- for inst_type in self.instance_data]
- return ClientPagedResultList(self.provider, inst_types,
- limit=limit, marker=marker)
- class GCERegionService(BaseRegionService):
- def __init__(self, provider):
- super(GCERegionService, self).__init__(provider)
- def get(self, region_id):
- try:
- 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
- except googleapiclient.errors.HttpError:
- return None
- if region:
- return GCERegion(self.provider, region)
- else:
- return None
- def list(self, limit=None, marker=None):
- regions_response = self.provider.gce_compute.regions().list(
- project=self.provider.project_name).execute()
- regions = [GCERegion(self.provider, region)
- for region in regions_response['items']]
- return ClientPagedResultList(self.provider, regions,
- limit=limit, marker=marker)
- @property
- def current(self):
- return self.get(self.provider.region_name)
- class GCEImageService(BaseImageService):
- def __init__(self, provider):
- super(GCEImageService, self).__init__(provider)
- def get(self, image_name):
- """
- Returns an Image given its name
- """
- try:
- image = self.provider.gce_compute \
- .images() \
- .get(project=self.provider.project_name,
- image=image_name) \
- .execute()
- if image:
- return GCEMachineImage(self.provider, image)
- except TypeError as type_error:
- # The API will throw an TypeError, if parameter `image` does not
- # match the pattern "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?".
- print("TypeError: {0}".format(type_error))
- return None
- except googleapiclient.errors.HttpError as http_error:
- print("googleapiclient.errors.HttpError: {0}".format(http_error))
- return None
- def find(self, name, limit=None, marker=None):
- """
- Searches for an image by a given list of attributes
- """
- filters = {'name': name}
- try:
- response = self.provider.gce_compute \
- .images() \
- .list(project=self.provider.project_name) \
- .execute()
- except googleapiclient.errors.HttpError as http_error:
- print("googleapiclient.errors.HttpError: {0}".format(http_error))
- return []
- if 'items' not in response:
- return []
- images = [GCEMachineImage(self.provider, image) for image
- in response['items']
- if 'name' in image and image['name'] == filters['name']]
- return ClientPagedResultList(self.provider, images,
- limit=limit, marker=marker)
- def list(self, limit=None, marker=None):
- """
- List all images.
- """
- try:
- response = self.provider.gce_compute \
- .images() \
- .list(project=self.provider.project_name) \
- .execute()
- except googleapiclient.errors.HttpError as http_error:
- print("googleapiclient.errors.HttpError: {0}".format(http_error))
- return []
- if 'items' not in response:
- return []
- images = [GCEMachineImage(self.provider, image) for image
- in response['items']]
- return ClientPagedResultList(self.provider, images,
- limit=limit, marker=marker)
- class GCEComputeService(BaseComputeService):
- # TODO: implement GCEComputeService
- def __init__(self, provider):
- super(GCEComputeService, self).__init__(provider)
- self._instance_type_svc = GCEInstanceTypesService(self.provider)
- self._region_svc = GCERegionService(self.provider)
- self._images_svc = GCEImageService(self.provider)
- @property
- def images(self):
- return self._images_svc
- @property
- def instance_types(self):
- return self._instance_type_svc
- @property
- def instances(self):
- raise NotImplementedError("To be implemented")
- @property
- def regions(self):
- return self._region_svc
|