services.py 54 KB

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