services.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  1. """Services implemented by the AWS provider."""
  2. import time
  3. import string
  4. from boto.ec2.blockdevicemapping import BlockDeviceMapping
  5. from boto.ec2.blockdevicemapping import BlockDeviceType
  6. from botocore.exceptions import ClientError as EC2ResponseError
  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.resources import InstanceType
  24. from cloudbridge.cloud.interfaces.resources \
  25. import InvalidConfigurationException
  26. from cloudbridge.cloud.interfaces.resources import KeyPair
  27. from cloudbridge.cloud.interfaces.resources import MachineImage
  28. # from cloudbridge.cloud.interfaces.resources import Network
  29. from cloudbridge.cloud.interfaces.resources import PlacementZone
  30. from cloudbridge.cloud.interfaces.resources import SecurityGroup
  31. from cloudbridge.cloud.interfaces.resources import Snapshot
  32. from cloudbridge.cloud.interfaces.resources import Volume
  33. import cloudbridge as cb
  34. import requests
  35. from .resources import AWSBucket
  36. from .resources import AWSFloatingIP
  37. from .resources import AWSInstance
  38. from .resources import AWSInstanceType
  39. from .resources import AWSKeyPair
  40. from .resources import AWSLaunchConfig
  41. from .resources import AWSMachineImage
  42. from .resources import AWSNetwork
  43. from .resources import AWSRegion
  44. from .resources import AWSRouter
  45. from .resources import AWSSecurityGroup
  46. from .resources import AWSSnapshot
  47. from .resources import AWSSubnet
  48. from .resources import AWSVolume
  49. # Uncomment to enable logging by default for this module
  50. # cb.set_stream_logger(__name__)
  51. class EC2ServiceFilter(object):
  52. '''
  53. Generic AWS EC2 service filter interface
  54. :param AWSCloudProvider provider: AWS EC2 provider interface
  55. :param str service: Name of the EC2 service to use
  56. :param BaseCloudResource cb_iface: CloudBridge class to use
  57. '''
  58. def __init__(self, provider, service, cb_iface):
  59. self.provider = provider
  60. self.service = getattr(self.provider.ec2_conn, service)
  61. self.iface = cb_iface
  62. def get(self, val, filter_name, wrapper=True):
  63. '''
  64. Returns a single resource by filter
  65. :param str val: Value to filter with
  66. :param str filter_name: Name of the filter to use
  67. :param bool wrapper: If True, wraps the resulting Boto
  68. object in a CloudBridge object
  69. :returns: Boto resource object or CloudBridge object or None
  70. '''
  71. try:
  72. objs = list(self.service.filter(Filters=[{
  73. 'Name': filter_name,
  74. 'Values': [val]
  75. }]).limit(1))
  76. obj = objs[0] if objs else None
  77. if wrapper:
  78. return self.iface(self.provider, obj) if obj else None
  79. return obj
  80. except EC2ResponseError:
  81. return None
  82. def list(self, limit=None, marker=None):
  83. '''Returns a list of resources'''
  84. try:
  85. objs = [self.iface(self.provider, obj)
  86. for obj in self.service.limit(limit)]
  87. except EC2ResponseError:
  88. objs = list()
  89. return ClientPagedResultList(self.provider, objs,
  90. limit=limit, marker=marker)
  91. def find(self, val, filter_name, limit=None, marker=None):
  92. '''
  93. Returns a list of resources by filter
  94. :param str val: Value to filter with
  95. :param str filter_name: Name of the filter to use
  96. '''
  97. try:
  98. objs = [
  99. self.iface(self.provider, obj)
  100. for obj in self.service.filter(Filters=[{
  101. 'Name': filter_name,
  102. 'Values': [val]
  103. }])
  104. ]
  105. except EC2ResponseError:
  106. objs = list()
  107. return ClientPagedResultList(self.provider, objs,
  108. limit=limit, marker=marker)
  109. def create(self, method, **kwargs):
  110. '''
  111. Creates a resource
  112. :param str method: Service method to invoke
  113. :param object kwargs: Arguments to be passed as-is to
  114. the service method
  115. '''
  116. res = getattr(self.provider.ec2_conn, method)(**kwargs)
  117. return self.iface(self.provider, res) if res else None
  118. def delete(self, val, filter_name):
  119. '''
  120. Deletes a resource by filter
  121. :param str val: Value to filter with
  122. :param str filter_name: Name of the filter to use
  123. :returns: False on error, True if the resource
  124. does not exist or was deleted successfully
  125. '''
  126. res = self.get(val, filter_name, wrapper=False)
  127. if res:
  128. try:
  129. res.delete()
  130. except EC2ResponseError:
  131. return False
  132. return True
  133. def wait_for_create(self, val, filter_name, timeout=15):
  134. '''
  135. Simple test for resource creation
  136. :returns: True on success, False on timeout, None if the
  137. object does not implement the refresh() method.
  138. '''
  139. while timeout > 0:
  140. time.sleep(2)
  141. obj = self.get(val, filter_name)
  142. if obj:
  143. if hasattr(obj, 'refresh') and callable(obj.refresh):
  144. obj.refresh()
  145. return True
  146. else:
  147. return None
  148. timeout = timeout - 1
  149. return False
  150. def wait_for_delete(self, val, filter_name, timeout=15):
  151. '''Simple test for resource deletion'''
  152. while timeout > 0:
  153. time.sleep(2)
  154. if not self.get(val, filter_name):
  155. return True
  156. timeout = timeout - 1
  157. return False
  158. class AWSSecurityService(BaseSecurityService):
  159. def __init__(self, provider):
  160. super(AWSSecurityService, self).__init__(provider)
  161. # Initialize provider services
  162. self._key_pairs = AWSKeyPairService(provider)
  163. self._security_groups = AWSSecurityGroupService(provider)
  164. @property
  165. def key_pairs(self):
  166. """
  167. Provides access to key pairs for this provider.
  168. :rtype: ``object`` of :class:`.KeyPairService`
  169. :return: a KeyPairService object
  170. """
  171. return self._key_pairs
  172. @property
  173. def security_groups(self):
  174. """
  175. Provides access to security groups for this provider.
  176. :rtype: ``object`` of :class:`.SecurityGroupService`
  177. :return: a SecurityGroupService object
  178. """
  179. return self._security_groups
  180. class AWSKeyPairService(BaseKeyPairService):
  181. def __init__(self, provider):
  182. super(AWSKeyPairService, self).__init__(provider)
  183. self.iface = EC2ServiceFilter(self.provider, 'key_pairs', AWSKeyPair)
  184. def get(self, key_pair_id):
  185. """Returns a key pair given its name"""
  186. return self.iface.get(key_pair_id, 'key-name')
  187. def list(self, limit=None, marker=None):
  188. """List all key pairs associated with this account"""
  189. return self.iface.list(limit=limit, marker=marker)
  190. def find(self, name, limit=None, marker=None):
  191. """Searches for a key pair by name"""
  192. return self.iface.find(name, 'key-name', limit=limit, marker=marker)
  193. def create(self, name):
  194. """Creates a new key pair"""
  195. return self.iface.create('create_key_pair', KeyName=name)
  196. def delete(self, key_pair_id):
  197. """Deletes a key pair by name"""
  198. return self.iface.delete(key_pair_id, 'key-name')
  199. class AWSSecurityGroupService(BaseSecurityGroupService):
  200. def __init__(self, provider):
  201. super(AWSSecurityGroupService, self).__init__(provider)
  202. self.iface = EC2ServiceFilter(self.provider,
  203. 'security_groups', AWSSecurityGroup)
  204. def get(self, group_id):
  205. """Returns a security group given its ID"""
  206. return self.iface.get(group_id, 'group-id')
  207. def list(self, limit=None, marker=None):
  208. """List all security groups associated with this account"""
  209. return self.iface.list(limit=limit, marker=marker)
  210. def find(self, name, limit=None, marker=None):
  211. """Searches for a security group by name"""
  212. return self.iface.find(name, 'group-name', limit=limit, marker=marker)
  213. def create(self, name, description, network_id=None):
  214. """Creates a security group pair"""
  215. res = self.iface.create('create_security_group',
  216. GroupName=name,
  217. Description=description)
  218. if not self.iface.wait_for_create(res.id, 'group-id'):
  219. return None
  220. return res
  221. def delete(self, group_id):
  222. """Deletes a security group by name"""
  223. res = self.iface.find(group_id, 'tag:Name')
  224. if res:
  225. res = res[0]
  226. self.iface.delete(res.id, 'group-name')
  227. return self.iface.wait_for_delete(res.id, 'group-name')
  228. return None
  229. class AWSBlockStoreService(BaseBlockStoreService):
  230. def __init__(self, provider):
  231. super(AWSBlockStoreService, self).__init__(provider)
  232. # Initialize provider services
  233. self._volume_svc = AWSVolumeService(self.provider)
  234. self._snapshot_svc = AWSSnapshotService(self.provider)
  235. @property
  236. def volumes(self):
  237. return self._volume_svc
  238. @property
  239. def snapshots(self):
  240. return self._snapshot_svc
  241. class AWSVolumeService(BaseVolumeService):
  242. def __init__(self, provider):
  243. super(AWSVolumeService, self).__init__(provider)
  244. self.iface = EC2ServiceFilter(self.provider,
  245. 'volumes', AWSVolume)
  246. def get(self, volume_id):
  247. """Returns a volume given its ID"""
  248. return self.iface.get(volume_id, 'volume-id')
  249. def list(self, limit=None, marker=None):
  250. """List all volumes associated with this account"""
  251. return self.iface.list(limit=limit, marker=marker)
  252. def find(self, name, limit=None, marker=None):
  253. """Searches for a volume by name"""
  254. return self.iface.find(name, 'tag:Name', limit=limit, marker=marker)
  255. def create(self, name, size, zone,
  256. snapshot=None, iops=None, description=None):
  257. """Creates a volume"""
  258. zone_id = zone.id if isinstance(zone, PlacementZone) else zone
  259. snapshot_id = snapshot.id if isinstance(
  260. snapshot, AWSSnapshot) and snapshot else snapshot
  261. params = {
  262. 'Size': size,
  263. 'AvailabilityZone': zone_id
  264. }
  265. if iops:
  266. params['Iops'] = iops
  267. if snapshot_id:
  268. params['SnapshotId'] = snapshot_id
  269. res = self.iface.create('create_volume', **params)
  270. if not self.iface.wait_for_create(res.id, 'volume-id'):
  271. return None
  272. res.name = name
  273. if res.description:
  274. res.description = description
  275. return res
  276. def delete(self, name):
  277. """Deletes a volume by name"""
  278. res = self.iface.find(name, 'tag:Name')
  279. if res:
  280. res = res[0]
  281. self.iface.delete(res.id, 'volume-id')
  282. return self.iface.wait_for_delete(res.id, 'volume-id')
  283. return None
  284. class AWSSnapshotService(BaseSnapshotService):
  285. def __init__(self, provider):
  286. super(AWSSnapshotService, self).__init__(provider)
  287. self.iface = EC2ServiceFilter(self.provider,
  288. 'snapshots', AWSSnapshot)
  289. def get(self, volume_id):
  290. """Returns a snapshot given its ID"""
  291. return self.iface.get(volume_id, 'snapshot-id')
  292. def list(self, limit=None, marker=None):
  293. """List all snapshots associated with this account"""
  294. return self.iface.list(limit=limit, marker=marker)
  295. def find(self, name, limit=None, marker=None):
  296. """Searches for a snapshot by name"""
  297. return self.iface.find(name, 'tag:Name', limit=limit, marker=marker)
  298. def create(self, name, volume, description=None):
  299. """Creates a snapshot"""
  300. volume_id = volume.id if isinstance(volume, AWSVolume) else volume
  301. res = self.iface.create('create_snapshot',
  302. VolumeId=volume_id)
  303. if not self.iface.wait_for_create(res.id, 'snapshot-id'):
  304. return None
  305. res.name = name
  306. if res.description:
  307. res.description = description
  308. return res
  309. def delete(self, name):
  310. """Deletes a snapshot by name"""
  311. res = self.iface.find(name, 'tag:Name')
  312. if res:
  313. res = res[0]
  314. self.iface.delete(res.id, 'snapshot-id')
  315. return self.iface.wait_for_delete(res.id, 'snapshot-id')
  316. return None
  317. class AWSObjectStoreService(BaseObjectStoreService):
  318. def __init__(self, provider):
  319. super(AWSObjectStoreService, self).__init__(provider)
  320. def get(self, bucket_id):
  321. """
  322. Returns a bucket given its ID. Returns ``None`` if the bucket
  323. does not exist.
  324. """
  325. bucket = self.provider.s3_conn.lookup(bucket_id)
  326. if bucket:
  327. return AWSBucket(self.provider, bucket)
  328. else:
  329. return None
  330. def find(self, name, limit=None, marker=None):
  331. """
  332. Searches for a bucket by a given list of attributes.
  333. """
  334. buckets = [AWSBucket(self.provider, bucket)
  335. for bucket in self.provider.s3_conn.get_all_buckets()
  336. if name in bucket.name]
  337. return ClientPagedResultList(self.provider, buckets,
  338. limit=limit, marker=marker)
  339. def list(self, limit=None, marker=None):
  340. """
  341. List all containers.
  342. """
  343. buckets = [AWSBucket(self.provider, bucket)
  344. for bucket in self.provider.s3_conn.get_all_buckets()]
  345. return ClientPagedResultList(self.provider, buckets,
  346. limit=limit, marker=marker)
  347. def create(self, name, location=None):
  348. """
  349. Create a new bucket.
  350. """
  351. bucket = self.provider.s3_conn.create_bucket(
  352. name,
  353. location=location if location else '')
  354. return AWSBucket(self.provider, bucket)
  355. class AWSImageService(BaseImageService):
  356. def __init__(self, provider):
  357. super(AWSImageService, self).__init__(provider)
  358. self.iface = EC2ServiceFilter(self.provider,
  359. 'images', AWSMachineImage)
  360. def get(self, image_id):
  361. """Returns a image given its ID"""
  362. return self.iface.get(image_id, 'image-id')
  363. def list(self, limit=None, marker=None):
  364. """List all images associated with this account"""
  365. return self.iface.list(limit=limit, marker=marker)
  366. def find(self, name, limit=None, marker=None):
  367. """Searches for a image by name"""
  368. return self.iface.find(name, 'name', limit=limit, marker=marker)
  369. class AWSComputeService(BaseComputeService):
  370. def __init__(self, provider):
  371. super(AWSComputeService, self).__init__(provider)
  372. self._instance_type_svc = AWSInstanceTypesService(self.provider)
  373. self._instance_svc = AWSInstanceService(self.provider)
  374. self._region_svc = AWSRegionService(self.provider)
  375. self._images_svc = AWSImageService(self.provider)
  376. @property
  377. def images(self):
  378. return self._images_svc
  379. @property
  380. def instance_types(self):
  381. return self._instance_type_svc
  382. @property
  383. def instances(self):
  384. return self._instance_svc
  385. @property
  386. def regions(self):
  387. return self._region_svc
  388. class AWSInstanceService(BaseInstanceService):
  389. def __init__(self, provider):
  390. super(AWSInstanceService, self).__init__(provider)
  391. def create(self, name, image, instance_type, zone=None,
  392. key_pair=None, security_groups=None, user_data=None,
  393. launch_config=None,
  394. **kwargs):
  395. """
  396. Creates a new virtual machine instance.
  397. If no VPC/subnet was specified (via ``launch_config`` parameter), this
  398. method will search for a default VPC and attempt to launch an instance
  399. into that VPC.
  400. """
  401. image_id = image.id if isinstance(image, MachineImage) else image
  402. instance_size = instance_type.id if \
  403. isinstance(instance_type, InstanceType) else instance_type
  404. zone_id = zone.id if isinstance(zone, PlacementZone) else zone
  405. key_pair_name = key_pair.name if isinstance(
  406. key_pair,
  407. KeyPair) else key_pair
  408. if launch_config:
  409. bdm = self._process_block_device_mappings(launch_config, zone_id)
  410. subnet_id = self._get_net_id(launch_config)
  411. else:
  412. bdm = subnet_id = None
  413. subnet_id, zone_id, security_group_ids = \
  414. self._resolve_launch_options(subnet_id, zone_id, security_groups)
  415. reservation = self.provider.ec2_conn.run_instances(
  416. image_id=image_id, instance_type=instance_size,
  417. min_count=1, max_count=1, placement=zone_id,
  418. key_name=key_pair_name, security_group_ids=security_group_ids,
  419. user_data=user_data, block_device_map=bdm, subnet_id=subnet_id)
  420. instance = None
  421. if reservation:
  422. time.sleep(2) # The instance does not always get created in time
  423. instance = AWSInstance(self.provider, reservation.instances[0])
  424. instance.name = name
  425. return instance
  426. def _resolve_launch_options(self, subnet_id, zone_id, security_groups):
  427. """
  428. Resolve inter-dependent launch options.
  429. With launching into VPC only, try to figure out a default
  430. VPC to launch into, making placement decisions along the way that
  431. are implied from the zone a subnet exists in.
  432. """
  433. def _deduce_subnet_and_zone(vpc, zone_id=None):
  434. """
  435. Figure out subnet ID from a VPC (and zone_id, if not supplied).
  436. """
  437. if zone_id:
  438. # A placement zone was specified. Choose the default
  439. # subnet in that zone.
  440. for sn in vpc.subnets():
  441. if sn._subnet.availability_zone == zone_id:
  442. subnet_id = sn.id
  443. else:
  444. # No zone was requested, so just pick one subnet
  445. sn = vpc.subnets()[0]
  446. subnet_id = sn.id
  447. zone_id = sn._subnet.availability_zone
  448. return subnet_id, zone_id
  449. vpc_id = None
  450. sg_ids = []
  451. if subnet_id:
  452. # Subnet was supplied - get the VPC so named SGs can be resolved
  453. subnet = self.provider.vpc_conn.get_all_subnets(subnet_id)[0]
  454. vpc_id = subnet.vpc_id
  455. # zone_id must match zone where the requested subnet lives
  456. if zone_id and subnet.availability_zone != zone_id:
  457. raise ValueError("Requested placement zone ({0}) must match "
  458. "specified subnet's availability zone ({1})."
  459. .format(zone_id, subnet.availability_zone))
  460. if security_groups:
  461. # Try to get a subnet via specified SGs. This will work only if
  462. # the specified SGs are within a VPC (which is a prerequisite to
  463. # launch into VPC anyhow).
  464. _sg_ids = self._process_security_groups(security_groups, vpc_id)
  465. # Must iterate through all the SGs here because a SG name may
  466. # exist in a VPC or EC2-Classic so opt for the VPC SG. This
  467. # applies in the case no subnet was specified.
  468. if not subnet_id:
  469. for sg_id in _sg_ids:
  470. sg = self.provider.security.security_groups.get(sg_id)
  471. if sg._security_group.vpc_id:
  472. if sg_ids and sg_id not in sg_ids:
  473. raise ValueError("Multiple matches for VPC "
  474. "security group(s) {0}."
  475. .format(security_groups))
  476. else:
  477. sg_ids.append(sg_id)
  478. vpc = self.provider.network.get(
  479. sg._security_group.vpc_id)
  480. subnet_id, zone_id = _deduce_subnet_and_zone(
  481. vpc, zone_id)
  482. else:
  483. sg_ids = _sg_ids
  484. if not subnet_id:
  485. raise AttributeError("Supplied security group(s) ({0}) must "
  486. "be associated with a VPC."
  487. .format(security_groups))
  488. if not subnet_id and not security_groups:
  489. # No VPC/subnet was supplied, search for the default VPC.
  490. for vpc in self.provider.network.list():
  491. if vpc._vpc.is_default:
  492. vpc_id = vpc.id
  493. subnet_id, zone_id = _deduce_subnet_and_zone(vpc, zone_id)
  494. if not vpc_id:
  495. raise AttributeError("No default VPC exists. Supply a "
  496. "subnet to launch into (via "
  497. "launch_config param).")
  498. return subnet_id, zone_id, sg_ids
  499. def _process_security_groups(self, security_groups, vpc_id=None):
  500. """
  501. Process security groups to create a list of SG ID's for launching.
  502. :type security_groups: A ``list`` of ``SecurityGroup`` objects or a
  503. list of ``str`` names
  504. :param security_groups: A list of ``SecurityGroup`` objects or a list
  505. of ``SecurityGroup`` names, which should be
  506. assigned to this instance.
  507. :type vpc_id: ``str``
  508. :param vpc_id: A VPC ID within which the supplied security groups exist
  509. :rtype: ``list``
  510. :return: A list of security group IDs.
  511. """
  512. if isinstance(security_groups, list) and \
  513. isinstance(security_groups[0], SecurityGroup):
  514. sg_ids = [sg.id for sg in security_groups]
  515. else:
  516. # SG names were supplied, need to map them to SG IDs.
  517. sg_ids = []
  518. # If a VPC was specified, need to map to the SGs in the VPC.
  519. flters = None
  520. if vpc_id:
  521. flters = {'vpc_id': vpc_id}
  522. sgs = self.provider.ec2_conn.get_all_security_groups(
  523. filters=flters)
  524. sg_ids = [sg.id for sg in sgs if sg.name in security_groups]
  525. return sg_ids
  526. def _process_block_device_mappings(self, launch_config, zone=None):
  527. """
  528. Processes block device mapping information
  529. and returns a Boto BlockDeviceMapping object. If new volumes
  530. are requested (source is None and destination is VOLUME), they will be
  531. created and the relevant volume ids included in the mapping.
  532. """
  533. bdm = BlockDeviceMapping()
  534. # Assign letters from f onwards
  535. # http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/device_naming.html
  536. next_letter = iter(list(string.ascii_lowercase[6:]))
  537. # assign ephemeral devices from 0 onwards
  538. ephemeral_counter = 0
  539. for device in launch_config.block_devices:
  540. bd_type = BlockDeviceType()
  541. if device.is_volume:
  542. if device.is_root:
  543. bdm['/dev/sda1'] = bd_type
  544. else:
  545. bdm['sd' + next(next_letter)] = bd_type
  546. if isinstance(device.source, Snapshot):
  547. bd_type.snapshot_id = device.source.id
  548. elif isinstance(device.source, Volume):
  549. bd_type.volume_id = device.source.id
  550. elif isinstance(device.source, MachineImage):
  551. # Not supported
  552. pass
  553. else:
  554. # source is None, but destination is volume, therefore
  555. # create a blank volume. If the Zone is None, this
  556. # could fail since the volume and instance may be created
  557. # in two different zones.
  558. if not zone:
  559. raise InvalidConfigurationException(
  560. "A zone must be specified when launching with a"
  561. " new blank volume block device mapping.")
  562. new_vol = self.provider.block_store.volumes.create(
  563. '',
  564. device.size,
  565. zone)
  566. bd_type.volume_id = new_vol.id
  567. bd_type.delete_on_terminate = device.delete_on_terminate
  568. if device.size:
  569. bd_type.size = device.size
  570. else: # device is ephemeral
  571. bd_type.ephemeral_name = 'ephemeral%s' % ephemeral_counter
  572. return bdm
  573. def _get_net_id(self, launch_config):
  574. return (launch_config.network_interfaces[0]
  575. if len(launch_config.network_interfaces) > 0
  576. else None)
  577. def create_launch_config(self):
  578. return AWSLaunchConfig(self.provider)
  579. def get(self, instance_id):
  580. """
  581. Returns an instance given its id. Returns None
  582. if the object does not exist.
  583. """
  584. reservation = self.provider.ec2_conn.get_all_reservations(
  585. instance_ids=[instance_id])
  586. if reservation:
  587. return AWSInstance(self.provider, reservation[0].instances[0])
  588. else:
  589. return None
  590. def find(self, name, limit=None, marker=None):
  591. """
  592. Searches for an instance by a given list of attributes.
  593. :rtype: ``object`` of :class:`.Instance`
  594. :return: an Instance object
  595. """
  596. filtr = {'tag:Name': name}
  597. reservations = self.provider.ec2_conn.get_all_reservations(
  598. filters=filtr,
  599. max_results=limit,
  600. next_token=marker)
  601. instances = [AWSInstance(self.provider, inst)
  602. for res in reservations
  603. for inst in res.instances]
  604. return ServerPagedResultList(reservations.is_truncated,
  605. reservations.next_token,
  606. False, data=instances)
  607. def list(self, limit=None, marker=None):
  608. """
  609. List all instances.
  610. """
  611. reservations = self.provider.ec2_conn.get_all_reservations(
  612. max_results=limit,
  613. next_token=marker)
  614. instances = [AWSInstance(self.provider, inst)
  615. for res in reservations
  616. for inst in res.instances]
  617. return ServerPagedResultList(reservations.is_truncated,
  618. reservations.next_token,
  619. False, data=instances)
  620. AWS_INSTANCE_DATA_DEFAULT_URL = "https://d168wakzal7fp0.cloudfront.net/" \
  621. "aws_instance_data.json"
  622. class AWSInstanceTypesService(BaseInstanceTypesService):
  623. def __init__(self, provider):
  624. super(AWSInstanceTypesService, self).__init__(provider)
  625. @property
  626. def instance_data(self):
  627. """
  628. TODO: Needs a caching function with timeout
  629. """
  630. r = requests.get(self.provider.config.get(
  631. "aws_instance_info_url", AWS_INSTANCE_DATA_DEFAULT_URL))
  632. return r.json()
  633. def list(self, limit=None, marker=None):
  634. inst_types = [AWSInstanceType(self.provider, inst_type)
  635. for inst_type in self.instance_data]
  636. return ClientPagedResultList(self.provider, inst_types,
  637. limit=limit, marker=marker)
  638. class AWSRegionService(BaseRegionService):
  639. def __init__(self, provider):
  640. super(AWSRegionService, self).__init__(provider)
  641. def get(self, region_id):
  642. region = self.provider.ec2_conn.meta.client.describe_regions(
  643. Filters=[{
  644. 'Name': 'region-name',
  645. 'Values': [region_id]
  646. }]
  647. ).get('Regions', list())
  648. return AWSRegion(self.provider, region[0]) if len(region) else None
  649. def list(self, limit=None, marker=None):
  650. regions = [
  651. AWSRegion(self.provider, x) for x in
  652. self.provider.ec2_conn.meta.client.describe_regions()
  653. .get('Regions', list())
  654. ]
  655. return ClientPagedResultList(self.provider, regions,
  656. limit=limit, marker=marker)
  657. @property
  658. def current(self):
  659. return self.get(self.provider.session.region_name)
  660. class AWSNetworkService(BaseNetworkService):
  661. def __init__(self, provider):
  662. super(AWSNetworkService, self).__init__(provider)
  663. self._subnet_svc = AWSSubnetService(self.provider)
  664. self.iface = EC2ServiceFilter(self.provider,
  665. 'vpcs', AWSNetwork)
  666. self.iface_vips = EC2ServiceFilter(self.provider,
  667. 'vpc_addresses', AWSFloatingIP)
  668. self.iface_igws = EC2ServiceFilter(self.provider,
  669. 'internet_gateways', AWSRouter)
  670. def get(self, network_id):
  671. """Returns a network given its ID"""
  672. return self.iface.get(network_id, 'vpc-id')
  673. def list(self, limit=None, marker=None):
  674. """List all networks associated with this account"""
  675. return self.iface.list(limit=limit, marker=marker)
  676. def find(self, name, limit=None, marker=None):
  677. """Searches for a network by name"""
  678. return self.iface.find(name, 'tag:Name', limit=limit, marker=marker)
  679. def floating_ips(self, network_id=None):
  680. return [
  681. x for x in self.iface_vips.list()
  682. if not network_id or x.network_interface_id == network_id
  683. ]
  684. def routers(self):
  685. return self.iface_igws.list()
  686. def create(self, name=None):
  687. # AWS requried CIDR block to be specified when creating a network
  688. # so set a default one and use the largest possible netmask.
  689. default_cidr = '10.0.0.0/16'
  690. res = self.iface.create('create_vpc', CidrBlock=default_cidr)
  691. if not wait_for_load(res):
  692. return None
  693. if name:
  694. res.name = name
  695. return res
  696. @property
  697. def subnets(self):
  698. return self._subnet_svc
  699. def create_floating_ip(self):
  700. return AWSFloatingIP(
  701. self.provider,
  702. self.provider.ec2_conn.VpcAddress(
  703. self.provider.ec2_conn.meta.client.allocate_address(
  704. Domain='vpc')['AllocationId']))
  705. def create_router(self, name=None):
  706. timeout = 10
  707. res = self.iface.create('create_internet_gateway')
  708. if not wait_for_load(res):
  709. return None
  710. if name:
  711. res.name = name
  712. return res
  713. class AWSSubnetService(BaseSubnetService):
  714. def __init__(self, provider):
  715. super(AWSSubnetService, self).__init__(provider)
  716. self.iface = EC2ServiceFilter(self.provider,
  717. 'subnets', AWSSubnet)
  718. def get(self, subnet_id):
  719. """Returns a subnet given its ID"""
  720. return self.iface.get(subnet_id, 'subnet-id')
  721. def list(self, network=None, limit=None, marker=None):
  722. """List all subnets associated with this account"""
  723. network_id = network.id if isinstance(network, AWSNetwork) else network
  724. return [
  725. x for x in self.iface.list(limit=limit, marker=marker)
  726. if not network_id or x.network_id == network_id
  727. ]
  728. def find(self, name, limit=None, marker=None):
  729. """Searches for a subnet by name"""
  730. return self.iface.find(name, 'tag:Name', limit=limit, marker=marker)
  731. def create(self, network, cidr_block, name=None):
  732. network_id = network.id if isinstance(network, AWSNetwork) else network
  733. res = self.iface.create('create_subnet',
  734. VpcId=network_id,
  735. CidrBlock=cidr_block)
  736. if name:
  737. res.name = name
  738. return res
  739. def delete(self, subnet):
  740. subnet_id = subnet.id if isinstance(subnet, AWSSubnet) else subnet
  741. return self.iface.delete(subnet_id, 'subnet-id')