resources.py 78 KB

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