resources.py 33 KB

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