resources.py 34 KB

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