resources.py 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205
  1. """
  2. DataTypes used by this provider
  3. """
  4. import hashlib
  5. import inspect
  6. import json
  7. from datetime import datetime
  8. from boto.exception import EC2ResponseError
  9. from boto.s3.key import Key
  10. from cloudbridge.cloud.base.resources import BaseAttachmentInfo
  11. from cloudbridge.cloud.base.resources import BaseBucket
  12. from cloudbridge.cloud.base.resources import BaseBucketObject
  13. from cloudbridge.cloud.base.resources import BaseFloatingIP
  14. from cloudbridge.cloud.base.resources import BaseInstance
  15. from cloudbridge.cloud.base.resources import BaseInstanceType
  16. from cloudbridge.cloud.base.resources import BaseKeyPair
  17. from cloudbridge.cloud.base.resources import BaseLaunchConfig
  18. from cloudbridge.cloud.base.resources import BaseMachineImage
  19. from cloudbridge.cloud.base.resources import BaseNetwork
  20. from cloudbridge.cloud.base.resources import BasePlacementZone
  21. from cloudbridge.cloud.base.resources import BaseRegion
  22. from cloudbridge.cloud.base.resources import BaseRouter
  23. from cloudbridge.cloud.base.resources import BaseSecurityGroup
  24. from cloudbridge.cloud.base.resources import BaseSecurityGroupRule
  25. from cloudbridge.cloud.base.resources import BaseSnapshot
  26. from cloudbridge.cloud.base.resources import BaseSubnet
  27. from cloudbridge.cloud.base.resources import BaseVolume
  28. from cloudbridge.cloud.base.resources import ClientPagedResultList
  29. from cloudbridge.cloud.interfaces.resources import InstanceState
  30. from cloudbridge.cloud.interfaces.resources import MachineImageState
  31. from cloudbridge.cloud.interfaces.resources import NetworkState
  32. from cloudbridge.cloud.interfaces.resources import RouterState
  33. from cloudbridge.cloud.interfaces.resources import SecurityGroup
  34. from cloudbridge.cloud.interfaces.resources import SnapshotState
  35. from cloudbridge.cloud.interfaces.resources import VolumeState
  36. from retrying import retry
  37. class AWSMachineImage(BaseMachineImage):
  38. IMAGE_STATE_MAP = {
  39. 'pending': MachineImageState.PENDING,
  40. 'available': MachineImageState.AVAILABLE,
  41. 'failed': MachineImageState.ERROR
  42. }
  43. def __init__(self, provider, image):
  44. super(AWSMachineImage, self).__init__(provider)
  45. if isinstance(image, AWSMachineImage):
  46. # pylint:disable=protected-access
  47. self._ec2_image = image._ec2_image
  48. else:
  49. self._ec2_image = image
  50. @property
  51. def id(self):
  52. """
  53. Get the image identifier.
  54. :rtype: ``str``
  55. :return: ID for this instance as returned by the cloud middleware.
  56. """
  57. return self._ec2_image.id
  58. @property
  59. def name(self):
  60. """
  61. Get the image name.
  62. :rtype: ``str``
  63. :return: Name for this image as returned by the cloud middleware.
  64. """
  65. return self._ec2_image.name
  66. @property
  67. def description(self):
  68. """
  69. Get the image description.
  70. :rtype: ``str``
  71. :return: Description for this image as returned by the cloud middleware
  72. """
  73. return self._ec2_image.description
  74. @property
  75. def min_disk(self):
  76. """
  77. Returns the minimum size of the disk that's required to
  78. boot this image (in GB)
  79. :rtype: ``int``
  80. :return: The minimum disk size needed by this image
  81. """
  82. bdm = self._ec2_image.block_device_mapping
  83. return bdm[self._ec2_image.root_device_name].size
  84. def delete(self):
  85. """
  86. Delete this image
  87. """
  88. self._ec2_image.deregister(delete_snapshot=True)
  89. @property
  90. def state(self):
  91. return AWSMachineImage.IMAGE_STATE_MAP.get(
  92. self._ec2_image.state, MachineImageState.UNKNOWN)
  93. def refresh(self):
  94. """
  95. Refreshes the state of this instance by re-querying the cloud provider
  96. for its latest state.
  97. """
  98. image = self._provider.compute.images.get(self.id)
  99. if image:
  100. # pylint:disable=protected-access
  101. self._ec2_image = image._ec2_image
  102. else:
  103. # image no longer exists
  104. self._ec2_image.state = "unknown"
  105. class AWSPlacementZone(BasePlacementZone):
  106. def __init__(self, provider, zone, region):
  107. super(AWSPlacementZone, self).__init__(provider)
  108. if isinstance(zone, AWSPlacementZone):
  109. # pylint:disable=protected-access
  110. self._aws_zone = zone._aws_zone
  111. self._aws_region = zone._aws_region
  112. else:
  113. self._aws_zone = zone
  114. self._aws_region = region
  115. @property
  116. def id(self):
  117. """
  118. Get the zone id
  119. :rtype: ``str``
  120. :return: ID for this zone as returned by the cloud middleware.
  121. """
  122. return self._aws_zone
  123. @property
  124. def name(self):
  125. """
  126. Get the zone name.
  127. :rtype: ``str``
  128. :return: Name for this zone as returned by the cloud middleware.
  129. """
  130. return self._aws_zone
  131. @property
  132. def region_name(self):
  133. """
  134. Get the region that this zone belongs to.
  135. :rtype: ``str``
  136. :return: Name of this zone's region as returned by the cloud middleware
  137. """
  138. return self._aws_region
  139. class AWSInstanceType(BaseInstanceType):
  140. def __init__(self, provider, instance_dict):
  141. super(AWSInstanceType, self).__init__(provider)
  142. self._inst_dict = instance_dict
  143. @property
  144. def id(self):
  145. return str(self._inst_dict['instance_type'])
  146. @property
  147. def name(self):
  148. return self._inst_dict['instance_type']
  149. @property
  150. def family(self):
  151. return self._inst_dict.get('family')
  152. @property
  153. def vcpus(self):
  154. return self._inst_dict.get('vCPU')
  155. @property
  156. def ram(self):
  157. return self._inst_dict.get('memory')
  158. @property
  159. def size_root_disk(self):
  160. return 0
  161. @property
  162. def size_ephemeral_disks(self):
  163. storage = self._inst_dict.get('storage')
  164. if storage:
  165. return storage.get('size') * storage.get("devices")
  166. else:
  167. return 0
  168. @property
  169. def num_ephemeral_disks(self):
  170. storage = self._inst_dict.get('storage')
  171. if storage:
  172. return storage.get("devices")
  173. else:
  174. return 0
  175. @property
  176. def extra_data(self):
  177. return {key: val for key, val in enumerate(self._inst_dict)
  178. if key not in ["instance_type", "family", "vCPU", "memory"]}
  179. class AWSInstance(BaseInstance):
  180. # ref:
  181. # http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-lifecycle.html
  182. INSTANCE_STATE_MAP = {
  183. 'pending': InstanceState.PENDING,
  184. 'running': InstanceState.RUNNING,
  185. 'shutting-down': InstanceState.CONFIGURING,
  186. 'terminated': InstanceState.TERMINATED,
  187. 'stopping': InstanceState.CONFIGURING,
  188. 'stopped': InstanceState.STOPPED
  189. }
  190. def __init__(self, provider, ec2_instance):
  191. super(AWSInstance, self).__init__(provider)
  192. self._ec2_instance = ec2_instance
  193. @property
  194. def id(self):
  195. """
  196. Get the instance identifier.
  197. """
  198. return self._ec2_instance.id
  199. @property
  200. def name(self):
  201. """
  202. Get the instance name.
  203. .. note:: an instance must have a (case sensitive) tag ``Name``
  204. """
  205. return self._ec2_instance.tags.get('Name')
  206. @name.setter
  207. # pylint:disable=arguments-differ
  208. def name(self, value):
  209. """
  210. Set the instance name.
  211. """
  212. self._ec2_instance.add_tag('Name', value)
  213. @property
  214. def public_ips(self):
  215. """
  216. Get all the public IP addresses for this instance.
  217. """
  218. return [self._ec2_instance.ip_address]
  219. @property
  220. def private_ips(self):
  221. """
  222. Get all the private IP addresses for this instance.
  223. """
  224. return [self._ec2_instance.private_ip_address]
  225. @property
  226. def instance_type_id(self):
  227. """
  228. Get the instance type name.
  229. """
  230. return self._ec2_instance.instance_type
  231. @property
  232. def instance_type(self):
  233. """
  234. Get the instance type.
  235. """
  236. return self._provider.compute.instance_types.find(
  237. name=self._ec2_instance.instance_type)[0]
  238. def reboot(self):
  239. """
  240. Reboot this instance (using the cloud middleware API).
  241. """
  242. self._ec2_instance.reboot()
  243. def terminate(self):
  244. """
  245. Permanently terminate this instance.
  246. """
  247. self._ec2_instance.terminate()
  248. @property
  249. def image_id(self):
  250. """
  251. Get the image ID for this insance.
  252. """
  253. return self._ec2_instance.image_id
  254. @property
  255. def zone_id(self):
  256. """
  257. Get the placement zone id where this instance is running.
  258. """
  259. return self._ec2_instance.placement
  260. @property
  261. def security_groups(self):
  262. """
  263. Get the security groups associated with this instance.
  264. """
  265. # boto instance.groups field returns a ``Group`` object so need to
  266. # convert that into a ``SecurityGroup`` object before creating a
  267. # cloudbridge SecurityGroup object
  268. return [self._provider.security.security_groups.get(group.id)
  269. for group in self._ec2_instance.groups]
  270. @property
  271. def security_group_ids(self):
  272. """
  273. Get the security groups IDs associated with this instance.
  274. """
  275. return [group.id for group in self._ec2_instance.groups]
  276. @property
  277. def key_pair_name(self):
  278. """
  279. Get the name of the key pair associated with this instance.
  280. """
  281. return self._ec2_instance.key_name
  282. def create_image(self, name):
  283. """
  284. Create a new image based on this instance.
  285. """
  286. image_id = self._ec2_instance.create_image(name)
  287. # Sometimes, the image takes a while to register, so retry a few times
  288. # if the image cannot be found
  289. retry_decorator = retry(retry_on_result=lambda result: result is None,
  290. stop_max_attempt_number=3, wait_fixed=1000)
  291. image = retry_decorator(self._provider.compute.images.get)(image_id)
  292. return image
  293. def add_floating_ip(self, ip_address):
  294. """
  295. Add an elastic IP address to this instance.
  296. """
  297. if self._ec2_instance.vpc_id:
  298. aid = self._provider._vpc_conn.get_all_addresses([ip_address])[0]
  299. return self._provider.ec2_conn.associate_address(
  300. self._ec2_instance.id, allocation_id=aid.allocation_id)
  301. else:
  302. return self._ec2_instance.use_ip(ip_address)
  303. def remove_floating_ip(self, ip_address):
  304. """
  305. Remove a elastic IP address from this instance.
  306. """
  307. ip_addr = self._provider._vpc_conn.get_all_addresses([ip_address])[0]
  308. if self._ec2_instance.vpc_id:
  309. return self._provider.ec2_conn.disassociate_address(
  310. association_id=ip_addr.association_id)
  311. else:
  312. return self._provider.ec2_conn.disassociate_address(
  313. public_ip=ip_addr.public_ip)
  314. def add_security_group(self, sg):
  315. """
  316. Add a security group to this instance
  317. """
  318. self._ec2_instance.modify_attribute(
  319. 'groupSet', [g.id for g in self._ec2_instance.groups] + [sg.id])
  320. def remove_security_group(self, sg):
  321. """
  322. Remove a security group from this instance
  323. """
  324. self._ec2_instance.modify_attribute(
  325. 'groupSet', [g.id for g in self._ec2_instance.groups
  326. if g.id != sg.id])
  327. @property
  328. def state(self):
  329. return AWSInstance.INSTANCE_STATE_MAP.get(
  330. self._ec2_instance.state, InstanceState.UNKNOWN)
  331. def refresh(self):
  332. """
  333. Refreshes the state of this instance by re-querying the cloud provider
  334. for its latest state.
  335. """
  336. try:
  337. self._ec2_instance.update(validate=True)
  338. except (EC2ResponseError, ValueError):
  339. # The volume no longer exists and cannot be refreshed.
  340. # set the status to unknown
  341. self._ec2_instance.status = 'unknown'
  342. class AWSVolume(BaseVolume):
  343. # Ref:
  344. # http://docs.aws.amazon.com/AWSEC2/latest/CommandLineReference/
  345. # ApiReference-cmd-DescribeVolumes.html
  346. VOLUME_STATE_MAP = {
  347. 'creating': VolumeState.CREATING,
  348. 'available': VolumeState.AVAILABLE,
  349. 'in-use': VolumeState.IN_USE,
  350. 'deleting': VolumeState.CONFIGURING,
  351. 'deleted': VolumeState.DELETED,
  352. 'error': VolumeState.ERROR
  353. }
  354. def __init__(self, provider, volume):
  355. super(AWSVolume, self).__init__(provider)
  356. self._volume = volume
  357. @property
  358. def id(self):
  359. return self._volume.id
  360. @property
  361. def name(self):
  362. """
  363. Get the volume name.
  364. .. note:: an instance must have a (case sensitive) tag ``Name``
  365. """
  366. return self._volume.tags.get('Name')
  367. @name.setter
  368. # pylint:disable=arguments-differ
  369. def name(self, value):
  370. """
  371. Set the volume name.
  372. """
  373. self._volume.add_tag('Name', value)
  374. @property
  375. def description(self):
  376. return self._volume.tags.get('Description')
  377. @description.setter
  378. def description(self, value):
  379. self._volume.add_tag('Description', value)
  380. @property
  381. def size(self):
  382. return self._volume.size
  383. @property
  384. def create_time(self):
  385. return self._volume.create_time
  386. @property
  387. def zone_id(self):
  388. return self._volume.zone
  389. @property
  390. def source(self):
  391. if self._volume.snapshot_id:
  392. return self._provider.block_store.snapshots.get(
  393. self._volume.snapshot_id)
  394. return None
  395. @property
  396. def attachments(self):
  397. if self._volume.attach_data and self._volume.attach_data.id:
  398. return BaseAttachmentInfo(self,
  399. self._volume.attach_data.instance_id,
  400. self._volume.attach_data.device)
  401. else:
  402. return None
  403. def attach(self, instance, device):
  404. """
  405. Attach this volume to an instance.
  406. """
  407. instance_id = instance.id if isinstance(
  408. instance,
  409. AWSInstance) else instance
  410. self._volume.attach(instance_id, device)
  411. def detach(self, force=False):
  412. """
  413. Detach this volume from an instance.
  414. """
  415. self._volume.detach()
  416. def create_snapshot(self, name, description=None):
  417. """
  418. Create a snapshot of this Volume.
  419. """
  420. snap = AWSSnapshot(
  421. self._provider,
  422. self._volume.create_snapshot(
  423. description=description))
  424. snap.name = name
  425. return snap
  426. def delete(self):
  427. """
  428. Delete this volume.
  429. """
  430. self._volume.delete()
  431. @property
  432. def state(self):
  433. return AWSVolume.VOLUME_STATE_MAP.get(
  434. self._volume.status, VolumeState.UNKNOWN)
  435. def refresh(self):
  436. """
  437. Refreshes the state of this volume by re-querying the cloud provider
  438. for its latest state.
  439. """
  440. try:
  441. self._volume.update(validate=True)
  442. except (EC2ResponseError, ValueError):
  443. # The volume no longer exists and cannot be refreshed.
  444. # set the status to unknown
  445. self._volume.status = 'unknown'
  446. class AWSSnapshot(BaseSnapshot):
  447. # Ref: http://docs.aws.amazon.com/AWSEC2/latest/CommandLineReference/
  448. # ApiReference-cmd-DescribeSnapshots.html
  449. SNAPSHOT_STATE_MAP = {
  450. 'pending': SnapshotState.PENDING,
  451. 'completed': SnapshotState.AVAILABLE,
  452. 'error': SnapshotState.ERROR
  453. }
  454. def __init__(self, provider, snapshot):
  455. super(AWSSnapshot, self).__init__(provider)
  456. self._snapshot = snapshot
  457. @property
  458. def id(self):
  459. return self._snapshot.id
  460. @property
  461. def name(self):
  462. """
  463. Get the snapshot name.
  464. .. note:: an instance must have a (case sensitive) tag ``Name``
  465. """
  466. return self._snapshot.tags.get('Name')
  467. @name.setter
  468. # pylint:disable=arguments-differ
  469. def name(self, value):
  470. """
  471. Set the snapshot name.
  472. """
  473. self._snapshot.add_tag('Name', value)
  474. @property
  475. def description(self):
  476. return self._snapshot.tags.get('Description')
  477. @description.setter
  478. def description(self, value):
  479. self._snapshot.add_tag('Description', value)
  480. @property
  481. def size(self):
  482. return self._snapshot.volume_size
  483. @property
  484. def volume_id(self):
  485. return self._snapshot.volume_id
  486. @property
  487. def create_time(self):
  488. return self._snapshot.start_time
  489. @property
  490. def state(self):
  491. return AWSSnapshot.SNAPSHOT_STATE_MAP.get(
  492. self._snapshot.status, SnapshotState.UNKNOWN)
  493. def refresh(self):
  494. """
  495. Refreshes the state of this snapshot by re-querying the cloud provider
  496. for its latest state.
  497. """
  498. try:
  499. self._snapshot.update(validate=True)
  500. except (EC2ResponseError, ValueError):
  501. # The snapshot no longer exists and cannot be refreshed.
  502. # set the status to unknown
  503. self._snapshot.status = 'unknown'
  504. def delete(self):
  505. """
  506. Delete this snapshot.
  507. """
  508. self._snapshot.delete()
  509. def create_volume(self, placement, size=None, volume_type=None, iops=None):
  510. """
  511. Create a new Volume from this Snapshot.
  512. """
  513. ec2_vol = self._snapshot.create_volume(placement, size, volume_type,
  514. iops)
  515. cb_vol = AWSVolume(self._provider, ec2_vol)
  516. cb_vol.name = "Created from {0} ({1})".format(self.id, self.name)
  517. return cb_vol
  518. class AWSKeyPair(BaseKeyPair):
  519. def __init__(self, provider, key_pair):
  520. super(AWSKeyPair, self).__init__(provider, key_pair)
  521. @property
  522. def material(self):
  523. """
  524. Unencrypted private key.
  525. :rtype: str
  526. :return: Unencrypted private key or ``None`` if not available.
  527. """
  528. return self._key_pair.material
  529. class AWSSecurityGroup(BaseSecurityGroup):
  530. def __init__(self, provider, security_group):
  531. super(AWSSecurityGroup, self).__init__(provider, security_group)
  532. @property
  533. def network_id(self):
  534. return self._security_group.vpc_id
  535. @property
  536. def rules(self):
  537. return [AWSSecurityGroupRule(self._provider, r, self)
  538. for r in self._security_group.rules]
  539. def add_rule(self, ip_protocol=None, from_port=None, to_port=None,
  540. cidr_ip=None, src_group=None):
  541. """
  542. Create a security group rule.
  543. You need to pass in either ``src_group`` OR ``ip_protocol``,
  544. ``from_port``, ``to_port``, and ``cidr_ip``. In other words, either
  545. you are authorizing another group or you are authorizing some
  546. ip-based rule.
  547. :type ip_protocol: str
  548. :param ip_protocol: Either ``tcp`` | ``udp`` | ``icmp``
  549. :type from_port: int
  550. :param from_port: The beginning port number you are enabling
  551. :type to_port: int
  552. :param to_port: The ending port number you are enabling
  553. :type cidr_ip: str or list of strings
  554. :param cidr_ip: The CIDR block you are providing access to.
  555. :type src_group: ``object`` of :class:`.SecurityGroup`
  556. :param src_group: The Security Group you are granting access to.
  557. :rtype: :class:``.SecurityGroupRule``
  558. :return: Rule object if successful or ``None``.
  559. """
  560. try:
  561. if src_group and not isinstance(src_group, SecurityGroup):
  562. src_group = self._provider.security.security_groups.get(
  563. src_group)
  564. if self._security_group.authorize(
  565. ip_protocol=ip_protocol,
  566. from_port=from_port,
  567. to_port=to_port,
  568. cidr_ip=cidr_ip,
  569. # pylint:disable=protected-access
  570. src_group=src_group._security_group if src_group
  571. else None):
  572. return self.get_rule(ip_protocol, from_port, to_port, cidr_ip,
  573. src_group)
  574. except EC2ResponseError as ec2e:
  575. if ec2e.code == "InvalidPermission.Duplicate":
  576. return self.get_rule(ip_protocol, from_port, to_port, cidr_ip,
  577. src_group)
  578. else:
  579. raise ec2e
  580. return None
  581. def get_rule(self, ip_protocol=None, from_port=None, to_port=None,
  582. cidr_ip=None, src_group=None):
  583. for rule in self._security_group.rules:
  584. if (rule.ip_protocol == ip_protocol and
  585. rule.from_port == from_port and
  586. rule.to_port == to_port and
  587. rule.grants[0].cidr_ip == cidr_ip) or \
  588. (rule.grants[0].group_id == src_group.id if src_group and
  589. hasattr(rule.grants[0], 'group_id') else False):
  590. return AWSSecurityGroupRule(self._provider, rule, self)
  591. return None
  592. def to_json(self):
  593. attr = inspect.getmembers(self, lambda a: not (inspect.isroutine(a)))
  594. js = {k: v for (k, v) in attr if not k.startswith('_')}
  595. json_rules = [r.to_json() for r in self.rules]
  596. js['rules'] = [json.loads(r) for r in json_rules]
  597. if js.get('network_id'):
  598. js.pop('network_id') # Omit for consistency across cloud providers
  599. return json.dumps(js, sort_keys=True)
  600. class AWSSecurityGroupRule(BaseSecurityGroupRule):
  601. def __init__(self, provider, rule, parent):
  602. super(AWSSecurityGroupRule, self).__init__(provider, rule, parent)
  603. @property
  604. def id(self):
  605. """
  606. AWS does not support rule IDs so compose one.
  607. """
  608. md5 = hashlib.md5()
  609. md5.update("{0}-{1}-{2}-{3}".format(
  610. self.ip_protocol, self.from_port, self.to_port, self.cidr_ip)
  611. .encode('ascii'))
  612. return md5.hexdigest()
  613. @property
  614. def ip_protocol(self):
  615. return self._rule.ip_protocol
  616. @property
  617. def from_port(self):
  618. if str(self._rule.from_port).isdigit():
  619. return int(self._rule.from_port)
  620. return 0
  621. @property
  622. def to_port(self):
  623. if str(self._rule.to_port).isdigit():
  624. return int(self._rule.to_port)
  625. return 0
  626. @property
  627. def cidr_ip(self):
  628. if len(self._rule.grants) > 0:
  629. return self._rule.grants[0].cidr_ip
  630. return None
  631. @property
  632. def group(self):
  633. if len(self._rule.grants) > 0:
  634. if self._rule.grants[0].group_id:
  635. cg = self._provider.ec2_conn.get_all_security_groups(
  636. group_ids=[self._rule.grants[0].group_id])[0]
  637. return AWSSecurityGroup(self._provider, cg)
  638. return None
  639. def to_json(self):
  640. attr = inspect.getmembers(self, lambda a: not (inspect.isroutine(a)))
  641. js = {k: v for (k, v) in attr if not k.startswith('_')}
  642. js['group'] = self.group.id if self.group else ''
  643. js['parent'] = self.parent.id if self.parent else ''
  644. return json.dumps(js, sort_keys=True)
  645. def delete(self):
  646. if self.group:
  647. # pylint:disable=protected-access
  648. self.parent._security_group.revoke(
  649. ip_protocol=self.ip_protocol,
  650. from_port=self.from_port,
  651. to_port=self.to_port,
  652. src_group=self.group._security_group)
  653. else:
  654. # pylint:disable=protected-access
  655. self.parent._security_group.revoke(self.ip_protocol,
  656. self.from_port,
  657. self.to_port,
  658. self.cidr_ip)
  659. class AWSBucketObject(BaseBucketObject):
  660. def __init__(self, provider, key):
  661. super(AWSBucketObject, self).__init__(provider)
  662. self._key = key
  663. @property
  664. def id(self):
  665. return self._key.name
  666. @property
  667. def name(self):
  668. """
  669. Get this object's name.
  670. """
  671. return self._key.name
  672. @property
  673. def size(self):
  674. """
  675. Get this object's size.
  676. """
  677. return self._key.size
  678. @property
  679. def last_modified(self):
  680. """
  681. Get the date and time this object was last modified.
  682. """
  683. lm = datetime.strptime(self._key.last_modified,
  684. "%Y-%m-%dT%H:%M:%S.%fZ")
  685. return lm.strftime("%Y-%m-%dT%H:%M:%S.%f")
  686. def iter_content(self):
  687. """
  688. Returns this object's content as an
  689. iterable.
  690. """
  691. return self._key
  692. def upload(self, data):
  693. """
  694. Set the contents of this object to the data read from the source
  695. string.
  696. """
  697. self._key.set_contents_from_string(data)
  698. def upload_from_file(self, path):
  699. """
  700. Store the contents of the file pointed by the "path" variable.
  701. """
  702. self._key.set_contents_from_filename(path)
  703. def delete(self):
  704. """
  705. Delete this object.
  706. :rtype: bool
  707. :return: True if successful
  708. """
  709. self._key.delete()
  710. def generate_url(self, expires_in=0):
  711. """
  712. Generate a URL to this object.
  713. """
  714. return self._key.generate_url(expires_in=expires_in)
  715. class AWSBucket(BaseBucket):
  716. def __init__(self, provider, bucket):
  717. super(AWSBucket, self).__init__(provider)
  718. self._bucket = bucket
  719. @property
  720. def id(self):
  721. return self._bucket.name
  722. @property
  723. def name(self):
  724. """
  725. Get this bucket's name.
  726. """
  727. return self._bucket.name
  728. def get(self, name):
  729. """
  730. Retrieve a given object from this bucket.
  731. """
  732. key = Key(self._bucket, name)
  733. if key and key.exists():
  734. return AWSBucketObject(self._provider, key)
  735. return None
  736. def list(self, limit=None, marker=None, prefix=None):
  737. """
  738. List all objects within this bucket.
  739. :rtype: BucketObject
  740. :return: List of all available BucketObjects within this bucket.
  741. """
  742. objects = [AWSBucketObject(self._provider, obj)
  743. for obj in self._bucket.list(prefix=prefix)]
  744. return ClientPagedResultList(self._provider, objects,
  745. limit=limit, marker=marker)
  746. def find(self, name, limit=None, marker=None):
  747. objects = [obj for obj in self if obj.name == name]
  748. return ClientPagedResultList(self._provider, objects,
  749. limit=limit, marker=marker)
  750. def delete(self, delete_contents=False):
  751. """
  752. Delete this bucket.
  753. """
  754. self._bucket.delete()
  755. def create_object(self, name):
  756. key = Key(self._bucket, name)
  757. return AWSBucketObject(self._provider, key)
  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.name
  765. @property
  766. def name(self):
  767. return self._aws_region.name
  768. @property
  769. def zones(self):
  770. """
  771. Accesss information about placement zones within this region.
  772. """
  773. if self.name == self._provider.region_name: # optimisation
  774. zones = self._provider.ec2_conn.get_all_zones()
  775. return [AWSPlacementZone(self._provider, zone.name,
  776. self._provider.region_name)
  777. for zone in zones]
  778. else:
  779. region = [region for region in
  780. self._provider.ec2_conn.get_all_regions()
  781. if self.name == region.name][0]
  782. conn = self._provider._conect_ec2_region(region)
  783. zones = conn.get_all_zones()
  784. return [AWSPlacementZone(self._provider, zone.name, region.name)
  785. for zone in zones]
  786. class AWSNetwork(BaseNetwork):
  787. # Ref:
  788. # docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeVpcs.html
  789. _NETWORK_STATE_MAP = {
  790. 'pending': NetworkState.PENDING,
  791. 'available': NetworkState.AVAILABLE,
  792. }
  793. def __init__(self, provider, network):
  794. super(AWSNetwork, self).__init__(provider)
  795. self._vpc = network
  796. @property
  797. def id(self):
  798. return self._vpc.id
  799. @property
  800. def name(self):
  801. """
  802. Get the network name.
  803. .. note:: the network must have a (case sensitive) tag ``Name``
  804. """
  805. return self._vpc.tags.get('Name')
  806. @name.setter
  807. # pylint:disable=arguments-differ
  808. def name(self, value):
  809. """
  810. Set the network name.
  811. """
  812. self._vpc.add_tag('Name', value)
  813. @property
  814. def external(self):
  815. """
  816. For AWS, all VPC networks can be connected to the Internet so always
  817. return ``True``.
  818. """
  819. return True
  820. @property
  821. def state(self):
  822. return AWSNetwork._NETWORK_STATE_MAP.get(
  823. self._vpc.update(), NetworkState.UNKNOWN)
  824. @property
  825. def cidr_block(self):
  826. return self._vpc.cidr_block
  827. def delete(self):
  828. return self._vpc.delete()
  829. def subnets(self):
  830. flter = {'vpc-id': self.id}
  831. subnets = self._provider.vpc_conn.get_all_subnets(filters=flter)
  832. return [AWSSubnet(self._provider, subnet) for subnet in subnets]
  833. def create_subnet(self, cidr_block, name=None, zone=None):
  834. subnet = self._provider.vpc_conn.create_subnet(self.id, cidr_block,
  835. availability_zone=zone)
  836. cb_subnet = AWSSubnet(self._provider, subnet)
  837. if name:
  838. cb_subnet.name = name
  839. return cb_subnet
  840. def refresh(self):
  841. """
  842. Refreshes the state of this instance by re-querying the cloud provider
  843. for its latest state.
  844. """
  845. return self.state
  846. class AWSSubnet(BaseSubnet):
  847. def __init__(self, provider, subnet):
  848. super(AWSSubnet, self).__init__(provider)
  849. self._subnet = subnet
  850. @property
  851. def id(self):
  852. return self._subnet.id
  853. @property
  854. def name(self):
  855. """
  856. Get the subnet name.
  857. .. note:: the subnet must have a (case sensitive) tag ``Name``
  858. """
  859. return self._subnet.tags.get('Name')
  860. @name.setter
  861. # pylint:disable=arguments-differ
  862. def name(self, value):
  863. """
  864. Set the subnet name.
  865. """
  866. self._subnet.add_tag('Name', value)
  867. @property
  868. def cidr_block(self):
  869. return self._subnet.cidr_block
  870. @property
  871. def network_id(self):
  872. return self._subnet.vpc_id
  873. @property
  874. def zone(self):
  875. return AWSPlacementZone(self._provider, self._subnet.availability_zone,
  876. self._provider.region_name)
  877. def delete(self):
  878. return self._provider.vpc_conn.delete_subnet(subnet_id=self.id)
  879. class AWSFloatingIP(BaseFloatingIP):
  880. def __init__(self, provider, floating_ip):
  881. super(AWSFloatingIP, self).__init__(provider)
  882. self._ip = floating_ip
  883. @property
  884. def id(self):
  885. return self._ip.allocation_id
  886. @property
  887. def public_ip(self):
  888. return self._ip.public_ip
  889. @property
  890. def private_ip(self):
  891. return self._ip.private_ip_address
  892. def in_use(self):
  893. return True if self._ip.instance_id else False
  894. def delete(self):
  895. return self._ip.delete()
  896. class AWSRouter(BaseRouter):
  897. def __init__(self, provider, router):
  898. super(AWSRouter, self).__init__(provider)
  899. self._router = router
  900. self._ROUTE_CIDR = '0.0.0.0/0'
  901. def _route_table(self, subnet_id):
  902. """
  903. Get the route table for the VPC to which the supplied subnet belongs.
  904. Note that only the first route table will be returned in case more
  905. exist.
  906. :type subnet_id: ``str``
  907. :param subnet_id: Filter the route table by the network in which the
  908. given subnet belongs.
  909. :rtype: :class:`boto.vpc.routetable.RouteTable`
  910. :return: A RouteTable object.
  911. """
  912. sn = self._provider.vpc_conn.get_all_subnets([subnet_id])[0]
  913. return self._provider.vpc_conn.get_all_route_tables(
  914. filters={'vpc-id': sn.vpc_id})[0]
  915. @property
  916. def id(self):
  917. return self._router.id
  918. @property
  919. def name(self):
  920. """
  921. Get the router name.
  922. .. note:: the router must have a (case sensitive) tag ``Name``
  923. """
  924. return self._router.tags.get('Name')
  925. @name.setter
  926. # pylint:disable=arguments-differ
  927. def name(self, value):
  928. """
  929. Set the router name.
  930. """
  931. self._router.add_tag('Name', value)
  932. def refresh(self):
  933. self._router = self._provider.vpc_conn.get_all_internet_gateways(
  934. [self.id])[0]
  935. @property
  936. def state(self):
  937. self.refresh() # Explicitly refresh the local object
  938. if self._router.attachments and \
  939. self._router.attachments[0].state == 'available':
  940. return RouterState.ATTACHED
  941. return RouterState.DETACHED
  942. @property
  943. def network_id(self):
  944. if self.state == RouterState.ATTACHED:
  945. return self._router.attachments[0].vpc_id
  946. return None
  947. def delete(self):
  948. return self._provider._vpc_conn.delete_internet_gateway(self.id)
  949. def attach_network(self, network_id):
  950. return self._provider.vpc_conn.attach_internet_gateway(
  951. self.id, network_id)
  952. def detach_network(self):
  953. return self._provider.vpc_conn.detach_internet_gateway(
  954. self.id, self.network_id)
  955. def add_route(self, subnet_id):
  956. """
  957. Add a default route to this router.
  958. For AWS, routes are added to a route table. A route table is assoc.
  959. with a network vs. a subnet so we retrieve the network via the subnet.
  960. Note that the subnet must belong to the same network as the router
  961. is attached to.
  962. Further, only a single route can be added, targeting the Internet
  963. (i.e., destination CIDR block ``0.0.0.0/0``).
  964. """
  965. rt = self._route_table(subnet_id)
  966. return self._provider.vpc_conn.create_route(
  967. rt.id, self._ROUTE_CIDR, self.id)
  968. def remove_route(self, subnet_id):
  969. """
  970. Remove the default Internet route from this router.
  971. .. seealso:: ``add_route`` method
  972. """
  973. rt = self._route_table(subnet_id)
  974. return self._provider.vpc_conn.delete_route(rt.id, self._ROUTE_CIDR)
  975. class AWSLaunchConfig(BaseLaunchConfig):
  976. def __init__(self, provider):
  977. super(AWSLaunchConfig, self).__init__(provider)