resources.py 40 KB

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