provider.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. """
  2. Provider implementation based on google-api-python-client library
  3. for GCE.
  4. """
  5. import json
  6. import os
  7. import re
  8. import time
  9. from string import Template
  10. from cloudbridge.cloud.base import BaseCloudProvider
  11. import googleapiclient.http
  12. from googleapiclient import discovery
  13. import httplib2
  14. from oauth2client.client import GoogleCredentials
  15. from oauth2client.service_account import ServiceAccountCredentials
  16. from .services import GCEBlockStoreService
  17. from .services import GCEComputeService
  18. from .services import GCENetworkService
  19. from .services import GCESecurityService
  20. from .services import GCSObjectStoreService
  21. class GCPResourceUrl(object):
  22. def __init__(self, resource, connection):
  23. self._resource = resource
  24. self._connection = connection
  25. self.parameters = {}
  26. def get(self):
  27. discovery_object = getattr(self._connection, self._resource)()
  28. return discovery_object.get(**self.parameters).execute()
  29. class GCPResources(object):
  30. def __init__(self, connection):
  31. self._connection = connection
  32. # Resource descriptions are already pulled into the internal
  33. # _resourceDesc field of the connection.
  34. #
  35. # TODO: We could fetch compute resource descriptions from
  36. # https://www.googleapis.com/discovery/v1/apis/compute/v1/rest and
  37. # storage resource descriptions from
  38. # https://www.googleapis.com/discovery/v1/apis/storage/v1/rest
  39. # ourselves.
  40. #
  41. # Resource descriptions are in JSON format which are then parsed into a
  42. # Python dictionary. The main fields we are interested are:
  43. #
  44. # {
  45. # "rootUrl": "https://www.googleapis.com/",
  46. # "servicePath": COMPUTE OR STORAGE SERVICE PATH
  47. # "resources": {
  48. # RESOURCE_NAME: {
  49. # "methods": {
  50. # "get": {
  51. # "path": RESOURCE PATH PATTERN
  52. # "parameters": {
  53. # PARAMETER: {
  54. # "pattern": REGEXP FOR VALID VALUES
  55. # ...
  56. # },
  57. # ...
  58. # },
  59. # "parameterOrder": [LIST OF PARAMETERS]
  60. # },
  61. # ...
  62. # }
  63. # },
  64. # ...
  65. # }
  66. # ...
  67. # }
  68. desc = connection._resourceDesc
  69. self._root_url = desc['rootUrl']
  70. self._service_path = desc['servicePath']
  71. self._resources = {}
  72. # We will not mutate self._desc; it's OK to use items() in Python 2.x.
  73. for resource, resource_desc in desc['resources'].items():
  74. methods = resource_desc.get('methods', {})
  75. if not methods.get('get'):
  76. continue
  77. method = methods['get']
  78. parameters = method['parameterOrder']
  79. # We would like to change a path like
  80. # {project}/regions/{region}/addresses/{address} to a pattern like
  81. # (PROJECT REGEX)/regions/(REGION REGEX)/addresses/(ADDRESS REGEX).
  82. template = Template('${'.join(method['path'].split('{')))
  83. mapping = {}
  84. for parameter in parameters:
  85. parameter_desc = method['parameters'][parameter]
  86. if 'pattern' in parameter_desc:
  87. mapping[parameter] = '(%s)' % parameter_desc['pattern']
  88. else:
  89. mapping[parameter] = '([^/]+)'
  90. pattern = template.substitute(**mapping)
  91. # Store the parameters and the regex pattern of this resource.
  92. self._resources[resource] = {'parameters': parameters,
  93. 'pattern': re.compile(pattern)}
  94. def parse_url(self, url):
  95. """
  96. Build a GCPResourceUrl from a resource's URL string. One can then call
  97. the get() method on the returned object to fetch resource details from
  98. GCP servers.
  99. """
  100. url = url.strip()
  101. if url.startswith(self._root_url):
  102. url = url[len(self._root_url):]
  103. if url.startswith(self._service_path):
  104. url = url[len(self._service_path):]
  105. for resource, desc in self._resources.items():
  106. m = re.match(desc['pattern'], url)
  107. if m is None or len(m.group(0)) < len(url):
  108. continue
  109. out = GCPResourceUrl(resource, self._connection)
  110. for index, parameter in enumerate(desc['parameters']):
  111. out.parameters[parameter] = m.group(index + 1)
  112. return out
  113. class GCECloudProvider(BaseCloudProvider):
  114. PROVIDER_ID = 'gce'
  115. def __init__(self, config):
  116. super(GCECloudProvider, self).__init__(config)
  117. # Initialize cloud connection fields
  118. self.client_email = self._get_config_value(
  119. 'gce_client_email', os.environ.get('GCE_CLIENT_EMAIL'))
  120. self.project_name = self._get_config_value(
  121. 'gce_project_name', os.environ.get('GCE_PROJECT_NAME'))
  122. self.credentials_file = self._get_config_value(
  123. 'gce_service_creds_file', os.environ.get('GCE_SERVICE_CREDS_FILE'))
  124. self.credentials_dict = self._get_config_value(
  125. 'gce_service_creds_dict', {})
  126. # If 'gce_service_creds_dict' is not passed in from config and
  127. # self.credentials_file is available, read and parse the json file to
  128. # self.credentials_dict.
  129. if self.credentials_file and not self.credentials_dict:
  130. with open(self.credentials_file) as creds_file:
  131. self.credentials_dict = json.load(creds_file)
  132. self.default_zone = self._get_config_value(
  133. 'gce_default_zone', os.environ.get('GCE_DEFAULT_ZONE'))
  134. self.region_name = self._get_config_value(
  135. 'gce_region_name', 'us-central1')
  136. # service connections, lazily initialized
  137. self._gce_compute = None
  138. self._gcp_storage = None
  139. # Initialize provider services
  140. self._compute = GCEComputeService(self)
  141. self._security = GCESecurityService(self)
  142. self._network = GCENetworkService(self)
  143. self._block_store = GCEBlockStoreService(self)
  144. self._object_store = GCSObjectStoreService(self)
  145. self._compute_resources = GCPResources(self.gce_compute)
  146. self._storage_resources = GCPResources(self.gcp_storage)
  147. @property
  148. def compute(self):
  149. return self._compute
  150. @property
  151. def network(self):
  152. return self._network
  153. @property
  154. def security(self):
  155. return self._security
  156. @property
  157. def block_store(self):
  158. return self._block_store
  159. @property
  160. def object_store(self):
  161. return self._object_store
  162. @property
  163. def gce_compute(self):
  164. if not self._gce_compute:
  165. self._gce_compute = self._connect_gce_compute()
  166. return self._gce_compute
  167. @property
  168. def gcp_storage(self):
  169. if not self._gcp_storage:
  170. self._gcp_storage = self._connect_gcp_storage()
  171. return self._gcp_storage
  172. @property
  173. def _credentials(self):
  174. if self.credentials_dict:
  175. return ServiceAccountCredentials.from_json_keyfile_dict(
  176. self.credentials_dict)
  177. else:
  178. return GoogleCredentials.get_application_default()
  179. def get_gce_resource_data(self, uri):
  180. """
  181. Retrieves GCE resoure data given its resource URI.
  182. """
  183. http = httplib2.Http()
  184. http = self._credentials.authorize(http)
  185. def _postproc(*kwargs):
  186. if len(kwargs) >= 2:
  187. # The first argument is request, and the second is response.
  188. resource_dict = json.loads(kwargs[1])
  189. return resource_dict
  190. request = googleapiclient.http.HttpRequest(http=http,
  191. postproc=_postproc,
  192. uri=uri)
  193. # The response is a dict representing the GCE resource data.
  194. response = request.execute()
  195. return response
  196. def _connect_gcp_storage(self):
  197. return discovery.build('storage', 'v1', credentials=self._credentials)
  198. def _connect_gce_compute(self):
  199. return discovery.build('compute', 'v1', credentials=self._credentials)
  200. def wait_for_operation(self, operation, region=None, zone=None):
  201. args = {'project': self.project_name, 'operation': operation['name']}
  202. if not region and not zone:
  203. operations = self.gce_compute.globalOperations()
  204. elif zone:
  205. operations = self.gce_compute.zoneOperations()
  206. args['zone'] = zone
  207. else:
  208. operations = self.gce_compute.regionOperations()
  209. args['region'] = region
  210. while True:
  211. result = operations.get(**args).execute()
  212. if result['status'] == 'DONE':
  213. if 'error' in result:
  214. raise Exception(result['error'])
  215. return result
  216. time.sleep(0.5)
  217. def parse_url(self, url):
  218. out = self._compute_resources.parse_url(url)
  219. return out if out else self._storage_resources.parse_url(url)