resources.py 84 KB

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