services.py 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072
  1. import hashlib
  2. import uuid
  3. from collections import namedtuple
  4. import cloudbridge as cb
  5. from cloudbridge.cloud.base.resources import ClientPagedResultList
  6. from cloudbridge.cloud.base.resources import ServerPagedResultList
  7. from cloudbridge.cloud.base.services import BaseBlockStoreService
  8. from cloudbridge.cloud.base.services import BaseComputeService
  9. from cloudbridge.cloud.base.services import BaseImageService
  10. from cloudbridge.cloud.base.services import BaseInstanceService
  11. from cloudbridge.cloud.base.services import BaseInstanceTypesService
  12. from cloudbridge.cloud.base.services import BaseKeyPairService
  13. from cloudbridge.cloud.base.services import BaseNetworkService
  14. from cloudbridge.cloud.base.services import BaseObjectStoreService
  15. from cloudbridge.cloud.base.services import BaseRegionService
  16. from cloudbridge.cloud.base.services import BaseSecurityGroupService
  17. from cloudbridge.cloud.base.services import BaseSecurityService
  18. from cloudbridge.cloud.base.services import BaseSnapshotService
  19. from cloudbridge.cloud.base.services import BaseSubnetService
  20. from cloudbridge.cloud.base.services import BaseVolumeService
  21. from cloudbridge.cloud.interfaces.resources import PlacementZone
  22. from cloudbridge.cloud.interfaces.resources import SecurityGroup
  23. from cloudbridge.cloud.providers.gce import helpers
  24. import googleapiclient
  25. from retrying import retry
  26. from .resources import GCEFirewallsDelegate
  27. from .resources import GCEFloatingIP
  28. from .resources import GCEInstance
  29. from .resources import GCEInstanceType
  30. from .resources import GCEKeyPair
  31. from .resources import GCEMachineImage
  32. from .resources import GCENetwork
  33. from .resources import GCEPlacementZone
  34. from .resources import GCERegion
  35. from .resources import GCERouter
  36. from .resources import GCESecurityGroup
  37. from .resources import GCESnapshot
  38. from .resources import GCESubnet
  39. from .resources import GCEVolume
  40. from .resources import GCSBucket
  41. class GCESecurityService(BaseSecurityService):
  42. def __init__(self, provider):
  43. super(GCESecurityService, self).__init__(provider)
  44. # Initialize provider services
  45. self._key_pairs = GCEKeyPairService(provider)
  46. self._security_groups = GCESecurityGroupService(provider)
  47. @property
  48. def key_pairs(self):
  49. return self._key_pairs
  50. @property
  51. def security_groups(self):
  52. return self._security_groups
  53. class GCEKeyPairService(BaseKeyPairService):
  54. GCEKeyInfo = namedtuple('GCEKeyInfo', 'format public_key email')
  55. def __init__(self, provider):
  56. super(GCEKeyPairService, self).__init__(provider)
  57. self._gce_projects = None
  58. @property
  59. def gce_projects(self):
  60. if not self._gce_projects:
  61. self._gce_projects = self.provider.gce_compute.projects()
  62. return self._gce_projects
  63. def get(self, key_pair_id):
  64. """
  65. Returns a KeyPair given its ID.
  66. """
  67. for kp in self.list():
  68. if kp.id == key_pair_id:
  69. return kp
  70. else:
  71. return None
  72. def _iter_gce_key_pairs(self):
  73. """
  74. Iterates through the project's metadata, yielding a GCEKeyInfo object
  75. for each entry in commonInstanceMetaData/items
  76. """
  77. metadata = self._get_common_metadata()
  78. for kpinfo in self._iter_gce_ssh_keys(metadata):
  79. yield kpinfo
  80. def _get_common_metadata(self):
  81. """
  82. Get a project's commonInstanceMetadata entry
  83. """
  84. metadata = self.gce_projects.get(
  85. project=self.provider.project_name).execute()
  86. return metadata["commonInstanceMetadata"]
  87. def _get_or_add_sshkey_entry(self, metadata):
  88. """
  89. Get the sshKeys entry from commonInstanceMetadata/items.
  90. If an entry does not exist, adds a new empty entry
  91. """
  92. sshkey_entry = None
  93. entries = [item for item in metadata["items"]
  94. if item["key"] == "sshKeys"]
  95. if entries:
  96. sshkey_entry = entries[0]
  97. else: # add a new entry
  98. sshkey_entry = {"key": "sshKeys", "value": ""}
  99. metadata["items"].append(sshkey_entry)
  100. return sshkey_entry
  101. def _iter_gce_ssh_keys(self, metadata):
  102. """
  103. Iterates through the ssh keys given a commonInstanceMetadata dict,
  104. yielding a GCEKeyInfo object for each entry in
  105. commonInstanceMetaData/items
  106. """
  107. sshkeys = self._get_or_add_sshkey_entry(metadata)["value"]
  108. for key in sshkeys.split("\n"):
  109. # elems should be "ssh-rsa <public_key> <email>"
  110. elems = key.split(" ")
  111. if elems and elems[0]: # ignore blank lines
  112. yield GCEKeyPairService.GCEKeyInfo(
  113. elems[0], elems[1].encode('ascii'), elems[2])
  114. def gce_metadata_save_op(self, callback):
  115. """
  116. Carries out a metadata save operation. In GCE, a fingerprint based
  117. locking mechanism is used to prevent lost updates. A new fingerprint
  118. is returned each time metadata is retrieved. Therefore, this method
  119. retrieves the metadata, invokes the provided callback with that
  120. metadata, and saves the metadata using the original fingerprint
  121. immediately afterwards, ensuring that update conflicts can be detected.
  122. """
  123. def _save_common_metadata():
  124. metadata = self._get_common_metadata()
  125. # add a new entry if one doesn'te xist
  126. sshkey_entry = self._get_or_add_sshkey_entry(metadata)
  127. gce_kp_list = callback(self._iter_gce_ssh_keys(metadata))
  128. entry = ""
  129. for gce_kp in gce_kp_list:
  130. entry = entry + u"{0} {1} {2}\n".format(gce_kp.format,
  131. gce_kp.public_key,
  132. gce_kp.email)
  133. sshkey_entry["value"] = entry.rstrip()
  134. # common_metadata will have the current fingerprint at this point
  135. operation = self.gce_projects.setCommonInstanceMetadata(
  136. project=self.provider.project_name, body=metadata).execute()
  137. self.provider.wait_for_operation(operation)
  138. # Retry a few times if the fingerprints conflict
  139. retry_decorator = retry(stop_max_attempt_number=5)
  140. retry_decorator(_save_common_metadata)()
  141. def gce_kp_to_id(self, gce_kp):
  142. """
  143. Accept a GCEKeyInfo object and return a unique
  144. ID for it
  145. """
  146. md5 = hashlib.md5()
  147. md5.update(gce_kp.public_key)
  148. return md5.hexdigest()
  149. def list(self, limit=None, marker=None):
  150. key_pairs = []
  151. for gce_kp in self._iter_gce_key_pairs():
  152. kp_id = self.gce_kp_to_id(gce_kp)
  153. kp_name = gce_kp.email
  154. key_pairs.append(GCEKeyPair(self.provider, kp_id, kp_name))
  155. return ClientPagedResultList(self.provider, key_pairs,
  156. limit=limit, marker=marker)
  157. def find(self, name, limit=None, marker=None):
  158. """
  159. Searches for a key pair by a given list of attributes.
  160. """
  161. found_kps = []
  162. for kp in self.list():
  163. if kp.name == name:
  164. found_kps.append(kp)
  165. return ClientPagedResultList(self.provider, found_kps,
  166. limit=limit, marker=marker)
  167. def create(self, name):
  168. kp = self.find(name=name)
  169. if kp:
  170. return kp
  171. private_key, public_key = helpers.generate_key_pair()
  172. kp_info = GCEKeyPairService.GCEKeyInfo(name + u":ssh-rsa",
  173. public_key, name)
  174. def _add_kp(gce_kp_generator):
  175. kp_list = []
  176. # Add the new key pair
  177. kp_list.append(kp_info)
  178. for gce_kp in gce_kp_generator:
  179. kp_list.append(gce_kp)
  180. return kp_list
  181. self.gce_metadata_save_op(_add_kp)
  182. return GCEKeyPair(self.provider, self.gce_kp_to_id(kp_info), name,
  183. kp_material=private_key)
  184. class GCESecurityGroupService(BaseSecurityGroupService):
  185. def __init__(self, provider):
  186. super(GCESecurityGroupService, self).__init__(provider)
  187. self._delegate = GCEFirewallsDelegate(provider)
  188. def get(self, group_id):
  189. tag, network_name = self._delegate.get_tag_network_from_id(group_id)
  190. if tag is None:
  191. return None
  192. network = self.provider.network.get_by_name(network_name)
  193. return GCESecurityGroup(self._delegate, tag, network)
  194. def list(self, limit=None, marker=None):
  195. security_groups = []
  196. for tag, network_name in self._delegate.tag_networks:
  197. network = self.provider.network.get_by_name(network_name)
  198. security_group = GCESecurityGroup(self._delegate, tag, network)
  199. security_groups.append(security_group)
  200. return ClientPagedResultList(self.provider, security_groups,
  201. limit=limit, marker=marker)
  202. def create(self, name, description, network_id=None):
  203. network = self.provider.network.get(network_id)
  204. return GCESecurityGroup(self._delegate, name, network, description)
  205. def find(self, name, limit=None, marker=None):
  206. """
  207. Finds a non-empty security group. If a security group with the given
  208. name does not exist, or if it does not contain any rules, an empty list
  209. is returned.
  210. """
  211. out = []
  212. for tag, network_name in self._delegate.tag_networks:
  213. if tag == name:
  214. network = self.provider.network.get_by_name(network_name)
  215. out.append(GCESecurityGroup(self._delegate, name, network))
  216. return out
  217. def delete(self, group_id):
  218. return self._delegate.delete_tag_network_with_id(group_id)
  219. def find_by_network_and_tags(self, network_name, tags):
  220. """
  221. Finds non-empty security groups by network name and security group
  222. names (tags). If no matching security group is found, an empty list
  223. is returned.
  224. """
  225. security_groups = []
  226. for tag, net_name in self._delegate.tag_networks:
  227. if network_name != net_name:
  228. continue
  229. if tag not in tags:
  230. continue
  231. network = self.provider.network.get_by_name(net_name)
  232. security_groups.append(
  233. GCESecurityGroup(self._delegate, tag, network))
  234. return security_groups
  235. class GCEInstanceTypesService(BaseInstanceTypesService):
  236. def __init__(self, provider):
  237. super(GCEInstanceTypesService, self).__init__(provider)
  238. @property
  239. def instance_data(self):
  240. response = (self.provider
  241. .gce_compute
  242. .machineTypes()
  243. .list(project=self.provider.project_name,
  244. zone=self.provider.default_zone)
  245. .execute())
  246. return response['items']
  247. def get(self, instance_type_id):
  248. inst_type = self.provider.get_resource('machineTypes', instance_type_id)
  249. return GCEInstanceType(self.provider, inst_type) if inst_type else None
  250. def find(self, **kwargs):
  251. matched_inst_types = []
  252. for inst_type in self.instance_data:
  253. is_match = True
  254. for key, value in kwargs.iteritems():
  255. if key not in inst_type:
  256. raise TypeError("The attribute key is not valid.")
  257. if inst_type.get(key) != value:
  258. is_match = False
  259. break
  260. if is_match:
  261. matched_inst_types.append(
  262. GCEInstanceType(self.provider, inst_type))
  263. return matched_inst_types
  264. def list(self, limit=None, marker=None):
  265. inst_types = [GCEInstanceType(self.provider, inst_type)
  266. for inst_type in self.instance_data]
  267. return ClientPagedResultList(self.provider, inst_types,
  268. limit=limit, marker=marker)
  269. class GCERegionService(BaseRegionService):
  270. def __init__(self, provider):
  271. super(GCERegionService, self).__init__(provider)
  272. def get(self, region_id):
  273. region = self.provider.get_resource('regions', region_id)
  274. return GCERegion(self.provider, region) if region else None
  275. def list(self, limit=None, marker=None):
  276. max_result = limit if limit is not None and limit < 500 else 500
  277. regions_response = (self.provider
  278. .gce_compute
  279. .regions()
  280. .list(project=self.provider.project_name,
  281. maxResults=max_result,
  282. pageToken=marker)
  283. .execute())
  284. regions = [GCERegion(self.provider, region)
  285. for region in regions_response['items']]
  286. if len(regions) > max_result:
  287. cb.log.warning('Expected at most %d results; got %d',
  288. max_result, len(regions))
  289. return ServerPagedResultList('nextPageToken' in regions_response,
  290. regions_response.get('nextPageToken'),
  291. False, data=regions)
  292. @property
  293. def current(self):
  294. return self.get(self.provider.region_name)
  295. class GCEImageService(BaseImageService):
  296. def __init__(self, provider):
  297. super(GCEImageService, self).__init__(provider)
  298. self._public_images = None
  299. _PUBLIC_IMAGE_PROJECTS = ['centos-cloud', 'coreos-cloud', 'debian-cloud',
  300. 'opensuse-cloud', 'ubuntu-os-cloud']
  301. def _retrieve_public_images(self):
  302. if self._public_images is not None:
  303. return
  304. self._public_images = []
  305. try:
  306. for project in GCEImageService._PUBLIC_IMAGE_PROJECTS:
  307. for image in helpers.iter_all(
  308. self.provider.gce_compute.images(), project=project):
  309. self._public_images.append(
  310. GCEMachineImage(self.provider, image))
  311. except googleapiclient.errors.HttpError as http_error:
  312. cb.log.warning("googleapiclient.errors.HttpError: {0}".format(
  313. http_error))
  314. def get(self, image_id):
  315. """
  316. Returns an Image given its id
  317. """
  318. image = self.provider.get_resource('images', image_id)
  319. return GCEMachineImage(self.provider, image) if image else None
  320. def find(self, name, limit=None, marker=None):
  321. """
  322. Searches for an image by a given list of attributes
  323. """
  324. filters = {'name': name}
  325. # Retrieve all available images by setting limit to sys.maxsize
  326. images = [image for image in self if image.name == filters['name']]
  327. return ClientPagedResultList(self.provider, images,
  328. limit=limit, marker=marker)
  329. def list(self, limit=None, marker=None):
  330. """
  331. List all images.
  332. """
  333. self._retrieve_public_images()
  334. images = []
  335. if (self.provider.project_name not in
  336. GCEImageService._PUBLIC_IMAGE_PROJECTS):
  337. try:
  338. for image in helpers.iter_all(
  339. self.provider.gce_compute.images(),
  340. project=self.provider.project_name):
  341. images.append(GCEMachineImage(self.provider, image))
  342. except googleapiclient.errors.HttpError as http_error:
  343. cb.log.warning(
  344. "googleapiclient.errors.HttpError: {0}".format(http_error))
  345. images.extend(self._public_images)
  346. return ClientPagedResultList(self.provider, images,
  347. limit=limit, marker=marker)
  348. class GCEInstanceService(BaseInstanceService):
  349. def __init__(self, provider):
  350. super(GCEInstanceService, self).__init__(provider)
  351. def create(self, name, image, instance_type, subnet, zone=None,
  352. key_pair=None, security_groups=None, user_data=None,
  353. launch_config=None, **kwargs):
  354. """
  355. Creates a new virtual machine instance.
  356. """
  357. if not zone:
  358. zone = self.provider.default_zone
  359. if not launch_config:
  360. if subnet:
  361. network = self.provider.network.get(subnet.network_id)
  362. network_url = (network.resource_url
  363. if isinstance(network, GCENetwork) else network)
  364. else:
  365. network_url = 'global/networks/default'
  366. config = {
  367. 'name': name,
  368. 'machineType': instance_type.resource_url,
  369. 'disks': [{'boot': True,
  370. 'autoDelete': True,
  371. 'initializeParams': {
  372. 'sourceImage': image.resource_url,
  373. }
  374. }],
  375. 'networkInterfaces': [
  376. {
  377. # TODO: Should replace network below with subnetwork
  378. 'network': network_url,
  379. 'accessConfigs': [{'type': 'ONE_TO_ONE_NAT',
  380. 'name': 'External NAT'}]
  381. }],
  382. }
  383. if security_groups and isinstance(security_groups, list):
  384. sg_names = []
  385. if isinstance(security_groups[0], SecurityGroup):
  386. sg_names = [sg.name for sg in security_groups]
  387. elif isinstance(security_groups[0], str):
  388. sg_names = security_groups
  389. if len(sg_names) > 0:
  390. config['tags'] = {}
  391. config['tags']['items'] = sg_names
  392. else:
  393. config = launch_config
  394. try:
  395. operation = (self.provider
  396. .gce_compute.instances()
  397. .insert(project=self.provider.project_name,
  398. zone=zone,
  399. body=config)
  400. .execute())
  401. except googleapiclient.errors.HttpError as http_error:
  402. # If the operation request fails, the API will raise
  403. # googleapiclient.errors.HttpError.
  404. cb.log.warning(
  405. "googleapiclient.errors.HttpError: {0}".format(http_error))
  406. return None
  407. zone_url = self.provider.parse_url(operation['zone'])
  408. instance_id = operation.get('targetLink')
  409. self.provider.wait_for_operation(operation,
  410. zone=zone_url.parameters['zone'])
  411. return self.get(instance_id)
  412. def get(self, instance_id):
  413. """
  414. Returns an instance given its name. Returns None
  415. if the object does not exist.
  416. A GCE instance is uniquely identified by its selfLink, which is used
  417. as its id.
  418. """
  419. instance = self.provider.get_resource('instances', instance_id)
  420. return GCEInstance(self.provider, instance) if instance else None
  421. def find(self, name, limit=None, marker=None):
  422. """
  423. Searches for instances by instance name.
  424. :return: a list of Instance objects
  425. """
  426. instances = [instance for instance in self.list()
  427. if instance.name == name]
  428. if limit and len(instances) > limit:
  429. instances = instances[:limit]
  430. return instances
  431. def list(self, limit=None, marker=None):
  432. """
  433. List all instances.
  434. """
  435. # For GCE API, Acceptable values are 0 to 500, inclusive.
  436. # (Default: 500).
  437. max_result = limit if limit is not None and limit < 500 else 500
  438. response = (self.provider
  439. .gce_compute
  440. .instances()
  441. .list(project=self.provider.project_name,
  442. zone=self.provider.default_zone,
  443. maxResults=max_result,
  444. pageToken=marker)
  445. .execute())
  446. instances = []
  447. if 'items' in response:
  448. instances = [GCEInstance(self.provider, inst)
  449. for inst in response['items']]
  450. if len(instances) > max_result:
  451. cb.log.warning('Expected at most %d results; got %d',
  452. max_result, len(instances))
  453. return ServerPagedResultList('nextPageToken' in response,
  454. response.get('nextPageToken'),
  455. False, data=instances)
  456. class GCEComputeService(BaseComputeService):
  457. # TODO: implement GCEComputeService
  458. def __init__(self, provider):
  459. super(GCEComputeService, self).__init__(provider)
  460. self._instance_svc = GCEInstanceService(self.provider)
  461. self._instance_type_svc = GCEInstanceTypesService(self.provider)
  462. self._region_svc = GCERegionService(self.provider)
  463. self._images_svc = GCEImageService(self.provider)
  464. @property
  465. def images(self):
  466. return self._images_svc
  467. @property
  468. def instance_types(self):
  469. return self._instance_type_svc
  470. @property
  471. def instances(self):
  472. return self._instance_svc
  473. @property
  474. def regions(self):
  475. return self._region_svc
  476. class GCENetworkService(BaseNetworkService):
  477. def __init__(self, provider):
  478. super(GCENetworkService, self).__init__(provider)
  479. self._subnet_svc = GCESubnetService(self.provider)
  480. def get(self, network_id):
  481. network = self.provider.get_resource('networks', network_id)
  482. return GCENetwork(self.provider, network) if network else None
  483. def get_by_name(self, network_name):
  484. if network_name is None:
  485. return None
  486. networks = self.list(filter='name eq %s' % network_name)
  487. return None if len(networks) == 0 else networks[0]
  488. def list(self, limit=None, marker=None, filter=None):
  489. try:
  490. response = (self.provider
  491. .gce_compute
  492. .networks()
  493. .list(project=self.provider.project_name,
  494. filter=filter)
  495. .execute())
  496. networks = []
  497. if 'items' in response:
  498. for network in response['items']:
  499. networks.append(GCENetwork(self.provider, network))
  500. return networks
  501. except:
  502. return []
  503. def _create(self, name, create_subnetworks):
  504. """
  505. Possible values for 'create_subnetworks' are:
  506. None: For creating a legacy (non-subnetted) network.
  507. True: For creating an auto mode VPC network. This also creates a
  508. subnetwork in every region.
  509. False: For creating a custom mode VPC network. Subnetworks should be
  510. created manually.
  511. """
  512. networks = self.list(filter='name eq %s' % name)
  513. if len(networks) > 0:
  514. return networks[0]
  515. body = {'name': name,
  516. 'autoCreateSubnetworks': create_subnetworks}
  517. try:
  518. response = (self.provider
  519. .gce_compute
  520. .networks()
  521. .insert(project=self.provider.project_name,
  522. body=body)
  523. .execute())
  524. if 'error' in response:
  525. return None
  526. self.provider.wait_for_operation(response)
  527. networks = self.list(filter='name eq %s' % name)
  528. return None if len(networks) == 0 else networks[0]
  529. except:
  530. return None
  531. def create(self, name):
  532. """
  533. Creates a custom mode VPC network.
  534. """
  535. return self._create(name, False)
  536. def get_or_create_default(self):
  537. return self._create(GCEFirewallsDelegate.DEFAULT_NETWORK, True)
  538. @property
  539. def subnets(self):
  540. return self._subnet_svc
  541. def floating_ips(self, network_id=None, region=None):
  542. if not region:
  543. region = self.provider.region_name
  544. try:
  545. ips = []
  546. for ip in helpers.iter_all(self.provider.gce_compute.addresses(),
  547. project=self.provider.project_name,
  548. region=region):
  549. ips.append(GCEFloatingIP(self.provider, ip))
  550. # TODO: if network_id is given, filter out IPs that are assigned to
  551. # resources in a different network.
  552. return ips
  553. except:
  554. return []
  555. def create_floating_ip(self, region=None):
  556. if not region:
  557. region = self.provider.region_name
  558. ip_name = 'ip-{0}'.format(uuid.uuid4())
  559. try:
  560. response = (self.provider
  561. .gce_compute
  562. .addresses()
  563. .insert(project=self.provider.project_name,
  564. region=region,
  565. body={'name': ip_name})
  566. .execute())
  567. if 'error' in response:
  568. return None
  569. self.provider.wait_for_operation(response, region=region)
  570. ips = self.floating_ips()
  571. for ip in ips:
  572. if ip.id == response['targetId']:
  573. return ip
  574. except:
  575. return None
  576. def routers(self, region=None):
  577. if not region:
  578. region = self.provider.region_name
  579. try:
  580. routers = []
  581. for router in helpers.iter_all(self.provider.gce_compute.routers(),
  582. project=self.provider.project_name,
  583. region=region):
  584. routers.append(GCERouter(self.provider, router))
  585. return routers
  586. except:
  587. return []
  588. def create_router(self, name=None, network=None, region=None):
  589. network_url = 'global/networks/default'
  590. if isinstance(network, GCENetwork):
  591. network_url = network.resource_url
  592. if not region:
  593. region = self.provider.region_name
  594. try:
  595. response = (self.provider
  596. .gce_compute
  597. .routers()
  598. .insert(project=self.provider.project_name,
  599. region=region,
  600. body={'name': name,
  601. 'network': network_url})
  602. .execute())
  603. if 'error' in response:
  604. return None
  605. self.provider.wait_for_operation(response, region=region)
  606. routers = self.routers()
  607. for router in routers:
  608. if router.id == response['targetId']:
  609. return router
  610. except:
  611. return None
  612. class GCESubnetService(BaseSubnetService):
  613. def __init__(self, provider):
  614. super(GCESubnetService, self).__init__(provider)
  615. def get(self, subnet_id):
  616. subnet = self.provider.get_resource('subnetworks', subnet_id)
  617. return GCESubnet(self.provider, subnet) if subnet else None
  618. def list(self, network=None, zone=None, limit=None, marker=None):
  619. region = zone.region_name if zone else self.provider.region_name
  620. filter = None
  621. if network is not None:
  622. filter = 'network eq %s' % network.resource_url
  623. max_result = limit if limit is not None and limit < 500 else 500
  624. response = (self.provider
  625. .gce_compute
  626. .subnetworks()
  627. .list(project=self.provider.project_name,
  628. region=region,
  629. filter=filter,
  630. maxResults=max_result,
  631. pageToken=marker)
  632. .execute())
  633. subnets = []
  634. for subnet in response.get('items', []):
  635. subnets.append(GCESubnet(self.provider, subnet))
  636. if len(subnets) > max_result:
  637. cb.log.warning('Expected at most %d results; got %d',
  638. max_result, len(subnets))
  639. return ServerPagedResultList('nextPageToken' in response,
  640. response.get('nextPageToken'),
  641. False, data=subnets)
  642. def create(self, network, cidr_block, name=None, zone=None):
  643. """
  644. GCE subnets are regional. The region is inferred from the zone if a
  645. zone is provided; otherwise, the default region, as set in the
  646. provider, is used.
  647. """
  648. if not name:
  649. name = 'subnet-{0}'.format(uuid.uuid4())
  650. region = self.provider.region_name
  651. if isinstance(zone, GCEPlacementZone):
  652. region = zone.region_name
  653. body = {'ipCidrRange': cidr_block,
  654. 'name': name,
  655. 'network': network.resource_url,
  656. 'region': region}
  657. try:
  658. response = (self.provider
  659. .gce_compute
  660. .subnetworks()
  661. .insert(project=self.provider.project_name,
  662. region=region,
  663. body=body)
  664. .execute())
  665. self.provider.wait_for_operation(response, region=region)
  666. if 'error' in response:
  667. return None
  668. subnets = self.list(network, region)
  669. for subnet in subnets:
  670. if subnet.id == response['targetId']:
  671. return subnet
  672. except:
  673. return None
  674. def get_or_create_default(self, zone=None):
  675. """
  676. Every GCP project comes with a default auto mode VPC network. An auto
  677. mode VPC network has exactly one subnetwork per region. This method
  678. returns the subnetwork of the default network that spans the given
  679. zone.
  680. """
  681. network = self.provider.network.get_or_create_default()
  682. subnets = self.list(network, zone)
  683. if len(subnets) > 1:
  684. cb.log.warning('The default network has more than one subnetwork '
  685. 'in a region')
  686. if len(subnets) > 0:
  687. return subnets[0]
  688. cb.log.warning('The default network has no subnetwork in a region')
  689. return None
  690. def delete(self, subnet):
  691. response = (self.provider
  692. .gce_compute
  693. .subnetworks()
  694. .delete(project=self.provider.project_name,
  695. region=subnet.region,
  696. router=subnet.name)
  697. .execute())
  698. self._provider.wait_for_operation(response, region=subnet.region)
  699. class GCEBlockStoreService(BaseBlockStoreService):
  700. def __init__(self, provider):
  701. super(GCEBlockStoreService, self).__init__(provider)
  702. # Initialize provider services
  703. self._volume_svc = GCEVolumeService(self.provider)
  704. self._snapshot_svc = GCESnapshotService(self.provider)
  705. @property
  706. def volumes(self):
  707. return self._volume_svc
  708. @property
  709. def snapshots(self):
  710. return self._snapshot_svc
  711. class GCEVolumeService(BaseVolumeService):
  712. def __init__(self, provider):
  713. super(GCEVolumeService, self).__init__(provider)
  714. def get(self, volume_id):
  715. """
  716. Returns a volume given its id.
  717. """
  718. vol = self.provider.get_resource('disks', volume_id)
  719. return GCEVolume(self.provider, vol) if vol else None
  720. def find(self, name, limit=None, marker=None):
  721. """
  722. Searches for a volume by a given list of attributes.
  723. """
  724. filtr = 'name eq ' + name
  725. max_result = limit if limit is not None and limit < 500 else 500
  726. response = (self.provider
  727. .gce_compute
  728. .disks()
  729. .list(project=self.provider.project_name,
  730. zone=self.provider.default_zone,
  731. filter=filtr,
  732. maxResults=max_result,
  733. pageToken=marker)
  734. .execute())
  735. if 'items' not in response:
  736. return []
  737. gce_vols = [GCEVolume(self.provider, vol)
  738. for vol in response['items']]
  739. if len(gce_vols) > max_result:
  740. cb.log.warning('Expected at most %d results; got %d',
  741. max_result, len(gce_vols))
  742. return ServerPagedResultList('nextPageToken' in response,
  743. response.get('nextPageToken'),
  744. False, data=gce_vols)
  745. def list(self, limit=None, marker=None):
  746. """
  747. List all volumes.
  748. limit: The maximum number of volumes to return. The returned
  749. ResultList's is_truncated property can be used to determine
  750. whether more records are available.
  751. """
  752. # For GCE API, Acceptable values are 0 to 500, inclusive.
  753. # (Default: 500).
  754. max_result = limit if limit is not None and limit < 500 else 500
  755. response = (self.provider
  756. .gce_compute
  757. .disks()
  758. .list(project=self.provider.project_name,
  759. zone=self.provider.default_zone,
  760. maxResults=max_result,
  761. pageToken=marker)
  762. .execute())
  763. if 'items' not in response:
  764. return []
  765. gce_vols = [GCEVolume(self.provider, vol)
  766. for vol in response['items']]
  767. if len(gce_vols) > max_result:
  768. cb.log.warning('Expected at most %d results; got %d',
  769. max_result, len(gce_vols))
  770. return ServerPagedResultList('nextPageToken' in response,
  771. response.get('nextPageToken'),
  772. False, data=gce_vols)
  773. def create(self, name, size, zone, snapshot=None, description=None):
  774. """
  775. Creates a new volume.
  776. Argument `name` must be 1-63 characters long, and comply with RFC1035.
  777. Specifically, the name must be 1-63 characters long and match the
  778. regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the first
  779. character must be a lowercase letter, and all following characters must
  780. be a dash, lowercase letter, or digit, except the last character, which
  781. cannot be a dash.
  782. """
  783. zone_name = zone.name if isinstance(zone, PlacementZone) else zone
  784. snapshot_id = snapshot.id if isinstance(
  785. snapshot, GCESnapshot) and snapshot else snapshot
  786. disk_body = {
  787. 'name': name,
  788. 'sizeGb': size,
  789. 'type': 'zones/{0}/diskTypes/{1}'.format(zone_name, 'pd-standard'),
  790. 'sourceSnapshot': snapshot_id,
  791. 'description': description,
  792. }
  793. operation = (self.provider
  794. .gce_compute
  795. .disks()
  796. .insert(
  797. project=self._provider.project_name,
  798. zone=zone_name,
  799. body=disk_body)
  800. .execute())
  801. return self.get(operation.get('targetLink'))
  802. class GCESnapshotService(BaseSnapshotService):
  803. def __init__(self, provider):
  804. super(GCESnapshotService, self).__init__(provider)
  805. def get(self, snapshot_id):
  806. """
  807. Returns a snapshot given its id.
  808. """
  809. snapshot = self.provider.get_resource('snapshots', snapshot_id)
  810. return GCESnapshot(self.provider, snapshot) if snapshot else None
  811. def find(self, name, limit=None, marker=None):
  812. """
  813. Searches for a snapshot by a given list of attributes.
  814. """
  815. filtr = 'name eq ' + name
  816. max_result = limit if limit is not None and limit < 500 else 500
  817. response = (self.provider
  818. .gce_compute
  819. .snapshots()
  820. .list(project=self.provider.project_name,
  821. filter=filtr,
  822. maxResults=max_result,
  823. pageToken=marker)
  824. .execute())
  825. if 'items' not in response:
  826. return []
  827. snapshots = [GCESnapshot(self.provider, snapshot)
  828. for snapshot in response['items']]
  829. if len(snapshots) > max_result:
  830. cb.log.warning('Expected at most %d results; got %d',
  831. max_result, len(snapshots))
  832. return ServerPagedResultList('nextPageToken' in response,
  833. response.get('nextPageToken'),
  834. False, data=snapshots)
  835. def list(self, limit=None, marker=None):
  836. """
  837. List all snapshots.
  838. """
  839. max_result = limit if limit is not None and limit < 500 else 500
  840. response = (self.provider
  841. .gce_compute
  842. .snapshots()
  843. .list(project=self.provider.project_name,
  844. maxResults=max_result,
  845. pageToken=marker)
  846. .execute())
  847. if 'items' not in response:
  848. return []
  849. snapshots = [GCESnapshot(self.provider, snapshot)
  850. for snapshot in response['items']]
  851. if len(snapshots) > max_result:
  852. cb.log.warning('Expected at most %d results; got %d',
  853. max_result, len(snapshots))
  854. return ServerPagedResultList('nextPageToken' in response,
  855. response.get('nextPageToken'),
  856. False, data=snapshots)
  857. def create(self, name, volume, description=None):
  858. """
  859. Creates a new snapshot of a given volume.
  860. """
  861. volume_name = volume.name if isinstance(volume, GCEVolume) else volume
  862. snapshot_body = {
  863. "name": name,
  864. "description": description
  865. }
  866. operation = (self.provider
  867. .gce_compute
  868. .disks()
  869. .createSnapshot(
  870. project=self.provider.project_name,
  871. zone=self.provider.default_zone,
  872. disk=volume_name, body=snapshot_body)
  873. .execute())
  874. if 'zone' not in operation:
  875. return None
  876. zone_url = self.provider.parse_url(operation['zone'])
  877. self.provider.wait_for_operation(operation,
  878. zone=zone_url.parameters['zone'])
  879. snapshots = self.provider.block_store.snapshots.find(name=name)
  880. if snapshots:
  881. return snapshots[0]
  882. else:
  883. return None
  884. class GCSObjectStoreService(BaseObjectStoreService):
  885. def __init__(self, provider):
  886. super(GCSObjectStoreService, self).__init__(provider)
  887. def get(self, bucket_id):
  888. """
  889. Returns a bucket given its ID. Returns ``None`` if the bucket
  890. does not exist or if the user does not have permission to access the
  891. bucket.
  892. """
  893. bucket = self.provider.get_resource('buckets', bucket_id)
  894. return GCSBucket(self.provider, bucket) if bucket else None
  895. def find(self, name, limit=None, marker=None):
  896. """
  897. Searches in bucket names for a substring.
  898. """
  899. buckets = [bucket for bucket in self if name in bucket.name]
  900. return ClientPagedResultList(self.provider, buckets, limit=limit,
  901. marker=marker)
  902. def list(self, limit=None, marker=None):
  903. """
  904. List all containers.
  905. """
  906. max_result = limit if limit is not None and limit < 500 else 500
  907. try:
  908. response = (self.provider
  909. .gcp_storage
  910. .buckets()
  911. .list(project=self.provider.project_name,
  912. maxResults=max_result,
  913. pageToken=marker)
  914. .execute())
  915. if 'error' in response:
  916. return ServerPagedResultList(False, None, False, data=[])
  917. buckets = []
  918. for bucket in response.get('items', []):
  919. buckets.append(GCSBucket(self.provider, bucket))
  920. if len(buckets) > max_result:
  921. cb.log.warning('Expected at most %d results; got %d',
  922. max_result, len(buckets))
  923. return ServerPagedResultList('nextPageToken' in response,
  924. response.get('nextPageToken'),
  925. False, data=buckets)
  926. except:
  927. return ServerPagedResultList(False, None, False, data=[])
  928. def create(self, name, location=None):
  929. """
  930. Create a new bucket and returns it. Returns None if creation fails.
  931. """
  932. body = {'name': name}
  933. if location:
  934. body['location'] = location
  935. try:
  936. response = (self.provider
  937. .gcp_storage
  938. .buckets()
  939. .insert(project=self.provider.project_name,
  940. body=body)
  941. .execute())
  942. if 'error' in response:
  943. return None
  944. return GCSBucket(self.provider, response)
  945. except:
  946. return None