services.py 67 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604
  1. import io
  2. import ipaddress
  3. import json
  4. import logging
  5. import time
  6. import uuid
  7. import googleapiclient
  8. from cloudbridge.cloud.base import helpers as cb_helpers
  9. from cloudbridge.cloud.base.middleware import dispatch
  10. from cloudbridge.cloud.base.resources import ClientPagedResultList
  11. from cloudbridge.cloud.base.resources import ServerPagedResultList
  12. from cloudbridge.cloud.base.services import BaseBucketObjectService
  13. from cloudbridge.cloud.base.services import BaseBucketService
  14. from cloudbridge.cloud.base.services import BaseComputeService
  15. from cloudbridge.cloud.base.services import BaseFloatingIPService
  16. from cloudbridge.cloud.base.services import BaseGatewayService
  17. from cloudbridge.cloud.base.services import BaseImageService
  18. from cloudbridge.cloud.base.services import BaseInstanceService
  19. from cloudbridge.cloud.base.services import BaseKeyPairService
  20. from cloudbridge.cloud.base.services import BaseNetworkService
  21. from cloudbridge.cloud.base.services import BaseNetworkingService
  22. from cloudbridge.cloud.base.services import BaseRegionService
  23. from cloudbridge.cloud.base.services import BaseRouterService
  24. from cloudbridge.cloud.base.services import BaseSecurityService
  25. from cloudbridge.cloud.base.services import BaseSnapshotService
  26. from cloudbridge.cloud.base.services import BaseStorageService
  27. from cloudbridge.cloud.base.services import BaseSubnetService
  28. from cloudbridge.cloud.base.services import BaseVMFirewallRuleService
  29. from cloudbridge.cloud.base.services import BaseVMFirewallService
  30. from cloudbridge.cloud.base.services import BaseVMTypeService
  31. from cloudbridge.cloud.base.services import BaseVolumeService
  32. from cloudbridge.cloud.interfaces.exceptions import DuplicateResourceException
  33. from cloudbridge.cloud.interfaces.exceptions import InvalidParamException
  34. from cloudbridge.cloud.interfaces.resources import TrafficDirection
  35. from cloudbridge.cloud.interfaces.resources import VMFirewall
  36. from cloudbridge.cloud.providers.gce import helpers
  37. from .resources import GCEFirewallsDelegate
  38. from .resources import GCEFloatingIP
  39. from .resources import GCEInstance
  40. from .resources import GCEInternetGateway
  41. from .resources import GCEKeyPair
  42. from .resources import GCELaunchConfig
  43. from .resources import GCEMachineImage
  44. from .resources import GCENetwork
  45. from .resources import GCEPlacementZone
  46. from .resources import GCERegion
  47. from .resources import GCERouter
  48. from .resources import GCESnapshot
  49. from .resources import GCESubnet
  50. from .resources import GCEVMFirewall
  51. from .resources import GCEVMFirewallRule
  52. from .resources import GCEVMType
  53. from .resources import GCEVolume
  54. from .resources import GCSBucket
  55. from .resources import GCSObject
  56. log = logging.getLogger(__name__)
  57. class GCESecurityService(BaseSecurityService):
  58. def __init__(self, provider):
  59. super(GCESecurityService, self).__init__(provider)
  60. # Initialize provider services
  61. self._key_pairs = GCEKeyPairService(provider)
  62. self._vm_firewalls = GCEVMFirewallService(provider)
  63. self._vm_firewall_rule_svc = GCEVMFirewallRuleService(provider)
  64. class GCEKeyPairService(BaseKeyPairService):
  65. def __init__(self, provider):
  66. super(GCEKeyPairService, self).__init__(provider)
  67. @dispatch(event="provider.security.key_pairs.get",
  68. priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
  69. def get(self, key_pair_id):
  70. """
  71. Returns a KeyPair given its ID.
  72. """
  73. for kp in self:
  74. if kp.id == key_pair_id:
  75. return kp
  76. else:
  77. return None
  78. @dispatch(event="provider.security.key_pairs.list",
  79. priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
  80. def list(self, limit=None, marker=None):
  81. key_pairs = []
  82. for item in helpers.find_matching_metadata_items(
  83. self.provider, GCEKeyPair.KP_TAG_REGEX):
  84. metadata_value = json.loads(item['value'])
  85. kp_info = GCEKeyPair.GCEKeyInfo(**metadata_value)
  86. key_pairs.append(GCEKeyPair(self.provider, kp_info))
  87. return ClientPagedResultList(self.provider, key_pairs,
  88. limit=limit, marker=marker)
  89. @dispatch(event="provider.security.key_pairs.find",
  90. priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
  91. def find(self, **kwargs):
  92. """
  93. Searches for a key pair by a given list of attributes.
  94. """
  95. obj_list = self
  96. filters = ['id', 'name']
  97. matches = cb_helpers.generic_find(filters, kwargs, obj_list)
  98. # All kwargs should have been popped at this time.
  99. if len(kwargs) > 0:
  100. raise InvalidParamException(
  101. "Unrecognised parameters for search: %s. Supported "
  102. "attributes: %s" % (kwargs, ", ".join(filters)))
  103. return ClientPagedResultList(self.provider,
  104. matches if matches else [])
  105. @dispatch(event="provider.security.key_pairs.create",
  106. priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
  107. def create(self, name, public_key_material=None):
  108. GCEKeyPair.assert_valid_resource_name(name)
  109. private_key = None
  110. if not public_key_material:
  111. public_key_material, private_key = cb_helpers.generate_key_pair()
  112. # TODO: Add support for other formats not assume ssh-rsa
  113. elif "ssh-rsa" not in public_key_material:
  114. public_key_material = "ssh-rsa {}".format(public_key_material)
  115. kp_info = GCEKeyPair.GCEKeyInfo(name, public_key_material)
  116. metadata_value = json.dumps(kp_info._asdict())
  117. try:
  118. helpers.add_metadata_item(self.provider,
  119. GCEKeyPair.KP_TAG_PREFIX + name,
  120. metadata_value)
  121. return GCEKeyPair(self.provider, kp_info, private_key)
  122. except googleapiclient.errors.HttpError as err:
  123. if err.resp.get('content-type', '').startswith('application/json'):
  124. message = (json.loads(err.content).get('error', {})
  125. .get('errors', [{}])[0].get('message'))
  126. if "duplicate keys" in message:
  127. raise DuplicateResourceException(
  128. 'A KeyPair with name {0} already exists'.format(name))
  129. raise
  130. @dispatch(event="provider.security.key_pairs.delete",
  131. priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
  132. def delete(self, key_pair):
  133. key_pair = (key_pair if isinstance(key_pair, GCEKeyPair) else
  134. self.get(key_pair))
  135. if key_pair:
  136. helpers.remove_metadata_item(
  137. self.provider, GCEKeyPair.KP_TAG_PREFIX + key_pair.name)
  138. class GCEVMFirewallService(BaseVMFirewallService):
  139. def __init__(self, provider):
  140. super(GCEVMFirewallService, self).__init__(provider)
  141. self._delegate = GCEFirewallsDelegate(provider)
  142. @dispatch(event="provider.security.vm_firewalls.get",
  143. priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
  144. def get(self, vm_firewall_id):
  145. tag, network_name = \
  146. self._delegate.get_tag_network_from_id(vm_firewall_id)
  147. if tag is None:
  148. return None
  149. network = self.provider.networking.networks.get(network_name)
  150. return GCEVMFirewall(self._delegate, tag, network)
  151. @dispatch(event="provider.security.vm_firewalls.list",
  152. priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
  153. def list(self, limit=None, marker=None):
  154. vm_firewalls = []
  155. for tag, network_name in self._delegate.tag_networks:
  156. network = self.provider.networking.networks.get(
  157. network_name)
  158. vm_firewall = GCEVMFirewall(self._delegate, tag, network)
  159. vm_firewalls.append(vm_firewall)
  160. return ClientPagedResultList(self.provider, vm_firewalls,
  161. limit=limit, marker=marker)
  162. @dispatch(event="provider.security.vm_firewalls.create",
  163. priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
  164. def create(self, label, network, description=None):
  165. GCEVMFirewall.assert_valid_resource_label(label)
  166. network = (network if isinstance(network, GCENetwork)
  167. else self.provider.networking.networks.get(network))
  168. fw = GCEVMFirewall(self._delegate, label, network, description)
  169. fw.label = label
  170. # This rule exists implicitly. Add it explicitly so that the firewall
  171. # is not empty and the rule is shown by list/get/find methods.
  172. self.provider.security._vm_firewall_rules.create_with_priority(
  173. fw, direction=TrafficDirection.OUTBOUND, protocol='tcp',
  174. priority=65534, cidr='0.0.0.0/0')
  175. return fw
  176. @dispatch(event="provider.security.vm_firewalls.delete",
  177. priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
  178. def delete(self, vm_firewall):
  179. fw_id = (vm_firewall.id if isinstance(vm_firewall, GCEVMFirewall)
  180. else vm_firewall)
  181. return self._delegate.delete_tag_network_with_id(fw_id)
  182. def find_by_network_and_tags(self, network_name, tags):
  183. """
  184. Finds non-empty VM firewalls by network name and VM firewall names
  185. (tags). If no matching VM firewall is found, an empty list is returned.
  186. """
  187. vm_firewalls = []
  188. for tag, net_name in self._delegate.tag_networks:
  189. if network_name != net_name:
  190. continue
  191. if tag not in tags:
  192. continue
  193. network = self.provider.networking.networks.get(net_name)
  194. vm_firewalls.append(
  195. GCEVMFirewall(self._delegate, tag, network))
  196. return vm_firewalls
  197. class GCEVMFirewallRuleService(BaseVMFirewallRuleService):
  198. def __init__(self, provider):
  199. super(GCEVMFirewallRuleService, self).__init__(provider)
  200. self._dummy_rule = None
  201. @dispatch(event="provider.security.vm_firewall_rules.list",
  202. priority=BaseVMFirewallRuleService.STANDARD_EVENT_PRIORITY)
  203. def list(self, firewall, limit=None, marker=None):
  204. rules = []
  205. for fw in firewall.delegate.iter_firewalls(
  206. firewall.name, firewall.network.name):
  207. rule = GCEVMFirewallRule(firewall, fw['id'])
  208. if rule.is_dummy_rule():
  209. self._dummy_rule = rule
  210. else:
  211. rules.append(rule)
  212. return ClientPagedResultList(self.provider, rules,
  213. limit=limit, marker=marker)
  214. @property
  215. def dummy_rule(self):
  216. if not self._dummy_rule:
  217. self.list()
  218. return self._dummy_rule
  219. @staticmethod
  220. def to_port_range(from_port, to_port):
  221. if from_port is not None and to_port is not None:
  222. return '%d-%d' % (from_port, to_port)
  223. elif from_port is not None:
  224. return from_port
  225. else:
  226. return to_port
  227. def create_with_priority(self, firewall, direction, protocol, priority,
  228. from_port=None, to_port=None, cidr=None,
  229. src_dest_fw=None):
  230. port = GCEVMFirewallRuleService.to_port_range(from_port, to_port)
  231. src_dest_tag = None
  232. src_dest_fw_id = None
  233. if src_dest_fw:
  234. src_dest_tag = src_dest_fw.name
  235. src_dest_fw_id = src_dest_fw.id
  236. if not firewall.delegate.add_firewall(
  237. firewall.name, direction, protocol, priority, port, cidr,
  238. src_dest_tag, firewall.description,
  239. firewall.network.name):
  240. return None
  241. rules = self.find(firewall, direction=direction, protocol=protocol,
  242. from_port=from_port, to_port=to_port, cidr=cidr,
  243. src_dest_fw_id=src_dest_fw_id)
  244. if len(rules) < 1:
  245. return None
  246. return rules[0]
  247. @dispatch(event="provider.security.vm_firewall_rules.create",
  248. priority=BaseVMFirewallRuleService.STANDARD_EVENT_PRIORITY)
  249. def create(self, firewall, direction, protocol, from_port=None,
  250. to_port=None, cidr=None, src_dest_fw=None):
  251. return self.create_with_priority(firewall, direction, protocol,
  252. 1000, from_port, to_port, cidr,
  253. src_dest_fw)
  254. @dispatch(event="provider.security.vm_firewall_rules.delete",
  255. priority=BaseVMFirewallRuleService.STANDARD_EVENT_PRIORITY)
  256. def delete(self, firewall, rule):
  257. rule = (rule if isinstance(rule, GCEVMFirewallRule)
  258. else self.get(firewall, rule))
  259. if rule.is_dummy_rule():
  260. return True
  261. firewall.delegate.delete_firewall_id(rule._rule)
  262. class GCEVMTypeService(BaseVMTypeService):
  263. def __init__(self, provider):
  264. super(GCEVMTypeService, self).__init__(provider)
  265. @property
  266. def instance_data(self):
  267. response = (self.provider
  268. .gce_compute
  269. .machineTypes()
  270. .list(project=self.provider.project_name,
  271. zone=self.provider.default_zone)
  272. .execute())
  273. return response['items']
  274. @dispatch(event="provider.compute.vm_types.get",
  275. priority=BaseVMTypeService.STANDARD_EVENT_PRIORITY)
  276. def get(self, vm_type_id):
  277. vm_type = self.provider.get_resource('machineTypes', vm_type_id)
  278. return GCEVMType(self.provider, vm_type) if vm_type else None
  279. @dispatch(event="provider.compute.vm_types.find",
  280. priority=BaseVMTypeService.STANDARD_EVENT_PRIORITY)
  281. def find(self, **kwargs):
  282. matched_inst_types = []
  283. for inst_type in self.instance_data:
  284. is_match = True
  285. for key, value in kwargs.items():
  286. if key not in inst_type:
  287. raise InvalidParamException(
  288. "Unrecognised parameters for search: %s." % key)
  289. if inst_type.get(key) != value:
  290. is_match = False
  291. break
  292. if is_match:
  293. matched_inst_types.append(
  294. GCEVMType(self.provider, inst_type))
  295. return matched_inst_types
  296. @dispatch(event="provider.compute.vm_types.list",
  297. priority=BaseVMTypeService.STANDARD_EVENT_PRIORITY)
  298. def list(self, limit=None, marker=None):
  299. inst_types = [GCEVMType(self.provider, inst_type)
  300. for inst_type in self.instance_data]
  301. return ClientPagedResultList(self.provider, inst_types,
  302. limit=limit, marker=marker)
  303. class GCERegionService(BaseRegionService):
  304. def __init__(self, provider):
  305. super(GCERegionService, self).__init__(provider)
  306. @dispatch(event="provider.compute.regions.get",
  307. priority=BaseRegionService.STANDARD_EVENT_PRIORITY)
  308. def get(self, region_id):
  309. region = self.provider.get_resource('regions', region_id,
  310. region=region_id)
  311. return GCERegion(self.provider, region) if region else None
  312. @dispatch(event="provider.compute.regions.list",
  313. priority=BaseRegionService.STANDARD_EVENT_PRIORITY)
  314. def list(self, limit=None, marker=None):
  315. max_result = limit if limit is not None and limit < 500 else 500
  316. regions_response = (self.provider
  317. .gce_compute
  318. .regions()
  319. .list(project=self.provider.project_name,
  320. maxResults=max_result,
  321. pageToken=marker)
  322. .execute())
  323. regions = [GCERegion(self.provider, region)
  324. for region in regions_response['items']]
  325. if len(regions) > max_result:
  326. log.warning('Expected at most %d results; got %d',
  327. max_result, len(regions))
  328. return ServerPagedResultList('nextPageToken' in regions_response,
  329. regions_response.get('nextPageToken'),
  330. False, data=regions)
  331. @property
  332. def current(self):
  333. return self.get(self.provider.region_name)
  334. class GCEImageService(BaseImageService):
  335. def __init__(self, provider):
  336. super(GCEImageService, self).__init__(provider)
  337. self._public_images = None
  338. _PUBLIC_IMAGE_PROJECTS = ['centos-cloud', 'coreos-cloud', 'debian-cloud',
  339. 'opensuse-cloud', 'ubuntu-os-cloud', 'cos-cloud']
  340. def _retrieve_public_images(self):
  341. if self._public_images is not None:
  342. return
  343. self._public_images = []
  344. for project in GCEImageService._PUBLIC_IMAGE_PROJECTS:
  345. for image in helpers.iter_all(
  346. self.provider.gce_compute.images(), project=project):
  347. self._public_images.append(
  348. GCEMachineImage(self.provider, image))
  349. def get(self, image_id):
  350. """
  351. Returns an Image given its id
  352. """
  353. image = self.provider.get_resource('images', image_id)
  354. if image:
  355. return GCEMachineImage(self.provider, image)
  356. self._retrieve_public_images()
  357. for public_image in self._public_images:
  358. if public_image.id == image_id or public_image.name == image_id:
  359. return public_image
  360. return None
  361. def find(self, limit=None, marker=None, **kwargs):
  362. """
  363. Searches for an image by a given list of attributes
  364. """
  365. label = kwargs.pop('label', None)
  366. # All kwargs should have been popped at this time.
  367. if len(kwargs) > 0:
  368. raise InvalidParamException(
  369. "Unrecognised parameters for search: %s. Supported "
  370. "attributes: %s" % (kwargs, 'label'))
  371. # Retrieve all available images by setting limit to sys.maxsize
  372. images = [image for image in self if image.label == label]
  373. return ClientPagedResultList(self.provider, images,
  374. limit=limit, marker=marker)
  375. def list(self, limit=None, marker=None):
  376. """
  377. List all images.
  378. """
  379. self._retrieve_public_images()
  380. images = []
  381. if (self.provider.project_name not in
  382. GCEImageService._PUBLIC_IMAGE_PROJECTS):
  383. for image in helpers.iter_all(
  384. self.provider.gce_compute.images(),
  385. project=self.provider.project_name):
  386. images.append(GCEMachineImage(self.provider, image))
  387. images.extend(self._public_images)
  388. return ClientPagedResultList(self.provider, images,
  389. limit=limit, marker=marker)
  390. class GCEInstanceService(BaseInstanceService):
  391. def __init__(self, provider):
  392. super(GCEInstanceService, self).__init__(provider)
  393. @dispatch(event="provider.compute.instances.create",
  394. priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
  395. def create(self, label, image, vm_type, subnet, zone=None,
  396. key_pair=None, vm_firewalls=None, user_data=None,
  397. launch_config=None, **kwargs):
  398. """
  399. Creates a new virtual machine instance.
  400. """
  401. GCEInstance.assert_valid_resource_name(label)
  402. zone_name = self.provider.default_zone
  403. if zone:
  404. if not isinstance(zone, GCEPlacementZone):
  405. zone = GCEPlacementZone(
  406. self.provider,
  407. self.provider.get_resource('zones', zone))
  408. zone_name = zone.name
  409. if not isinstance(vm_type, GCEVMType):
  410. vm_type = self.provider.compute.vm_types.get(vm_type)
  411. network_interface = {'accessConfigs': [{'type': 'ONE_TO_ONE_NAT',
  412. 'name': 'External NAT'}]}
  413. if subnet:
  414. network_interface['subnetwork'] = subnet.id
  415. else:
  416. network_interface['network'] = 'global/networks/default'
  417. num_roots = 0
  418. disks = []
  419. boot_disk = None
  420. if isinstance(launch_config, GCELaunchConfig):
  421. for disk in launch_config.block_devices:
  422. if not disk.source:
  423. volume_name = 'disk-{0}'.format(uuid.uuid4())
  424. volume_size = disk.size if disk.size else 1
  425. volume = self.provider.storage.volumes.create(
  426. volume_name, volume_size, zone)
  427. volume.wait_till_ready()
  428. source_field = 'source'
  429. source_value = volume.id
  430. elif isinstance(disk.source, GCEMachineImage):
  431. source_field = 'initializeParams'
  432. # Explicitly set diskName; otherwise, instance label will
  433. # be used by default which may collide with existing disks.
  434. source_value = {
  435. 'sourceImage': disk.source.id,
  436. 'diskName': 'image-disk-{0}'.format(uuid.uuid4()),
  437. 'diskSizeGb': disk.size if disk.size else 20}
  438. elif isinstance(disk.source, GCEVolume):
  439. source_field = 'source'
  440. source_value = disk.source.id
  441. elif isinstance(disk.source, GCESnapshot):
  442. volume = disk.source.create_volume(zone, size=disk.size)
  443. volume.wait_till_ready()
  444. source_field = 'source'
  445. source_value = volume.id
  446. else:
  447. log.warning('Unknown disk source')
  448. continue
  449. autoDelete = True
  450. if disk.delete_on_terminate is not None:
  451. autoDelete = disk.delete_on_terminate
  452. num_roots += 1 if disk.is_root else 0
  453. if disk.is_root and not boot_disk:
  454. boot_disk = {'boot': True,
  455. 'autoDelete': autoDelete,
  456. source_field: source_value}
  457. else:
  458. disks.append({'boot': False,
  459. 'autoDelete': autoDelete,
  460. source_field: source_value})
  461. if num_roots > 1:
  462. log.warning('The launch config contains %d boot disks. Will '
  463. 'use the first one', num_roots)
  464. if image:
  465. if boot_disk:
  466. log.warning('A boot image is given while the launch config '
  467. 'contains a boot disk, too. The launch config '
  468. 'will be used.')
  469. else:
  470. if not isinstance(image, GCEMachineImage):
  471. image = self.provider.compute.images.get(image)
  472. # Explicitly set diskName; otherwise, instance name will be
  473. # used by default which may conflict with existing disks.
  474. boot_disk = {
  475. 'boot': True,
  476. 'autoDelete': True,
  477. 'initializeParams': {
  478. 'sourceImage': image.id,
  479. 'diskName': 'image-disk-{0}'.format(uuid.uuid4())}}
  480. if not boot_disk:
  481. log.warning('No boot disk is given for instance %s.', label)
  482. return None
  483. # The boot disk must be the first disk attached to the instance.
  484. disks.insert(0, boot_disk)
  485. config = {
  486. 'name': GCEInstance._generate_name_from_label(label, 'cb-inst'),
  487. 'machineType': vm_type.resource_url,
  488. 'disks': disks,
  489. 'networkInterfaces': [network_interface]
  490. }
  491. if vm_firewalls and isinstance(vm_firewalls, list):
  492. vm_firewall_names = []
  493. if isinstance(vm_firewalls[0], VMFirewall):
  494. vm_firewall_names = [f.name for f in vm_firewalls]
  495. elif isinstance(vm_firewalls[0], str):
  496. vm_firewall_names = vm_firewalls
  497. if len(vm_firewall_names) > 0:
  498. config['tags'] = {}
  499. config['tags']['items'] = vm_firewall_names
  500. if user_data:
  501. entry = {'key': 'user-data', 'value': user_data}
  502. config['metadata'] = {'items': [entry]}
  503. if key_pair:
  504. if not isinstance(key_pair, GCEKeyPair):
  505. key_pair = self._provider.security.key_pairs.get(key_pair)
  506. if key_pair:
  507. kp = key_pair._key_pair
  508. kp_entry = {
  509. "key": "ssh-keys",
  510. # Format is not removed from public key portion
  511. "value": "{}:{} {}".format(
  512. self.provider.vm_default_user_name,
  513. kp.public_key,
  514. kp.name)
  515. }
  516. meta = config.get('metadata', {})
  517. if meta:
  518. items = meta.get('items', [])
  519. items.append(kp_entry)
  520. else:
  521. config['metadata'] = {'items': [kp_entry]}
  522. config['labels'] = {'cblabel': label}
  523. operation = (self.provider
  524. .gce_compute.instances()
  525. .insert(project=self.provider.project_name,
  526. zone=zone_name,
  527. body=config)
  528. .execute())
  529. instance_id = operation.get('targetLink')
  530. self.provider.wait_for_operation(operation, zone=zone_name)
  531. cb_inst = self.get(instance_id)
  532. return cb_inst
  533. @dispatch(event="provider.compute.instances.get",
  534. priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
  535. def get(self, instance_id):
  536. """
  537. Returns an instance given its name. Returns None
  538. if the object does not exist.
  539. A GCE instance is uniquely identified by its selfLink, which is used
  540. as its id.
  541. """
  542. instance = self.provider.get_resource('instances', instance_id)
  543. return GCEInstance(self.provider, instance) if instance else None
  544. @dispatch(event="provider.compute.instances.find",
  545. priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
  546. def find(self, limit=None, marker=None, **kwargs):
  547. """
  548. Searches for instances by instance label.
  549. :return: a list of Instance objects
  550. """
  551. label = kwargs.pop('label', None)
  552. # All kwargs should have been popped at this time.
  553. if len(kwargs) > 0:
  554. raise InvalidParamException(
  555. "Unrecognised parameters for search: %s. Supported "
  556. "attributes: %s" % (kwargs, 'label'))
  557. instances = [instance for instance in self.list()
  558. if instance.label == label]
  559. return ClientPagedResultList(self.provider, instances,
  560. limit=limit, marker=marker)
  561. @dispatch(event="provider.compute.instances.list",
  562. priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
  563. def list(self, limit=None, marker=None):
  564. """
  565. List all instances.
  566. """
  567. # For GCE API, Acceptable values are 0 to 500, inclusive.
  568. # (Default: 500).
  569. max_result = limit if limit is not None and limit < 500 else 500
  570. response = (self.provider
  571. .gce_compute
  572. .instances()
  573. .list(project=self.provider.project_name,
  574. zone=self.provider.default_zone,
  575. maxResults=max_result,
  576. pageToken=marker)
  577. .execute())
  578. instances = [GCEInstance(self.provider, inst)
  579. for inst in response.get('items', [])]
  580. if len(instances) > max_result:
  581. log.warning('Expected at most %d results; got %d',
  582. max_result, len(instances))
  583. return ServerPagedResultList('nextPageToken' in response,
  584. response.get('nextPageToken'),
  585. False, data=instances)
  586. @dispatch(event="provider.compute.instances.delete",
  587. priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
  588. def delete(self, instance):
  589. instance = (instance if isinstance(instance, GCEInstance) else
  590. self.get(instance))
  591. if instance:
  592. (self._provider
  593. .gce_compute
  594. .instances()
  595. .delete(project=self.provider.project_name,
  596. zone=instance.zone_name,
  597. instance=instance.name)
  598. .execute())
  599. def create_launch_config(self):
  600. return GCELaunchConfig(self.provider)
  601. class GCEComputeService(BaseComputeService):
  602. # TODO: implement GCEComputeService
  603. def __init__(self, provider):
  604. super(GCEComputeService, self).__init__(provider)
  605. self._instance_svc = GCEInstanceService(self.provider)
  606. self._vm_type_svc = GCEVMTypeService(self.provider)
  607. self._region_svc = GCERegionService(self.provider)
  608. self._images_svc = GCEImageService(self.provider)
  609. @property
  610. def images(self):
  611. return self._images_svc
  612. @property
  613. def vm_types(self):
  614. return self._vm_type_svc
  615. @property
  616. def instances(self):
  617. return self._instance_svc
  618. @property
  619. def regions(self):
  620. return self._region_svc
  621. class GCENetworkingService(BaseNetworkingService):
  622. def __init__(self, provider):
  623. super(GCENetworkingService, self).__init__(provider)
  624. self._network_service = GCENetworkService(self.provider)
  625. self._subnet_service = GCESubnetService(self.provider)
  626. self._router_service = GCERouterService(self.provider)
  627. self._gateway_service = GCEGatewayService(self.provider)
  628. self._floating_ip_service = GCEFloatingIPService(self.provider)
  629. class GCENetworkService(BaseNetworkService):
  630. def __init__(self, provider):
  631. super(GCENetworkService, self).__init__(provider)
  632. @dispatch(event="provider.networking.networks.get",
  633. priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
  634. def get(self, network_id):
  635. network = self.provider.get_resource('networks', network_id)
  636. return GCENetwork(self.provider, network) if network else None
  637. @dispatch(event="provider.networking.networks.find",
  638. priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
  639. def find(self, limit=None, marker=None, **kwargs):
  640. """
  641. GCE networks are global. There is at most one network with a given
  642. name.
  643. """
  644. obj_list = self
  645. filters = ['name', 'label']
  646. matches = cb_helpers.generic_find(filters, kwargs, obj_list)
  647. return ClientPagedResultList(self._provider, list(matches),
  648. limit=limit, marker=marker)
  649. @dispatch(event="provider.networking.networks.list",
  650. priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
  651. def list(self, limit=None, marker=None, filter=None):
  652. # TODO: Decide whether we keep filter in 'list'
  653. networks = []
  654. response = (self.provider
  655. .gce_compute
  656. .networks()
  657. .list(project=self.provider.project_name,
  658. filter=filter)
  659. .execute())
  660. for network in response.get('items', []):
  661. networks.append(GCENetwork(self.provider, network))
  662. return ClientPagedResultList(self.provider, networks,
  663. limit=limit, marker=marker)
  664. @dispatch(event="provider.networking.networks.create",
  665. priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
  666. def create(self, label, cidr_block):
  667. """
  668. Creates an auto mode VPC network with default subnets. It is possible
  669. to add additional subnets later.
  670. """
  671. GCENetwork.assert_valid_resource_label(label)
  672. name = GCENetwork._generate_name_from_label(label, 'cbnet')
  673. body = {'name': name}
  674. # This results in a custom mode network
  675. body['autoCreateSubnetworks'] = False
  676. response = (self.provider
  677. .gce_compute
  678. .networks()
  679. .insert(project=self.provider.project_name,
  680. body=body)
  681. .execute())
  682. self.provider.wait_for_operation(response)
  683. cb_net = self.get(name)
  684. cb_net.label = label
  685. return cb_net
  686. def get_or_create_default(self):
  687. default_nets = self.provider.networking.networks.find(
  688. label=GCENetwork.CB_DEFAULT_NETWORK_LABEL)
  689. if default_nets:
  690. return default_nets[0]
  691. else:
  692. log.info("Creating a CloudBridge-default network labeled %s",
  693. GCENetwork.CB_DEFAULT_NETWORK_LABEL)
  694. return self.create(
  695. label=GCENetwork.CB_DEFAULT_NETWORK_LABEL,
  696. cidr_block=GCENetwork.CB_DEFAULT_IPV4RANGE)
  697. @dispatch(event="provider.networking.networks.delete",
  698. priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
  699. def delete(self, network):
  700. # Accepts network object
  701. if isinstance(network, GCENetwork):
  702. name = network.name
  703. # Accepts both name and ID
  704. elif 'googleapis' in network:
  705. name = network.split('/')[-1]
  706. else:
  707. name = network
  708. response = (self.provider
  709. .gce_compute
  710. .networks()
  711. .delete(project=self.provider.project_name,
  712. network=name)
  713. .execute())
  714. self.provider.wait_for_operation(response)
  715. # Remove label
  716. tag_name = "_".join(["network", name, "label"])
  717. if not helpers.remove_metadata_item(self.provider, tag_name):
  718. log.warning('No label was found associated with this network '
  719. '"{}" when deleted.'.format(network))
  720. return True
  721. class GCERouterService(BaseRouterService):
  722. def __init__(self, provider):
  723. super(GCERouterService, self).__init__(provider)
  724. @dispatch(event="provider.networking.routers.get",
  725. priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
  726. def get(self, router_id):
  727. router = self.provider.get_resource(
  728. 'routers', router_id, region=self.provider.region_name)
  729. return GCERouter(self.provider, router) if router else None
  730. @dispatch(event="provider.networking.routers.find",
  731. priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
  732. def find(self, limit=None, marker=None, **kwargs):
  733. obj_list = self
  734. filters = ['name', 'label']
  735. matches = cb_helpers.generic_find(filters, kwargs, obj_list)
  736. return ClientPagedResultList(self._provider, list(matches),
  737. limit=limit, marker=marker)
  738. @dispatch(event="provider.networking.routers.list",
  739. priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
  740. def list(self, limit=None, marker=None):
  741. region = self.provider.region_name
  742. max_result = limit if limit is not None and limit < 500 else 500
  743. response = (self.provider
  744. .gce_compute
  745. .routers()
  746. .list(project=self.provider.project_name,
  747. region=region,
  748. maxResults=max_result,
  749. pageToken=marker)
  750. .execute())
  751. routers = []
  752. for router in response.get('items', []):
  753. routers.append(GCERouter(self.provider, router))
  754. if len(routers) > max_result:
  755. log.warning('Expected at most %d results; go %d',
  756. max_result, len(routers))
  757. return ServerPagedResultList('nextPageToken' in response,
  758. response.get('nextPageToken'),
  759. False, data=routers)
  760. @dispatch(event="provider.networking.routers.create",
  761. priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
  762. def create(self, label, network):
  763. log.debug("Creating GCE Router Service with params "
  764. "[label: %s network: %s]", label, network)
  765. GCERouter.assert_valid_resource_label(label)
  766. name = GCERouter._generate_name_from_label(label, 'cb-router')
  767. if not isinstance(network, GCENetwork):
  768. network = self.provider.networking.networks.get(network)
  769. network_url = network.resource_url
  770. region_name = self.provider.region_name
  771. response = (self.provider
  772. .gce_compute
  773. .routers()
  774. .insert(project=self.provider.project_name,
  775. region=region_name,
  776. body={'name': name,
  777. 'network': network_url})
  778. .execute())
  779. self.provider.wait_for_operation(response, region=region_name)
  780. cb_router = self.get(name)
  781. cb_router.label = label
  782. return cb_router
  783. @dispatch(event="provider.networking.routers.delete",
  784. priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
  785. def delete(self, router):
  786. r = router if isinstance(router, GCERouter) else self.get(router)
  787. if r:
  788. (self.provider
  789. .gce_compute
  790. .routers()
  791. .delete(project=self.provider.project_name,
  792. region=r.region_name,
  793. router=r.name)
  794. .execute())
  795. # Remove label
  796. tag_name = "_".join(["router", r.name, "label"])
  797. if not helpers.remove_metadata_item(self.provider, tag_name):
  798. log.warning('No label was found associated with this router '
  799. '"{}" when deleted.'.format(r.name))
  800. def _get_in_region(self, router_id, region=None):
  801. region_name = self.provider.region_name
  802. if region:
  803. if not isinstance(region, GCERegion):
  804. region = self.provider.compute.regions.get(region)
  805. region_name = region.name
  806. router = self.provider.get_resource(
  807. 'routers', router_id, region=region_name)
  808. return GCERouter(self.provider, router) if router else None
  809. class GCESubnetService(BaseSubnetService):
  810. def __init__(self, provider):
  811. super(GCESubnetService, self).__init__(provider)
  812. @dispatch(event="provider.networking.subnets.get",
  813. priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
  814. def get(self, subnet_id):
  815. subnet = self.provider.get_resource('subnetworks', subnet_id)
  816. return GCESubnet(self.provider, subnet) if subnet else None
  817. @dispatch(event="provider.networking.subnets.list",
  818. priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
  819. def list(self, network=None, zone=None, limit=None, marker=None):
  820. """
  821. If the zone is not given, we list all subnets in the default region.
  822. """
  823. filter = None
  824. if network is not None:
  825. network = (network if isinstance(network, GCENetwork)
  826. else self.provider.networking.networks.get(network))
  827. filter = 'network eq %s' % network.resource_url
  828. if zone:
  829. region_name = self._zone_to_region(zone)
  830. else:
  831. region_name = self.provider.region_name
  832. subnets = []
  833. response = (self.provider
  834. .gce_compute
  835. .subnetworks()
  836. .list(project=self.provider.project_name,
  837. region=region_name,
  838. filter=filter)
  839. .execute())
  840. for subnet in response.get('items', []):
  841. subnets.append(GCESubnet(self.provider, subnet))
  842. return ClientPagedResultList(self.provider, subnets,
  843. limit=limit, marker=marker)
  844. @dispatch(event="provider.networking.subnets.create",
  845. priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
  846. def create(self, label, network, cidr_block, zone):
  847. """
  848. GCE subnets are regional. The region is inferred from the zone;
  849. otherwise, the default region, as set in the
  850. provider, is used.
  851. If a subnet with overlapping IP range exists already, we return that
  852. instead of creating a new subnet. In this case, other parameters, i.e.
  853. the name and the zone, are ignored.
  854. """
  855. GCESubnet.assert_valid_resource_label(label)
  856. name = GCESubnet._generate_name_from_label(label, 'cbsubnet')
  857. region_name = self._zone_to_region(zone)
  858. # for subnet in self.iter(network=network):
  859. # if BaseNetwork.cidr_blocks_overlap(subnet.cidr_block, cidr_block):
  860. # if subnet.region_name != region_name:
  861. # log.error('Failed to create subnetwork in region %s: '
  862. # 'the given IP range %s overlaps with a '
  863. # 'subnetwork in a different region %s',
  864. # region_name, cidr_block, subnet.region_name)
  865. # return None
  866. # return subnet
  867. # if subnet.label == label and subnet.region_name == region_name:
  868. # return subnet
  869. body = {'ipCidrRange': cidr_block,
  870. 'name': name,
  871. 'network': network.resource_url,
  872. 'region': region_name
  873. }
  874. response = (self.provider
  875. .gce_compute
  876. .subnetworks()
  877. .insert(project=self.provider.project_name,
  878. region=region_name,
  879. body=body)
  880. .execute())
  881. self.provider.wait_for_operation(response, region=region_name)
  882. cb_subnet = self.get(name)
  883. cb_subnet.label = label
  884. return cb_subnet
  885. @dispatch(event="provider.networking.subnets.delete",
  886. priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
  887. def delete(self, subnet):
  888. sn = subnet if isinstance(subnet, GCESubnet) else self.get(subnet)
  889. if not sn:
  890. return
  891. response = (self.provider
  892. .gce_compute
  893. .subnetworks()
  894. .delete(project=self.provider.project_name,
  895. region=sn.region_name,
  896. subnetwork=sn.name)
  897. .execute())
  898. self.provider.wait_for_operation(response, region=sn.region_name)
  899. # Remove label
  900. tag_name = "_".join(["subnet", sn.name, "label"])
  901. if not helpers.remove_metadata_item(self._provider, tag_name):
  902. log.warning('No label was found associated with this subnet '
  903. '"{}" when deleted.'.format(sn.name))
  904. def get_or_create_default(self, zone):
  905. """
  906. Return an existing or create a new subnet for the supplied zone.
  907. In GCP, subnets are a regional resource so a single subnet can services
  908. an entire region. The supplied zone parameter is used to derive the
  909. parent region under which the default subnet then exists.
  910. """
  911. # In case the supplied zone param is `None`, resort to the default one
  912. region = self._zone_to_region(zone or self.provider.default_zone,
  913. return_name_only=False)
  914. # Check if a default subnet already exists for the given region/zone
  915. for sn in self.find(label=GCESubnet.CB_DEFAULT_SUBNET_LABEL):
  916. if sn.region == region.id:
  917. return sn
  918. # No default subnet in the supplied zone. Look for a default network,
  919. # then create a subnet whose address space does not overlap with any
  920. # other existing subnets. If there are existing subnets, this process
  921. # largely assumes the subnet address spaces are contiguous when it
  922. # does the calculations (e.g., 10.0.0.0/24, 10.0.1.0/24).
  923. cidr_block = GCESubnet.CB_DEFAULT_SUBNET_IPV4RANGE
  924. net = self.provider.networking.networks.get_or_create_default()
  925. if net.subnets:
  926. max_sn = net.subnets[0]
  927. # Find the maximum address subnet address space within the network
  928. for esn in net.subnets:
  929. if (ipaddress.ip_network(esn.cidr_block) >
  930. ipaddress.ip_network(max_sn.cidr_block)):
  931. max_sn = esn
  932. max_sn_ipa = ipaddress.ip_network(max_sn.cidr_block)
  933. # Find the next available subnet after the max one, based on the
  934. # max subnet size
  935. next_sn_address = (
  936. next(max_sn_ipa.hosts()) + max_sn_ipa.num_addresses - 1)
  937. cidr_block = "{}/{}".format(next_sn_address, max_sn_ipa.prefixlen)
  938. sn = self.provider.networking.subnets.create(
  939. label=GCESubnet.CB_DEFAULT_SUBNET_LABEL,
  940. cidr_block=cidr_block, network=net, zone=zone)
  941. router = self.provider.networking.routers.get_or_create_default(net)
  942. router.attach_subnet(sn)
  943. gateway = net.gateways.get_or_create()
  944. router.attach_gateway(gateway)
  945. return sn
  946. def _zone_to_region(self, zone, return_name_only=True):
  947. """
  948. Given a GCE zone, return parent region.
  949. Supplied `zone` param can be a `str` or `GCEPlacementZone`.
  950. If ``return_name_only`` is set, return the region name as a string;
  951. otherwise, return a GCERegion object.
  952. """
  953. region_name = self.provider.region_name
  954. if zone:
  955. if isinstance(zone, GCEPlacementZone):
  956. region_name = zone.region_name
  957. else:
  958. region_name = zone[:-2]
  959. if return_name_only:
  960. return region_name
  961. return self.provider.compute.regions.get(region_name)
  962. class GCPStorageService(BaseStorageService):
  963. def __init__(self, provider):
  964. super(GCPStorageService, self).__init__(provider)
  965. # Initialize provider services
  966. self._volume_svc = GCEVolumeService(self.provider)
  967. self._snapshot_svc = GCESnapshotService(self.provider)
  968. self._bucket_svc = GCSBucketService(self.provider)
  969. self._bucket_obj_svc = GCSBucketObjectService(self.provider)
  970. @property
  971. def volumes(self):
  972. return self._volume_svc
  973. @property
  974. def snapshots(self):
  975. return self._snapshot_svc
  976. @property
  977. def buckets(self):
  978. return self._bucket_svc
  979. @property
  980. def _bucket_objects(self):
  981. return self._bucket_obj_svc
  982. class GCEVolumeService(BaseVolumeService):
  983. def __init__(self, provider):
  984. super(GCEVolumeService, self).__init__(provider)
  985. @dispatch(event="provider.storage.volumes.get",
  986. priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
  987. def get(self, volume_id):
  988. vol = self.provider.get_resource('disks', volume_id)
  989. return GCEVolume(self.provider, vol) if vol else None
  990. @dispatch(event="provider.storage.volumes.find",
  991. priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
  992. def find(self, limit=None, marker=None, **kwargs):
  993. """
  994. Searches for a volume by a given list of attributes.
  995. """
  996. label = kwargs.pop('label', None)
  997. # All kwargs should have been popped at this time.
  998. if len(kwargs) > 0:
  999. raise InvalidParamException(
  1000. "Unrecognised parameters for search: %s. Supported "
  1001. "attributes: %s" % (kwargs, 'label'))
  1002. filtr = 'labels.cblabel eq ' + label
  1003. max_result = limit if limit is not None and limit < 500 else 500
  1004. response = (self.provider
  1005. .gce_compute
  1006. .disks()
  1007. .list(project=self.provider.project_name,
  1008. zone=self.provider.default_zone,
  1009. filter=filtr,
  1010. maxResults=max_result,
  1011. pageToken=marker)
  1012. .execute())
  1013. gce_vols = [GCEVolume(self.provider, vol)
  1014. for vol in response.get('items', [])]
  1015. if len(gce_vols) > max_result:
  1016. log.warning('Expected at most %d results; got %d',
  1017. max_result, len(gce_vols))
  1018. return ServerPagedResultList('nextPageToken' in response,
  1019. response.get('nextPageToken'),
  1020. False, data=gce_vols)
  1021. @dispatch(event="provider.storage.volumes.list",
  1022. priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
  1023. def list(self, limit=None, marker=None):
  1024. """
  1025. List all volumes.
  1026. limit: The maximum number of volumes to return. The returned
  1027. ResultList's is_truncated property can be used to determine
  1028. whether more records are available.
  1029. """
  1030. # For GCE API, Acceptable values are 0 to 500, inclusive.
  1031. # (Default: 500).
  1032. max_result = limit if limit is not None and limit < 500 else 500
  1033. response = (self.provider
  1034. .gce_compute
  1035. .disks()
  1036. .list(project=self.provider.project_name,
  1037. zone=self.provider.default_zone,
  1038. maxResults=max_result,
  1039. pageToken=marker)
  1040. .execute())
  1041. gce_vols = [GCEVolume(self.provider, vol)
  1042. for vol in response.get('items', [])]
  1043. if len(gce_vols) > max_result:
  1044. log.warning('Expected at most %d results; got %d',
  1045. max_result, len(gce_vols))
  1046. return ServerPagedResultList('nextPageToken' in response,
  1047. response.get('nextPageToken'),
  1048. False, data=gce_vols)
  1049. @dispatch(event="provider.storage.volumes.create",
  1050. priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
  1051. def create(self, label, size, zone, snapshot=None, description=None):
  1052. GCEVolume.assert_valid_resource_label(label)
  1053. name = GCEVolume._generate_name_from_label(label, 'cb-vol')
  1054. if not isinstance(zone, GCEPlacementZone):
  1055. zone = GCEPlacementZone(
  1056. self.provider,
  1057. self.provider.get_resource('zones', zone))
  1058. zone_name = zone.name
  1059. snapshot_id = snapshot.id if isinstance(
  1060. snapshot, GCESnapshot) and snapshot else snapshot
  1061. disk_body = {
  1062. 'name': name,
  1063. 'sizeGb': size,
  1064. 'type': 'zones/{0}/diskTypes/{1}'.format(zone_name, 'pd-standard'),
  1065. 'sourceSnapshot': snapshot_id,
  1066. 'description': description,
  1067. 'labels': {'cblabel': label}
  1068. }
  1069. operation = (self.provider
  1070. .gce_compute
  1071. .disks()
  1072. .insert(
  1073. project=self._provider.project_name,
  1074. zone=zone_name,
  1075. body=disk_body)
  1076. .execute())
  1077. cb_vol = self.get(operation.get('targetLink'))
  1078. return cb_vol
  1079. @dispatch(event="provider.storage.volumes.delete",
  1080. priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
  1081. def delete(self, volume):
  1082. volume = volume if isinstance(volume, GCEVolume) else self.get(volume)
  1083. if volume:
  1084. (self._provider.gce_compute
  1085. .disks()
  1086. .delete(project=self.provider.project_name,
  1087. zone=volume.zone_name,
  1088. disk=volume.name)
  1089. .execute())
  1090. class GCESnapshotService(BaseSnapshotService):
  1091. def __init__(self, provider):
  1092. super(GCESnapshotService, self).__init__(provider)
  1093. @dispatch(event="provider.storage.snapshots.get",
  1094. priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
  1095. def get(self, snapshot_id):
  1096. snapshot = self.provider.get_resource('snapshots', snapshot_id)
  1097. return GCESnapshot(self.provider, snapshot) if snapshot else None
  1098. @dispatch(event="provider.storage.snapshots.find",
  1099. priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
  1100. def find(self, limit=None, marker=None, **kwargs):
  1101. label = kwargs.pop('label', None)
  1102. # All kwargs should have been popped at this time.
  1103. if len(kwargs) > 0:
  1104. raise InvalidParamException(
  1105. "Unrecognised parameters for search: %s. Supported "
  1106. "attributes: %s" % (kwargs, 'label'))
  1107. filtr = 'labels.cblabel eq ' + label
  1108. max_result = limit if limit is not None and limit < 500 else 500
  1109. response = (self.provider
  1110. .gce_compute
  1111. .snapshots()
  1112. .list(project=self.provider.project_name,
  1113. filter=filtr,
  1114. maxResults=max_result,
  1115. pageToken=marker)
  1116. .execute())
  1117. snapshots = [GCESnapshot(self.provider, snapshot)
  1118. for snapshot in response.get('items', [])]
  1119. if len(snapshots) > max_result:
  1120. log.warning('Expected at most %d results; got %d',
  1121. max_result, len(snapshots))
  1122. return ServerPagedResultList('nextPageToken' in response,
  1123. response.get('nextPageToken'),
  1124. False, data=snapshots)
  1125. @dispatch(event="provider.storage.snapshots.list",
  1126. priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
  1127. def list(self, limit=None, marker=None):
  1128. max_result = limit if limit is not None and limit < 500 else 500
  1129. response = (self.provider
  1130. .gce_compute
  1131. .snapshots()
  1132. .list(project=self.provider.project_name,
  1133. maxResults=max_result,
  1134. pageToken=marker)
  1135. .execute())
  1136. snapshots = [GCESnapshot(self.provider, snapshot)
  1137. for snapshot in response.get('items', [])]
  1138. if len(snapshots) > max_result:
  1139. log.warning('Expected at most %d results; got %d',
  1140. max_result, len(snapshots))
  1141. return ServerPagedResultList('nextPageToken' in response,
  1142. response.get('nextPageToken'),
  1143. False, data=snapshots)
  1144. @dispatch(event="provider.storage.snapshots.create",
  1145. priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
  1146. def create(self, label, volume, description=None):
  1147. GCESnapshot.assert_valid_resource_label(label)
  1148. name = GCESnapshot._generate_name_from_label(label, 'cbsnap')
  1149. volume_name = volume.name if isinstance(volume, GCEVolume) else volume
  1150. snapshot_body = {
  1151. "name": name,
  1152. "description": description,
  1153. "labels": {'cblabel': label}
  1154. }
  1155. operation = (self.provider
  1156. .gce_compute
  1157. .disks()
  1158. .createSnapshot(
  1159. project=self.provider.project_name,
  1160. zone=self.provider.default_zone,
  1161. disk=volume_name, body=snapshot_body)
  1162. .execute())
  1163. if 'zone' not in operation:
  1164. return None
  1165. self.provider.wait_for_operation(operation,
  1166. zone=self.provider.default_zone)
  1167. cb_snap = self.get(name)
  1168. return cb_snap
  1169. @dispatch(event="provider.storage.snapshots.delete",
  1170. priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
  1171. def delete(self, snapshot):
  1172. snapshot = (snapshot if isinstance(snapshot, GCESnapshot)
  1173. else self.get(snapshot))
  1174. if snapshot:
  1175. (self.provider
  1176. .gce_compute
  1177. .snapshots()
  1178. .delete(project=self.provider.project_name,
  1179. snapshot=snapshot.name)
  1180. .execute())
  1181. class GCSBucketService(BaseBucketService):
  1182. def __init__(self, provider):
  1183. super(GCSBucketService, self).__init__(provider)
  1184. @dispatch(event="provider.storage.buckets.get",
  1185. priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
  1186. def get(self, bucket_id):
  1187. """
  1188. Returns a bucket given its ID. Returns ``None`` if the bucket
  1189. does not exist or if the user does not have permission to access the
  1190. bucket.
  1191. """
  1192. bucket = self.provider.get_resource('buckets', bucket_id)
  1193. return GCSBucket(self.provider, bucket) if bucket else None
  1194. @dispatch(event="provider.storage.buckets.find",
  1195. priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
  1196. def find(self, limit=None, marker=None, **kwargs):
  1197. name = kwargs.pop('name', None)
  1198. # All kwargs should have been popped at this time.
  1199. if len(kwargs) > 0:
  1200. raise InvalidParamException(
  1201. "Unrecognised parameters for search: %s. Supported "
  1202. "attributes: %s" % (kwargs, 'name'))
  1203. buckets = [bucket for bucket in self if name in bucket.name]
  1204. return ClientPagedResultList(self.provider, buckets, limit=limit,
  1205. marker=marker)
  1206. @dispatch(event="provider.storage.buckets.list",
  1207. priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
  1208. def list(self, limit=None, marker=None):
  1209. """
  1210. List all containers.
  1211. """
  1212. max_result = limit if limit is not None and limit < 500 else 500
  1213. response = (self.provider
  1214. .gcs_storage
  1215. .buckets()
  1216. .list(project=self.provider.project_name,
  1217. maxResults=max_result,
  1218. pageToken=marker)
  1219. .execute())
  1220. buckets = []
  1221. for bucket in response.get('items', []):
  1222. buckets.append(GCSBucket(self.provider, bucket))
  1223. if len(buckets) > max_result:
  1224. log.warning('Expected at most %d results; got %d',
  1225. max_result, len(buckets))
  1226. return ServerPagedResultList('nextPageToken' in response,
  1227. response.get('nextPageToken'),
  1228. False, data=buckets)
  1229. @dispatch(event="provider.storage.buckets.create",
  1230. priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
  1231. def create(self, name, location=None):
  1232. GCSBucket.assert_valid_resource_name(name)
  1233. body = {'name': name}
  1234. if location:
  1235. body['location'] = location
  1236. try:
  1237. response = (self.provider
  1238. .gcs_storage
  1239. .buckets()
  1240. .insert(project=self.provider.project_name,
  1241. body=body)
  1242. .execute())
  1243. # GCS has a rate limit of 1 operation per 2 seconds for bucket
  1244. # creation/deletion: https://cloud.google.com/storage/quotas.
  1245. # Throttle here to avoid future failures.
  1246. time.sleep(2)
  1247. return GCSBucket(self.provider, response)
  1248. except googleapiclient.errors.HttpError as http_error:
  1249. # 409 = conflict
  1250. if http_error.resp.status in [409]:
  1251. raise DuplicateResourceException(
  1252. 'Bucket already exists with name {0}'.format(name))
  1253. else:
  1254. raise
  1255. @dispatch(event="provider.storage.buckets.delete",
  1256. priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
  1257. def delete(self, bucket):
  1258. """
  1259. Delete this bucket.
  1260. """
  1261. b = bucket if isinstance(bucket, GCSBucket) else self.get(bucket)
  1262. if b:
  1263. (self.provider
  1264. .gcs_storage
  1265. .buckets()
  1266. .delete(bucket=b.name)
  1267. .execute())
  1268. # GCS has a rate limit of 1 operation per 2 seconds for bucket
  1269. # creation/deletion: https://cloud.google.com/storage/quotas.
  1270. # Throttle here to avoid future failures.
  1271. time.sleep(2)
  1272. class GCSBucketObjectService(BaseBucketObjectService):
  1273. def __init__(self, provider):
  1274. super(GCSBucketObjectService, self).__init__(provider)
  1275. def get(self, bucket, name):
  1276. """
  1277. Retrieve a given object from this bucket.
  1278. """
  1279. obj = self.provider.get_resource('objects', name,
  1280. bucket=bucket.name)
  1281. return GCSObject(self.provider, bucket, obj) if obj else None
  1282. def list(self, bucket, limit=None, marker=None, prefix=None):
  1283. """
  1284. List all objects within this bucket.
  1285. """
  1286. max_result = limit if limit is not None and limit < 500 else 500
  1287. response = (self.provider
  1288. .gcs_storage
  1289. .objects()
  1290. .list(bucket=bucket.name,
  1291. prefix=prefix if prefix else '',
  1292. maxResults=max_result,
  1293. pageToken=marker)
  1294. .execute())
  1295. objects = []
  1296. for obj in response.get('items', []):
  1297. objects.append(GCSObject(self.provider, bucket, obj))
  1298. if len(objects) > max_result:
  1299. log.warning('Expected at most %d results; got %d',
  1300. max_result, len(objects))
  1301. return ServerPagedResultList('nextPageToken' in response,
  1302. response.get('nextPageToken'),
  1303. False, data=objects)
  1304. def find(self, bucket, limit=None, marker=None, **kwargs):
  1305. filters = ['name']
  1306. matches = cb_helpers.generic_find(filters, kwargs, bucket.objects)
  1307. return ClientPagedResultList(self._provider, list(matches),
  1308. limit=limit, marker=marker)
  1309. def _create_object_with_media_body(self, bucket, name, media_body):
  1310. response = (self.provider
  1311. .gcs_storage
  1312. .objects()
  1313. .insert(bucket=bucket.name,
  1314. body={'name': name},
  1315. media_body=media_body)
  1316. .execute())
  1317. return response
  1318. def create(self, bucket, name):
  1319. response = self._create_object_with_media_body(
  1320. bucket,
  1321. name,
  1322. googleapiclient.http.MediaIoBaseUpload(
  1323. io.BytesIO(b''), mimetype='plain/text'))
  1324. return GCSObject(self._provider,
  1325. bucket,
  1326. response) if response else None
  1327. class GCEGatewayService(BaseGatewayService):
  1328. _DEFAULT_GATEWAY_NAME = 'default-internet-gateway'
  1329. _GATEWAY_URL_PREFIX = 'global/gateways/'
  1330. def __init__(self, provider):
  1331. super(GCEGatewayService, self).__init__(provider)
  1332. self._default_internet_gateway = GCEInternetGateway(
  1333. provider,
  1334. {'id': (GCEGatewayService._GATEWAY_URL_PREFIX +
  1335. GCEGatewayService._DEFAULT_GATEWAY_NAME),
  1336. 'name': GCEGatewayService._DEFAULT_GATEWAY_NAME})
  1337. @dispatch(event="provider.networking.gateways.get_or_create",
  1338. priority=BaseGatewayService.STANDARD_EVENT_PRIORITY)
  1339. def get_or_create(self, network):
  1340. return self._default_internet_gateway
  1341. @dispatch(event="provider.networking.gateways.delete",
  1342. priority=BaseGatewayService.STANDARD_EVENT_PRIORITY)
  1343. def delete(self, network, gateway):
  1344. pass
  1345. @dispatch(event="provider.networking.gateways.list",
  1346. priority=BaseGatewayService.STANDARD_EVENT_PRIORITY)
  1347. def list(self, network, limit=None, marker=None):
  1348. gws = [self._default_internet_gateway]
  1349. return ClientPagedResultList(self._provider,
  1350. gws,
  1351. limit=limit, marker=marker)
  1352. class GCEFloatingIPService(BaseFloatingIPService):
  1353. def __init__(self, provider):
  1354. super(GCEFloatingIPService, self).__init__(provider)
  1355. @dispatch(event="provider.networking.floating_ips.get",
  1356. priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
  1357. def get(self, gateway, floating_ip_id):
  1358. fip = self.provider.get_resource('addresses', floating_ip_id)
  1359. return (GCEFloatingIP(self.provider, fip)
  1360. if fip else None)
  1361. @dispatch(event="provider.networking.floating_ips.list",
  1362. priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
  1363. def list(self, gateway, limit=None, marker=None):
  1364. max_result = limit if limit is not None and limit < 500 else 500
  1365. response = (self.provider
  1366. .gce_compute
  1367. .addresses()
  1368. .list(project=self.provider.project_name,
  1369. region=self.provider.region_name,
  1370. maxResults=max_result,
  1371. pageToken=marker)
  1372. .execute())
  1373. ips = [GCEFloatingIP(self.provider, ip)
  1374. for ip in response.get('items', [])]
  1375. if len(ips) > max_result:
  1376. log.warning('Expected at most %d results; got %d',
  1377. max_result, len(ips))
  1378. return ServerPagedResultList('nextPageToken' in response,
  1379. response.get('nextPageToken'),
  1380. False, data=ips)
  1381. @dispatch(event="provider.networking.floating_ips.create",
  1382. priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
  1383. def create(self, gateway):
  1384. region_name = self.provider.region_name
  1385. ip_name = 'ip-{0}'.format(uuid.uuid4())
  1386. response = (self.provider
  1387. .gce_compute
  1388. .addresses()
  1389. .insert(project=self.provider.project_name,
  1390. region=region_name,
  1391. body={'name': ip_name})
  1392. .execute())
  1393. self.provider.wait_for_operation(response, region=region_name)
  1394. return self.get(gateway, ip_name)
  1395. @dispatch(event="provider.networking.floating_ips.delete",
  1396. priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
  1397. def delete(self, gateway, fip):
  1398. fip = (fip if isinstance(fip, GCEFloatingIP)
  1399. else self.get(gateway, fip))
  1400. project_name = self.provider.project_name
  1401. # First, delete the forwarding rule, if there is any.
  1402. if fip._rule:
  1403. response = (self.provider
  1404. .gce_compute
  1405. .forwardingRules()
  1406. .delete(project=project_name,
  1407. region=fip.region_name,
  1408. forwardingRule=fip._rule['name'])
  1409. .execute())
  1410. self.provider.wait_for_operation(response,
  1411. region=fip.region_name)
  1412. # Release the address.
  1413. response = (self.provider
  1414. .gce_compute
  1415. .addresses()
  1416. .delete(project=project_name,
  1417. region=fip.region_name,
  1418. address=fip._ip['name'])
  1419. .execute())
  1420. self.provider.wait_for_operation(response,
  1421. region=fip.region_name)