resources.py 37 KB

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