resources.py 75 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207
  1. """
  2. DataTypes used by this provider
  3. """
  4. import base64
  5. import calendar
  6. import hashlib
  7. import inspect
  8. import io
  9. import logging
  10. import math
  11. import re
  12. import time
  13. import uuid
  14. from collections import namedtuple
  15. import googleapiclient
  16. import cloudbridge as cb
  17. from cloudbridge.cloud.base.resources import BaseAttachmentInfo
  18. from cloudbridge.cloud.base.resources import BaseBucket
  19. from cloudbridge.cloud.base.resources import BaseBucketContainer
  20. from cloudbridge.cloud.base.resources import BaseBucketObject
  21. from cloudbridge.cloud.base.resources import BaseFloatingIP
  22. from cloudbridge.cloud.base.resources import BaseFloatingIPContainer
  23. from cloudbridge.cloud.base.resources import BaseGatewayContainer
  24. from cloudbridge.cloud.base.resources import BaseInstance
  25. from cloudbridge.cloud.base.resources import BaseInternetGateway
  26. from cloudbridge.cloud.base.resources import BaseKeyPair
  27. from cloudbridge.cloud.base.resources import BaseLaunchConfig
  28. from cloudbridge.cloud.base.resources import BaseMachineImage
  29. from cloudbridge.cloud.base.resources import BaseNetwork
  30. from cloudbridge.cloud.base.resources import BasePlacementZone
  31. from cloudbridge.cloud.base.resources import BaseRegion
  32. from cloudbridge.cloud.base.resources import BaseRouter
  33. from cloudbridge.cloud.base.resources import BaseSnapshot
  34. from cloudbridge.cloud.base.resources import BaseSubnet
  35. from cloudbridge.cloud.base.resources import BaseVMFirewall
  36. from cloudbridge.cloud.base.resources import BaseVMFirewallRule
  37. from cloudbridge.cloud.base.resources import BaseVMFirewallRuleContainer
  38. from cloudbridge.cloud.base.resources import BaseVMType
  39. from cloudbridge.cloud.base.resources import BaseVolume
  40. from cloudbridge.cloud.base.resources import ClientPagedResultList
  41. from cloudbridge.cloud.base.resources import ServerPagedResultList
  42. from cloudbridge.cloud.interfaces.resources import GatewayState
  43. from cloudbridge.cloud.interfaces.resources import InstanceState
  44. from cloudbridge.cloud.interfaces.resources import MachineImageState
  45. from cloudbridge.cloud.interfaces.resources import NetworkState
  46. from cloudbridge.cloud.interfaces.resources import RouterState
  47. from cloudbridge.cloud.interfaces.resources import SnapshotState
  48. from cloudbridge.cloud.interfaces.resources import SubnetState
  49. from cloudbridge.cloud.interfaces.resources import TrafficDirection
  50. from cloudbridge.cloud.interfaces.resources import VolumeState
  51. from cloudbridge.cloud.providers.gce import helpers
  52. # Older versions of Python do not have a built-in set data-structure.
  53. try:
  54. set
  55. except NameError:
  56. from sets import Set as set
  57. log = logging.getLogger(__name__)
  58. class GCEKeyPair(BaseKeyPair):
  59. KP_TAG_PREFIX = "cb_key_pair_"
  60. KP_TAG_REGEX = re.compile("^" + KP_TAG_PREFIX + ".*")
  61. GCEKeyInfo = namedtuple('GCEKeyInfo', ['name', 'public_key'])
  62. def __init__(self, provider, kp_info, private_key=None):
  63. super(GCEKeyPair, self).__init__(provider, None)
  64. self._key_pair = kp_info
  65. self._private_key = private_key
  66. @property
  67. def id(self):
  68. return self._key_pair.name
  69. @property
  70. def name(self):
  71. return self._key_pair.name
  72. def delete(self):
  73. self._provider.security.key_pairs.delete(self.id)
  74. @property
  75. def material(self):
  76. return self._private_key
  77. class GCEVMType(BaseVMType):
  78. def __init__(self, provider, instance_dict):
  79. super(GCEVMType, self).__init__(provider)
  80. self._inst_dict = instance_dict
  81. @property
  82. def resource_url(self):
  83. return self._inst_dict.get('selfLink')
  84. @property
  85. def id(self):
  86. return self._inst_dict.get('selfLink')
  87. @property
  88. def name(self):
  89. return self._inst_dict.get('name')
  90. @property
  91. def family(self):
  92. return self._inst_dict.get('kind')
  93. @property
  94. def vcpus(self):
  95. return self._inst_dict.get('guestCpus')
  96. @property
  97. def ram(self):
  98. return float("{0:.2f}".format(self._inst_dict.get('memoryMb') / 1024))
  99. @property
  100. def size_root_disk(self):
  101. return 0
  102. @property
  103. def size_ephemeral_disks(self):
  104. return int(self._inst_dict.get('maximumPersistentDisksSizeGb'))
  105. @property
  106. def num_ephemeral_disks(self):
  107. return self._inst_dict.get('maximumPersistentDisks')
  108. @property
  109. def extra_data(self):
  110. return {key: val for key, val in self._inst_dict.items()
  111. if key not in ['id', 'name', 'kind', 'guestCpus', 'memoryMb',
  112. 'maximumPersistentDisksSizeGb',
  113. 'maximumPersistentDisks']}
  114. class GCEPlacementZone(BasePlacementZone):
  115. def __init__(self, provider, zone):
  116. super(GCEPlacementZone, self).__init__(provider)
  117. self._zone = zone
  118. @property
  119. def id(self):
  120. """
  121. Get the zone id
  122. :rtype: ``str``
  123. :return: ID for this zone as returned by the cloud middleware.
  124. """
  125. return self._zone['selfLink']
  126. @property
  127. def name(self):
  128. """
  129. Get the zone name.
  130. :rtype: ``str``
  131. :return: Name for this zone as returned by the cloud middleware.
  132. """
  133. return self._zone['name']
  134. @property
  135. def region_name(self):
  136. """
  137. Get the region that this zone belongs to.
  138. :rtype: ``str``
  139. :return: Name of this zone's region as returned by the cloud middleware
  140. """
  141. parsed_region_url = self._provider.parse_url(self._zone['region'])
  142. return parsed_region_url.parameters['region']
  143. class GCERegion(BaseRegion):
  144. def __init__(self, provider, gce_region):
  145. super(GCERegion, self).__init__(provider)
  146. self._gce_region = gce_region
  147. @property
  148. def id(self):
  149. return self._gce_region.get('selfLink')
  150. @property
  151. def name(self):
  152. return self._gce_region.get('name')
  153. @property
  154. def zones(self):
  155. """
  156. Accesss information about placement zones within this region.
  157. """
  158. zones_response = (self._provider
  159. .gce_compute
  160. .zones()
  161. .list(project=self._provider.project_name)
  162. .execute())
  163. zones = [zone for zone in zones_response['items']
  164. if zone['region'] == self._gce_region['selfLink']]
  165. return [GCEPlacementZone(self._provider, zone) for zone in zones]
  166. class GCEFirewallsDelegate(object):
  167. _NETWORK_URL_PREFIX = 'global/networks/'
  168. def __init__(self, provider):
  169. self._provider = provider
  170. self._list_response = None
  171. @staticmethod
  172. def tag_network_id(tag, network_name):
  173. """
  174. Generate an ID for a (tag, network name) pair.
  175. """
  176. md5 = hashlib.md5()
  177. md5.update("{0}-{1}".format(tag, network_name).encode('ascii'))
  178. return md5.hexdigest()
  179. @property
  180. def provider(self):
  181. return self._provider
  182. @property
  183. def tag_networks(self):
  184. """
  185. List all (tag, network name) pairs that are in at least one firewall.
  186. """
  187. out = set()
  188. for firewall in self.iter_firewalls():
  189. network_name = self.network_name(firewall)
  190. if network_name is not None:
  191. out.add((firewall['targetTags'][0], network_name))
  192. return out
  193. def network_name(self, firewall):
  194. """
  195. Extract the network name of a firewall.
  196. """
  197. if 'network' not in firewall:
  198. return GCENetwork.CB_DEFAULT_NETWORK_LABEL
  199. url = self._provider.parse_url(firewall['network'])
  200. return url.parameters['network']
  201. def get_tag_network_from_id(self, tag_network_id):
  202. """
  203. Map an ID back to the (tag, network name) pair.
  204. """
  205. for tag, network_name in self.tag_networks:
  206. current_id = GCEFirewallsDelegate.tag_network_id(tag, network_name)
  207. if current_id == tag_network_id:
  208. return (tag, network_name)
  209. return (None, None)
  210. def delete_tag_network_with_id(self, tag_network_id):
  211. """
  212. Delete all firewalls in a given network with a specific target tag.
  213. """
  214. tag, network_name = self.get_tag_network_from_id(tag_network_id)
  215. if tag is None:
  216. return
  217. for firewall in self.iter_firewalls(tag, network_name):
  218. self._delete_firewall(firewall)
  219. self._update_list_response()
  220. def add_firewall(self, tag, direction, protocol, priority, port,
  221. src_dest_range, src_dest_tag, description, network_name):
  222. """
  223. Create a new firewall.
  224. """
  225. if self.find_firewall(
  226. tag, direction, protocol, port, src_dest_range, src_dest_tag,
  227. network_name) is not None:
  228. return True
  229. # Do not let the user accidentally open traffic from the world by not
  230. # explicitly specifying the source.
  231. if src_dest_tag is None and src_dest_range is None:
  232. return False
  233. firewall = {
  234. 'name': 'firewall-{0}'.format(uuid.uuid4()),
  235. 'network': GCEFirewallsDelegate._NETWORK_URL_PREFIX + network_name,
  236. 'allowed': [{'IPProtocol': str(protocol)}],
  237. 'targetTags': [tag]}
  238. if description is not None:
  239. firewall['description'] = description
  240. if port is not None:
  241. firewall['allowed'][0]['ports'] = [port]
  242. if direction == TrafficDirection.INBOUND:
  243. firewall['direction'] = 'INGRESS'
  244. src_dest_str = 'source'
  245. else:
  246. firewall['direction'] = 'EGRESS'
  247. src_dest_str = 'destination'
  248. if src_dest_range is not None:
  249. firewall[src_dest_str + 'Ranges'] = [src_dest_range]
  250. if src_dest_tag is not None:
  251. if direction == TrafficDirection.OUTBOUND:
  252. cb.log.warning('GCP does not support egress rules to network '
  253. 'tags. Only IP ranges are acceptable.')
  254. else:
  255. firewall['sourceTags'] = [src_dest_tag]
  256. if priority is not None:
  257. firewall['priority'] = priority
  258. project_name = self._provider.project_name
  259. try:
  260. response = (self._provider
  261. .gce_compute
  262. .firewalls()
  263. .insert(project=project_name,
  264. body=firewall)
  265. .execute())
  266. self._provider.wait_for_operation(response)
  267. # TODO: process the response and handle errors.
  268. finally:
  269. self._update_list_response()
  270. return True
  271. def find_firewall(self, tag, direction, protocol, port, src_dest_range,
  272. src_dest_tag, network_name):
  273. """
  274. Find a firewall with give parameters.
  275. """
  276. if src_dest_range is None and src_dest_tag is None:
  277. src_dest_range = '0.0.0.0/0'
  278. if direction == TrafficDirection.INBOUND:
  279. src_dest_str = 'source'
  280. else:
  281. src_dest_str = 'destination'
  282. for firewall in self.iter_firewalls(tag, network_name):
  283. if firewall['allowed'][0]['IPProtocol'] != protocol:
  284. continue
  285. if not self._check_list_in_dict(firewall['allowed'][0], 'ports',
  286. port):
  287. continue
  288. if not self._check_list_in_dict(firewall, src_dest_str + 'Ranges',
  289. src_dest_range):
  290. continue
  291. if not self._check_list_in_dict(firewall, src_dest_str + 'Tags',
  292. src_dest_tag):
  293. continue
  294. return firewall['id']
  295. return None
  296. def get_firewall_info(self, firewall_id):
  297. """
  298. Extract firewall properties to into a dictionary for easy of use.
  299. """
  300. info = {}
  301. for firewall in self.iter_firewalls():
  302. if firewall['id'] != firewall_id:
  303. continue
  304. if ('sourceRanges' in firewall and
  305. len(firewall['sourceRanges']) == 1):
  306. info['src_dest_range'] = firewall['sourceRanges'][0]
  307. elif ('destinationRanges' in firewall and
  308. len(firewall['destinationRanges']) == 1):
  309. info['src_dest_range'] = firewall['destinationRanges'][0]
  310. if 'sourceTags' in firewall and len(firewall['sourceTags']) == 1:
  311. info['src_dest_tag'] = firewall['sourceTags'][0]
  312. if 'targetTags' in firewall and len(firewall['targetTags']) == 1:
  313. info['target_tag'] = firewall['targetTags'][0]
  314. if 'IPProtocol' in firewall['allowed'][0]:
  315. info['protocol'] = firewall['allowed'][0]['IPProtocol']
  316. if ('ports' in firewall['allowed'][0] and
  317. len(firewall['allowed'][0]['ports']) == 1):
  318. info['port'] = firewall['allowed'][0]['ports'][0]
  319. info['network_name'] = self.network_name(firewall)
  320. if 'direction' in firewall:
  321. info['direction'] = firewall['direction']
  322. if 'priority' in firewall:
  323. info['priority'] = firewall['priority']
  324. return info
  325. return info
  326. def delete_firewall_id(self, firewall_id):
  327. """
  328. Delete a firewall with a given ID.
  329. """
  330. for firewall in self.iter_firewalls():
  331. if firewall['id'] == firewall_id:
  332. self._delete_firewall(firewall)
  333. self._update_list_response()
  334. def iter_firewalls(self, tag=None, network_name=None):
  335. """
  336. Iterate through all firewalls. Can optionally iterate through firewalls
  337. with a given tag and/or in a network.
  338. """
  339. if self._list_response is None:
  340. self._update_list_response()
  341. for firewall in self._list_response:
  342. if ('targetTags' not in firewall or
  343. len(firewall['targetTags']) != 1):
  344. continue
  345. if 'allowed' not in firewall or len(firewall['allowed']) != 1:
  346. continue
  347. if tag is not None and firewall['targetTags'][0] != tag:
  348. continue
  349. if network_name is None:
  350. yield firewall
  351. continue
  352. firewall_network_name = self.network_name(firewall)
  353. if firewall_network_name == network_name:
  354. yield firewall
  355. def _delete_firewall(self, firewall):
  356. """
  357. Delete a given firewall.
  358. """
  359. project_name = self._provider.project_name
  360. response = (self._provider
  361. .gce_compute
  362. .firewalls()
  363. .delete(project=project_name,
  364. firewall=firewall['name'])
  365. .execute())
  366. self._provider.wait_for_operation(response)
  367. # TODO: process the response and handle errors.
  368. tag_name = "_".join(["firewall", self.name, "label"])
  369. if not helpers.remove_metadata_item(self._provider, tag_name):
  370. log.warning('No label was found associated with this firewall '
  371. '"{}" when deleted.'.format(self.name))
  372. return True
  373. def _update_list_response(self):
  374. """
  375. Sync the local cache of all firewalls with the server.
  376. """
  377. self._list_response = list(
  378. helpers.iter_all(self._provider.gce_compute.firewalls(),
  379. project=self._provider.project_name))
  380. def _check_list_in_dict(self, dictionary, field_name, value):
  381. """
  382. Verify that a given field in a dictionary is a singlton list [value].
  383. """
  384. if field_name not in dictionary:
  385. return value is None
  386. if (value is None or len(dictionary[field_name]) != 1 or
  387. dictionary[field_name][0] != value):
  388. return False
  389. return True
  390. class GCEVMFirewall(BaseVMFirewall):
  391. def __init__(self, delegate, tag, network=None, description=None):
  392. super(GCEVMFirewall, self).__init__(delegate.provider, tag)
  393. self._delegate = delegate
  394. self._description = description
  395. if network is None:
  396. self._network = (delegate.provider.networking.networks
  397. .get_or_create_default())
  398. else:
  399. self._network = network
  400. self._rule_container = GCEVMFirewallRuleContainer(self)
  401. @property
  402. def id(self):
  403. """
  404. Return the ID of this VM firewall which is determined based on the
  405. network and the target tag corresponding to this VM firewall.
  406. """
  407. return GCEFirewallsDelegate.tag_network_id(self._vm_firewall,
  408. self._network.name)
  409. @property
  410. def name(self):
  411. """
  412. Return the name of the VM firewall which is the same as the
  413. corresponding tag name.
  414. """
  415. return self._vm_firewall
  416. @property
  417. def label(self):
  418. tag_name = "_".join(["firewall", self.name, "label"])
  419. return helpers.get_metadata_item_value(self._provider, tag_name)
  420. @label.setter
  421. def label(self, value):
  422. self.assert_valid_resource_label(value)
  423. tag_name = "_".join(["firewall", self.name, "label"])
  424. helpers.modify_or_add_metadata_item(self._provider, tag_name, value)
  425. @property
  426. def description(self):
  427. """
  428. The description of the VM firewall is even explicitly given when the
  429. VM firewall is created or is determined from a VM firewall rule, i.e. a
  430. GCE firewall, in the VM firewall.
  431. If the GCE firewalls are created using this API, they all have the same
  432. description.
  433. """
  434. if self._description is None:
  435. for firewall in self._delegate.iter_firewalls(self._vm_firewall,
  436. self._network.name):
  437. if 'description' in firewall:
  438. self._description = firewall['description']
  439. if self._description is None:
  440. self._description = ''
  441. return self._description
  442. @property
  443. def network_id(self):
  444. return self._network.id
  445. @property
  446. def rules(self):
  447. return self._rule_container
  448. def to_json(self):
  449. attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
  450. js = {k: v for(k, v) in attr if not k.startswith('_')}
  451. json_rules = [r.to_json() for r in self.rules]
  452. js['rules'] = json_rules
  453. return js
  454. def refresh(self):
  455. fw = self._provider.security.vm_firewalls.get(self.id)
  456. # restore all internal state
  457. if fw:
  458. # pylint:disable=protected-access
  459. self._delegate = fw._delegate
  460. self._description = fw._description
  461. self._network = fw._network
  462. self._rule_container = fw._rule_container
  463. @property
  464. def network(self):
  465. return self._network
  466. @property
  467. def delegate(self):
  468. return self._delegate
  469. class GCEVMFirewallRuleContainer(BaseVMFirewallRuleContainer):
  470. def __init__(self, firewall):
  471. super(GCEVMFirewallRuleContainer, self).__init__(
  472. firewall.delegate.provider, firewall)
  473. self._dummy_rule = None
  474. def list(self, limit=None, marker=None):
  475. rules = []
  476. for firewall in self.firewall.delegate.iter_firewalls(
  477. self.firewall.name, self.firewall.network.name):
  478. rule = GCEVMFirewallRule(self.firewall, firewall['id'])
  479. if rule.is_dummy_rule():
  480. self._dummy_rule = rule
  481. else:
  482. rules.append(rule)
  483. return ClientPagedResultList(self._provider, rules,
  484. limit=limit, marker=marker)
  485. @property
  486. def dummy_rule(self):
  487. if not self._dummy_rule:
  488. self.list()
  489. return self._dummy_rule
  490. @staticmethod
  491. def to_port_range(from_port, to_port):
  492. if from_port is not None and to_port is not None:
  493. return '%d-%d' % (from_port, to_port)
  494. elif from_port is not None:
  495. return from_port
  496. else:
  497. return to_port
  498. def create_with_priority(self, direction, protocol, priority,
  499. from_port=None, to_port=None, cidr=None,
  500. src_dest_fw=None):
  501. port = GCEVMFirewallRuleContainer.to_port_range(from_port, to_port)
  502. src_dest_tag = None
  503. src_dest_fw_id = None
  504. if src_dest_fw:
  505. src_dest_tag = src_dest_fw.name
  506. src_dest_fw_id = src_dest_fw.id
  507. if not self.firewall.delegate.add_firewall(
  508. self.firewall.name, direction, protocol, priority, port, cidr,
  509. src_dest_tag, self.firewall.description,
  510. self.firewall.network.name):
  511. return None
  512. rules = self.find(direction=direction, protocol=protocol,
  513. from_port=from_port, to_port=to_port, cidr=cidr,
  514. src_dest_fw_id=src_dest_fw_id)
  515. if len(rules) < 1:
  516. return None
  517. return rules[0]
  518. def create(self, direction, protocol, from_port=None, to_port=None,
  519. cidr=None, src_dest_fw=None):
  520. return self.create_with_priority(direction, protocol, 1000, from_port,
  521. to_port, cidr, src_dest_fw)
  522. class GCEVMFirewallRule(BaseVMFirewallRule):
  523. def __init__(self, parent_fw, rule):
  524. super(GCEVMFirewallRule, self).__init__(parent_fw, rule)
  525. @property
  526. def id(self):
  527. return self._rule
  528. @property
  529. def direction(self):
  530. info = self.firewall.delegate.get_firewall_info(self._rule)
  531. if info is None:
  532. return None
  533. if 'direction' in info and info['direction'] == 'EGRESS':
  534. return TrafficDirection.OUTBOUND
  535. return TrafficDirection.INBOUND
  536. @property
  537. def protocol(self):
  538. info = self.firewall.delegate.get_firewall_info(self._rule)
  539. if info is None or 'protocol' not in info:
  540. return None
  541. return info['protocol']
  542. @property
  543. def from_port(self):
  544. info = self.firewall.delegate.get_firewall_info(self._rule)
  545. if info is None or 'port' not in info:
  546. return 0
  547. port = info['port']
  548. if port.isdigit():
  549. return int(port)
  550. parts = port.split('-')
  551. if len(parts) > 2 or len(parts) < 1:
  552. return 0
  553. if parts[0].isdigit():
  554. return int(parts[0])
  555. return 0
  556. @property
  557. def to_port(self):
  558. info = self.firewall.delegate.get_firewall_info(self._rule)
  559. if info is None or 'port' not in info:
  560. return 0
  561. port = info['port']
  562. if port.isdigit():
  563. return int(port)
  564. parts = port.split('-')
  565. if len(parts) > 2 or len(parts) < 1:
  566. return 0
  567. if parts[-1].isdigit():
  568. return int(parts[-1])
  569. return 0
  570. @property
  571. def cidr(self):
  572. info = self.firewall.delegate.get_firewall_info(self._rule)
  573. if info is None or 'src_dest_range' not in info:
  574. return None
  575. return info['src_dest_range']
  576. @property
  577. def src_dest_fw_id(self):
  578. """
  579. Return the VM firewall given access by this rule.
  580. """
  581. info = self.firewall.delegate.get_firewall_info(self._rule)
  582. if info is None or 'src_dest_tag' not in info:
  583. return None
  584. return GCEFirewallsDelegate.tag_network_id(info['src_dest_tag'],
  585. self.firewall.network.name)
  586. @property
  587. def src_dest_fw(self):
  588. """
  589. Return the VM firewall given access by this rule.
  590. """
  591. info = self.firewall.delegate.get_firewall_info(self._rule)
  592. if info is None or 'src_dest_tag' not in info:
  593. return None
  594. return GCEVMFirewall(
  595. self.firewall.delegate, info['src_dest_tag'],
  596. self.firewall.network)
  597. @property
  598. def priority(self):
  599. info = self.firewall.delegate.get_firewall_info(self._rule)
  600. # The default firewall rule priority, when not specified, is 1000.
  601. if info is None or 'priority' not in info:
  602. return 1000
  603. return info['priority']
  604. def is_dummy_rule(self):
  605. if self.priority != 65534:
  606. return False
  607. if self.direction != TrafficDirection.OUTBOUND:
  608. return False
  609. if self.protocol != 'tcp':
  610. return False
  611. if self.cidr != '0.0.0.0/0':
  612. return False
  613. return True
  614. def delete(self):
  615. if (self.is_dummy_rule()):
  616. return
  617. self.force_delete()
  618. def force_delete(self):
  619. self.firewall.delegate.delete_firewall_id(self._rule)
  620. class GCEMachineImage(BaseMachineImage):
  621. IMAGE_STATE_MAP = {
  622. 'PENDING': MachineImageState.PENDING,
  623. 'READY': MachineImageState.AVAILABLE,
  624. 'FAILED': MachineImageState.ERROR
  625. }
  626. def __init__(self, provider, image):
  627. super(GCEMachineImage, self).__init__(provider)
  628. if isinstance(image, GCEMachineImage):
  629. # pylint:disable=protected-access
  630. self._gce_image = image._gce_image
  631. else:
  632. self._gce_image = image
  633. @property
  634. def resource_url(self):
  635. return self._gce_image.get('selfLink')
  636. @property
  637. def id(self):
  638. """
  639. Get the image identifier.
  640. :rtype: ``str``
  641. :return: ID for this instance as returned by the cloud middleware.
  642. """
  643. return self._gce_image.get('selfLink')
  644. @property
  645. def name(self):
  646. """
  647. Get the image name.
  648. :rtype: ``str``
  649. :return: Name for this image as returned by the cloud middleware.
  650. """
  651. return self._gce_image['name']
  652. @property
  653. def label(self):
  654. labels = self._gce_image.get('labels')
  655. return labels.get('cblabel', '') if labels else ''
  656. @label.setter
  657. # pylint:disable=arguments-differ
  658. def label(self, value):
  659. req = (self._provider
  660. .gce_compute
  661. .images()
  662. .setLabels(project=self._provider.project_name,
  663. resource=self.name,
  664. body={}))
  665. helpers.change_label(self, 'cblabel', value, '_gce_image', req)
  666. @property
  667. def description(self):
  668. """
  669. Get the image description.
  670. :rtype: ``str``
  671. :return: Description for this image as returned by the cloud middleware
  672. """
  673. return self._gce_image.get('description', '')
  674. @property
  675. def min_disk(self):
  676. """
  677. Returns the minimum size of the disk that's required to
  678. boot this image (in GB)
  679. :rtype: ``int``
  680. :return: The minimum disk size needed by this image
  681. """
  682. return int(math.ceil(float(self._gce_image.get('diskSizeGb'))))
  683. def delete(self):
  684. """
  685. Delete this image
  686. """
  687. (self._provider
  688. .gce_compute
  689. .images()
  690. .delete(project=self._provider.project_name,
  691. image=self.name)
  692. .execute())
  693. @property
  694. def state(self):
  695. return GCEMachineImage.IMAGE_STATE_MAP.get(
  696. self._gce_image['status'], MachineImageState.UNKNOWN)
  697. def refresh(self):
  698. """
  699. Refreshes the state of this instance by re-querying the cloud provider
  700. for its latest state.
  701. """
  702. image = self._provider.compute.images.get(self.id)
  703. if image:
  704. # pylint:disable=protected-access
  705. self._gce_image = image._gce_image
  706. else:
  707. # image no longer exists
  708. self._gce_image['status'] = MachineImageState.UNKNOWN
  709. class GCEInstance(BaseInstance):
  710. # https://cloud.google.com/compute/docs/reference/latest/instances
  711. # The status of the instance. One of the following values:
  712. # PROVISIONING, STAGING, RUNNING, STOPPING, SUSPENDING, SUSPENDED,
  713. # and TERMINATED.
  714. INSTANCE_STATE_MAP = {
  715. 'PROVISIONING': InstanceState.PENDING,
  716. 'STAGING': InstanceState.PENDING,
  717. 'RUNNING': InstanceState.RUNNING,
  718. 'STOPPING': InstanceState.CONFIGURING,
  719. 'TERMINATED': InstanceState.STOPPED,
  720. 'SUSPENDING': InstanceState.CONFIGURING,
  721. 'SUSPENDED': InstanceState.STOPPED
  722. }
  723. def __init__(self, provider, gce_instance):
  724. super(GCEInstance, self).__init__(provider)
  725. self._gce_instance = gce_instance
  726. self._inet_gateway = None
  727. @property
  728. def resource_url(self):
  729. return self._gce_instance.get('selfLink')
  730. @property
  731. def id(self):
  732. """
  733. Get the instance identifier.
  734. A GCE instance is uniquely identified by its selfLink, which is used
  735. as its id.
  736. """
  737. return self._gce_instance.get('selfLink')
  738. @property
  739. def name(self):
  740. """
  741. Get the instance name.
  742. """
  743. return self._gce_instance['name']
  744. @property
  745. def label(self):
  746. labels = self._gce_instance.get('labels')
  747. return labels.get('cblabel', '') if labels else ''
  748. @label.setter
  749. # pylint:disable=arguments-differ
  750. def label(self, value):
  751. req = (self._provider
  752. .gce_compute
  753. .instances()
  754. .setLabels(project=self._provider.project_name,
  755. zone=self.zone_name,
  756. instance=self.name,
  757. body={}))
  758. helpers.change_label(self, 'cblabel', value, '_gce_instance', req)
  759. @property
  760. def public_ips(self):
  761. """
  762. Get all the public IP addresses for this instance.
  763. """
  764. ips = []
  765. network_interfaces = self._gce_instance.get('networkInterfaces')
  766. if network_interfaces is not None and len(network_interfaces) > 0:
  767. access_configs = network_interfaces[0].get('accessConfigs')
  768. if access_configs is not None and len(access_configs) > 0:
  769. # https://cloud.google.com/compute/docs/reference/beta/instances
  770. # An array of configurations for this interface. Currently,
  771. # only one access config, ONE_TO_ONE_NAT, is supported. If
  772. # there are no accessConfigs specified, then this instance will
  773. # have no external internet access.
  774. access_config = access_configs[0]
  775. if 'natIP' in access_config:
  776. ips.append(access_config['natIP'])
  777. for ip in self.inet_gateway.floating_ips:
  778. if ip.in_use:
  779. if ip.private_ip in self.private_ips:
  780. ips.append(ip.public_ip)
  781. return ips
  782. @property
  783. def private_ips(self):
  784. """
  785. Get all the private IP addresses for this instance.
  786. """
  787. network_interfaces = self._gce_instance.get('networkInterfaces')
  788. if network_interfaces is None or len(network_interfaces) == 0:
  789. return []
  790. if 'networkIP' in network_interfaces[0]:
  791. return [network_interfaces[0]['networkIP']]
  792. else:
  793. return []
  794. @property
  795. def vm_type_id(self):
  796. """
  797. Get the instance type name.
  798. """
  799. return self._gce_instance.get('machineType')
  800. @property
  801. def vm_type(self):
  802. """
  803. Get the instance type.
  804. """
  805. machine_type_uri = self._gce_instance.get('machineType')
  806. if machine_type_uri is None:
  807. return None
  808. parsed_uri = self._provider.parse_url(machine_type_uri)
  809. return GCEVMType(self._provider, parsed_uri.get_resource())
  810. @property
  811. def subnet_id(self):
  812. """
  813. Get the zone for this instance.
  814. """
  815. return (self._gce_instance.get('networkInterfaces', [{}])[0]
  816. .get('subnetwork'))
  817. def reboot(self):
  818. """
  819. Reboot this instance.
  820. """
  821. if self.state == InstanceState.STOPPED:
  822. (self._provider
  823. .gce_compute
  824. .instances()
  825. .start(project=self._provider.project_name,
  826. zone=self.zone_name,
  827. instance=self.name)
  828. .execute())
  829. else:
  830. (self._provider
  831. .gce_compute
  832. .instances()
  833. .reset(project=self._provider.project_name,
  834. zone=self.zone_name,
  835. instance=self.name)
  836. .execute())
  837. def delete(self):
  838. """
  839. Permanently terminate this instance.
  840. """
  841. name = self.name
  842. (self._provider
  843. .gce_compute
  844. .instances()
  845. .delete(project=self._provider.project_name,
  846. zone=self.zone_name,
  847. instance=name)
  848. .execute())
  849. def stop(self):
  850. """
  851. Stop this instance.
  852. """
  853. (self._provider
  854. .gce_compute
  855. .instances()
  856. .stop(project=self._provider.project_name,
  857. zone=self.zone_name,
  858. instance=self.name)
  859. .execute())
  860. @property
  861. def image_id(self):
  862. """
  863. Get the image ID for this insance.
  864. """
  865. if 'disks' not in self._gce_instance:
  866. return None
  867. for disk in self._gce_instance['disks']:
  868. if 'boot' in disk and disk['boot']:
  869. disk_url = self._provider.parse_url(disk['source'])
  870. return disk_url.get_resource().get('sourceImage')
  871. return None
  872. @property
  873. def zone_id(self):
  874. """
  875. Get the placement zone id where this instance is running.
  876. """
  877. return self._gce_instance.get('zone')
  878. @property
  879. def zone_name(self):
  880. return self._provider.parse_url(self.zone_id).parameters['zone']
  881. @property
  882. def vm_firewalls(self):
  883. """
  884. Get the VM firewalls associated with this instance.
  885. """
  886. network_url = self._gce_instance.get('networkInterfaces')[0].get(
  887. 'network')
  888. url = self._provider.parse_url(network_url)
  889. network_name = url.parameters['network']
  890. if 'items' not in self._gce_instance['tags']:
  891. return []
  892. tags = self._gce_instance['tags']['items']
  893. # Tags are mapped to non-empty VM firewalls under the instance network.
  894. # Unmatched tags are ignored.
  895. sgs = (self._provider.security
  896. .vm_firewalls.find_by_network_and_tags(
  897. network_name, tags))
  898. return sgs
  899. @property
  900. def vm_firewall_ids(self):
  901. """
  902. Get the VM firewall IDs associated with this instance.
  903. """
  904. sg_ids = []
  905. for sg in self.vm_firewalls:
  906. sg_ids.append(sg.id)
  907. return sg_ids
  908. @property
  909. def key_pair_id(self):
  910. """
  911. Get the id of the key pair associated with this instance.
  912. Assume there is only 1 key pair
  913. """
  914. # Get instance again to avoid stale metadata
  915. ins = self._provider.compute.instances.get(self.id)
  916. meta = ins._gce_instance.get('metadata', {})
  917. if meta:
  918. items = meta.get("items", [])
  919. for item in items:
  920. if item.get("key") == "ssh-keys":
  921. # The key pair name/id is stored last, after the public key
  922. return item.get("value").split(" ")[-1]
  923. return None
  924. @property
  925. def inet_gateway(self):
  926. if self._inet_gateway:
  927. return self._inet_gateway
  928. network_url = self._gce_instance.get('networkInterfaces')[0].get(
  929. 'network')
  930. network = self._provider.networking.networks.get(network_url)
  931. self._inet_gateway = network.gateways.get_or_create_inet_gateway()
  932. return self._inet_gateway
  933. def create_image(self, label):
  934. """
  935. Create a new image based on this instance.
  936. """
  937. self.assert_valid_resource_label(label)
  938. name = self._generate_name_from_label(label, 'cb-img')
  939. if 'disks' not in self._gce_instance:
  940. cb.log.error('Failed to create image: no disks found.')
  941. return
  942. for disk in self._gce_instance['disks']:
  943. if 'boot' in disk and disk['boot']:
  944. image_body = {
  945. 'name': name,
  946. 'sourceDisk': disk['source'],
  947. 'labels': {'cblabel': label.replace(' ', '_').lower()},
  948. }
  949. operation = (self._provider
  950. .gce_compute
  951. .images()
  952. .insert(project=self._provider.project_name,
  953. body=image_body,
  954. forceCreate=True)
  955. .execute())
  956. self._provider.wait_for_operation(operation)
  957. img = self._provider.get_resource('images', name)
  958. return GCEMachineImage(self._provider, img) if img else None
  959. cb.log.error('Failed to create image: no boot disk found.')
  960. def _get_existing_target_instance(self):
  961. """
  962. Return the target instance corresponding to this instance.
  963. If there is no target instance for this instance, return None.
  964. """
  965. try:
  966. for target_instance in helpers.iter_all(
  967. self._provider.gce_compute.targetInstances(),
  968. project=self._provider.project_name,
  969. zone=self.zone_name):
  970. url = self._provider.parse_url(target_instance['instance'])
  971. if url.parameters['instance'] == self.name:
  972. return target_instance
  973. except Exception as e:
  974. cb.log.warning('Exception while listing target instances: %s', e)
  975. return None
  976. def _get_target_instance(self):
  977. """
  978. Return the target instance corresponding to this instance.
  979. If there is no target instance for this instance, create one.
  980. """
  981. existing_target_instance = self._get_existing_target_instance()
  982. if existing_target_instance:
  983. return existing_target_instance
  984. # No targetInstance exists for this instance. Create one.
  985. body = {'name': 'target-instance-{0}'.format(uuid.uuid4()),
  986. 'instance': self._gce_instance['selfLink']}
  987. try:
  988. response = (self._provider
  989. .gce_compute
  990. .targetInstances()
  991. .insert(project=self._provider.project_name,
  992. zone=self.zone_name,
  993. body=body)
  994. .execute())
  995. self._provider.wait_for_operation(response, zone=self.zone_name)
  996. except Exception as e:
  997. cb.log.warning('Exception while inserting a target instance: %s',
  998. e)
  999. return None
  1000. # The following method should find the target instance that we
  1001. # successfully created above.
  1002. return self._get_existing_target_instance()
  1003. def _redirect_existing_rule(self, ip, target_instance):
  1004. """
  1005. Redirect the forwarding rule of the given IP to the given Instance.
  1006. """
  1007. new_zone = (self._provider.parse_url(target_instance['zone'])
  1008. .parameters['zone'])
  1009. new_name = target_instance['name']
  1010. new_url = target_instance['selfLink']
  1011. try:
  1012. for rule in helpers.iter_all(
  1013. self._provider.gce_compute.forwardingRules(),
  1014. project=self._provider.project_name,
  1015. region=ip.region_name):
  1016. if rule['IPAddress'] != ip.public_ip:
  1017. continue
  1018. parsed_target_url = self._provider.parse_url(rule['target'])
  1019. old_zone = parsed_target_url.parameters['zone']
  1020. old_name = parsed_target_url.parameters['targetInstance']
  1021. if old_zone == new_zone and old_name == new_name:
  1022. return True
  1023. response = (self._provider
  1024. .gce_compute
  1025. .forwardingRules()
  1026. .setTarget(
  1027. project=self._provider.project_name,
  1028. region=ip.region_name,
  1029. forwardingRule=rule['name'],
  1030. body={'target': new_url})
  1031. .execute())
  1032. self._provider.wait_for_operation(response,
  1033. region=ip.region_name)
  1034. return True
  1035. except Exception as e:
  1036. cb.log.warning(
  1037. 'Exception while listing/changing forwarding rules: %s', e)
  1038. return False
  1039. def _forward(self, ip, target_instance):
  1040. """
  1041. Forward the traffic to a given IP to a given instance.
  1042. If there is already a forwarding rule for the IP, it is redirected;
  1043. otherwise, a new forwarding rule is created.
  1044. """
  1045. if self._redirect_existing_rule(ip, target_instance):
  1046. return True
  1047. body = {'name': 'forwarding-rule-{0}'.format(uuid.uuid4()),
  1048. 'IPAddress': ip.public_ip,
  1049. 'target': target_instance['selfLink']}
  1050. try:
  1051. response = (self._provider
  1052. .gce_compute
  1053. .forwardingRules()
  1054. .insert(project=self._provider.project_name,
  1055. region=ip.region_name,
  1056. body=body)
  1057. .execute())
  1058. self._provider.wait_for_operation(response, region=ip.region_name)
  1059. except Exception as e:
  1060. cb.log.warning('Exception while inserting a forwarding rule: %s',
  1061. e)
  1062. return False
  1063. return True
  1064. def _delete_existing_rule(self, ip, target_instance):
  1065. """
  1066. Stop forwarding traffic to an instance by deleting the forwarding rule.
  1067. """
  1068. zone = (self._provider.parse_url(target_instance['zone'])
  1069. .parameters['zone'])
  1070. name = target_instance['name']
  1071. try:
  1072. for rule in helpers.iter_all(
  1073. self._provider.gce_compute.forwardingRules(),
  1074. project=self._provider.project_name,
  1075. region=ip.region_name):
  1076. if rule['IPAddress'] != ip.public_ip:
  1077. continue
  1078. parsed_target_url = self._provider.parse_url(rule['target'])
  1079. temp_zone = parsed_target_url.parameters['zone']
  1080. temp_name = parsed_target_url.parameters['targetInstance']
  1081. if temp_zone != zone or temp_name != name:
  1082. cb.log.warning(
  1083. '"%s" is forwarded to "%s" in zone "%s"',
  1084. ip.public_ip, temp_name, temp_zone)
  1085. return False
  1086. response = (self._provider
  1087. .gce_compute
  1088. .forwardingRules()
  1089. .delete(
  1090. project=self._provider.project_name,
  1091. region=ip.region_name,
  1092. forwardingRule=rule['name'])
  1093. .execute())
  1094. self._provider.wait_for_operation(response,
  1095. region=ip.region_name)
  1096. return True
  1097. except Exception as e:
  1098. cb.log.warning(
  1099. 'Exception while listing/deleting forwarding rules: %s', e)
  1100. return False
  1101. return True
  1102. def add_floating_ip(self, floating_ip):
  1103. """
  1104. Add an elastic IP address to this instance.
  1105. """
  1106. fip = (floating_ip if isinstance(floating_ip, GCEFloatingIP)
  1107. else self.inet_gateway.floating_ips.get(floating_ip))
  1108. if fip.in_use:
  1109. if fip.private_ip not in self.private_ips:
  1110. cb.log.warning('Floating IP "%s" is not associated to "%s"',
  1111. fip.public_ip, self.name)
  1112. return
  1113. target_instance = self._get_target_instance()
  1114. if not target_instance:
  1115. cb.log.warning('Could not create a targetInstance for "%s"',
  1116. self.name)
  1117. return
  1118. if not self._forward(fip, target_instance):
  1119. cb.log.warning('Could not forward "%s" to "%s"',
  1120. fip.public_ip, target_instance['selfLink'])
  1121. def remove_floating_ip(self, floating_ip):
  1122. """
  1123. Remove a elastic IP address from this instance.
  1124. """
  1125. fip = (floating_ip if isinstance(floating_ip, GCEFloatingIP)
  1126. else self.inet_gateway.floating_ips.get(floating_ip))
  1127. if not fip.in_use or fip.private_ip not in self.private_ips:
  1128. cb.log.warning('Floating IP "%s" is not associated to "%s"',
  1129. fip.public_ip, self.name)
  1130. return
  1131. target_instance = self._get_target_instance()
  1132. if not target_instance:
  1133. # We should not be here.
  1134. cb.log.warning('Something went wrong! "%s" is associated to "%s" '
  1135. 'with no target instance', fip.public_ip, self.name)
  1136. return
  1137. if not self._delete_existing_rule(fip, target_instance):
  1138. cb.log.warning(
  1139. 'Could not remove floating IP "%s" from instance "%s"',
  1140. fip.public_ip, self.name)
  1141. @property
  1142. def state(self):
  1143. return GCEInstance.INSTANCE_STATE_MAP.get(
  1144. self._gce_instance['status'], InstanceState.UNKNOWN)
  1145. def refresh(self):
  1146. """
  1147. Refreshes the state of this instance by re-querying the cloud provider
  1148. for its latest state.
  1149. """
  1150. inst = self._provider.compute.instances.get(self.id)
  1151. if inst:
  1152. # pylint:disable=protected-access
  1153. self._gce_instance = inst._gce_instance
  1154. else:
  1155. # instance no longer exists
  1156. self._gce_instance['status'] = InstanceState.UNKNOWN
  1157. def add_vm_firewall(self, sg):
  1158. tag = sg.name if isinstance(sg, GCEVMFirewall) else sg
  1159. tags = self._gce_instance.get('tags', {}).get('items', [])
  1160. tags.append(tag)
  1161. self._set_tags(tags)
  1162. def remove_vm_firewall(self, sg):
  1163. tag = sg.name if isinstance(sg, GCEVMFirewall) else sg
  1164. tags = self._gce_instance.get('tags', {}).get('items', [])
  1165. if tag in tags:
  1166. tags.remove(tag)
  1167. self._set_tags(tags)
  1168. def _set_tags(self, tags):
  1169. # Refresh to make sure we are using the most recent tags fingerprint.
  1170. self.refresh()
  1171. fingerprint = self._gce_instance.get('tags', {}).get('fingerprint', '')
  1172. response = (self._provider
  1173. .gce_compute
  1174. .instances()
  1175. .setTags(project=self._provider.project_name,
  1176. zone=self.zone_name,
  1177. instance=self.name,
  1178. body={'items': tags,
  1179. 'fingerprint': fingerprint})
  1180. .execute())
  1181. self._provider.wait_for_operation(response, zone=self.zone_name)
  1182. class GCENetwork(BaseNetwork):
  1183. def __init__(self, provider, network):
  1184. super(GCENetwork, self).__init__(provider)
  1185. self._network = network
  1186. self._gateway_container = GCEGatewayContainer(provider, self)
  1187. @property
  1188. def resource_url(self):
  1189. return self._network['selfLink']
  1190. @property
  1191. def id(self):
  1192. return self._network['selfLink']
  1193. @property
  1194. def name(self):
  1195. return self._network['name']
  1196. @property
  1197. def label(self):
  1198. tag_name = "_".join(["network", self.name, "label"])
  1199. return helpers.get_metadata_item_value(self._provider, tag_name)
  1200. @label.setter
  1201. def label(self, value):
  1202. self.assert_valid_resource_label(value)
  1203. tag_name = "_".join(["network", self.name, "label"])
  1204. helpers.modify_or_add_metadata_item(self._provider, tag_name, value)
  1205. @property
  1206. def external(self):
  1207. """
  1208. All GCP networks can be connected to the Internet.
  1209. """
  1210. return True
  1211. @property
  1212. def state(self):
  1213. """
  1214. When a GCP network created by the CloudBridge API, we wait until the
  1215. network is ready.
  1216. """
  1217. if self._network.get('status') == NetworkState.UNKNOWN:
  1218. return NetworkState.UNKNOWN
  1219. return NetworkState.AVAILABLE
  1220. @property
  1221. def cidr_block(self):
  1222. if 'IPv4Range' in self._network:
  1223. # This is a legacy network.
  1224. return self._network['IPv4Range']
  1225. return GCENetwork.CB_DEFAULT_IPV4RANGE
  1226. @property
  1227. def subnets(self):
  1228. return list(self._provider.networking.subnets.iter(network=self))
  1229. def delete(self):
  1230. self._provider.networking.networks.delete(self)
  1231. def create_subnet(self, label, cidr_block, zone):
  1232. return self._provider.networking.subnets.create(
  1233. label, self, cidr_block, zone)
  1234. def refresh(self):
  1235. net = self._provider.networking.networks.get(self.id)
  1236. if net:
  1237. # pylint:disable=protected-access
  1238. self._network = net._network
  1239. else:
  1240. # network no longer exists
  1241. self._network['status'] = NetworkState.UNKNOWN
  1242. @property
  1243. def gateways(self):
  1244. return self._gateway_container
  1245. class GCEFloatingIPContainer(BaseFloatingIPContainer):
  1246. def __init__(self, provider, gateway):
  1247. super(GCEFloatingIPContainer, self).__init__(provider, gateway)
  1248. def get(self, floating_ip_id):
  1249. fip = self._provider.get_resource('addresses', floating_ip_id)
  1250. return (GCEFloatingIP(self._provider, self.gateway, fip)
  1251. if fip else None)
  1252. def list(self, limit=None, marker=None):
  1253. max_result = limit if limit is not None and limit < 500 else 500
  1254. response = (self._provider
  1255. .gce_compute
  1256. .addresses()
  1257. .list(project=self._provider.project_name,
  1258. region=self._provider.region_name,
  1259. maxResults=max_result,
  1260. pageToken=marker)
  1261. .execute())
  1262. ips = [GCEFloatingIP(self._provider, self.gateway, ip)
  1263. for ip in response.get('items', [])]
  1264. if len(ips) > max_result:
  1265. cb.log.warning('Expected at most %d results; got %d',
  1266. max_result, len(ips))
  1267. return ServerPagedResultList('nextPageToken' in response,
  1268. response.get('nextPageToken'),
  1269. False, data=ips)
  1270. def create(self):
  1271. region_name = self._provider.region_name
  1272. ip_name = 'ip-{0}'.format(uuid.uuid4())
  1273. response = (self._provider
  1274. .gce_compute
  1275. .addresses()
  1276. .insert(project=self._provider.project_name,
  1277. region=region_name,
  1278. body={'name': ip_name})
  1279. .execute())
  1280. self._provider.wait_for_operation(response, region=region_name)
  1281. return self.get(ip_name)
  1282. class GCEFloatingIP(BaseFloatingIP):
  1283. _DEAD_INSTANCE = 'dead instance'
  1284. def __init__(self, provider, gateway, floating_ip):
  1285. super(GCEFloatingIP, self).__init__(provider)
  1286. self._gateway = gateway
  1287. self._ip = floating_ip
  1288. self._process_ip_users()
  1289. @property
  1290. def id(self):
  1291. return self._ip['selfLink']
  1292. @property
  1293. def region_name(self):
  1294. # We use regional IPs to simulate floating IPs not global IPs because
  1295. # global IPs can be forwarded only to load balancing resources, not to
  1296. # a specific instance. Find out the region to which the IP belongs.
  1297. url = self._provider.parse_url(self._ip['region'])
  1298. return url.parameters['region']
  1299. @property
  1300. def public_ip(self):
  1301. return self._ip.get('address')
  1302. @property
  1303. def private_ip(self):
  1304. if (not self._target_instance or
  1305. self._target_instance == GCEFloatingIP._DEAD_INSTANCE):
  1306. return None
  1307. return self._target_instance['networkInterfaces'][0]['networkIP']
  1308. @property
  1309. def in_use(self):
  1310. return True if self._target_instance else False
  1311. def delete(self):
  1312. project_name = self._provider.project_name
  1313. # First, delete the forwarding rule, if there is any.
  1314. if self._rule:
  1315. response = (self._provider
  1316. .gce_compute
  1317. .forwardingRules()
  1318. .delete(project=project_name,
  1319. region=self.region_name,
  1320. forwardingRule=self._rule['name'])
  1321. .execute())
  1322. self._provider.wait_for_operation(response,
  1323. region=self.region_name)
  1324. # Release the address.
  1325. response = (self._provider
  1326. .gce_compute
  1327. .addresses()
  1328. .delete(project=project_name,
  1329. region=self.region_name,
  1330. address=self._ip['name'])
  1331. .execute())
  1332. self._provider.wait_for_operation(response, region=self.region_name)
  1333. def refresh(self):
  1334. fip = self._gateway.floating_ips.get(self.id)
  1335. # pylint:disable=protected-access
  1336. self._ip = fip._ip
  1337. self._process_ip_users()
  1338. def _process_ip_users(self):
  1339. self._rule = None
  1340. self._target_instance = None
  1341. if 'users' in self._ip and len(self._ip['users']) > 0:
  1342. provider = self._provider
  1343. if len(self._ip['users']) > 1:
  1344. cb.log.warning('Address "%s" in use by more than one resource',
  1345. self._ip.get('address'))
  1346. resource_parsed_url = provider.parse_url(self._ip['users'][0])
  1347. resource = resource_parsed_url.get_resource()
  1348. if resource['kind'] == 'compute#forwardingRule':
  1349. self._rule = resource
  1350. target = provider.parse_url(resource['target']).get_resource()
  1351. if target['kind'] == 'compute#targetInstance':
  1352. url = provider.parse_url(target['instance'])
  1353. try:
  1354. self._target_instance = url.get_resource()
  1355. except googleapiclient.errors.HttpError:
  1356. self._target_instance = GCEFloatingIP._DEAD_INSTANCE
  1357. else:
  1358. cb.log.warning('Address "%s" is forwarded to a %s',
  1359. self._ip.get('address'), target['kind'])
  1360. else:
  1361. cb.log.warning('Address "%s" in use by a %s',
  1362. self._ip.get('address'), resource['kind'])
  1363. class GCERouter(BaseRouter):
  1364. def __init__(self, provider, router):
  1365. super(GCERouter, self).__init__(provider)
  1366. self._router = router
  1367. @property
  1368. def id(self):
  1369. return self._router['selfLink']
  1370. @property
  1371. def name(self):
  1372. return self._router['name']
  1373. @property
  1374. def label(self):
  1375. tag_name = "_".join(["router", self.name, "label"])
  1376. return helpers.get_metadata_item_value(self._provider, tag_name)
  1377. @label.setter
  1378. def label(self, value):
  1379. self.assert_valid_resource_label(value)
  1380. tag_name = "_".join(["router", self.name, "label"])
  1381. helpers.modify_or_add_metadata_item(self._provider, tag_name, value)
  1382. @property
  1383. def region_name(self):
  1384. parsed_url = self._provider.parse_url(self.id)
  1385. return parsed_url.parameters['region']
  1386. def refresh(self):
  1387. router = self._provider.networking.routers.get(self.id)
  1388. if router:
  1389. # pylint:disable=protected-access
  1390. self._router = router._router
  1391. else:
  1392. # router no longer exists
  1393. self._router['status'] = RouterState.UNKNOWN
  1394. @property
  1395. def state(self):
  1396. # If the router info is refreshed after it is deleted, its status will
  1397. # be UNKNOWN.
  1398. if self._router.get('status') == RouterState.UNKNOWN:
  1399. return RouterState.UNKNOWN
  1400. # GCE routers are always attached to a network.
  1401. return RouterState.ATTACHED
  1402. @property
  1403. def network_id(self):
  1404. parsed_url = self._provider.parse_url(self._router['network'])
  1405. network = parsed_url.get_resource()
  1406. return network['selfLink']
  1407. @property
  1408. def subnets(self):
  1409. network = self._provider.networking.networks.get(self.network_id)
  1410. return network.subnets
  1411. def delete(self):
  1412. operation = (self._provider
  1413. .gce_compute
  1414. .routers()
  1415. .delete(project=self._provider.project_name,
  1416. region=self.region_name,
  1417. router=self.name)
  1418. .execute())
  1419. self._provider.wait_for_operation(operation, region=self.region_name)
  1420. # Remove label
  1421. tag_name = "_".join(["router", self.name, "label"])
  1422. if not helpers.remove_metadata_item(self._provider, tag_name):
  1423. log.warning('No label was found associated with this router '
  1424. '"{}" when deleted.'.format(self.name))
  1425. def attach_subnet(self, subnet):
  1426. if not isinstance(subnet, GCESubnet):
  1427. subnet = self._provider.networking.subnets.get(subnet)
  1428. if subnet.network_id == self.network_id:
  1429. return
  1430. cb.log.warning('Google Cloud Routers automatically learn new subnets '
  1431. 'in your VPC network and announces them to your '
  1432. 'on-premises network')
  1433. def detach_subnet(self, network_id):
  1434. cb.log.warning('Cannot detach from subnet. Google Cloud Routers '
  1435. 'automatically learn new subnets in your VPC network '
  1436. 'and announces them to your on-premises network')
  1437. def attach_gateway(self, gateway):
  1438. pass
  1439. def detach_gateway(self, gateway):
  1440. pass
  1441. class GCEGatewayContainer(BaseGatewayContainer):
  1442. _DEFAULT_GATEWAY_NAME = 'default-internet-gateway'
  1443. _GATEWAY_URL_PREFIX = 'global/gateways/'
  1444. def __init__(self, provider, network):
  1445. super(GCEGatewayContainer, self).__init__(provider, network)
  1446. self._default_internet_gateway = GCEInternetGateway(
  1447. provider,
  1448. {'id': (GCEGatewayContainer._GATEWAY_URL_PREFIX +
  1449. GCEGatewayContainer._DEFAULT_GATEWAY_NAME),
  1450. 'name': GCEGatewayContainer._DEFAULT_GATEWAY_NAME})
  1451. def get_or_create_inet_gateway(self, name=None):
  1452. return self._default_internet_gateway
  1453. def delete(self, gateway):
  1454. pass
  1455. def list(self, limit=None, marker=None):
  1456. return ClientPagedResultList(self._provider,
  1457. [self._default_internet_gateway],
  1458. limit=limit, marker=marker)
  1459. class GCEInternetGateway(BaseInternetGateway):
  1460. def __init__(self, provider, gateway):
  1461. super(GCEInternetGateway, self).__init__(provider)
  1462. self._gateway = gateway
  1463. self._fip_container = GCEFloatingIPContainer(provider, self)
  1464. @property
  1465. def id(self):
  1466. return self._gateway['id']
  1467. @property
  1468. def name(self):
  1469. return self._gateway['name']
  1470. def refresh(self):
  1471. pass
  1472. @property
  1473. def state(self):
  1474. return GatewayState.AVAILABLE
  1475. @property
  1476. def network_id(self):
  1477. """
  1478. GCE internet gateways are not attached to a network.
  1479. """
  1480. return None
  1481. def delete(self):
  1482. pass
  1483. @property
  1484. def floating_ips(self):
  1485. return self._fip_container
  1486. class GCESubnet(BaseSubnet):
  1487. def __init__(self, provider, subnet):
  1488. super(GCESubnet, self).__init__(provider)
  1489. self._subnet = subnet
  1490. @property
  1491. def id(self):
  1492. return self._subnet['selfLink']
  1493. @property
  1494. def name(self):
  1495. return self._subnet['name']
  1496. @property
  1497. def label(self):
  1498. tag_name = "_".join(["subnet", self.name, "label"])
  1499. return helpers.get_metadata_item_value(self._provider, tag_name)
  1500. @label.setter
  1501. def label(self, value):
  1502. self.assert_valid_resource_label(value)
  1503. tag_name = "_".join(["subnet", self.name, "label"])
  1504. helpers.modify_or_add_metadata_item(self._provider, tag_name, value)
  1505. @property
  1506. def cidr_block(self):
  1507. return self._subnet['ipCidrRange']
  1508. @property
  1509. def network_url(self):
  1510. return self._subnet['network']
  1511. @property
  1512. def network_id(self):
  1513. return self.network_url
  1514. @property
  1515. def region(self):
  1516. return self._subnet['region']
  1517. @property
  1518. def region_name(self):
  1519. parsed_url = self._provider.parse_url(self.id)
  1520. return parsed_url.parameters['region']
  1521. @property
  1522. def zone(self):
  1523. return None
  1524. def delete(self):
  1525. return self._provider.networking.subnets.delete(self)
  1526. @property
  1527. def state(self):
  1528. if self._subnet.get('status') == SubnetState.UNKNOWN:
  1529. return SubnetState.UNKNOWN
  1530. return SubnetState.AVAILABLE
  1531. def refresh(self):
  1532. subnet = self._provider.networking.subnets.get(self.id)
  1533. if subnet:
  1534. # pylint:disable=protected-access
  1535. self._subnet = subnet._subnet
  1536. else:
  1537. # subnet no longer exists
  1538. self._subnet['status'] = SubnetState.UNKNOWN
  1539. class GCEVolume(BaseVolume):
  1540. VOLUME_STATE_MAP = {
  1541. 'CREATING': VolumeState.CONFIGURING,
  1542. 'FAILED': VolumeState.ERROR,
  1543. 'READY': VolumeState.AVAILABLE,
  1544. 'RESTORING': VolumeState.CONFIGURING,
  1545. }
  1546. def __init__(self, provider, volume):
  1547. super(GCEVolume, self).__init__(provider)
  1548. self._volume = volume
  1549. @property
  1550. def id(self):
  1551. return self._volume.get('selfLink')
  1552. @property
  1553. def name(self):
  1554. """
  1555. Get the volume name.
  1556. """
  1557. return self._volume.get('name')
  1558. @property
  1559. def label(self):
  1560. labels = self._volume.get('labels')
  1561. return labels.get('cblabel', '') if labels else ''
  1562. @label.setter
  1563. def label(self, value):
  1564. req = (self._provider
  1565. .gce_compute
  1566. .disks()
  1567. .setLabels(project=self._provider.project_name,
  1568. zone=self.zone_name,
  1569. resource=self.name,
  1570. body={}))
  1571. helpers.change_label(self, 'cblabel', value, '_volume', req)
  1572. @property
  1573. def description(self):
  1574. labels = self._volume.get('labels')
  1575. if labels and 'description' in labels:
  1576. return labels.get('description', '')
  1577. return self._volume.get('description', '')
  1578. @description.setter
  1579. def description(self, value):
  1580. req = (self._provider
  1581. .gce_compute
  1582. .disks()
  1583. .setLabels(project=self._provider.project_name,
  1584. zone=self.zone_name,
  1585. resource=self.name,
  1586. body={}))
  1587. helpers.change_label(self, 'description', value, '_volume', req)
  1588. @property
  1589. def size(self):
  1590. return int(self._volume.get('sizeGb'))
  1591. @property
  1592. def create_time(self):
  1593. return self._volume.get('creationTimestamp')
  1594. @property
  1595. def zone_id(self):
  1596. return self._volume.get('zone')
  1597. @property
  1598. def zone_name(self):
  1599. return self._provider.parse_url(self.zone_id).parameters['zone']
  1600. @property
  1601. def source(self):
  1602. if 'sourceSnapshot' in self._volume:
  1603. snapshot_uri = self._volume.get('sourceSnapshot')
  1604. return GCESnapshot(
  1605. self._provider,
  1606. self._provider.parse_url(snapshot_uri).get_resource())
  1607. if 'sourceImage' in self._volume:
  1608. image_uri = self._volume.get('sourceImage')
  1609. return GCEMachineImage(
  1610. self._provider,
  1611. self._provider.parse_url(image_uri).get_resource())
  1612. return None
  1613. @property
  1614. def attachments(self):
  1615. # GCE Persistent Disk supports multiple instances attaching a READ-ONLY
  1616. # disk. In cloudbridge, volume usage pattern is that a disk is attached
  1617. # to a single instance in a read-write mode. Therefore, we only check
  1618. # the first user of a disk.
  1619. if 'users' in self._volume and len(self._volume) > 0:
  1620. if len(self._volume) > 1:
  1621. cb.log.warning("This volume is attached to multiple instances")
  1622. return BaseAttachmentInfo(self,
  1623. self._volume.get('users')[0],
  1624. None)
  1625. else:
  1626. return None
  1627. def attach(self, instance, device):
  1628. """
  1629. Attach this volume to an instance.
  1630. instance: The ID of an instance or an ``Instance`` object to
  1631. which this volume will be attached.
  1632. To use the disk, the user needs to mount the disk so that the operating
  1633. system can use the available storage space.
  1634. https://cloud.google.com/compute/docs/disks/add-persistent-disk
  1635. """
  1636. attach_disk_body = {
  1637. "source": self.id,
  1638. "deviceName": device.split('/')[-1],
  1639. }
  1640. if not isinstance(instance, GCEInstance):
  1641. instance = self._provider.get_resource('instances', instance)
  1642. (self._provider
  1643. .gce_compute
  1644. .instances()
  1645. .attachDisk(project=self._provider.project_name,
  1646. zone=instance.zone_name,
  1647. instance=instance.name,
  1648. body=attach_disk_body)
  1649. .execute())
  1650. def detach(self, force=False):
  1651. """
  1652. Detach this volume from an instance.
  1653. """
  1654. # Check whether this volume is attached to an instance.
  1655. if not self.attachments:
  1656. return
  1657. parsed_uri = self._provider.parse_url(self.attachments.instance_id)
  1658. instance_data = parsed_uri.get_resource()
  1659. # Check whether the instance has this volume attached.
  1660. if 'disks' not in instance_data:
  1661. return
  1662. device_name = None
  1663. for disk in instance_data['disks']:
  1664. if ('source' in disk and 'deviceName' in disk and
  1665. disk['source'] == self.id):
  1666. device_name = disk['deviceName']
  1667. if not device_name:
  1668. return
  1669. (self._provider
  1670. .gce_compute
  1671. .instances()
  1672. .detachDisk(project=self._provider.project_name,
  1673. zone=self.zone_name,
  1674. instance=instance_data.get('name'),
  1675. deviceName=device_name)
  1676. .execute())
  1677. def create_snapshot(self, label, description=None):
  1678. """
  1679. Create a snapshot of this Volume.
  1680. """
  1681. return self._provider.storage.snapshots.create(
  1682. label, self, description)
  1683. @property
  1684. def state(self):
  1685. if len(self._volume.get('users', [])) > 0:
  1686. return VolumeState.IN_USE
  1687. return GCEVolume.VOLUME_STATE_MAP.get(
  1688. self._volume.get('status'), VolumeState.UNKNOWN)
  1689. def refresh(self):
  1690. """
  1691. Refreshes the state of this volume by re-querying the cloud provider
  1692. for its latest state.
  1693. """
  1694. vol = self._provider.storage.volumes.get(self.id)
  1695. if vol:
  1696. # pylint:disable=protected-access
  1697. self._volume = vol._volume
  1698. else:
  1699. # volume no longer exists
  1700. self._volume['status'] = VolumeState.UNKNOWN
  1701. class GCESnapshot(BaseSnapshot):
  1702. SNAPSHOT_STATE_MAP = {
  1703. 'PENDING': SnapshotState.PENDING,
  1704. 'READY': SnapshotState.AVAILABLE,
  1705. }
  1706. def __init__(self, provider, snapshot):
  1707. super(GCESnapshot, self).__init__(provider)
  1708. self._snapshot = snapshot
  1709. @property
  1710. def id(self):
  1711. return self._snapshot.get('selfLink')
  1712. @property
  1713. def name(self):
  1714. """
  1715. Get the snapshot name.
  1716. """
  1717. return self._snapshot.get('name')
  1718. @property
  1719. def label(self):
  1720. labels = self._snapshot.get('labels')
  1721. return labels.get('cblabel', '') if labels else ''
  1722. @label.setter
  1723. # pylint:disable=arguments-differ
  1724. def label(self, value):
  1725. req = (self._provider
  1726. .gce_compute
  1727. .snapshots()
  1728. .setLabels(project=self._provider.project_name,
  1729. resource=self.name,
  1730. body={}))
  1731. helpers.change_label(self, 'cblabel', value, '_snapshot', req)
  1732. @property
  1733. def description(self):
  1734. labels = self._snapshot.get('labels')
  1735. if labels and 'description' in labels:
  1736. return labels.get('description', '')
  1737. return self._snapshot.get('description', '')
  1738. @description.setter
  1739. def description(self, value):
  1740. req = (self._provider
  1741. .gce_compute
  1742. .snapshots()
  1743. .setLabels(project=self._provider.project_name,
  1744. resource=self.name,
  1745. body={}))
  1746. helpers.change_label(self, 'description', value, '_snapshot', req)
  1747. @property
  1748. def size(self):
  1749. return int(self._snapshot.get('diskSizeGb'))
  1750. @property
  1751. def volume_id(self):
  1752. return self._snapshot.get('sourceDisk')
  1753. @property
  1754. def create_time(self):
  1755. return self._snapshot.get('creationTimestamp')
  1756. @property
  1757. def state(self):
  1758. return GCESnapshot.SNAPSHOT_STATE_MAP.get(
  1759. self._snapshot.get('status'), SnapshotState.UNKNOWN)
  1760. def refresh(self):
  1761. """
  1762. Refreshes the state of this snapshot by re-querying the cloud provider
  1763. for its latest state.
  1764. """
  1765. snap = self._provider.storage.snapshots.get(self.id)
  1766. if snap:
  1767. # pylint:disable=protected-access
  1768. self._snapshot = snap._snapshot
  1769. else:
  1770. # snapshot no longer exists
  1771. self._snapshot['status'] = SnapshotState.UNKNOWN
  1772. def create_volume(self, placement, size=None, volume_type=None, iops=None):
  1773. """
  1774. Create a new Volume from this Snapshot.
  1775. Args:
  1776. placement: GCE zone name, e.g. 'us-central1-f'.
  1777. size: The size of the new volume, in GiB (optional). Defaults to
  1778. the size of the snapshot.
  1779. volume_type: Type of persistent disk. Either 'pd-standard' or
  1780. 'pd-ssd'.
  1781. iops: Not supported by GCE.
  1782. """
  1783. zone_name = placement
  1784. if isinstance(placement, GCEPlacementZone):
  1785. zone_name = placement.name
  1786. vol_type = 'zones/{0}/diskTypes/{1}'.format(
  1787. zone_name,
  1788. 'pd-standard' if (volume_type != 'pd-standard' or
  1789. volume_type != 'pd-ssd') else volume_type)
  1790. disk_body = {
  1791. 'name': ('created-from-{0}'.format(self.name))[:63],
  1792. 'sizeGb': size if size is not None else self.size,
  1793. 'type': vol_type,
  1794. 'sourceSnapshot': self.id
  1795. }
  1796. operation = (self._provider
  1797. .gce_compute
  1798. .disks()
  1799. .insert(project=self._provider.project_name,
  1800. zone=zone_name,
  1801. body=disk_body)
  1802. .execute())
  1803. return self._provider.storage.volumes.get(
  1804. operation.get('targetLink'))
  1805. class GCSObject(BaseBucketObject):
  1806. def __init__(self, provider, bucket, obj):
  1807. super(GCSObject, self).__init__(provider)
  1808. self._bucket = bucket
  1809. self._obj = obj
  1810. @property
  1811. def id(self):
  1812. return self._obj['selfLink']
  1813. @property
  1814. def name(self):
  1815. return self._obj['name']
  1816. @property
  1817. def size(self):
  1818. return int(self._obj['size'])
  1819. @property
  1820. def last_modified(self):
  1821. return self._obj['updated']
  1822. def iter_content(self):
  1823. return io.BytesIO(self._provider
  1824. .gcs_storage
  1825. .objects()
  1826. .get_media(bucket=self._obj['bucket'],
  1827. object=self.name)
  1828. .execute())
  1829. def upload(self, data):
  1830. """
  1831. Set the contents of this object to the given text.
  1832. """
  1833. if type(data) is str:
  1834. data = data.encode()
  1835. media_body = googleapiclient.http.MediaIoBaseUpload(
  1836. io.BytesIO(data), mimetype='plain/text')
  1837. response = (self._provider
  1838. .storage.bucket_objects
  1839. ._create_object_with_media_body(self._bucket,
  1840. self.name,
  1841. media_body))
  1842. if response:
  1843. self._obj = response
  1844. def upload_from_file(self, path):
  1845. """
  1846. Upload a binary file.
  1847. """
  1848. with open(path, 'rb') as f:
  1849. media_body = googleapiclient.http.MediaIoBaseUpload(
  1850. f, 'application/octet-stream')
  1851. response = (self._provider
  1852. .storage.bucket_objects
  1853. ._create_object_with_media_body(self._bucket,
  1854. self.name,
  1855. media_body))
  1856. if response:
  1857. self._obj = response
  1858. def delete(self):
  1859. (self._provider
  1860. .gcs_storage
  1861. .objects()
  1862. .delete(bucket=self._obj['bucket'], object=self.name)
  1863. .execute())
  1864. def generate_url(self, expires_in):
  1865. """
  1866. Generates a signed URL accessible to everyone.
  1867. """
  1868. expiration = calendar.timegm(time.gmtime()) + expires_in
  1869. signed_signature = self._provider.sign_blob(
  1870. 'GET\n\n\n%d\n/%s/%s' %
  1871. (expiration, self._obj['bucket'], self.name))
  1872. encoded_signature = base64.b64encode(signed_signature).decode("utf-8")
  1873. url_encoded_signature = (encoded_signature.replace('+', '%2B')
  1874. .replace('/', '%2F'))
  1875. return ('https://storage.googleapis.com/%s/%s?GoogleAccessId=%s'
  1876. '&Expires=%d&Signature=%s' % (self._obj['bucket'], self.name,
  1877. self._provider.client_id,
  1878. expiration,
  1879. url_encoded_signature))
  1880. def refresh(self):
  1881. # pylint:disable=protected-access
  1882. self._obj = self.bucket.objects.get(self.id)._obj
  1883. class GCSBucketContainer(BaseBucketContainer):
  1884. def __init__(self, provider, bucket):
  1885. super(GCSBucketContainer, self).__init__(provider, bucket)
  1886. class GCSBucket(BaseBucket):
  1887. def __init__(self, provider, bucket):
  1888. super(GCSBucket, self).__init__(provider)
  1889. self._bucket = bucket
  1890. self._object_container = GCSBucketContainer(provider, self)
  1891. @property
  1892. def id(self):
  1893. return self._bucket['selfLink']
  1894. @property
  1895. def name(self):
  1896. """
  1897. Get this bucket's name.
  1898. """
  1899. return self._bucket['name']
  1900. @property
  1901. def objects(self):
  1902. return self._object_container
  1903. class GCELaunchConfig(BaseLaunchConfig):
  1904. def __init__(self, provider):
  1905. super(GCELaunchConfig, self).__init__(provider)