services.py 53 KB

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