services.py 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499
  1. """Services implemented by the AWS provider."""
  2. import ipaddress
  3. import logging
  4. import string
  5. import uuid
  6. from botocore.exceptions import ClientError
  7. import cachetools
  8. import requests
  9. import cloudbridge.base.helpers as cb_helpers
  10. from cloudbridge.base.middleware import dispatch
  11. from cloudbridge.base.resources import ClientPagedResultList
  12. from cloudbridge.base.resources import ServerPagedResultList
  13. from cloudbridge.base.services import BaseBucketObjectService
  14. from cloudbridge.base.services import BaseBucketService
  15. from cloudbridge.base.services import BaseComputeService
  16. from cloudbridge.base.services import BaseDnsRecordService
  17. from cloudbridge.base.services import BaseDnsService
  18. from cloudbridge.base.services import BaseDnsZoneService
  19. from cloudbridge.base.services import BaseFloatingIPService
  20. from cloudbridge.base.services import BaseGatewayService
  21. from cloudbridge.base.services import BaseImageService
  22. from cloudbridge.base.services import BaseInstanceService
  23. from cloudbridge.base.services import BaseKeyPairService
  24. from cloudbridge.base.services import BaseNetworkService
  25. from cloudbridge.base.services import BaseNetworkingService
  26. from cloudbridge.base.services import BaseRegionService
  27. from cloudbridge.base.services import BaseRouterService
  28. from cloudbridge.base.services import BaseSecurityService
  29. from cloudbridge.base.services import BaseSnapshotService
  30. from cloudbridge.base.services import BaseStorageService
  31. from cloudbridge.base.services import BaseSubnetService
  32. from cloudbridge.base.services import BaseVMFirewallRuleService
  33. from cloudbridge.base.services import BaseVMFirewallService
  34. from cloudbridge.base.services import BaseVMTypeService
  35. from cloudbridge.base.services import BaseVolumeService
  36. from cloudbridge.interfaces.exceptions import DuplicateResourceException
  37. from cloudbridge.interfaces.exceptions import \
  38. InvalidConfigurationException
  39. from cloudbridge.interfaces.exceptions import InvalidParamException
  40. from cloudbridge.interfaces.exceptions import InvalidValueException
  41. from cloudbridge.interfaces.resources import KeyPair
  42. from cloudbridge.interfaces.resources import MachineImage
  43. from cloudbridge.interfaces.resources import Network
  44. from cloudbridge.interfaces.resources import Snapshot
  45. from cloudbridge.interfaces.resources import TrafficDirection
  46. from cloudbridge.interfaces.resources import VMFirewall
  47. from cloudbridge.interfaces.resources import VMType
  48. from cloudbridge.interfaces.resources import Volume
  49. from .helpers import BotoEC2Service
  50. from .helpers import BotoS3Service
  51. from .helpers import trim_empty_params
  52. from .resources import AWSBucket
  53. from .resources import AWSBucketObject
  54. from .resources import AWSDnsRecord
  55. from .resources import AWSDnsZone
  56. from .resources import AWSFloatingIP
  57. from .resources import AWSInstance
  58. from .resources import AWSInternetGateway
  59. from .resources import AWSKeyPair
  60. from .resources import AWSLaunchConfig
  61. from .resources import AWSMachineImage
  62. from .resources import AWSNetwork
  63. from .resources import AWSRegion
  64. from .resources import AWSRouter
  65. from .resources import AWSSnapshot
  66. from .resources import AWSSubnet
  67. from .resources import AWSVMFirewall
  68. from .resources import AWSVMFirewallRule
  69. from .resources import AWSVMType
  70. from .resources import AWSVolume
  71. log = logging.getLogger(__name__)
  72. class AWSSecurityService(BaseSecurityService):
  73. def __init__(self, provider):
  74. super(AWSSecurityService, self).__init__(provider)
  75. # Initialize provider services
  76. self._key_pairs = AWSKeyPairService(provider)
  77. self._vm_firewalls = AWSVMFirewallService(provider)
  78. self._vm_firewall_rule_svc = AWSVMFirewallRuleService(provider)
  79. @property
  80. def key_pairs(self):
  81. return self._key_pairs
  82. @property
  83. def vm_firewalls(self):
  84. return self._vm_firewalls
  85. @property
  86. def _vm_firewall_rules(self):
  87. return self._vm_firewall_rule_svc
  88. class AWSKeyPairService(BaseKeyPairService):
  89. def __init__(self, provider):
  90. super(AWSKeyPairService, self).__init__(provider)
  91. self.svc = BotoEC2Service(provider=self.provider,
  92. cb_resource=AWSKeyPair,
  93. boto_collection_name='key_pairs')
  94. @dispatch(event="provider.security.key_pairs.get",
  95. priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
  96. def get(self, key_pair_id):
  97. log.debug("Getting Key Pair Service %s", key_pair_id)
  98. return self.svc.get(key_pair_id)
  99. @dispatch(event="provider.security.key_pairs.list",
  100. priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
  101. def list(self, limit=None, marker=None):
  102. return self.svc.list(limit=limit, marker=marker)
  103. @dispatch(event="provider.security.key_pairs.find",
  104. priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
  105. def find(self, **kwargs):
  106. name = kwargs.pop('name', None)
  107. # All kwargs should have been popped at this time.
  108. if len(kwargs) > 0:
  109. raise InvalidParamException(
  110. "Unrecognised parameters for search: %s. Supported "
  111. "attributes: %s" % (kwargs, 'name'))
  112. log.debug("Searching for Key Pair %s", name)
  113. return self.svc.find(filters={'key-name': name})
  114. @dispatch(event="provider.security.key_pairs.create",
  115. priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
  116. def create(self, name, public_key_material=None):
  117. AWSKeyPair.assert_valid_resource_name(name)
  118. private_key = None
  119. if not public_key_material:
  120. public_key_material, private_key = cb_helpers.generate_key_pair()
  121. try:
  122. kp = self.svc.create('import_key_pair', KeyName=name,
  123. PublicKeyMaterial=public_key_material)
  124. kp.material = private_key
  125. return kp
  126. except ClientError as e:
  127. if e.response['Error']['Code'] == 'InvalidKeyPair.Duplicate':
  128. raise DuplicateResourceException(
  129. 'Keypair already exists with name {0}'.format(name))
  130. else:
  131. raise e
  132. @dispatch(event="provider.security.key_pairs.delete",
  133. priority=BaseKeyPairService.STANDARD_EVENT_PRIORITY)
  134. def delete(self, key_pair):
  135. key_pair = (key_pair if isinstance(key_pair, AWSKeyPair) else
  136. self.get(key_pair))
  137. if key_pair:
  138. # pylint:disable=protected-access
  139. key_pair._key_pair.delete()
  140. class AWSVMFirewallService(BaseVMFirewallService):
  141. def __init__(self, provider):
  142. super(AWSVMFirewallService, self).__init__(provider)
  143. self.svc = BotoEC2Service(provider=self.provider,
  144. cb_resource=AWSVMFirewall,
  145. boto_collection_name='security_groups')
  146. @dispatch(event="provider.security.vm_firewalls.get",
  147. priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
  148. def get(self, vm_firewall_id):
  149. log.debug("Getting Firewall Service with the id: %s", vm_firewall_id)
  150. return self.svc.get(vm_firewall_id)
  151. @dispatch(event="provider.security.vm_firewalls.list",
  152. priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
  153. def list(self, limit=None, marker=None):
  154. return self.svc.list(limit=limit, marker=marker)
  155. @cb_helpers.deprecated_alias(network_id='network')
  156. @dispatch(event="provider.security.vm_firewalls.create",
  157. priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
  158. def create(self, label, network, description=None):
  159. AWSVMFirewall.assert_valid_resource_label(label)
  160. name = AWSVMFirewall._generate_name_from_label(label, 'cb-fw')
  161. network_id = network.id if isinstance(network, Network) else network
  162. obj = self.svc.create('create_security_group', GroupName=name,
  163. Description=name,
  164. VpcId=network_id)
  165. obj.label = label
  166. obj.description = description
  167. return obj
  168. @dispatch(event="provider.security.vm_firewalls.find",
  169. priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
  170. def find(self, **kwargs):
  171. # Filter by name or label
  172. label = kwargs.pop('label', None)
  173. log.debug("Searching for Firewall Service %s", label)
  174. # All kwargs should have been popped at this time.
  175. if len(kwargs) > 0:
  176. raise InvalidParamException(
  177. "Unrecognised parameters for search: %s. Supported "
  178. "attributes: %s" % (kwargs, 'label'))
  179. return self.svc.find(filters={'tag:Name': label})
  180. @dispatch(event="provider.security.vm_firewalls.delete",
  181. priority=BaseVMFirewallService.STANDARD_EVENT_PRIORITY)
  182. def delete(self, vm_firewall):
  183. firewall = (vm_firewall if isinstance(vm_firewall, AWSVMFirewall)
  184. else self.get(vm_firewall))
  185. if firewall:
  186. # pylint:disable=protected-access
  187. firewall._vm_firewall.delete()
  188. class AWSVMFirewallRuleService(BaseVMFirewallRuleService):
  189. def __init__(self, provider):
  190. super(AWSVMFirewallRuleService, self).__init__(provider)
  191. @dispatch(event="provider.security.vm_firewall_rules.list",
  192. priority=BaseVMFirewallRuleService.STANDARD_EVENT_PRIORITY)
  193. def list(self, firewall, limit=None, marker=None):
  194. # pylint:disable=protected-access
  195. rules = [AWSVMFirewallRule(firewall,
  196. TrafficDirection.INBOUND, r)
  197. for r in firewall._vm_firewall.ip_permissions]
  198. # pylint:disable=protected-access
  199. rules = rules + [
  200. AWSVMFirewallRule(
  201. firewall, TrafficDirection.OUTBOUND, r)
  202. for r in firewall._vm_firewall.ip_permissions_egress]
  203. return ClientPagedResultList(self.provider, rules,
  204. limit=limit, marker=marker)
  205. @dispatch(event="provider.security.vm_firewall_rules.create",
  206. priority=BaseVMFirewallRuleService.STANDARD_EVENT_PRIORITY)
  207. def create(self, firewall, direction, protocol=None, from_port=None,
  208. to_port=None, cidr=None, src_dest_fw=None):
  209. src_dest_fw_id = (
  210. src_dest_fw.id if isinstance(src_dest_fw, AWSVMFirewall)
  211. else src_dest_fw)
  212. # pylint:disable=protected-access
  213. ip_perm_entry = AWSVMFirewallRule._construct_ip_perms(
  214. protocol, from_port, to_port, cidr, src_dest_fw_id)
  215. # Filter out empty values to please Boto
  216. ip_perms = [trim_empty_params(ip_perm_entry)]
  217. try:
  218. if direction == TrafficDirection.INBOUND:
  219. # pylint:disable=protected-access
  220. firewall._vm_firewall.authorize_ingress(
  221. IpPermissions=ip_perms)
  222. elif direction == TrafficDirection.OUTBOUND:
  223. # pylint:disable=protected-access
  224. firewall._vm_firewall.authorize_egress(
  225. IpPermissions=ip_perms)
  226. else:
  227. raise InvalidValueException("direction", direction)
  228. firewall.refresh()
  229. return AWSVMFirewallRule(firewall, direction, ip_perm_entry)
  230. except ClientError as ec2e:
  231. if ec2e.response['Error']['Code'] == "InvalidPermission.Duplicate":
  232. return AWSVMFirewallRule(
  233. firewall, direction, ip_perm_entry)
  234. else:
  235. raise ec2e
  236. @dispatch(event="provider.security.vm_firewall_rules.delete",
  237. priority=BaseVMFirewallRuleService.STANDARD_EVENT_PRIORITY)
  238. def delete(self, firewall, rule):
  239. # pylint:disable=protected-access
  240. ip_perm_entry = rule._construct_ip_perms(
  241. rule.protocol, rule.from_port, rule.to_port,
  242. rule.cidr, rule.src_dest_fw_id)
  243. # Filter out empty values to please Boto
  244. ip_perms = [trim_empty_params(ip_perm_entry)]
  245. # pylint:disable=protected-access
  246. if rule.direction == TrafficDirection.INBOUND:
  247. firewall._vm_firewall.revoke_ingress(
  248. IpPermissions=ip_perms)
  249. else:
  250. # pylint:disable=protected-access
  251. firewall._vm_firewall.revoke_egress(
  252. IpPermissions=ip_perms)
  253. firewall.refresh()
  254. class AWSStorageService(BaseStorageService):
  255. def __init__(self, provider):
  256. super(AWSStorageService, self).__init__(provider)
  257. # Initialize provider services
  258. self._volume_svc = AWSVolumeService(self.provider)
  259. self._snapshot_svc = AWSSnapshotService(self.provider)
  260. self._bucket_svc = AWSBucketService(self.provider)
  261. self._bucket_obj_svc = AWSBucketObjectService(self.provider)
  262. @property
  263. def volumes(self):
  264. return self._volume_svc
  265. @property
  266. def snapshots(self):
  267. return self._snapshot_svc
  268. @property
  269. def buckets(self):
  270. return self._bucket_svc
  271. @property
  272. def _bucket_objects(self):
  273. return self._bucket_obj_svc
  274. class AWSVolumeService(BaseVolumeService):
  275. def __init__(self, provider):
  276. super(AWSVolumeService, self).__init__(provider)
  277. self.svc = BotoEC2Service(provider=self.provider,
  278. cb_resource=AWSVolume,
  279. boto_collection_name='volumes')
  280. @dispatch(event="provider.storage.volumes.get",
  281. priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
  282. def get(self, volume_id):
  283. return self.svc.get(volume_id)
  284. @dispatch(event="provider.storage.volumes.find",
  285. priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
  286. def find(self, **kwargs):
  287. label = kwargs.pop('label', None)
  288. # All kwargs should have been popped at this time.
  289. if len(kwargs) > 0:
  290. raise InvalidParamException(
  291. "Unrecognised parameters for search: %s. Supported "
  292. "attributes: %s" % (kwargs, 'label'))
  293. log.debug("Searching for AWS Volume Service %s", label)
  294. return self.svc.find(
  295. filters={'tag:Name': label,
  296. 'availability-zone': self.provider.zone_name})
  297. @dispatch(event="provider.storage.volumes.list",
  298. priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
  299. def list(self, limit=None, marker=None):
  300. return self.svc.find(
  301. filters={'availability-zone': self.provider.zone_name},
  302. limit=limit, marker=marker)
  303. @dispatch(event="provider.storage.volumes.create",
  304. priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
  305. def create(self, label, size, snapshot=None, description=None):
  306. AWSVolume.assert_valid_resource_label(label)
  307. zone_name = self.provider.zone_name
  308. snapshot_id = snapshot.id if isinstance(
  309. snapshot, AWSSnapshot) and snapshot else snapshot
  310. cb_vol = self.svc.create('create_volume', Size=size,
  311. AvailabilityZone=zone_name,
  312. SnapshotId=snapshot_id)
  313. # Wait until ready to tag instance
  314. cb_vol.wait_till_ready()
  315. cb_vol.label = label
  316. if description:
  317. cb_vol.description = description
  318. return cb_vol
  319. @dispatch(event="provider.storage.volumes.delete",
  320. priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
  321. def delete(self, vol):
  322. volume = vol if isinstance(vol, AWSVolume) else self.get(vol)
  323. if volume:
  324. # pylint:disable=protected-access
  325. volume._volume.delete()
  326. class AWSSnapshotService(BaseSnapshotService):
  327. def __init__(self, provider):
  328. super(AWSSnapshotService, self).__init__(provider)
  329. self.svc = BotoEC2Service(provider=self.provider,
  330. cb_resource=AWSSnapshot,
  331. boto_collection_name='snapshots')
  332. @dispatch(event="provider.storage.snapshots.get",
  333. priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
  334. def get(self, snapshot_id):
  335. return self.svc.get(snapshot_id)
  336. @dispatch(event="provider.storage.snapshots.find",
  337. priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
  338. def find(self, **kwargs):
  339. # Filter by description or label
  340. label = kwargs.get('label', None)
  341. obj_list = []
  342. if label:
  343. log.debug("Searching for AWS Snapshot with label %s", label)
  344. obj_list.extend(self.svc.find(filters={'tag:Name': label},
  345. OwnerIds=['self']))
  346. else:
  347. obj_list = list(self)
  348. filters = ['label']
  349. return cb_helpers.generic_find(filters, kwargs, obj_list)
  350. @dispatch(event="provider.storage.snapshots.list",
  351. priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
  352. def list(self, limit=None, marker=None):
  353. return self.svc.list(limit=limit, marker=marker,
  354. OwnerIds=['self'])
  355. @dispatch(event="provider.storage.snapshots.create",
  356. priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
  357. def create(self, label, volume, description=None):
  358. AWSSnapshot.assert_valid_resource_label(label)
  359. volume_id = volume.id if isinstance(volume, AWSVolume) else volume
  360. cb_snap = self.svc.create('create_snapshot', VolumeId=volume_id)
  361. # Wait until ready to tag instance
  362. cb_snap.wait_till_ready()
  363. cb_snap.label = label
  364. if cb_snap.description:
  365. cb_snap.description = description
  366. return cb_snap
  367. @dispatch(event="provider.storage.snapshots.delete",
  368. priority=BaseSnapshotService.STANDARD_EVENT_PRIORITY)
  369. def delete(self, snapshot):
  370. snapshot = (snapshot if isinstance(snapshot, AWSSnapshot) else
  371. self.get(snapshot))
  372. if snapshot:
  373. # pylint:disable=protected-access
  374. snapshot._snapshot.delete()
  375. class AWSBucketService(BaseBucketService):
  376. def __init__(self, provider):
  377. super(AWSBucketService, self).__init__(provider)
  378. self.svc = BotoS3Service(provider=self.provider,
  379. cb_resource=AWSBucket,
  380. boto_collection_name='buckets')
  381. @dispatch(event="provider.storage.buckets.get",
  382. priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
  383. def get(self, bucket_id):
  384. """
  385. Returns a bucket given its ID. Returns ``None`` if the bucket
  386. does not exist.
  387. """
  388. try:
  389. # Make a call to make sure the bucket exists. There's an edge case
  390. # where a 403 response can occur when the bucket exists but the
  391. # user simply does not have permissions to access it. See below.
  392. self.provider.s3_conn.meta.client.head_bucket(Bucket=bucket_id)
  393. return AWSBucket(self.provider,
  394. self.provider.s3_conn.Bucket(bucket_id))
  395. except ClientError as e:
  396. # If 403, it means the bucket exists, but the user does not have
  397. # permissions to access the bucket. However, limited operations
  398. # may be permitted (with a session token for example), so return a
  399. # Bucket instance to allow further operations.
  400. # http://stackoverflow.com/questions/32331456/using-boto-upload-file-to-s3-
  401. # sub-folder-when-i-have-no-permissions-on-listing-fo
  402. if e.response['Error']['Code'] == "403":
  403. log.warning("AWS Bucket %s already exists but user doesn't "
  404. "have enough permissions to list its contents."
  405. "Other operations may be available.",
  406. bucket_id)
  407. return AWSBucket(self.provider,
  408. self.provider.s3_conn.Bucket(bucket_id))
  409. # For all other responses, it's assumed that the bucket does not exist.
  410. return None
  411. @dispatch(event="provider.storage.buckets.list",
  412. priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
  413. def list(self, limit=None, marker=None):
  414. return self.svc.list(limit=limit, marker=marker)
  415. @dispatch(event="provider.storage.buckets.create",
  416. priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
  417. def create(self, name, location=None):
  418. AWSBucket.assert_valid_resource_name(name)
  419. location = location or self.provider.region_name
  420. # Due to an API issue in S3, specifying us-east-1 as a
  421. # LocationConstraint results in an InvalidLocationConstraint.
  422. # Therefore, it must be special-cased and omitted altogether.
  423. # See: https://github.com/boto/boto3/issues/125
  424. # In addition, us-east-1 also behaves differently when it comes
  425. # to raising duplicate resource exceptions, so perform a manual
  426. # check
  427. if location == 'us-east-1':
  428. try:
  429. # check whether bucket already exists
  430. self.provider.s3_conn.meta.client.head_bucket(Bucket=name)
  431. except ClientError as e:
  432. if e.response['Error']['Code'] == "404":
  433. # bucket doesn't exist, go ahead and create it
  434. return self.svc.create('create_bucket', Bucket=name)
  435. raise DuplicateResourceException(
  436. 'Bucket already exists with name {0}'.format(name))
  437. else:
  438. try:
  439. return self.svc.create('create_bucket', Bucket=name,
  440. CreateBucketConfiguration={
  441. 'LocationConstraint': location
  442. })
  443. except ClientError as e:
  444. if e.response['Error']['Code'] == "BucketAlreadyOwnedByYou":
  445. raise DuplicateResourceException(
  446. 'Bucket already exists with name {0}'.format(name))
  447. else:
  448. raise
  449. @dispatch(event="provider.storage.buckets.delete",
  450. priority=BaseBucketService.STANDARD_EVENT_PRIORITY)
  451. def delete(self, bucket):
  452. b = bucket if isinstance(bucket, AWSBucket) else self.get(bucket)
  453. if b:
  454. # pylint:disable=protected-access
  455. b._bucket.delete()
  456. class AWSBucketObjectService(BaseBucketObjectService):
  457. def __init__(self, provider):
  458. super(AWSBucketObjectService, self).__init__(provider)
  459. def get(self, bucket, object_id):
  460. try:
  461. # pylint:disable=protected-access
  462. obj = bucket._bucket.Object(object_id)
  463. # load() throws an error if object does not exist
  464. obj.load()
  465. return AWSBucketObject(self.provider, obj)
  466. except ClientError:
  467. return None
  468. def list(self, bucket, limit=None, marker=None, prefix=None):
  469. if prefix:
  470. # pylint:disable=protected-access
  471. boto_objs = bucket._bucket.objects.filter(Prefix=prefix)
  472. else:
  473. # pylint:disable=protected-access
  474. boto_objs = bucket._bucket.objects.all()
  475. objects = [AWSBucketObject(self.provider, obj) for obj in boto_objs]
  476. return ClientPagedResultList(self.provider, objects,
  477. limit=limit, marker=marker)
  478. def find(self, bucket, **kwargs):
  479. # pylint:disable=protected-access
  480. obj_list = [AWSBucketObject(self.provider, o)
  481. for o in bucket._bucket.objects.all()]
  482. filters = ['name']
  483. matches = cb_helpers.generic_find(filters, kwargs, obj_list)
  484. return ClientPagedResultList(self.provider, list(matches),
  485. limit=None, marker=None)
  486. def create(self, bucket, name):
  487. # pylint:disable=protected-access
  488. obj = bucket._bucket.Object(name)
  489. return AWSBucketObject(self.provider, obj)
  490. class AWSComputeService(BaseComputeService):
  491. def __init__(self, provider):
  492. super(AWSComputeService, self).__init__(provider)
  493. self._vm_type_svc = AWSVMTypeService(self.provider)
  494. self._instance_svc = AWSInstanceService(self.provider)
  495. self._region_svc = AWSRegionService(self.provider)
  496. self._images_svc = AWSImageService(self.provider)
  497. @property
  498. def images(self):
  499. return self._images_svc
  500. @property
  501. def vm_types(self):
  502. return self._vm_type_svc
  503. @property
  504. def instances(self):
  505. return self._instance_svc
  506. @property
  507. def regions(self):
  508. return self._region_svc
  509. class AWSImageService(BaseImageService):
  510. def __init__(self, provider):
  511. super(AWSImageService, self).__init__(provider)
  512. self.svc = BotoEC2Service(provider=self.provider,
  513. cb_resource=AWSMachineImage,
  514. boto_collection_name='images')
  515. def get(self, image_id):
  516. log.debug("Getting AWS Image Service with the id: %s", image_id)
  517. return self.svc.get(image_id)
  518. def find(self, **kwargs):
  519. # Filter by name or label
  520. label = kwargs.pop('label', None)
  521. # Popped here, not used in the generic find
  522. owner = kwargs.pop('owners', None)
  523. # All kwargs should have been popped at this time.
  524. if len(kwargs) > 0:
  525. raise InvalidParamException(
  526. "Unrecognised parameters for search: %s. Supported "
  527. "attributes: %s" % (kwargs, 'label'))
  528. extra_args = {}
  529. if owner:
  530. extra_args.update(Owners=owner)
  531. # The original list is made by combining both searches by "tag:Name"
  532. # and "AMI name" to allow for searches of public images
  533. if label:
  534. log.debug("Searching for AWS Image Service %s", label)
  535. obj_list = []
  536. obj_list.extend(
  537. self.svc.find(filters={'name': label}, **extra_args))
  538. obj_list.extend(
  539. self.svc.find(filters={'tag:Name': label}, **extra_args))
  540. return obj_list
  541. else:
  542. return []
  543. def list(self, filter_by_owner=True, limit=None, marker=None):
  544. return self.svc.list(Owners=['self'] if filter_by_owner else
  545. ['amazon', 'self'],
  546. limit=limit, marker=marker)
  547. class AWSInstanceService(BaseInstanceService):
  548. def __init__(self, provider):
  549. super(AWSInstanceService, self).__init__(provider)
  550. self.svc = BotoEC2Service(provider=self.provider,
  551. cb_resource=AWSInstance,
  552. boto_collection_name='instances')
  553. def _resolve_launch_options(self, subnet=None, zone_id=None,
  554. vm_firewalls=None):
  555. """
  556. Work out interdependent launch options.
  557. Some launch options are required and interdependent so make sure
  558. they conform to the interface contract.
  559. :type subnet: ``Subnet``
  560. :param subnet: Subnet object within which to launch.
  561. :type zone_id: ``str``
  562. :param zone_id: ID of the zone where the launch should happen.
  563. :type vm_firewalls: ``list`` of ``id``
  564. :param vm_firewalls: List of firewall IDs.
  565. :rtype: triplet of ``str``
  566. :return: Subnet ID, zone ID and VM firewall IDs for launch.
  567. :raise ValueError: In case a conflicting combination is found.
  568. """
  569. if subnet:
  570. # subnet's zone takes precedence
  571. zone_id = subnet.zone.id
  572. if vm_firewalls and isinstance(vm_firewalls, list) and isinstance(
  573. vm_firewalls[0], VMFirewall):
  574. vm_firewall_ids = [fw.id for fw in vm_firewalls]
  575. else:
  576. vm_firewall_ids = vm_firewalls
  577. return subnet.id if subnet else None, zone_id, vm_firewall_ids
  578. def _process_block_device_mappings(self, launch_config):
  579. """
  580. Processes block device mapping information
  581. and returns a Boto BlockDeviceMapping object. If new volumes
  582. are requested (source is None and destination is VOLUME), they will be
  583. created and the relevant volume ids included in the mapping.
  584. """
  585. bdml = []
  586. # Assign letters from f onwards
  587. # http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/device_naming.html
  588. next_letter = iter(list(string.ascii_lowercase[6:]))
  589. # assign ephemeral devices from 0 onwards
  590. ephemeral_counter = 0
  591. for device in launch_config.block_devices:
  592. bdm = {}
  593. if device.is_volume:
  594. # Generate the device path
  595. bdm['DeviceName'] = \
  596. '/dev/sd' + ('a1' if device.is_root else next(next_letter))
  597. ebs_def = {}
  598. if isinstance(device.source, Snapshot):
  599. ebs_def['SnapshotId'] = device.source.id
  600. elif isinstance(device.source, Volume):
  601. # TODO: We could create a snapshot from the volume
  602. # and use that instead.
  603. # Not supported
  604. pass
  605. elif isinstance(device.source, MachineImage):
  606. # Not supported
  607. pass
  608. else:
  609. # source is None, but destination is volume, therefore
  610. # create a blank volume. This requires a size though.
  611. if not device.size:
  612. raise InvalidConfigurationException(
  613. "The source is none and the destination is a"
  614. " volume. Therefore, you must specify a size.")
  615. ebs_def['DeleteOnTermination'] = device.delete_on_terminate \
  616. or True
  617. if device.size:
  618. ebs_def['VolumeSize'] = device.size
  619. if ebs_def:
  620. bdm['Ebs'] = ebs_def
  621. else: # device is ephemeral
  622. bdm['VirtualName'] = 'ephemeral%s' % ephemeral_counter
  623. # Append the config
  624. bdml.append(bdm)
  625. return bdml
  626. def create_launch_config(self):
  627. return AWSLaunchConfig(self.provider)
  628. @dispatch(event="provider.compute.instances.create",
  629. priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
  630. def create(self, label, image, vm_type, subnet,
  631. key_pair=None, vm_firewalls=None, user_data=None,
  632. launch_config=None, **kwargs):
  633. AWSInstance.assert_valid_resource_label(label)
  634. image_id = image.id if isinstance(image, MachineImage) else image
  635. vm_size = vm_type.id if \
  636. isinstance(vm_type, VMType) else vm_type
  637. subnet = (self.provider.networking.subnets.get(subnet)
  638. if isinstance(subnet, str) else subnet)
  639. zone_name = self.provider.zone_name
  640. key_pair_name = key_pair.name if isinstance(
  641. key_pair,
  642. KeyPair) else key_pair
  643. if launch_config:
  644. bdm = self._process_block_device_mappings(launch_config)
  645. else:
  646. bdm = None
  647. subnet_id, zone_id, vm_firewall_ids = \
  648. self._resolve_launch_options(subnet, zone_name, vm_firewalls)
  649. placement = {'AvailabilityZone': zone_id} if zone_id else None
  650. inst = self.svc.create(
  651. 'create_instances',
  652. ImageId=image_id,
  653. MinCount=1,
  654. MaxCount=1,
  655. KeyName=key_pair_name,
  656. SecurityGroupIds=vm_firewall_ids or None,
  657. UserData=str(user_data) or None,
  658. InstanceType=vm_size,
  659. Placement=placement,
  660. BlockDeviceMappings=bdm,
  661. SubnetId=subnet_id,
  662. IamInstanceProfile=kwargs.pop('iam_instance_profile', None)
  663. )
  664. if inst and len(inst) == 1:
  665. # Wait until the resource exists
  666. # pylint:disable=protected-access
  667. inst[0]._wait_till_exists()
  668. # Tag the instance w/ the name
  669. inst[0].label = label
  670. return inst[0]
  671. raise ValueError(
  672. 'Expected a single object response, got a list: %s' % inst)
  673. @dispatch(event="provider.compute.instances.get",
  674. priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
  675. def get(self, instance_id):
  676. return self.svc.get(instance_id)
  677. @dispatch(event="provider.compute.instances.find",
  678. priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
  679. def find(self, **kwargs):
  680. label = kwargs.pop('label', None)
  681. # All kwargs should have been popped at this time.
  682. if len(kwargs) > 0:
  683. raise InvalidParamException(
  684. "Unrecognised parameters for search: %s. Supported "
  685. "attributes: %s" % (kwargs, 'label'))
  686. return self.svc.find(
  687. filters={'tag:Name': label,
  688. 'availability-zone': self.provider.zone_name})
  689. @dispatch(event="provider.compute.instances.list",
  690. priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
  691. def list(self, limit=None, marker=None):
  692. return self.svc.find(
  693. filters={'availability-zone': self.provider.zone_name},
  694. limit=limit, marker=marker)
  695. @dispatch(event="provider.compute.instances.delete",
  696. priority=BaseInstanceService.STANDARD_EVENT_PRIORITY)
  697. def delete(self, instance):
  698. aws_inst = (instance if isinstance(instance, AWSInstance) else
  699. self.get(instance))
  700. if aws_inst:
  701. # pylint:disable=protected-access
  702. aws_inst._ec2_instance.terminate()
  703. class AWSVMTypeService(BaseVMTypeService):
  704. def __init__(self, provider):
  705. super(AWSVMTypeService, self).__init__(provider)
  706. @property
  707. @cachetools.cached(cachetools.TTLCache(maxsize=1, ttl=24*3600))
  708. def instance_data(self):
  709. """
  710. Fetch info about the available instances.
  711. To update this information, update the file pointed to by the
  712. ``provider.AWS_INSTANCE_DATA_DEFAULT_URL`` above. The content for this
  713. file should be obtained from this repo:
  714. https://github.com/powdahound/ec2instances.info (in particular, this
  715. file: https://raw.githubusercontent.com/powdahound/ec2instances.info/
  716. master/www/instances.json).
  717. """
  718. r = requests.get(self.provider.config.get(
  719. "aws_instance_info_url",
  720. self.provider.AWS_INSTANCE_DATA_DEFAULT_URL))
  721. # Some instances are only available in certain regions. Use pricing
  722. # info to determine and filter out instance types that are not
  723. # available in the current region
  724. vm_types_list = r.json()
  725. return [vm_type for vm_type in vm_types_list
  726. if vm_type.get('pricing', {}).get(self.provider.region_name)]
  727. @dispatch(event="provider.compute.vm_types.list",
  728. priority=BaseVMTypeService.STANDARD_EVENT_PRIORITY)
  729. def list(self, limit=None, marker=None):
  730. vm_types = [AWSVMType(self.provider, vm_type)
  731. for vm_type in self.instance_data]
  732. return ClientPagedResultList(self.provider, vm_types,
  733. limit=limit, marker=marker)
  734. class AWSRegionService(BaseRegionService):
  735. def __init__(self, provider):
  736. super(AWSRegionService, self).__init__(provider)
  737. @dispatch(event="provider.compute.regions.get",
  738. priority=BaseRegionService.STANDARD_EVENT_PRIORITY)
  739. def get(self, region_id):
  740. log.debug("Getting AWS Region Service with the id: %s",
  741. region_id)
  742. region = [r for r in self if r.id == region_id]
  743. if region:
  744. return region[0]
  745. else:
  746. return None
  747. @dispatch(event="provider.compute.regions.list",
  748. priority=BaseRegionService.STANDARD_EVENT_PRIORITY)
  749. def list(self, limit=None, marker=None):
  750. regions = [
  751. AWSRegion(self.provider, region) for region in
  752. self.provider.ec2_conn.meta.client.describe_regions()
  753. .get('Regions', [])]
  754. return ClientPagedResultList(self.provider, regions,
  755. limit=limit, marker=marker)
  756. @property
  757. def current(self):
  758. return self.get(self._provider.region_name)
  759. class AWSNetworkingService(BaseNetworkingService):
  760. def __init__(self, provider):
  761. super(AWSNetworkingService, self).__init__(provider)
  762. self._network_service = AWSNetworkService(self.provider)
  763. self._subnet_service = AWSSubnetService(self.provider)
  764. self._router_service = AWSRouterService(self.provider)
  765. self._gateway_service = AWSGatewayService(self.provider)
  766. self._floating_ip_service = AWSFloatingIPService(self.provider)
  767. @property
  768. def networks(self):
  769. return self._network_service
  770. @property
  771. def subnets(self):
  772. return self._subnet_service
  773. @property
  774. def routers(self):
  775. return self._router_service
  776. @property
  777. def _gateways(self):
  778. return self._gateway_service
  779. @property
  780. def _floating_ips(self):
  781. return self._floating_ip_service
  782. class AWSNetworkService(BaseNetworkService):
  783. def __init__(self, provider):
  784. super(AWSNetworkService, self).__init__(provider)
  785. self.svc = BotoEC2Service(provider=self.provider,
  786. cb_resource=AWSNetwork,
  787. boto_collection_name='vpcs')
  788. @dispatch(event="provider.networking.networks.get",
  789. priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
  790. def get(self, network_id):
  791. return self.svc.get(network_id)
  792. @dispatch(event="provider.networking.networks.list",
  793. priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
  794. def list(self, limit=None, marker=None):
  795. return self.svc.list(limit=limit, marker=marker)
  796. @dispatch(event="provider.networking.networks.find",
  797. priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
  798. def find(self, **kwargs):
  799. label = kwargs.pop('label', None)
  800. # All kwargs should have been popped at this time.
  801. if len(kwargs) > 0:
  802. raise InvalidParamException(
  803. "Unrecognised parameters for search: %s. Supported "
  804. "attributes: %s" % (kwargs, 'label'))
  805. log.debug("Searching for AWS Network Service %s", label)
  806. return self.svc.find(filters={'tag:Name': label})
  807. @dispatch(event="provider.networking.networks.create",
  808. priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
  809. def create(self, label, cidr_block):
  810. AWSNetwork.assert_valid_resource_label(label)
  811. cb_net = self.svc.create('create_vpc', CidrBlock=cidr_block)
  812. # Wait until ready to tag instance
  813. cb_net.wait_till_ready()
  814. if label:
  815. cb_net.label = label
  816. self.provider.ec2_conn.meta.client.modify_vpc_attribute(
  817. VpcId=cb_net.id, EnableDnsHostnames={'Value': True})
  818. return cb_net
  819. @dispatch(event="provider.networking.networks.delete",
  820. priority=BaseNetworkService.STANDARD_EVENT_PRIORITY)
  821. def delete(self, network):
  822. network = (network if isinstance(network, AWSNetwork)
  823. else self.get(network))
  824. if network:
  825. # pylint:disable=protected-access
  826. network._vpc.delete()
  827. def get_or_create_default(self):
  828. # # Look for provided default network
  829. # for net in self.provider.networking.networks:
  830. # pylint:disable=protected-access
  831. # if net._vpc.is_default:
  832. # return net
  833. # No provider-default, try CB-default instead
  834. default_nets = self.provider.networking.networks.find(
  835. label=AWSNetwork.CB_DEFAULT_NETWORK_LABEL)
  836. if default_nets:
  837. return default_nets[0]
  838. else:
  839. log.info("Creating a CloudBridge-default network labeled %s",
  840. AWSNetwork.CB_DEFAULT_NETWORK_LABEL)
  841. return self.provider.networking.networks.create(
  842. label=AWSNetwork.CB_DEFAULT_NETWORK_LABEL,
  843. cidr_block=AWSNetwork.CB_DEFAULT_IPV4RANGE)
  844. class AWSSubnetService(BaseSubnetService):
  845. def __init__(self, provider):
  846. super(AWSSubnetService, self).__init__(provider)
  847. self.svc = BotoEC2Service(provider=self.provider,
  848. cb_resource=AWSSubnet,
  849. boto_collection_name='subnets')
  850. @dispatch(event="provider.networking.subnets.get",
  851. priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
  852. def get(self, subnet_id):
  853. return self.svc.get(subnet_id)
  854. @dispatch(event="provider.networking.subnets.list",
  855. priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
  856. def list(self, network=None, limit=None, marker=None):
  857. network_id = network.id if isinstance(network, AWSNetwork) else network
  858. if network_id:
  859. return self.svc.find(
  860. filters={'vpc-id': network_id,
  861. 'availability-zone': self.provider.zone_name},
  862. limit=limit, marker=marker)
  863. else:
  864. return self.svc.find(
  865. filters={'availability-zone': self.provider.zone_name},
  866. limit=limit, marker=marker)
  867. @dispatch(event="provider.networking.subnets.find",
  868. priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
  869. def find(self, network=None, **kwargs):
  870. label = kwargs.pop('label', None)
  871. # All kwargs should have been popped at this time.
  872. if len(kwargs) > 0:
  873. raise InvalidParamException(
  874. "Unrecognised parameters for search: %s. Supported "
  875. "attributes: %s" % (kwargs, 'label'))
  876. log.debug("Searching for AWS Subnet Service %s", label)
  877. return self.svc.find(
  878. filters={'tag:Name': label,
  879. 'availability-zone': self.provider.zone_name})
  880. @dispatch(event="provider.networking.subnets.create",
  881. priority=BaseSubnetService.STANDARD_EVENT_PRIORITY)
  882. def create(self, label, network, cidr_block):
  883. AWSSubnet.assert_valid_resource_label(label)
  884. zone_name = self.provider.zone_name
  885. network_id = network.id if isinstance(network, AWSNetwork) else network
  886. subnet = self.svc.create('create_subnet',
  887. VpcId=network_id,
  888. CidrBlock=cidr_block,
  889. AvailabilityZone=zone_name)
  890. if label:
  891. subnet.label = label
  892. return 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, AWSSubnet) else self.get(subnet)
  897. if sn:
  898. # pylint:disable=protected-access
  899. sn._subnet.delete()
  900. def get_or_create_default(self):
  901. zone_name = self.provider.zone_name
  902. # # Look for provider default subnet in current zone
  903. # if zone_name:
  904. # snl = self.svc.find('availabilityZone', zone_name)
  905. #
  906. # else:
  907. # snl = self.svc.list()
  908. # # Find first available default subnet by sorted order
  909. # # of availability zone. Prefer zone us-east-1a over 1e,
  910. # # because newer zones tend to have less compatibility
  911. # # with different instance types (e.g. c5.large not available
  912. # # on us-east-1e as of 14 Dec. 2017).
  913. # # pylint:disable=protected-access
  914. # snl.sort(key=lambda sn: sn._subnet.availability_zone)
  915. #
  916. # for sn in snl:
  917. # # pylint:disable=protected-access
  918. # if sn._subnet.default_for_az:
  919. # return sn
  920. # If no provider-default subnet has been found, look for
  921. # cloudbridge-default by label. We suffix labels by availability zone,
  922. # thus we add the wildcard for the regular expression to find the
  923. # subnet
  924. snl = self.find(label=AWSSubnet.CB_DEFAULT_SUBNET_LABEL + "*")
  925. if snl:
  926. # pylint:disable=protected-access
  927. snl.sort(key=lambda sn: sn._subnet.availability_zone)
  928. for subnet in snl:
  929. if subnet.zone.name == zone_name:
  930. return subnet
  931. # No default Subnet exists, try to create a CloudBridge-specific
  932. # subnet. This involves creating the network, subnets, internet
  933. # gateway, and connecting it all together so that the network has
  934. # Internet connectivity.
  935. # Check if a default net already exists and get it or create on
  936. default_net = self.provider.networking.networks.get_or_create_default()
  937. # Get/create an internet gateway for the default network and a
  938. # corresponding router if it does not already exist.
  939. # NOTE: Comment this out because the docs instruct users to setup
  940. # network connectivity manually. There's a bit of discrepancy here
  941. # though because the provider-default network will have Internet
  942. # connectivity (unlike the CloudBridge-default network with this
  943. # being commented) and is hence left in the codebase.
  944. # default_gtw = default_net.gateways.get_or_create()
  945. # router_label = "{0}-router".format(
  946. # AWSNetwork.CB_DEFAULT_NETWORK_LABEL)
  947. # default_routers = self.provider.networking.routers.find(
  948. # label=router_label)
  949. # if len(default_routers) == 0:
  950. # default_router = self.provider.networking.routers.create(
  951. # router_label, default_net)
  952. # default_router.attach_gateway(default_gtw)
  953. # else:
  954. # default_router = default_routers[0]
  955. # Create a subnet in each of the region's zones
  956. region = self.provider.compute.regions.get(self.provider.region_name)
  957. default_sn = None
  958. # Determine how many subnets we'll need for the default network and the
  959. # number of available zones. We need to derive a non-overlapping
  960. # network size for each subnet within the parent net so figure those
  961. # subnets here. `<net>.subnets` method will do this but we need to give
  962. # it a prefix. Determining that prefix depends on the size of the
  963. # network and should be incorporate the number of zones. So iterate
  964. # over potential number of subnets until enough can be created to
  965. # accommodate the number of available zones. That is where the fixed
  966. # number comes from in the for loop as that many iterations will yield
  967. # more potential subnets than any region has zones.
  968. ip_net = ipaddress.ip_network(AWSNetwork.CB_DEFAULT_IPV4RANGE)
  969. for x in range(5):
  970. if len(region.zones) <= len(list(ip_net.subnets(
  971. prefixlen_diff=x))):
  972. prefixlen_diff = x
  973. break
  974. subnets = list(ip_net.subnets(prefixlen_diff=prefixlen_diff))
  975. for i, z in reversed(list(enumerate(region.zones))):
  976. if zone_name == z.name:
  977. sn_label = "{0}-{1}".format(AWSSubnet.CB_DEFAULT_SUBNET_LABEL,
  978. z.id[-1])
  979. log.info("Creating a default CloudBridge subnet %s: %s" %
  980. (sn_label, str(subnets[i])))
  981. sn = self.create(sn_label, default_net, str(subnets[i]))
  982. # Create a route table entry between the SN and the inet
  983. # gateway. See note above about why this is commented
  984. # default_router.attach_subnet(sn)
  985. default_sn = sn
  986. return default_sn
  987. class AWSRouterService(BaseRouterService):
  988. """For AWS, a CloudBridge router corresponds to an AWS Route Table."""
  989. def __init__(self, provider):
  990. super(AWSRouterService, self).__init__(provider)
  991. self.svc = BotoEC2Service(provider=self.provider,
  992. cb_resource=AWSRouter,
  993. boto_collection_name='route_tables')
  994. @dispatch(event="provider.networking.routers.get",
  995. priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
  996. def get(self, router_id):
  997. return self.svc.get(router_id)
  998. @dispatch(event="provider.networking.routers.find",
  999. priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
  1000. def find(self, **kwargs):
  1001. label = kwargs.pop('label', None)
  1002. # All kwargs should have been popped at this time.
  1003. if len(kwargs) > 0:
  1004. raise InvalidParamException(
  1005. "Unrecognised parameters for search: %s. Supported "
  1006. "attributes: %s" % (kwargs, 'label'))
  1007. log.debug("Searching for AWS Router Service %s", label)
  1008. return self.svc.find(filters={'tag:Name': label})
  1009. @dispatch(event="provider.networking.routers.list",
  1010. priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
  1011. def list(self, limit=None, marker=None):
  1012. return self.svc.list(limit=limit, marker=marker)
  1013. @dispatch(event="provider.networking.routers.create",
  1014. priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
  1015. def create(self, label, network):
  1016. network_id = network.id if isinstance(network, AWSNetwork) else network
  1017. cb_router = self.svc.create('create_route_table', VpcId=network_id)
  1018. if label:
  1019. cb_router.label = label
  1020. return cb_router
  1021. @dispatch(event="provider.networking.routers.delete",
  1022. priority=BaseRouterService.STANDARD_EVENT_PRIORITY)
  1023. def delete(self, router):
  1024. r = router if isinstance(router, AWSRouter) else self.get(router)
  1025. if r:
  1026. # pylint:disable=protected-access
  1027. r._route_table.delete()
  1028. class AWSGatewayService(BaseGatewayService):
  1029. def __init__(self, provider):
  1030. super(AWSGatewayService, self).__init__(provider)
  1031. self.svc = BotoEC2Service(provider=provider,
  1032. cb_resource=AWSInternetGateway,
  1033. boto_collection_name='internet_gateways')
  1034. @dispatch(event="provider.networking.gateways.get_or_create",
  1035. priority=BaseGatewayService.STANDARD_EVENT_PRIORITY)
  1036. def get_or_create(self, network):
  1037. network_id = network.id if isinstance(
  1038. network, AWSNetwork) else network
  1039. # Don't filter by label because it may conflict with at least the
  1040. # default VPC that most accounts have but that network is typically
  1041. # without a name.
  1042. gtw = self.svc.find(filters={'attachment.vpc-id': network_id})
  1043. if gtw:
  1044. return gtw[0] # There can be only one gtw attached to a VPC
  1045. # Gateway does not exist so create one and attach to the supplied net
  1046. cb_gateway = self.svc.create('create_internet_gateway')
  1047. cb_gateway._gateway.create_tags(
  1048. Tags=[{'Key': 'Name',
  1049. 'Value': AWSInternetGateway.CB_DEFAULT_INET_GATEWAY_NAME
  1050. }])
  1051. cb_gateway._gateway.attach_to_vpc(VpcId=network_id)
  1052. return cb_gateway
  1053. @dispatch(event="provider.networking.gateways.delete",
  1054. priority=BaseGatewayService.STANDARD_EVENT_PRIORITY)
  1055. def delete(self, network, gateway):
  1056. gw = (gateway if isinstance(gateway, AWSInternetGateway)
  1057. else self.svc.get(gateway))
  1058. try:
  1059. if gw.network_id:
  1060. # pylint:disable=protected-access
  1061. gw._gateway.detach_from_vpc(VpcId=gw.network_id)
  1062. except ClientError as e:
  1063. log.warn("Error deleting gateway {0}: {1}".format(self.id, e))
  1064. # pylint:disable=protected-access
  1065. gw._gateway.delete()
  1066. @dispatch(event="provider.networking.gateways.list",
  1067. priority=BaseGatewayService.STANDARD_EVENT_PRIORITY)
  1068. def list(self, network, limit=None, marker=None):
  1069. log.debug("Listing current AWS internet gateways for net %s.",
  1070. network.id)
  1071. fltr = [{'Name': 'attachment.vpc-id', 'Values': [network.id]}]
  1072. return self.svc.list(limit=None, marker=None, Filters=fltr)
  1073. class AWSFloatingIPService(BaseFloatingIPService):
  1074. def __init__(self, provider):
  1075. super(AWSFloatingIPService, self).__init__(provider)
  1076. self.svc = BotoEC2Service(provider=self.provider,
  1077. cb_resource=AWSFloatingIP,
  1078. boto_collection_name='vpc_addresses')
  1079. @dispatch(event="provider.networking.floating_ips.get",
  1080. priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
  1081. def get(self, gateway, fip_id):
  1082. log.debug("Getting AWS Floating IP Service with the id: %s", fip_id)
  1083. return self.svc.get(fip_id)
  1084. @dispatch(event="provider.networking.floating_ips.list",
  1085. priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
  1086. def list(self, gateway, limit=None, marker=None):
  1087. return self.svc.list(limit, marker)
  1088. @dispatch(event="provider.networking.floating_ips.create",
  1089. priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
  1090. def create(self, gateway):
  1091. log.debug("Creating a floating IP under gateway %s", gateway)
  1092. ip = self.provider.ec2_conn.meta.client.allocate_address(
  1093. Domain='vpc')
  1094. return AWSFloatingIP(
  1095. self.provider,
  1096. self.provider.ec2_conn.VpcAddress(ip.get('AllocationId')))
  1097. @dispatch(event="provider.networking.floating_ips.delete",
  1098. priority=BaseFloatingIPService.STANDARD_EVENT_PRIORITY)
  1099. def delete(self, gateway, fip):
  1100. if isinstance(fip, AWSFloatingIP):
  1101. # pylint:disable=protected-access
  1102. aws_fip = fip._ip
  1103. else:
  1104. aws_fip = self.svc.get_raw(fip)
  1105. aws_fip.release()
  1106. class AWSDnsService(BaseDnsService):
  1107. def __init__(self, provider):
  1108. super(AWSDnsService, self).__init__(provider)
  1109. self.client = self._provider.session.client(
  1110. 'route53', region_name=self._provider.region_name)
  1111. # Initialize provider services
  1112. self._zone_svc = AWSDnsZoneService(self.provider)
  1113. self._record_svc = AWSDnsRecordService(self.provider)
  1114. @property
  1115. def host_zones(self):
  1116. return self._zone_svc
  1117. @property
  1118. def _records(self):
  1119. return self._record_svc
  1120. class AWSDnsZoneService(BaseDnsZoneService):
  1121. def __init__(self, provider):
  1122. super(AWSDnsZoneService, self).__init__(provider)
  1123. @dispatch(event="provider.dns.host_zones.get",
  1124. priority=BaseDnsZoneService.STANDARD_EVENT_PRIORITY)
  1125. def get(self, dns_zone_id):
  1126. try:
  1127. dns_zone = self.provider.dns.client.get_hosted_zone(
  1128. Id=AWSDnsZone.unescape_zone_id(dns_zone_id))
  1129. return AWSDnsZone(self.provider, dns_zone.get('HostedZone'))
  1130. except self.provider.dns.client.exceptions.NoSuchHostedZone:
  1131. return None
  1132. except ClientError as exc:
  1133. error_code = exc.response['Error']['Code']
  1134. if any(status in error_code for status in
  1135. ('NotFound', 'InvalidParameterValue', 'Malformed', '404')):
  1136. log.debug("Object not found: %s", dns_zone_id)
  1137. return None
  1138. else:
  1139. raise exc
  1140. @dispatch(event="provider.dns.host_zones.list",
  1141. priority=BaseDnsZoneService.STANDARD_EVENT_PRIORITY)
  1142. def list(self, limit=None, marker=None):
  1143. response = self.provider.dns.client.list_hosted_zones(
  1144. **trim_empty_params({'MaxItems': limit, 'Marker': marker}))
  1145. cb_objs = [AWSDnsZone(self.provider, zone)
  1146. for zone in response.get('HostedZones')]
  1147. return ServerPagedResultList(is_truncated=response.get('IsTruncated'),
  1148. marker=response.get('NextMarker'),
  1149. supports_total=False,
  1150. data=cb_objs)
  1151. @dispatch(event="provider.dns.host_zones.find",
  1152. priority=BaseDnsZoneService.STANDARD_EVENT_PRIORITY)
  1153. def find(self, **kwargs):
  1154. filters = ['name']
  1155. matches = cb_helpers.generic_find(filters, kwargs, self)
  1156. return ClientPagedResultList(self.provider, list(matches),
  1157. limit=None, marker=None)
  1158. @dispatch(event="provider.dns.host_zones.create",
  1159. priority=BaseDnsZoneService.STANDARD_EVENT_PRIORITY)
  1160. def create(self, name, admin_email):
  1161. AWSDnsZone.assert_valid_resource_name(name)
  1162. response = self.provider.dns.client.create_hosted_zone(
  1163. Name=name, CallerReference=uuid.uuid4().hex,
  1164. HostedZoneConfig={
  1165. 'Comment': 'admin_email=' + admin_email
  1166. }
  1167. )
  1168. return AWSDnsZone(self.provider, response.get('HostedZone'))
  1169. @dispatch(event="provider.dns.host_zones.delete",
  1170. priority=BaseDnsZoneService.STANDARD_EVENT_PRIORITY)
  1171. def delete(self, dns_zone):
  1172. dns_zone = (dns_zone if isinstance(dns_zone, AWSDnsZone)
  1173. else self.get(dns_zone))
  1174. if dns_zone:
  1175. self.provider.dns.client.delete_hosted_zone(Id=dns_zone.aws_id)
  1176. class AWSDnsRecordService(BaseDnsRecordService):
  1177. def __init__(self, provider):
  1178. super(AWSDnsRecordService, self).__init__(provider)
  1179. def get(self, dns_zone, rec_id):
  1180. try:
  1181. if rec_id and ":" in rec_id:
  1182. rec_name, rec_type = rec_id.split(":")
  1183. response = self.provider.dns.client.list_resource_record_sets(
  1184. HostedZoneId=dns_zone.aws_id,
  1185. StartRecordName=rec_name,
  1186. StartRecordType=rec_type)
  1187. return AWSDnsRecord(self.provider, dns_zone,
  1188. response.get('ResourceRecordSets')[0])
  1189. else:
  1190. return None
  1191. except ClientError as exc:
  1192. error_code = exc.response['Error']['Code']
  1193. if any(status in error_code for status in
  1194. ('NotFound', 'InvalidParameterValue', 'Malformed', '404')):
  1195. log.debug("Object not found: %s", rec_id)
  1196. return None
  1197. else:
  1198. raise exc
  1199. def list(self, dns_zone, limit=None, marker=None):
  1200. response = self.provider.dns.client.list_resource_record_sets(
  1201. **trim_empty_params({
  1202. 'HostedZoneId': dns_zone.aws_id,
  1203. 'MaxItems': limit,
  1204. 'StartRecordIdentifier': marker
  1205. })
  1206. )
  1207. cb_objs = [AWSDnsRecord(self.provider, dns_zone, rec)
  1208. for rec in response.get('ResourceRecordSets')]
  1209. return ServerPagedResultList(
  1210. is_truncated=response.get('IsTruncated'),
  1211. marker=response.get('NextRecordIdentifier'),
  1212. supports_total=False, data=cb_objs)
  1213. def find(self, dns_zone, **kwargs):
  1214. filters = ['name']
  1215. matches = cb_helpers.generic_find(filters, kwargs, dns_zone.records)
  1216. return ClientPagedResultList(self.provider, list(matches),
  1217. limit=None, marker=None)
  1218. def _to_resource_records(self, data, rec_type):
  1219. if isinstance(data, list):
  1220. records = data
  1221. else:
  1222. records = [data]
  1223. return [{'Value': self._standardize_record(r, rec_type)}
  1224. for r in records]
  1225. def create(self, dns_zone, name, type, data, ttl=None):
  1226. AWSDnsRecord.assert_valid_resource_name(name)
  1227. response = self.provider.dns.client.change_resource_record_sets(
  1228. HostedZoneId=dns_zone.aws_id,
  1229. ChangeBatch={
  1230. 'Changes': [{
  1231. 'Action': 'CREATE',
  1232. 'ResourceRecordSet': trim_empty_params({
  1233. 'Name': name,
  1234. 'Type': type,
  1235. 'TTL': ttl or 300,
  1236. 'ResourceRecords': self._to_resource_records(
  1237. data, type)
  1238. })
  1239. }]
  1240. }
  1241. )
  1242. # FIXME: Since Moto's implementation of route53 doesn't support
  1243. # waiting, this is skipped for mock tests.
  1244. if not self.provider.PROVIDER_ID == 'mock':
  1245. waiter = self.provider.dns.client.get_waiter(
  1246. 'resource_record_sets_changed')
  1247. waiter.wait(Id=response.get('ChangeInfo').get('Id'))
  1248. return self.get(dns_zone, name + ":" + type)
  1249. def delete(self, dns_zone, record):
  1250. rec_id = record.id if isinstance(record, AWSDnsRecord) else record
  1251. rec_name, rec_type = rec_id.split(":")
  1252. response = self.provider.dns.client.change_resource_record_sets(
  1253. HostedZoneId=dns_zone.aws_id,
  1254. ChangeBatch={
  1255. 'Changes': [{
  1256. 'Action': 'DELETE',
  1257. 'ResourceRecordSet': {
  1258. 'Name': rec_name,
  1259. 'Type': rec_type,
  1260. 'TTL': record.ttl,
  1261. 'ResourceRecords': self._to_resource_records(
  1262. record.data, rec_type)
  1263. }
  1264. }]
  1265. })
  1266. # FIXME: Since Moto's implementation of route53 doesn't support
  1267. # waiting, this is skipped for mock tests.
  1268. if not self.provider.PROVIDER_ID == 'mock':
  1269. waiter = self.provider.dns.client.get_waiter(
  1270. 'resource_record_sets_changed')
  1271. waiter.wait(Id=response.get('ChangeInfo').get('Id'))