resources.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101
  1. """
  2. DataTypes used by this provider
  3. """
  4. import hashlib
  5. import inspect
  6. from botocore.exceptions import ClientError
  7. from cloudbridge.cloud.base.resources import BaseAttachmentInfo
  8. from cloudbridge.cloud.base.resources import BaseBucket
  9. from cloudbridge.cloud.base.resources import BaseBucketObject
  10. from cloudbridge.cloud.base.resources import BaseFloatingIP
  11. from cloudbridge.cloud.base.resources import BaseInstance
  12. from cloudbridge.cloud.base.resources import BaseInternetGateway
  13. from cloudbridge.cloud.base.resources import BaseKeyPair
  14. from cloudbridge.cloud.base.resources import BaseLaunchConfig
  15. from cloudbridge.cloud.base.resources import BaseMachineImage
  16. from cloudbridge.cloud.base.resources import BaseNetwork
  17. from cloudbridge.cloud.base.resources import BasePlacementZone
  18. from cloudbridge.cloud.base.resources import BaseRegion
  19. from cloudbridge.cloud.base.resources import BaseRouter
  20. from cloudbridge.cloud.base.resources import BaseSecurityGroup
  21. from cloudbridge.cloud.base.resources import BaseSecurityGroupRule
  22. from cloudbridge.cloud.base.resources import BaseSnapshot
  23. from cloudbridge.cloud.base.resources import BaseSubnet
  24. from cloudbridge.cloud.base.resources import BaseVMType
  25. from cloudbridge.cloud.base.resources import BaseVolume
  26. from cloudbridge.cloud.base.resources import ClientPagedResultList
  27. from cloudbridge.cloud.interfaces.resources import GatewayState
  28. from cloudbridge.cloud.interfaces.resources import InstanceState
  29. from cloudbridge.cloud.interfaces.resources import MachineImageState
  30. from cloudbridge.cloud.interfaces.resources import NetworkState
  31. from cloudbridge.cloud.interfaces.resources import RouterState
  32. from cloudbridge.cloud.interfaces.resources import SecurityGroup
  33. from cloudbridge.cloud.interfaces.resources import SnapshotState
  34. from cloudbridge.cloud.interfaces.resources import SubnetState
  35. from cloudbridge.cloud.interfaces.resources import VolumeState
  36. from .helpers import find_tag_value
  37. from .helpers import trim_empty_params
  38. class AWSMachineImage(BaseMachineImage):
  39. IMAGE_STATE_MAP = {
  40. 'pending': MachineImageState.PENDING,
  41. 'transient': MachineImageState.PENDING,
  42. 'available': MachineImageState.AVAILABLE,
  43. 'deregistered': MachineImageState.ERROR,
  44. 'failed': MachineImageState.ERROR,
  45. 'error': MachineImageState.ERROR,
  46. 'invalid': MachineImageState.ERROR
  47. }
  48. def __init__(self, provider, image):
  49. super(AWSMachineImage, self).__init__(provider)
  50. if isinstance(image, AWSMachineImage):
  51. # pylint:disable=protected-access
  52. self._ec2_image = image._ec2_image
  53. else:
  54. self._ec2_image = image
  55. @property
  56. def id(self):
  57. return self._ec2_image.id
  58. @property
  59. def name(self):
  60. try:
  61. return self._ec2_image.name
  62. except AttributeError:
  63. return None
  64. @property
  65. def description(self):
  66. try:
  67. return self._ec2_image.description
  68. except AttributeError:
  69. return None
  70. @property
  71. def min_disk(self):
  72. vols = [bdm.get('Ebs', {}) for bdm in
  73. self._ec2_image.block_device_mappings if
  74. bdm.get('DeviceName') == self._ec2_image.root_device_name]
  75. if vols:
  76. return vols[0].get('VolumeSize')
  77. else:
  78. return None
  79. def delete(self):
  80. snapshot_id = [
  81. bdm.get('Ebs', {}).get('SnapshotId') for bdm in
  82. self._ec2_image.block_device_mappings if
  83. bdm.get('DeviceName') == self._ec2_image.root_device_name]
  84. self._ec2_image.deregister()
  85. self.wait_for([MachineImageState.UNKNOWN, MachineImageState.ERROR])
  86. snapshot = self._provider.block_store.snapshots.get(snapshot_id[0])
  87. if snapshot:
  88. snapshot.delete()
  89. @property
  90. def state(self):
  91. try:
  92. return AWSMachineImage.IMAGE_STATE_MAP.get(
  93. self._ec2_image.state, MachineImageState.UNKNOWN)
  94. except AttributeError:
  95. return MachineImageState.UNKNOWN
  96. def refresh(self):
  97. self._ec2_image.reload()
  98. class AWSPlacementZone(BasePlacementZone):
  99. def __init__(self, provider, zone, region):
  100. super(AWSPlacementZone, self).__init__(provider)
  101. if isinstance(zone, AWSPlacementZone):
  102. # pylint:disable=protected-access
  103. self._aws_zone = zone._aws_zone
  104. self._aws_region = zone._aws_region
  105. else:
  106. self._aws_zone = zone
  107. self._aws_region = region
  108. @property
  109. def id(self):
  110. return self._aws_zone
  111. @property
  112. def name(self):
  113. return self._aws_zone
  114. @property
  115. def region_name(self):
  116. return self._aws_region
  117. class AWSVMType(BaseVMType):
  118. def __init__(self, provider, instance_dict):
  119. super(AWSVMType, self).__init__(provider)
  120. self._inst_dict = instance_dict
  121. @property
  122. def id(self):
  123. return str(self._inst_dict['instance_type'])
  124. @property
  125. def name(self):
  126. return self._inst_dict['instance_type']
  127. @property
  128. def family(self):
  129. return self._inst_dict.get('family')
  130. @property
  131. def vcpus(self):
  132. return self._inst_dict.get('vCPU')
  133. @property
  134. def ram(self):
  135. return self._inst_dict.get('memory')
  136. @property
  137. def size_root_disk(self):
  138. return 0
  139. @property
  140. def size_ephemeral_disks(self):
  141. storage = self._inst_dict.get('storage')
  142. if storage:
  143. return storage.get('size') * storage.get("devices")
  144. else:
  145. return 0
  146. @property
  147. def num_ephemeral_disks(self):
  148. storage = self._inst_dict.get('storage')
  149. if storage:
  150. return storage.get("devices")
  151. else:
  152. return 0
  153. @property
  154. def extra_data(self):
  155. return {key: val for key, val in enumerate(self._inst_dict)
  156. if key not in ["instance_type", "family", "vCPU", "memory"]}
  157. class AWSInstance(BaseInstance):
  158. # ref:
  159. # http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-lifecycle.html
  160. INSTANCE_STATE_MAP = {
  161. 'pending': InstanceState.PENDING,
  162. 'running': InstanceState.RUNNING,
  163. 'shutting-down': InstanceState.CONFIGURING,
  164. 'terminated': InstanceState.DELETED,
  165. 'stopping': InstanceState.CONFIGURING,
  166. 'stopped': InstanceState.STOPPED
  167. }
  168. def __init__(self, provider, ec2_instance):
  169. super(AWSInstance, self).__init__(provider)
  170. self._ec2_instance = ec2_instance
  171. @property
  172. def id(self):
  173. return self._ec2_instance.id
  174. @property
  175. def name(self):
  176. """
  177. .. note:: an instance must have a (case sensitive) tag ``Name``
  178. """
  179. return find_tag_value(self._ec2_instance.tags, 'Name')
  180. @name.setter
  181. # pylint:disable=arguments-differ
  182. def name(self, value):
  183. self.assert_valid_resource_name(value)
  184. self._ec2_instance.create_tags(Tags=[{'Key': 'Name', 'Value': value}])
  185. @property
  186. def public_ips(self):
  187. return [self._ec2_instance.public_ip_address]
  188. @property
  189. def private_ips(self):
  190. return [self._ec2_instance.private_ip_address]
  191. @property
  192. def vm_type_id(self):
  193. return self._ec2_instance.instance_type
  194. @property
  195. def vm_type(self):
  196. return self._provider.compute.vm_types.find(
  197. name=self._ec2_instance.instance_type)[0]
  198. def reboot(self):
  199. self._ec2_instance.reboot()
  200. def delete(self):
  201. self._ec2_instance.terminate()
  202. @property
  203. def image_id(self):
  204. return self._ec2_instance.image_id
  205. @property
  206. def zone_id(self):
  207. return self._ec2_instance.placement.get('AvailabilityZone')
  208. @property
  209. def security_groups(self):
  210. return [
  211. self._provider.security.security_groups.get(group_id)
  212. for group_id in self.security_group_ids
  213. ]
  214. @property
  215. def security_group_ids(self):
  216. return list(set([
  217. group.get('GroupId') for group in
  218. self._ec2_instance.security_groups
  219. ]))
  220. @property
  221. def key_pair_name(self):
  222. return self._ec2_instance.key_name
  223. def create_image(self, name):
  224. self.assert_valid_resource_name(name)
  225. image = AWSMachineImage(self._provider,
  226. self._ec2_instance.create_image(Name=name))
  227. # Wait for the image to exist
  228. self._provider.ec2_conn.meta.client.get_waiter('image_exists').wait(
  229. ImageIds=[image.id])
  230. # Return the image
  231. image.refresh()
  232. return image
  233. def add_floating_ip(self, ip_address):
  234. allocation_id = (
  235. None if not self._ec2_instance.vpc_id else
  236. ip_address.id if isinstance(ip_address, AWSFloatingIP) else
  237. [x for x in self._provider.networking.networks.floating_ips
  238. if x.public_ip == ip_address][0].id)
  239. params = trim_empty_params({
  240. 'InstanceId': self.id,
  241. 'PublicIp': None if self._ec2_instance.vpc_id else ip_address,
  242. 'AllocationId': allocation_id})
  243. self._provider.ec2_conn.meta.client.associate_address(**params)
  244. self.refresh()
  245. def remove_floating_ip(self, ip_address):
  246. association_id = (
  247. None if not self._ec2_instance.vpc_id else
  248. ip_address._ip.association_id
  249. if isinstance(ip_address, AWSFloatingIP) else
  250. [x for x in self._ec2_instance.vpc_addresses.all()
  251. if x.public_ip == ip_address][0].association_id)
  252. params = trim_empty_params({
  253. 'PublicIp': None if self._ec2_instance.vpc_id else ip_address,
  254. 'AssociationId': association_id})
  255. self._provider.ec2_conn.meta.client.disassociate_address(**params)
  256. self.refresh()
  257. def add_security_group(self, sg):
  258. self._ec2_instance.modify_attribute(
  259. Groups=self.security_group_ids + [sg.id])
  260. def remove_security_group(self, sg):
  261. self._ec2_instance.modify_attribute(
  262. Groups=([sg_id for sg_id in self.security_group_ids
  263. if sg_id != sg.id]))
  264. @property
  265. def state(self):
  266. try:
  267. return AWSInstance.INSTANCE_STATE_MAP.get(
  268. self._ec2_instance.state['Name'], InstanceState.UNKNOWN)
  269. except AttributeError:
  270. return InstanceState.UNKNOWN
  271. def refresh(self):
  272. try:
  273. self._ec2_instance.reload()
  274. except ClientError:
  275. # The instance no longer exists and cannot be refreshed.
  276. # set the state to unknown
  277. self._ec2_instance.state = {'Name': InstanceState.UNKNOWN}
  278. def _wait_till_exists(self, timeout=None, interval=None):
  279. self._ec2_instance.wait_until_exists()
  280. class AWSVolume(BaseVolume):
  281. # Ref:
  282. # http://docs.aws.amazon.com/AWSEC2/latest/CommandLineReference/
  283. # ApiReference-cmd-DescribeVolumes.html
  284. VOLUME_STATE_MAP = {
  285. 'creating': VolumeState.CREATING,
  286. 'available': VolumeState.AVAILABLE,
  287. 'in-use': VolumeState.IN_USE,
  288. 'deleting': VolumeState.CONFIGURING,
  289. 'deleted': VolumeState.DELETED,
  290. 'error': VolumeState.ERROR
  291. }
  292. def __init__(self, provider, volume):
  293. super(AWSVolume, self).__init__(provider)
  294. self._volume = volume
  295. @property
  296. def id(self):
  297. return self._volume.id
  298. @property
  299. def name(self):
  300. return find_tag_value(self._volume.tags, 'Name')
  301. @name.setter
  302. # pylint:disable=arguments-differ
  303. def name(self, value):
  304. self.assert_valid_resource_name(value)
  305. self._volume.create_tags(Tags=[{'Key': 'Name', 'Value': value}])
  306. @property
  307. def description(self):
  308. return find_tag_value(self._volume.tags, 'Description')
  309. @description.setter
  310. def description(self, value):
  311. self._volume.create_tags(Tags=[{'Key': 'Description', 'Value': value}])
  312. @property
  313. def size(self):
  314. return self._volume.size
  315. @property
  316. def create_time(self):
  317. return self._volume.create_time
  318. @property
  319. def zone_id(self):
  320. return self._volume.availability_zone
  321. @property
  322. def source(self):
  323. if self._volume.snapshot_id:
  324. return self._provider.block_store.snapshots.get(
  325. self._volume.snapshot_id)
  326. return None
  327. @property
  328. def attachments(self):
  329. return [
  330. BaseAttachmentInfo(self,
  331. a.get('InstanceId'),
  332. a.get('Device'))
  333. for a in self._volume.attachments
  334. ][0] if self._volume.attachments else None
  335. def attach(self, instance, device):
  336. instance_id = instance.id if isinstance(
  337. instance,
  338. AWSInstance) else instance
  339. self._volume.attach_to_instance(InstanceId=instance_id,
  340. Device=device)
  341. def detach(self, force=False):
  342. a = self.attachments
  343. if a:
  344. self._volume.detach_from_instance(
  345. InstanceId=a.instance_id,
  346. Device=a.device,
  347. Force=force)
  348. def create_snapshot(self, name, description=None):
  349. snap = AWSSnapshot(
  350. self._provider,
  351. self._volume.create_snapshot(
  352. Description=description))
  353. snap.name = name
  354. return snap
  355. def delete(self):
  356. self._volume.delete()
  357. @property
  358. def state(self):
  359. try:
  360. return AWSVolume.VOLUME_STATE_MAP.get(
  361. self._volume.state, VolumeState.UNKNOWN)
  362. except AttributeError:
  363. return VolumeState.UNKNOWN
  364. def refresh(self):
  365. try:
  366. self._volume.reload()
  367. except ClientError:
  368. # The volume no longer exists and cannot be refreshed.
  369. # set the status to unknown
  370. self._volume.state = VolumeState.UNKNOWN
  371. class AWSSnapshot(BaseSnapshot):
  372. # Ref: http://docs.aws.amazon.com/AWSEC2/latest/CommandLineReference/
  373. # ApiReference-cmd-DescribeSnapshots.html
  374. SNAPSHOT_STATE_MAP = {
  375. 'pending': SnapshotState.PENDING,
  376. 'deleting': SnapshotState.PENDING,
  377. 'completed': SnapshotState.AVAILABLE,
  378. 'error': SnapshotState.ERROR
  379. }
  380. def __init__(self, provider, snapshot):
  381. super(AWSSnapshot, self).__init__(provider)
  382. self._snapshot = snapshot
  383. @property
  384. def id(self):
  385. return self._snapshot.id
  386. @property
  387. def name(self):
  388. return find_tag_value(self._snapshot.tags, 'Name')
  389. @name.setter
  390. # pylint:disable=arguments-differ
  391. def name(self, value):
  392. self.assert_valid_resource_name(value)
  393. self._snapshot.create_tags(Tags=[{'Key': 'Name', 'Value': value}])
  394. @property
  395. def description(self):
  396. return find_tag_value(self._snapshot.tags, 'Description')
  397. @description.setter
  398. def description(self, value):
  399. self._snapshot.create_tags(Tags=[{
  400. 'Key': 'Description', 'Value': value}])
  401. @property
  402. def size(self):
  403. return self._snapshot.volume_size
  404. @property
  405. def volume_id(self):
  406. return self._snapshot.volume_id
  407. @property
  408. def create_time(self):
  409. return self._snapshot.start_time
  410. @property
  411. def state(self):
  412. try:
  413. return AWSSnapshot.SNAPSHOT_STATE_MAP.get(
  414. self._snapshot.state, SnapshotState.UNKNOWN)
  415. except AttributeError:
  416. return SnapshotState.UNKNOWN
  417. def refresh(self):
  418. try:
  419. self._snapshot.reload()
  420. except ClientError:
  421. # The snapshot no longer exists and cannot be refreshed.
  422. # set the status to unknown
  423. self._snapshot.state = SnapshotState.UNKNOWN
  424. def delete(self):
  425. self._snapshot.delete()
  426. def create_volume(self, placement, size=None, volume_type=None, iops=None):
  427. cb_vol = self._provider.block_store.volumes.create(
  428. name=self.name,
  429. size=size,
  430. zone=placement,
  431. snapshot=self.id)
  432. cb_vol.wait_till_ready()
  433. cb_vol.name = "from_snap_{0}".format(self.name or self.id)
  434. return cb_vol
  435. class AWSKeyPair(BaseKeyPair):
  436. def __init__(self, provider, key_pair):
  437. super(AWSKeyPair, self).__init__(provider, key_pair)
  438. @property
  439. def material(self):
  440. # boto3 object will only have this field if the value is not empty
  441. if hasattr(self._key_pair, 'key_material'):
  442. return self._key_pair.key_material
  443. else:
  444. return None
  445. class AWSSecurityGroup(BaseSecurityGroup):
  446. def __init__(self, provider, security_group):
  447. super(AWSSecurityGroup, self).__init__(provider, security_group)
  448. @property
  449. def name(self):
  450. return self._security_group.group_name
  451. @property
  452. def network_id(self):
  453. return self._security_group.vpc_id
  454. @property
  455. def rules(self):
  456. return [AWSSecurityGroupRule(self._provider, r, self)
  457. for r in self._security_group.ip_permissions]
  458. def add_rule(self, ip_protocol=None, from_port=None, to_port=None,
  459. cidr_ip=None, src_group=None):
  460. try:
  461. src_group_id = (
  462. src_group.id if isinstance(src_group, SecurityGroup)
  463. else src_group)
  464. ip_perm_entry = {
  465. 'IpProtocol': ip_protocol,
  466. 'FromPort': from_port,
  467. 'ToPort': to_port,
  468. 'IpRanges': [{'CidrIp': cidr_ip}] if cidr_ip else None,
  469. 'UserIdGroupPairs': [{
  470. 'GroupId': src_group_id}
  471. ] if src_group_id else None
  472. }
  473. # Filter out empty values to please Boto
  474. ip_perms = [trim_empty_params(ip_perm_entry)]
  475. self._security_group.authorize_ingress(IpPermissions=ip_perms)
  476. self._security_group.reload()
  477. return self.get_rule(ip_protocol, from_port, to_port, cidr_ip,
  478. src_group_id)
  479. except ClientError as ec2e:
  480. if ec2e.response['Error']['Code'] == "InvalidPermission.Duplicate":
  481. return self.get_rule(ip_protocol, from_port, to_port, cidr_ip,
  482. src_group)
  483. else:
  484. raise ec2e
  485. def get_rule(self, ip_protocol=None, from_port=None, to_port=None,
  486. cidr_ip=None, src_group=None):
  487. src_group_id = (src_group.id if isinstance(src_group, SecurityGroup)
  488. else src_group)
  489. for rule in self._security_group.ip_permissions:
  490. if ip_protocol and rule['IpProtocol'] != ip_protocol:
  491. continue
  492. elif from_port and rule['FromPort'] != from_port:
  493. continue
  494. elif to_port and rule['ToPort'] != to_port:
  495. continue
  496. elif cidr_ip:
  497. if cidr_ip not in [x['CidrIp'] for x in rule['IpRanges']]:
  498. continue
  499. elif src_group_id:
  500. if src_group_id not in [
  501. group_pair.get('GroupId') for group_pair in
  502. rule.get('UserIdGroupPairs', [])]:
  503. continue
  504. return AWSSecurityGroupRule(self._provider, rule, self)
  505. return None
  506. def to_json(self):
  507. attr = inspect.getmembers(self, lambda a: not inspect.isroutine(a))
  508. js = {k: v for (k, v) in attr if not k.startswith('_')}
  509. json_rules = [r.to_json() for r in self.rules]
  510. js['rules'] = json_rules
  511. if js.get('network_id'):
  512. js.pop('network_id') # Omit for consistency across cloud providers
  513. return js
  514. class AWSSecurityGroupRule(BaseSecurityGroupRule):
  515. def __init__(self, provider, rule, parent):
  516. super(AWSSecurityGroupRule, self).__init__(provider, rule, parent)
  517. @property
  518. def id(self):
  519. md5 = hashlib.md5()
  520. md5.update("{0}-{1}-{2}-{3}".format(
  521. self.ip_protocol, self.from_port, self.to_port, self.cidr_ip)
  522. .encode('ascii'))
  523. return md5.hexdigest()
  524. @property
  525. def ip_protocol(self):
  526. return self._rule.get('IpProtocol')
  527. @property
  528. def from_port(self):
  529. return self._rule.get('FromPort', 0)
  530. @property
  531. def to_port(self):
  532. return self._rule.get('ToPort', 0)
  533. @property
  534. def cidr_ip(self):
  535. if len(self._rule.get('IpRanges', [])) > 0:
  536. return self._rule['IpRanges'][0].get('CidrIp')
  537. return None
  538. @property
  539. def group_id(self):
  540. if len(self._rule['UserIdGroupPairs']) > 0:
  541. return self._rule['UserIdGroupPairs'][0]['GroupId']
  542. else:
  543. return None
  544. @property
  545. def group(self):
  546. if self.group_id:
  547. return AWSSecurityGroup(
  548. self._provider,
  549. self._provider.ec2_conn.SecurityGroup(self.group_id))
  550. else:
  551. return None
  552. def to_json(self):
  553. attr = inspect.getmembers(self, lambda a: not (inspect.isroutine(a)))
  554. js = {k: v for (k, v) in attr if not k.startswith('_')}
  555. js['group'] = self.group.id if self.group else ''
  556. js['parent'] = self.parent.id if self.parent else ''
  557. return js
  558. def delete(self):
  559. ip_perm_entry = {
  560. 'IpProtocol': self.ip_protocol,
  561. 'FromPort': self.from_port,
  562. 'ToPort': self.to_port,
  563. 'IpRanges': [{'CidrIp': self.cidr_ip}] if self.cidr_ip else None,
  564. 'UserIdGroupPairs': [{
  565. 'GroupId': self.group_id}
  566. ] if self.group_id else None
  567. }
  568. # Filter out empty values to please Boto
  569. ip_perms = [trim_empty_params(ip_perm_entry)]
  570. self.parent._security_group.revoke_ingress(IpPermissions=ip_perms)
  571. self.parent._security_group.reload()
  572. class AWSBucketObject(BaseBucketObject):
  573. class BucketObjIterator():
  574. CHUNK_SIZE = 4096
  575. def __init__(self, body):
  576. self.body = body
  577. def __iter__(self):
  578. while True:
  579. data = self.read(self.CHUNK_SIZE)
  580. if data:
  581. yield data
  582. else:
  583. break
  584. def read(self, length):
  585. return self.body.read(amt=length)
  586. def close(self):
  587. return self.body.close()
  588. def __init__(self, provider, obj):
  589. super(AWSBucketObject, self).__init__(provider)
  590. self._obj = obj
  591. @property
  592. def id(self):
  593. return self._obj.key
  594. @property
  595. def name(self):
  596. return self.id
  597. @property
  598. def size(self):
  599. return self._obj.size
  600. @property
  601. def last_modified(self):
  602. return self._obj.last_modified.strftime("%Y-%m-%dT%H:%M:%S.%f")
  603. def iter_content(self):
  604. return self.BucketObjIterator(self._obj.get().get('Body'))
  605. def upload(self, data):
  606. self._obj.put(Body=data)
  607. def upload_from_file(self, path):
  608. self._obj.upload_file(path)
  609. def delete(self):
  610. self._obj.delete()
  611. def generate_url(self, expires_in=0):
  612. return self._provider.s3_conn.meta.client.generate_presigned_url(
  613. 'get_object',
  614. Params={'Bucket': self._obj.bucket_name, 'Key': self.id},
  615. ExpiresIn=expires_in)
  616. class AWSBucket(BaseBucket):
  617. def __init__(self, provider, bucket):
  618. super(AWSBucket, self).__init__(provider)
  619. self._bucket = bucket
  620. @property
  621. def id(self):
  622. return self._bucket.name
  623. @property
  624. def name(self):
  625. return self._bucket.name
  626. def get(self, name):
  627. try:
  628. obj = self._bucket.Object(name)
  629. # load() throws an error if object does not exist
  630. obj.load()
  631. return AWSBucketObject(self._provider, obj)
  632. except ClientError:
  633. return None
  634. def list(self, limit=None, marker=None, prefix=None):
  635. if prefix:
  636. boto_objs = self._bucket.objects.filter(Prefix=prefix)
  637. else:
  638. boto_objs = self._bucket.objects.all()
  639. objects = [AWSBucketObject(self._provider, obj)
  640. for obj in boto_objs]
  641. return ClientPagedResultList(self._provider, objects,
  642. limit=limit, marker=marker)
  643. def find(self, name, limit=None, marker=None):
  644. objects = [obj for obj in self if obj.name == name]
  645. return ClientPagedResultList(self._provider, objects,
  646. limit=limit, marker=marker)
  647. def delete(self, delete_contents=False):
  648. self._bucket.delete()
  649. def create_object(self, name):
  650. obj = self._bucket.Object(name)
  651. return AWSBucketObject(self._provider, obj)
  652. class AWSRegion(BaseRegion):
  653. def __init__(self, provider, aws_region):
  654. super(AWSRegion, self).__init__(provider)
  655. self._aws_region = aws_region
  656. @property
  657. def id(self):
  658. return self._aws_region.get('RegionName')
  659. @property
  660. def name(self):
  661. return self.id
  662. @property
  663. def zones(self):
  664. if self.id == self._provider.region_name: # optimisation
  665. conn = self._provider.ec2_conn
  666. else:
  667. conn = self._provider._conect_ec2_region(region_name=self.id)
  668. zones = (conn.meta.client.describe_availability_zones()
  669. .get('AvailabilityZones', []))
  670. return [AWSPlacementZone(self._provider, zone.get('ZoneName'),
  671. self.id)
  672. for zone in zones]
  673. class AWSNetwork(BaseNetwork):
  674. # Ref:
  675. # docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeVpcs.html
  676. _NETWORK_STATE_MAP = {
  677. 'pending': NetworkState.PENDING,
  678. 'available': NetworkState.AVAILABLE,
  679. }
  680. def __init__(self, provider, network):
  681. super(AWSNetwork, self).__init__(provider)
  682. self._vpc = network
  683. @property
  684. def id(self):
  685. return self._vpc.id
  686. @property
  687. def name(self):
  688. return find_tag_value(self._vpc.tags, 'Name')
  689. @name.setter
  690. # pylint:disable=arguments-differ
  691. def name(self, value):
  692. self.assert_valid_resource_name(value)
  693. self._vpc.create_tags(Tags=[{'Key': 'Name', 'Value': value}])
  694. @property
  695. def external(self):
  696. """
  697. For AWS, all VPC networks can be connected to the Internet so always
  698. return ``True``.
  699. """
  700. return True
  701. @property
  702. def state(self):
  703. try:
  704. return AWSNetwork._NETWORK_STATE_MAP.get(
  705. self._vpc.state, NetworkState.UNKNOWN)
  706. except AttributeError:
  707. return NetworkState.UNKNOWN
  708. @property
  709. def cidr_block(self):
  710. return self._vpc.cidr_block
  711. def delete(self):
  712. return self._vpc.delete()
  713. @property
  714. def subnets(self):
  715. return [AWSSubnet(self._provider, s) for s in self._vpc.subnets.all()]
  716. def refresh(self):
  717. try:
  718. self._vpc.reload()
  719. except ClientError:
  720. # The network no longer exists and cannot be refreshed.
  721. # set the status to unknown
  722. self._vpc.state = NetworkState.UNKNOWN
  723. def wait_till_ready(self, timeout=None, interval=None):
  724. self._provider.ec2_conn.meta.client.get_waiter('vpc_available').wait(
  725. VpcIds=[self.id])
  726. self.refresh()
  727. class AWSSubnet(BaseSubnet):
  728. # http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSubnets.html
  729. _SUBNET_STATE_MAP = {
  730. 'pending': SubnetState.PENDING,
  731. 'available': SubnetState.AVAILABLE,
  732. }
  733. def __init__(self, provider, subnet):
  734. super(AWSSubnet, self).__init__(provider)
  735. self._subnet = subnet
  736. @property
  737. def id(self):
  738. return self._subnet.id
  739. @property
  740. def name(self):
  741. return find_tag_value(self._subnet.tags, 'Name')
  742. @name.setter
  743. # pylint:disable=arguments-differ
  744. def name(self, value):
  745. self.assert_valid_resource_name(value)
  746. self._subnet.create_tags(Tags=[{'Key': 'Name', 'Value': value}])
  747. @property
  748. def cidr_block(self):
  749. return self._subnet.cidr_block
  750. @property
  751. def network_id(self):
  752. return self._subnet.vpc_id
  753. @property
  754. def zone(self):
  755. return AWSPlacementZone(self._provider, self._subnet.availability_zone,
  756. self._provider.region_name)
  757. def delete(self):
  758. self._subnet.delete()
  759. @property
  760. def state(self):
  761. try:
  762. return self._SUBNET_STATE_MAP.get(
  763. self._subnet.state, SubnetState.UNKNOWN)
  764. except AttributeError:
  765. return SubnetState.UNKNOWN
  766. def refresh(self):
  767. subnet = self._provider.networking.subnets.get(self.id)
  768. if subnet:
  769. # pylint:disable=protected-access
  770. self._subnet = subnet._subnet
  771. else:
  772. # subnet no longer exists
  773. self._subnet.state = SubnetState.UNKNOWN
  774. class AWSFloatingIP(BaseFloatingIP):
  775. def __init__(self, provider, floating_ip):
  776. super(AWSFloatingIP, self).__init__(provider)
  777. self._ip = floating_ip
  778. @property
  779. def id(self):
  780. return self._ip.allocation_id
  781. @property
  782. def public_ip(self):
  783. return self._ip.public_ip
  784. @property
  785. def private_ip(self):
  786. return self._ip.private_ip_address
  787. def in_use(self):
  788. return True if self._ip.instance_id else False
  789. def delete(self):
  790. return self._ip.release()
  791. class AWSRouter(BaseRouter):
  792. def __init__(self, provider, route_table):
  793. super(AWSRouter, self).__init__(provider)
  794. self._route_table = route_table
  795. @property
  796. def id(self):
  797. return self._route_table.id
  798. @property
  799. def name(self):
  800. return find_tag_value(self._route_table.tags, 'Name')
  801. @name.setter
  802. # pylint:disable=arguments-differ
  803. def name(self, value):
  804. self.assert_valid_resource_name(value)
  805. self._route_table.create_tags(Tags=[{'Key': 'Name', 'Value': value}])
  806. def refresh(self):
  807. try:
  808. self._route_table.reload()
  809. except ClientError:
  810. self._route_table.associations = None
  811. @property
  812. def state(self):
  813. if self._route_table.associations:
  814. return RouterState.ATTACHED
  815. return RouterState.DETACHED
  816. @property
  817. def network_id(self):
  818. return self._route_table.vpc_id
  819. def delete(self):
  820. self._route_table.delete()
  821. def attach_subnet(self, subnet):
  822. subnet_id = subnet.id if isinstance(subnet, AWSSubnet) else subnet
  823. self._route_table.associate_with_subnet(SubnetId=subnet_id)
  824. self.refresh()
  825. def detach_subnet(self, subnet):
  826. subnet_id = subnet.id if isinstance(subnet, AWSSubnet) else subnet
  827. associations = [a for a in self._route_table.associations
  828. if a.subnet_id == subnet_id]
  829. for a in associations:
  830. a.delete()
  831. self.refresh()
  832. def attach_gateway(self, gateway):
  833. gw_id = (gateway.id if isinstance(gateway, AWSInternetGateway)
  834. else gateway)
  835. return self._provider.ec2_conn.meta.client.attach_internet_gateway(
  836. InternetGatewayId=gw_id, VpcId=self._route_table.vpc_id)
  837. def detach_gateway(self, gateway):
  838. gw_id = (gateway.id if isinstance(gateway, AWSInternetGateway)
  839. else gateway)
  840. return self._provider.ec2_conn.meta.client.detach_internet_gateway(
  841. InternetGatewayId=gw_id, VpcId=self._route_table.vpc_id)
  842. class AWSInternetGateway(BaseInternetGateway):
  843. def __init__(self, provider, gateway):
  844. super(AWSInternetGateway, self).__init__(provider)
  845. self._gateway = gateway
  846. self._gateway.state = ''
  847. @property
  848. def id(self):
  849. return self._gateway.id
  850. @property
  851. def name(self):
  852. return find_tag_value(self._gateway.tags, 'Name')
  853. @name.setter
  854. # pylint:disable=arguments-differ
  855. def name(self, value):
  856. self.assert_valid_resource_name(value)
  857. self._gateway.create_tags(Tags=[{'Key': 'Name', 'Value': value}])
  858. def refresh(self):
  859. try:
  860. self._gateway.reload()
  861. except ClientError:
  862. self._gateway.state = GatewayState.UNKNOWN
  863. @property
  864. def state(self):
  865. if self._gateway.state == GatewayState.UNKNOWN:
  866. return GatewayState.UNKNOWN
  867. else:
  868. return GatewayState.AVAILABLE
  869. @property
  870. def network_id(self):
  871. if self._gateway.attachments:
  872. return self._gateway.attachments[0].get('VpcId')
  873. return None
  874. def delete(self):
  875. self._gateway.delete()
  876. class AWSLaunchConfig(BaseLaunchConfig):
  877. def __init__(self, provider):
  878. super(AWSLaunchConfig, self).__init__(provider)