resources.py 85 KB

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