services.py 66 KB

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