|
|
@@ -0,0 +1,255 @@
|
|
|
+from cloudbridge.cloud.base.resources import ClientPagedResultList
|
|
|
+from cloudbridge.cloud.base.services import BaseComputeService
|
|
|
+from cloudbridge.cloud.base.services import BaseInstanceTypesService
|
|
|
+from cloudbridge.cloud.base.services import BaseKeyPairService
|
|
|
+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
|
|
|
+
|
|
|
+
|
|
|
+from retrying import retry
|
|
|
+
|
|
|
+from .resources import GCEInstanceType
|
|
|
+from .resources import GCEKeyPair
|
|
|
+
|
|
|
+
|
|
|
+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 GCEComputeService(BaseComputeService):
|
|
|
+ # TODO: implement GCEComputeService
|
|
|
+ def __init__(self, provider):
|
|
|
+ super(GCEComputeService, self).__init__(provider)
|
|
|
+ self._instance_type_svc = GCEInstanceTypesService(self.provider)
|
|
|
+
|
|
|
+ @property
|
|
|
+ def images(self):
|
|
|
+ raise NotImplementedError("To be implemented")
|
|
|
+
|
|
|
+ @property
|
|
|
+ def instance_types(self):
|
|
|
+ return self._instance_type_svc
|
|
|
+
|
|
|
+ @property
|
|
|
+ def instances(self):
|
|
|
+ raise NotImplementedError("To be implemented")
|
|
|
+
|
|
|
+ @property
|
|
|
+ def regions(self):
|
|
|
+ raise NotImplementedError("To be implemented")
|