services.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879
  1. """Services implemented by the AWS provider."""
  2. import string
  3. import time
  4. from boto.ec2.blockdevicemapping import BlockDeviceMapping
  5. from boto.ec2.blockdevicemapping import BlockDeviceType
  6. from boto.exception import EC2ResponseError, S3ResponseError
  7. from cloudbridge.cloud.base.resources import ClientPagedResultList
  8. from cloudbridge.cloud.base.resources import ServerPagedResultList
  9. from cloudbridge.cloud.base.services import BaseBlockStoreService
  10. from cloudbridge.cloud.base.services import BaseComputeService
  11. from cloudbridge.cloud.base.services import BaseImageService
  12. from cloudbridge.cloud.base.services import BaseInstanceService
  13. from cloudbridge.cloud.base.services import BaseInstanceTypesService
  14. from cloudbridge.cloud.base.services import BaseKeyPairService
  15. from cloudbridge.cloud.base.services import BaseNetworkService
  16. from cloudbridge.cloud.base.services import BaseObjectStoreService
  17. from cloudbridge.cloud.base.services import BaseRegionService
  18. from cloudbridge.cloud.base.services import BaseSecurityGroupService
  19. from cloudbridge.cloud.base.services import BaseSecurityService
  20. from cloudbridge.cloud.base.services import BaseSnapshotService
  21. from cloudbridge.cloud.base.services import BaseSubnetService
  22. from cloudbridge.cloud.base.services import BaseVolumeService
  23. from cloudbridge.cloud.interfaces.exceptions \
  24. import InvalidConfigurationException
  25. from cloudbridge.cloud.interfaces.resources import InstanceState
  26. from cloudbridge.cloud.interfaces.resources import InstanceType
  27. from cloudbridge.cloud.interfaces.resources import KeyPair
  28. from cloudbridge.cloud.interfaces.resources import MachineImage
  29. from cloudbridge.cloud.interfaces.resources import NetworkState
  30. from cloudbridge.cloud.interfaces.resources import PlacementZone
  31. from cloudbridge.cloud.interfaces.resources import SecurityGroup
  32. from cloudbridge.cloud.interfaces.resources import Snapshot
  33. from cloudbridge.cloud.interfaces.resources import SubnetState
  34. from cloudbridge.cloud.interfaces.resources import Volume
  35. import requests
  36. from .resources import AWSBucket
  37. from .resources import AWSFloatingIP
  38. from .resources import AWSInstance
  39. from .resources import AWSInstanceType
  40. from .resources import AWSKeyPair
  41. from .resources import AWSLaunchConfig
  42. from .resources import AWSMachineImage
  43. from .resources import AWSNetwork
  44. from .resources import AWSRegion
  45. from .resources import AWSRouter
  46. from .resources import AWSSecurityGroup
  47. from .resources import AWSSnapshot
  48. from .resources import AWSSubnet
  49. from .resources import AWSVolume
  50. # Uncomment to enable logging by default for this module
  51. # import cloudbridge as cb
  52. # cb.set_stream_logger(__name__)
  53. class AWSSecurityService(BaseSecurityService):
  54. def __init__(self, provider):
  55. super(AWSSecurityService, self).__init__(provider)
  56. # Initialize provider services
  57. self._key_pairs = AWSKeyPairService(provider)
  58. self._security_groups = AWSSecurityGroupService(provider)
  59. @property
  60. def key_pairs(self):
  61. """
  62. Provides access to key pairs for this provider.
  63. :rtype: ``object`` of :class:`.KeyPairService`
  64. :return: a KeyPairService object
  65. """
  66. return self._key_pairs
  67. @property
  68. def security_groups(self):
  69. """
  70. Provides access to security groups for this provider.
  71. :rtype: ``object`` of :class:`.SecurityGroupService`
  72. :return: a SecurityGroupService object
  73. """
  74. return self._security_groups
  75. class AWSKeyPairService(BaseKeyPairService):
  76. def __init__(self, provider):
  77. super(AWSKeyPairService, self).__init__(provider)
  78. def get(self, key_pair_id):
  79. """
  80. Returns a KeyPair given its ID.
  81. """
  82. try:
  83. kps = self.provider.ec2_conn.get_all_key_pairs(
  84. keynames=[key_pair_id])
  85. return AWSKeyPair(self.provider, kps[0])
  86. except EC2ResponseError as ec2e:
  87. if ec2e.code == 'InvalidKeyPair.NotFound':
  88. return None
  89. elif ec2e.code == 'InvalidParameterValue':
  90. return None
  91. else:
  92. raise ec2e
  93. def list(self, limit=None, marker=None):
  94. """
  95. List all key pairs associated with this account.
  96. :rtype: ``list`` of :class:`.KeyPair`
  97. :return: list of KeyPair objects
  98. """
  99. key_pairs = [AWSKeyPair(self.provider, kp)
  100. for kp in self.provider.ec2_conn.get_all_key_pairs()]
  101. return ClientPagedResultList(self.provider, key_pairs,
  102. limit=limit, marker=marker)
  103. def find(self, name, limit=None, marker=None):
  104. """
  105. Searches for a key pair by a given list of attributes.
  106. """
  107. try:
  108. key_pairs = [
  109. AWSKeyPair(self.provider, kp) for kp in
  110. self.provider.ec2_conn.get_all_key_pairs(keynames=[name])]
  111. return ClientPagedResultList(self.provider, key_pairs,
  112. limit=limit, marker=marker)
  113. except EC2ResponseError as ec2e:
  114. if ec2e.code == 'InvalidKeyPair.NotFound':
  115. return []
  116. elif ec2e.code == 'InvalidParameterValue':
  117. return []
  118. else:
  119. raise ec2e
  120. def create(self, name):
  121. """
  122. Create a new key pair or raise an exception if one already exists.
  123. :type name: str
  124. :param name: The name of the key pair to be created.
  125. :rtype: ``object`` of :class:`.KeyPair`
  126. :return: A key pair instance or ``None`` if one was not be created.
  127. """
  128. kp = self.provider.ec2_conn.create_key_pair(name)
  129. if kp:
  130. return AWSKeyPair(self.provider, kp)
  131. return None
  132. class AWSSecurityGroupService(BaseSecurityGroupService):
  133. def __init__(self, provider):
  134. super(AWSSecurityGroupService, self).__init__(provider)
  135. def get(self, sg_id):
  136. """
  137. Returns a SecurityGroup given its id.
  138. """
  139. try:
  140. sgs = self.provider.ec2_conn.get_all_security_groups(
  141. group_ids=[sg_id])
  142. return AWSSecurityGroup(self.provider, sgs[0]) if sgs else None
  143. except EC2ResponseError as ec2e:
  144. if ec2e.code == 'InvalidGroup.NotFound':
  145. return None
  146. elif ec2e.code == 'InvalidGroupId.Malformed':
  147. return None
  148. else:
  149. raise ec2e
  150. def list(self, limit=None, marker=None):
  151. """
  152. List all security groups associated with this account.
  153. :rtype: ``list`` of :class:`.SecurityGroup`
  154. :return: list of SecurityGroup objects
  155. """
  156. sgs = [AWSSecurityGroup(self.provider, sg)
  157. for sg in self.provider.ec2_conn.get_all_security_groups()]
  158. return ClientPagedResultList(self.provider, sgs,
  159. limit=limit, marker=marker)
  160. def create(self, name, description, network_id):
  161. """
  162. Create a new SecurityGroup.
  163. :type name: str
  164. :param name: The name of the new security group.
  165. :type description: str
  166. :param description: The description of the new security group.
  167. :type network_id: ``str``
  168. :param network_id: The ID of the VPC under which to create the security
  169. group.
  170. :rtype: ``object`` of :class:`.SecurityGroup`
  171. :return: A SecurityGroup instance or ``None`` if one was not created.
  172. """
  173. sg = self.provider.ec2_conn.create_security_group(name, description,
  174. network_id)
  175. if sg:
  176. return AWSSecurityGroup(self.provider, sg)
  177. return None
  178. def find(self, name, limit=None, marker=None):
  179. """
  180. Get all security groups associated with your account.
  181. """
  182. flters = {'group-name': name}
  183. ec2_sgs = self.provider.ec2_conn.get_all_security_groups(
  184. filters=flters)
  185. sgs = [AWSSecurityGroup(self.provider, sg) for sg in ec2_sgs]
  186. return ClientPagedResultList(self.provider, sgs,
  187. limit=limit, marker=marker)
  188. def delete(self, group_id):
  189. """
  190. Delete an existing SecurityGroup.
  191. :type group_id: str
  192. :param group_id: The security group ID to be deleted.
  193. :rtype: ``bool``
  194. :return: ``True`` if the security group does not exist, ``False``
  195. otherwise. Note that this implies that the group may not have
  196. been deleted by this method but instead has not existed in
  197. the first place.
  198. """
  199. sg = self.get(group_id)
  200. if sg:
  201. sg.delete()
  202. return True
  203. return False
  204. class AWSBlockStoreService(BaseBlockStoreService):
  205. def __init__(self, provider):
  206. super(AWSBlockStoreService, self).__init__(provider)
  207. # Initialize provider services
  208. self._volume_svc = AWSVolumeService(self.provider)
  209. self._snapshot_svc = AWSSnapshotService(self.provider)
  210. @property
  211. def volumes(self):
  212. return self._volume_svc
  213. @property
  214. def snapshots(self):
  215. return self._snapshot_svc
  216. class AWSVolumeService(BaseVolumeService):
  217. def __init__(self, provider):
  218. super(AWSVolumeService, self).__init__(provider)
  219. def get(self, volume_id):
  220. """
  221. Returns a volume given its id.
  222. """
  223. try:
  224. vols = self.provider.ec2_conn.get_all_volumes(
  225. volume_ids=[volume_id])
  226. return AWSVolume(self.provider, vols[0]) if vols else None
  227. except EC2ResponseError as ec2e:
  228. if ec2e.code == 'InvalidVolume.NotFound':
  229. return None
  230. elif ec2e.code == 'InvalidParameterValue':
  231. # Occurs if volume_id does not start with 'vol-...'
  232. return None
  233. raise ec2e
  234. def find(self, name, limit=None, marker=None):
  235. """
  236. Searches for a volume by a given list of attributes.
  237. """
  238. filtr = {'tag:Name': name}
  239. aws_vols = self.provider.ec2_conn.get_all_volumes(filters=filtr)
  240. cb_vols = [AWSVolume(self.provider, vol) for vol in aws_vols]
  241. return ClientPagedResultList(self.provider, cb_vols,
  242. limit=limit, marker=marker)
  243. def list(self, limit=None, marker=None):
  244. """
  245. List all volumes.
  246. """
  247. aws_vols = self.provider.ec2_conn.get_all_volumes()
  248. cb_vols = [AWSVolume(self.provider, vol) for vol in aws_vols]
  249. return ClientPagedResultList(self.provider, cb_vols,
  250. limit=limit, marker=marker)
  251. def create(self, name, size, zone, snapshot=None, description=None):
  252. """
  253. Creates a new volume.
  254. """
  255. zone_id = zone.id if isinstance(zone, PlacementZone) else zone
  256. snapshot_id = snapshot.id if isinstance(
  257. snapshot, AWSSnapshot) and snapshot else snapshot
  258. ec2_vol = self.provider.ec2_conn.create_volume(
  259. size,
  260. zone_id,
  261. snapshot=snapshot_id)
  262. cb_vol = AWSVolume(self.provider, ec2_vol)
  263. cb_vol.name = name
  264. if description:
  265. cb_vol.description = description
  266. return cb_vol
  267. class AWSSnapshotService(BaseSnapshotService):
  268. def __init__(self, provider):
  269. super(AWSSnapshotService, self).__init__(provider)
  270. def get(self, snapshot_id):
  271. """
  272. Returns a snapshot given its id.
  273. """
  274. try:
  275. snaps = self.provider.ec2_conn.get_all_snapshots(
  276. snapshot_ids=[snapshot_id])
  277. return AWSSnapshot(self.provider, snaps[0]) if snaps else None
  278. except EC2ResponseError as ec2e:
  279. if ec2e.code == 'InvalidSnapshot.NotFound':
  280. return None
  281. elif ec2e.code == 'InvalidParameterValue':
  282. # Occurs if snapshot_id does not start with 'snap-...'
  283. return None
  284. raise ec2e
  285. def find(self, name, limit=None, marker=None):
  286. """
  287. Searches for a snapshot by a given list of attributes.
  288. """
  289. filtr = {'tag-value': name}
  290. snaps = [AWSSnapshot(self.provider, snap) for snap in
  291. self.provider.ec2_conn.get_all_snapshots(filters=filtr)]
  292. return ClientPagedResultList(self.provider, snaps,
  293. limit=limit, marker=marker)
  294. def list(self, limit=None, marker=None):
  295. """
  296. List all snapshots.
  297. """
  298. snaps = [AWSSnapshot(self.provider, snap)
  299. for snap in self.provider.ec2_conn.get_all_snapshots(
  300. owner='self')]
  301. return ClientPagedResultList(self.provider, snaps,
  302. limit=limit, marker=marker)
  303. def create(self, name, volume, description=None):
  304. """
  305. Creates a new snapshot of a given volume.
  306. """
  307. volume_id = volume.id if isinstance(volume, AWSVolume) else volume
  308. ec2_snap = self.provider.ec2_conn.create_snapshot(
  309. volume_id,
  310. description=description)
  311. cb_snap = AWSSnapshot(self.provider, ec2_snap)
  312. cb_snap.name = name
  313. if description:
  314. cb_snap.description = description
  315. return cb_snap
  316. class AWSObjectStoreService(BaseObjectStoreService):
  317. def __init__(self, provider):
  318. super(AWSObjectStoreService, self).__init__(provider)
  319. def get(self, bucket_id):
  320. """
  321. Returns a bucket given its ID. Returns ``None`` if the bucket
  322. does not exist.
  323. """
  324. try:
  325. # Make a call to make sure the bucket exists. While this would
  326. # normally return a Bucket instance, there's an edge case where a
  327. # 403 response can occur when the bucket exists but the
  328. # user simply does not have permissions to access it. See below.
  329. bucket = self.provider.s3_conn.get_bucket(bucket_id)
  330. return AWSBucket(self.provider, bucket)
  331. except S3ResponseError as e:
  332. # If 403, it means the bucket exists, but the user does not have
  333. # permissions to access the bucket. However, limited operations
  334. # may be permitted (with a session token for example), so return a
  335. # Bucket instance to allow further operations.
  336. # http://stackoverflow.com/questions/32331456/using-boto-upload-file-to-s3-
  337. # sub-folder-when-i-have-no-permissions-on-listing-fo
  338. if e.status == 403:
  339. bucket = self.provider.s3_conn.get_bucket(bucket_id,
  340. validate=False)
  341. return AWSBucket(self.provider, bucket)
  342. # For all other responses, it's assumed that the bucket does not exist.
  343. return None
  344. def find(self, name, limit=None, marker=None):
  345. """
  346. Searches for a bucket by a given list of attributes.
  347. """
  348. buckets = [AWSBucket(self.provider, bucket)
  349. for bucket in self.provider.s3_conn.get_all_buckets()
  350. if name in bucket.name]
  351. return ClientPagedResultList(self.provider, buckets,
  352. limit=limit, marker=marker)
  353. def list(self, limit=None, marker=None):
  354. """
  355. List all containers.
  356. """
  357. buckets = [AWSBucket(self.provider, bucket)
  358. for bucket in self.provider.s3_conn.get_all_buckets()]
  359. return ClientPagedResultList(self.provider, buckets,
  360. limit=limit, marker=marker)
  361. def create(self, name, location=None):
  362. """
  363. Create a new bucket.
  364. """
  365. bucket = self.provider.s3_conn.create_bucket(
  366. name,
  367. location=location if location else '')
  368. return AWSBucket(self.provider, bucket)
  369. class AWSImageService(BaseImageService):
  370. def __init__(self, provider):
  371. super(AWSImageService, self).__init__(provider)
  372. def get(self, image_id):
  373. """
  374. Returns an Image given its id
  375. """
  376. try:
  377. image = self.provider.ec2_conn.get_image(image_id)
  378. return AWSMachineImage(self.provider, image) if image else None
  379. except EC2ResponseError as ec2e:
  380. if ec2e.code == 'InvalidAMIID.NotFound':
  381. return None
  382. elif ec2e.code == 'InvalidAMIID.Malformed':
  383. # Occurs if image_id does not start with 'ami-...'
  384. return None
  385. raise ec2e
  386. def find(self, name, limit=None, marker=None):
  387. """
  388. Searches for an image by a given list of attributes
  389. """
  390. filters = {'name': name}
  391. images = [AWSMachineImage(self.provider, image) for image in
  392. self.provider.ec2_conn.get_all_images(filters=filters)]
  393. return ClientPagedResultList(self.provider, images,
  394. limit=limit, marker=marker)
  395. def list(self, limit=None, marker=None):
  396. """
  397. List all images.
  398. """
  399. images = [AWSMachineImage(self.provider, image)
  400. for image in self.provider.ec2_conn.get_all_images()]
  401. return ClientPagedResultList(self.provider, images,
  402. limit=limit, marker=marker)
  403. class AWSComputeService(BaseComputeService):
  404. def __init__(self, provider):
  405. super(AWSComputeService, self).__init__(provider)
  406. self._instance_type_svc = AWSInstanceTypesService(self.provider)
  407. self._instance_svc = AWSInstanceService(self.provider)
  408. self._region_svc = AWSRegionService(self.provider)
  409. self._images_svc = AWSImageService(self.provider)
  410. @property
  411. def images(self):
  412. return self._images_svc
  413. @property
  414. def instance_types(self):
  415. return self._instance_type_svc
  416. @property
  417. def instances(self):
  418. return self._instance_svc
  419. @property
  420. def regions(self):
  421. return self._region_svc
  422. class AWSInstanceService(BaseInstanceService):
  423. def __init__(self, provider):
  424. super(AWSInstanceService, self).__init__(provider)
  425. def create(self, name, image, instance_type, subnet, zone=None,
  426. key_pair=None, security_groups=None, user_data=None,
  427. launch_config=None, **kwargs):
  428. image_id = image.id if isinstance(image, MachineImage) else image
  429. instance_size = instance_type.id if \
  430. isinstance(instance_type, InstanceType) else instance_type
  431. subnet = (self.provider.network.subnets.get(subnet)
  432. if isinstance(subnet, str) else subnet)
  433. zone_id = zone.id if isinstance(zone, PlacementZone) else zone
  434. key_pair_name = key_pair.name if isinstance(
  435. key_pair,
  436. KeyPair) else key_pair
  437. if launch_config:
  438. bdm = self._process_block_device_mappings(launch_config, zone_id)
  439. else:
  440. bdm = None
  441. subnet_id, zone_id, security_group_ids = \
  442. self._resolve_launch_options(subnet, zone_id, security_groups)
  443. reservation = self.provider.ec2_conn.run_instances(
  444. image_id=image_id, instance_type=instance_size,
  445. min_count=1, max_count=1, placement=zone_id,
  446. key_name=key_pair_name, security_group_ids=security_group_ids,
  447. user_data=user_data, block_device_map=bdm, subnet_id=subnet_id)
  448. instance = None
  449. if reservation:
  450. instance = AWSInstance(self.provider, reservation.instances[0])
  451. instance.wait_for(
  452. [InstanceState.PENDING, InstanceState.RUNNING],
  453. terminal_states=[InstanceState.TERMINATED,
  454. InstanceState.ERROR])
  455. instance.name = name
  456. return instance
  457. def _resolve_launch_options(self, subnet=None, zone_id=None,
  458. security_groups=None):
  459. """
  460. Work out interdependent launch options.
  461. Some launch options are required and interdependent so make sure
  462. they conform to the interface contract.
  463. :type subnet: ``Subnet``
  464. :param subnet: Subnet object within which to launch.
  465. :type zone_id: ``str``
  466. :param zone_id: ID of the zone where the launch should happen.
  467. :type security_groups: ``list`` of ``id``
  468. :param zone_id: List of security group IDs.
  469. :rtype: triplet of ``str``
  470. :return: Subnet ID, zone ID and security group IDs for launch.
  471. :raise ValueError: In case a conflicting combination is found.
  472. """
  473. if subnet:
  474. # subnet's zone takes precedence
  475. zone_id = subnet.zone.id
  476. if isinstance(security_groups, list) and isinstance(
  477. security_groups[0], SecurityGroup):
  478. security_group_ids = [sg.id for sg in security_groups]
  479. else:
  480. security_group_ids = security_groups
  481. return subnet.id, zone_id, security_group_ids
  482. def _process_block_device_mappings(self, launch_config, zone=None):
  483. """
  484. Processes block device mapping information
  485. and returns a Boto BlockDeviceMapping object. If new volumes
  486. are requested (source is None and destination is VOLUME), they will be
  487. created and the relevant volume ids included in the mapping.
  488. """
  489. bdm = BlockDeviceMapping()
  490. # Assign letters from f onwards
  491. # http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/device_naming.html
  492. next_letter = iter(list(string.ascii_lowercase[6:]))
  493. # assign ephemeral devices from 0 onwards
  494. ephemeral_counter = 0
  495. for device in launch_config.block_devices:
  496. bd_type = BlockDeviceType()
  497. if device.is_volume:
  498. if device.is_root:
  499. bdm['/dev/sda1'] = bd_type
  500. else:
  501. bdm['sd' + next(next_letter)] = bd_type
  502. if isinstance(device.source, Snapshot):
  503. bd_type.snapshot_id = device.source.id
  504. elif isinstance(device.source, Volume):
  505. bd_type.volume_id = device.source.id
  506. elif isinstance(device.source, MachineImage):
  507. # Not supported
  508. pass
  509. else:
  510. # source is None, but destination is volume, therefore
  511. # create a blank volume. If the Zone is None, this
  512. # could fail since the volume and instance may be created
  513. # in two different zones.
  514. if not zone:
  515. raise InvalidConfigurationException(
  516. "A zone must be specified when launching with a"
  517. " new blank volume block device mapping.")
  518. new_vol = self.provider.block_store.volumes.create(
  519. '',
  520. device.size,
  521. zone)
  522. bd_type.volume_id = new_vol.id
  523. bd_type.delete_on_terminate = device.delete_on_terminate
  524. if device.size:
  525. bd_type.size = device.size
  526. else: # device is ephemeral
  527. bd_type.ephemeral_name = 'ephemeral%s' % ephemeral_counter
  528. return bdm
  529. def create_launch_config(self):
  530. return AWSLaunchConfig(self.provider)
  531. def get(self, instance_id):
  532. """
  533. Returns an instance given its id. Returns None
  534. if the object does not exist.
  535. """
  536. try:
  537. reservation = self.provider.ec2_conn.get_all_reservations(
  538. instance_ids=[instance_id])
  539. return (AWSInstance(self.provider, reservation[0].instances[0])
  540. if reservation else None)
  541. except EC2ResponseError as ec2e:
  542. if ec2e.code == 'InvalidInstanceID.NotFound':
  543. return None
  544. elif ec2e.code == 'InvalidParameterValue':
  545. # Occurs if id does not start with 'inst-...'
  546. return None
  547. raise ec2e
  548. def find(self, name, limit=None, marker=None):
  549. """
  550. Searches for an instance by a given list of attributes.
  551. :rtype: ``object`` of :class:`.Instance`
  552. :return: an Instance object
  553. """
  554. filtr = {'tag:Name': name}
  555. reservations = self.provider.ec2_conn.get_all_reservations(
  556. filters=filtr,
  557. max_results=limit,
  558. next_token=marker)
  559. instances = [AWSInstance(self.provider, inst)
  560. for res in reservations
  561. for inst in res.instances]
  562. return ServerPagedResultList(reservations.is_truncated,
  563. reservations.next_token,
  564. False, data=instances)
  565. def list(self, limit=None, marker=None):
  566. """
  567. List all instances.
  568. """
  569. reservations = self.provider.ec2_conn.get_all_reservations(
  570. max_results=limit,
  571. next_token=marker)
  572. instances = [AWSInstance(self.provider, inst)
  573. for res in reservations
  574. for inst in res.instances]
  575. return ServerPagedResultList(reservations.is_truncated,
  576. reservations.next_token,
  577. False, data=instances)
  578. class AWSInstanceTypesService(BaseInstanceTypesService):
  579. def __init__(self, provider):
  580. super(AWSInstanceTypesService, self).__init__(provider)
  581. @property
  582. def instance_data(self):
  583. """
  584. Fetch info about the available instances.
  585. To update this information, update the file pointed to by the
  586. ``provider.AWS_INSTANCE_DATA_DEFAULT_URL`` above. The content for this
  587. file should be obtained from this repo:
  588. https://github.com/powdahound/ec2instances.info (in particular, this
  589. file: https://raw.githubusercontent.com/powdahound/ec2instances.info/
  590. master/www/instances.json).
  591. TODO: Needs a caching function with timeout
  592. """
  593. r = requests.get(self.provider.config.get(
  594. "aws_instance_info_url",
  595. self.provider.AWS_INSTANCE_DATA_DEFAULT_URL))
  596. return r.json()
  597. def list(self, limit=None, marker=None):
  598. inst_types = [AWSInstanceType(self.provider, inst_type)
  599. for inst_type in self.instance_data]
  600. return ClientPagedResultList(self.provider, inst_types,
  601. limit=limit, marker=marker)
  602. class AWSRegionService(BaseRegionService):
  603. def __init__(self, provider):
  604. super(AWSRegionService, self).__init__(provider)
  605. def get(self, region_id):
  606. region = [r for r in self if r.id == region_id]
  607. if region:
  608. return region[0]
  609. else:
  610. return None
  611. def list(self, limit=None, marker=None):
  612. regions = [AWSRegion(self.provider, region)
  613. for region in self.provider.ec2_conn.get_all_regions()]
  614. return ClientPagedResultList(self.provider, regions,
  615. limit=limit, marker=marker)
  616. @property
  617. def current(self):
  618. return self.get(self._provider.region_name)
  619. class AWSNetworkService(BaseNetworkService):
  620. def __init__(self, provider):
  621. super(AWSNetworkService, self).__init__(provider)
  622. self._subnet_svc = AWSSubnetService(self.provider)
  623. def get(self, network_id):
  624. try:
  625. network = self.provider.vpc_conn.get_all_vpcs(vpc_ids=[network_id])
  626. return AWSNetwork(self.provider, network[0]) if network else None
  627. except EC2ResponseError as ec2e:
  628. if ec2e.code == 'InvalidVpcID.NotFound':
  629. return None
  630. elif ec2e.code == 'InvalidParameterValue':
  631. # Occurs if id does not start with 'vpc-...'
  632. return None
  633. raise ec2e
  634. def list(self, limit=None, marker=None):
  635. networks = [AWSNetwork(self.provider, network)
  636. for network in self.provider.vpc_conn.get_all_vpcs()]
  637. return ClientPagedResultList(self.provider, networks,
  638. limit=limit, marker=marker)
  639. def find(self, name, limit=None, marker=None):
  640. filtr = {'tag:Name': name}
  641. networks = [AWSNetwork(self.provider, network)
  642. for network in self.provider.vpc_conn.get_all_vpcs(
  643. filters=filtr)]
  644. return ClientPagedResultList(self.provider, networks,
  645. limit=limit, marker=marker)
  646. def create(self, name=None):
  647. # AWS requires CIDR block to be specified when creating a network
  648. # so set a default one and use the largest allowed netmask.
  649. default_cidr = '10.0.0.0/16'
  650. network = self.provider.vpc_conn.create_vpc(cidr_block=default_cidr)
  651. cb_network = AWSNetwork(self.provider, network)
  652. if name:
  653. cb_network.wait_for(
  654. [NetworkState.PENDING, NetworkState.AVAILABLE],
  655. terminal_states=[NetworkState.ERROR])
  656. cb_network.name = name
  657. return cb_network
  658. @property
  659. def subnets(self):
  660. return self._subnet_svc
  661. def floating_ips(self, network_id=None):
  662. fltrs = None
  663. if network_id:
  664. fltrs = {'network-interface-id': network_id}
  665. al = self.provider.vpc_conn.get_all_addresses(filters=fltrs)
  666. return [AWSFloatingIP(self.provider, a) for a in al]
  667. def create_floating_ip(self):
  668. ip = self.provider.ec2_conn.allocate_address(domain='vpc')
  669. return AWSFloatingIP(self.provider, ip)
  670. def routers(self):
  671. routers = self.provider.vpc_conn.get_all_internet_gateways()
  672. return [AWSRouter(self.provider, r) for r in routers]
  673. def create_router(self, name=None):
  674. router = self.provider.vpc_conn.create_internet_gateway()
  675. cb_router = AWSRouter(self.provider, router)
  676. if name:
  677. time.sleep(2) # Some time is required
  678. cb_router.name = name
  679. return cb_router
  680. class AWSSubnetService(BaseSubnetService):
  681. def __init__(self, provider):
  682. super(AWSSubnetService, self).__init__(provider)
  683. def get(self, subnet_id):
  684. try:
  685. subnets = self.provider.vpc_conn.get_all_subnets([subnet_id])
  686. return AWSSubnet(self.provider, subnets[0]) if subnets else None
  687. except EC2ResponseError as ec2e:
  688. if ec2e.code == 'InvalidSubnetID.NotFound':
  689. return None
  690. elif ec2e.code == 'InvalidParameterValue':
  691. # Occurs if id does not start with 'subnet-...'
  692. return None
  693. raise ec2e
  694. def list(self, network=None, limit=None, marker=None):
  695. fltr = None
  696. if network:
  697. network_id = (network.id if isinstance(network, AWSNetwork) else
  698. network)
  699. fltr = {'vpc-id': network_id}
  700. subnets = [AWSSubnet(self.provider, subnet) for subnet in
  701. self.provider.vpc_conn.get_all_subnets(filters=fltr)]
  702. return ClientPagedResultList(self.provider, subnets,
  703. limit=limit, marker=marker)
  704. def create(self, network, cidr_block, name=None, zone=None):
  705. network_id = network.id if isinstance(network, AWSNetwork) else network
  706. subnet = self.provider.vpc_conn.create_subnet(network_id, cidr_block,
  707. availability_zone=zone)
  708. cb_subnet = AWSSubnet(self.provider, subnet)
  709. if name:
  710. cb_subnet.wait_for(
  711. [SubnetState.PENDING, SubnetState.AVAILABLE],
  712. terminal_states=[SubnetState.ERROR])
  713. cb_subnet.name = name
  714. return cb_subnet
  715. def get_or_create_default(self, zone=None):
  716. filtr = {'availabilityZone': zone} if zone else None
  717. sns = self.provider.vpc_conn.get_all_subnets(filters=filtr)
  718. for sn in sns:
  719. if sn.defaultForAz:
  720. return AWSSubnet(self.provider, sn)
  721. # No provider-default Subnet exists, look for a library-default one
  722. for sn in sns:
  723. if sn.tags.get('Name') == AWSSubnet.CB_DEFAULT_SUBNET_NAME:
  724. return AWSSubnet(self.provider, sn)
  725. # No provider-default Subnet exists, try to create it (net + subnets)
  726. default_net = self.provider.network.create(
  727. name=AWSNetwork.CB_DEFAULT_NETWORK_NAME)
  728. # Create a subnet in each of the region's zones
  729. region = self.provider.compute.regions.get(
  730. self.provider.vpc_conn.region.name)
  731. default_sn = None
  732. for i, z in enumerate(region.zones):
  733. sn = self.create(default_net, '10.0.{0}.0/24'.format(i),
  734. AWSSubnet.CB_DEFAULT_SUBNET_NAME, z.name)
  735. if zone and zone == z.name:
  736. default_sn = sn
  737. # No specific zone was supplied; return the last created subnet
  738. if not default_sn:
  739. default_sn = sn
  740. return default_sn
  741. def delete(self, subnet):
  742. subnet_id = subnet.id if isinstance(subnet, AWSSubnet) else subnet
  743. return self.provider.vpc_conn.delete_subnet(subnet_id=subnet_id)