resources.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170
  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. for tag in self._volume.tags or list():
  339. if tag.get('Key') == 'Name':
  340. return tag.get('Value')
  341. return None
  342. @name.setter
  343. # pylint:disable=arguments-differ
  344. def name(self, value):
  345. """
  346. Set the volume name.
  347. """
  348. self._volume.create_tags(Tags=[{'Key': 'Name', 'Value': value}])
  349. @property
  350. def description(self):
  351. for tag in self._volume.tags or list():
  352. if tag.get('Key') == 'Description':
  353. return tag.get('Value')
  354. return None
  355. @description.setter
  356. def description(self, value):
  357. self._volume.create_tags(Tags=[{'Key': 'Description', 'Value': value}])
  358. @property
  359. def size(self):
  360. return self._volume.size
  361. @property
  362. def create_time(self):
  363. return self._volume.create_time
  364. @property
  365. def zone_id(self):
  366. return self._volume.availability_zone
  367. @property
  368. def source(self):
  369. if self._volume.snapshot_id:
  370. return self._provider.block_store.snapshots.get(
  371. self._volume.snapshot_id)
  372. return None
  373. @property
  374. def attachments(self):
  375. return [
  376. BaseAttachmentInfo(self,
  377. x.InstanceId,
  378. x.Device)
  379. for x in self._volume.attachments
  380. ] if self._volume.attachments else None
  381. def attach(self, instance, device):
  382. """
  383. Attach this volume to an instance.
  384. """
  385. instance_id = instance.id if isinstance(
  386. instance,
  387. AWSInstance) else instance
  388. self._volume.attach_to_instance(InstanceId=instance_id,
  389. Device=device)
  390. def detach(self, instance, device, force=False):
  391. """
  392. Detach this volume from an instance.
  393. """
  394. instance_id = instance.id if isinstance(
  395. instance,
  396. AWSInstance) else instance
  397. self._volume.detach_from_instance(
  398. InstanceId=instance_id,
  399. Device=device,
  400. Force=force)
  401. def create_snapshot(self, name, description=None):
  402. """
  403. Create a snapshot of this Volume.
  404. """
  405. snap = AWSSnapshot(
  406. self._provider,
  407. self._volume.create_snapshot(
  408. Description=description))
  409. snap.name = name
  410. return snap
  411. def delete(self):
  412. """
  413. Delete this volume.
  414. """
  415. self._volume.delete()
  416. @property
  417. def state(self):
  418. return AWSVolume.VOLUME_STATE_MAP.get(
  419. self._volume.state, VolumeState.UNKNOWN)
  420. def refresh(self):
  421. """
  422. Refreshes the state of this volume by re-querying the cloud provider
  423. for its latest state.
  424. """
  425. try:
  426. self._volume.reload()
  427. except (EC2ResponseError, ValueError):
  428. # The volume no longer exists and cannot be refreshed.
  429. # set the status to unknown
  430. self._volume.status = 'unknown'
  431. class AWSSnapshot(BaseSnapshot):
  432. # Ref: http://docs.aws.amazon.com/AWSEC2/latest/CommandLineReference/
  433. # ApiReference-cmd-DescribeSnapshots.html
  434. SNAPSHOT_STATE_MAP = {
  435. 'pending': SnapshotState.PENDING,
  436. 'completed': SnapshotState.AVAILABLE,
  437. 'error': SnapshotState.ERROR
  438. }
  439. def __init__(self, provider, snapshot):
  440. super(AWSSnapshot, self).__init__(provider)
  441. self._snapshot = snapshot
  442. @property
  443. def id(self):
  444. return self._snapshot.id
  445. @property
  446. def name(self):
  447. """
  448. Get the snapshot name.
  449. .. note:: an instance must have a (case sensitive) tag ``Name``
  450. """
  451. return self._snapshot.tags.get('Name')
  452. @name.setter
  453. # pylint:disable=arguments-differ
  454. def name(self, value):
  455. """
  456. Set the snapshot name.
  457. """
  458. self._snapshot.add_tag('Name', value)
  459. @property
  460. def description(self):
  461. return self._snapshot.tags.get('Description')
  462. @description.setter
  463. def description(self, value):
  464. self._snapshot.add_tag('Description', value)
  465. @property
  466. def size(self):
  467. return self._snapshot.volume_size
  468. @property
  469. def volume_id(self):
  470. return self._snapshot.volume_id
  471. @property
  472. def create_time(self):
  473. return self._snapshot.start_time
  474. @property
  475. def state(self):
  476. return AWSSnapshot.SNAPSHOT_STATE_MAP.get(
  477. self._snapshot.status, SnapshotState.UNKNOWN)
  478. def refresh(self):
  479. """
  480. Refreshes the state of this snapshot by re-querying the cloud provider
  481. for its latest state.
  482. """
  483. try:
  484. self._snapshot.update(validate=True)
  485. except (EC2ResponseError, ValueError):
  486. # The snapshot no longer exists and cannot be refreshed.
  487. # set the status to unknown
  488. self._snapshot.status = 'unknown'
  489. def delete(self):
  490. """
  491. Delete this snapshot.
  492. """
  493. self._snapshot.delete()
  494. def create_volume(self, placement, size=None, volume_type=None, iops=None):
  495. """
  496. Create a new Volume from this Snapshot.
  497. """
  498. ec2_vol = self._snapshot.create_volume(placement, size, volume_type,
  499. iops)
  500. cb_vol = AWSVolume(self._provider, ec2_vol)
  501. cb_vol.name = "Created from {0} ({1})".format(self.id, self.name)
  502. return cb_vol
  503. class AWSKeyPair(BaseKeyPair):
  504. def __init__(self, provider, key_pair):
  505. super(AWSKeyPair, self).__init__(provider, key_pair)
  506. @property
  507. def material(self):
  508. """
  509. Unencrypted private key.
  510. :rtype: str
  511. :return: Unencrypted private key or ``None`` if not available.
  512. """
  513. return self._key_pair.material
  514. class AWSSecurityGroup(BaseSecurityGroup):
  515. def __init__(self, provider, security_group):
  516. super(AWSSecurityGroup, self).__init__(provider, security_group)
  517. @property
  518. def name(self):
  519. """
  520. Return the name of this security group.
  521. """
  522. return self._security_group.group_name
  523. @property
  524. def rules(self):
  525. return [AWSSecurityGroupRule(self._provider, r, self)
  526. for r in self._security_group.ip_permissions]
  527. def add_rule(self, ip_protocol=None, from_port=None, to_port=None,
  528. cidr_ip=None, src_group=None):
  529. """
  530. Create a security group rule.
  531. You need to pass in either ``src_group`` OR ``ip_protocol``,
  532. ``from_port``, ``to_port``, and ``cidr_ip``. In other words, either
  533. you are authorizing another group or you are authorizing some
  534. ip-based rule.
  535. :type ip_protocol: str
  536. :param ip_protocol: Either ``tcp`` | ``udp`` | ``icmp``
  537. :type from_port: int
  538. :param from_port: The beginning port number you are enabling
  539. :type to_port: int
  540. :param to_port: The ending port number you are enabling
  541. :type cidr_ip: str or list of strings
  542. :param cidr_ip: The CIDR block you are providing access to.
  543. :type src_group: ``object`` of :class:`.SecurityGroup`
  544. :param src_group: The Security Group you are granting access to.
  545. :rtype: :class:``.SecurityGroupRule``
  546. :return: Rule object if successful or ``None``.
  547. """
  548. try:
  549. if not isinstance(src_group, SecurityGroup):
  550. src_group = self._provider.security.security_groups.get(
  551. src_group)
  552. if self._security_group.authorize_ingress(
  553. IpProtocol=ip_protocol,
  554. FromPort=from_port,
  555. ToPort=to_port,
  556. CidrIp=cidr_ip,
  557. # pylint:disable=protected-access
  558. SourceSecurityGroupName=src_group._security_group.group_name
  559. if src_group else None):
  560. return self.get_rule(ip_protocol, from_port, to_port, cidr_ip,
  561. src_group)
  562. except EC2ResponseError as ec2e:
  563. if ec2e.code == "InvalidPermission.Duplicate":
  564. return self.get_rule(ip_protocol, from_port, to_port, cidr_ip,
  565. src_group)
  566. else:
  567. raise ec2e
  568. return None
  569. def get_rule(self, ip_protocol=None, from_port=None, to_port=None,
  570. cidr_ip=None, src_group=None):
  571. for rule in self._security_group.ip_permissions:
  572. if (rule['IpProtocol'] == ip_protocol and
  573. rule['IpProtocol'] == from_port and
  574. rule['FromPort'] == to_port and
  575. rule['IpRanges'][0]['CidrIp'] == cidr_ip) or \
  576. (rule['UserIdGroupPairs'][0]['GroupName'] == src_group.name
  577. if src_group and
  578. hasattr(rule['UserIdGroupPairs'][0], 'GroupName')
  579. else False):
  580. return AWSSecurityGroupRule(self._provider, rule, self)
  581. return None
  582. def to_json(self):
  583. attr = inspect.getmembers(self, lambda a: not(inspect.isroutine(a)))
  584. js = {k: v for(k, v) in attr if not k.startswith('_')}
  585. json_rules = [r.to_json() for r in self.rules]
  586. js['rules'] = [json.loads(r) for r in json_rules]
  587. return json.dumps(js, sort_keys=True)
  588. class AWSSecurityGroupRule(BaseSecurityGroupRule):
  589. def __init__(self, provider, rule, parent):
  590. super(AWSSecurityGroupRule, self).__init__(provider, rule, parent)
  591. @property
  592. def id(self):
  593. """
  594. AWS does not support rule IDs so compose one.
  595. """
  596. md5 = hashlib.md5()
  597. md5.update("{0}-{1}-{2}-{3}".format(
  598. self.ip_protocol, self.from_port, self.to_port, self.cidr_ip)
  599. .encode('ascii'))
  600. return md5.hexdigest()
  601. @property
  602. def ip_protocol(self):
  603. return self._rule.get('IpProtocol')
  604. @property
  605. def from_port(self):
  606. return self._rule.get('FromPort', 0)
  607. @property
  608. def to_port(self):
  609. return self._rule.get('ToPort', 0)
  610. @property
  611. def cidr_ip(self):
  612. if len(self._rule.IpRanges) > 0:
  613. return self._rule['IpRanges'][0].get('CidrIp')
  614. return None
  615. @property
  616. def group(self):
  617. if len(self._rule['UserIdGroupPairs']) > 0:
  618. if self._rule['UserIdGroupPairs'][0]['GroupId']:
  619. return AWSSecurityGroup(
  620. self._provider,
  621. self._provider.ec2_conn.SecurityGroup(
  622. self._rule['UserIdGroupPairs'][0]['GroupId']))
  623. return None
  624. def to_json(self):
  625. attr = inspect.getmembers(self, lambda a: not inspect.isroutine(a))
  626. _js = {k: v for(k, v) in attr if not k.startswith('_')}
  627. _js['group'] = self.group.id if self.group else ''
  628. _js['parent'] = self.parent.id if self.parent else ''
  629. return json.dumps(_js, sort_keys=True)
  630. def delete(self):
  631. if self.group:
  632. # pylint:disable=protected-access
  633. self.parent._security_group.revoke_ingress(
  634. SourceSecurityGroupName=self.group._security_group.group_name)
  635. else:
  636. # pylint:disable=protected-access
  637. self.parent._security_group.revoke_ingress(
  638. IpProtocol=self.ip_protocol,
  639. FromPort=self.from_port,
  640. ToPort=self.to_port,
  641. CidrIp=self.cidr_ip)
  642. class AWSBucketObject(BaseBucketObject):
  643. def __init__(self, provider, key):
  644. super(AWSBucketObject, self).__init__(provider)
  645. self._key = key
  646. @property
  647. def id(self):
  648. return self._key.name
  649. @property
  650. def name(self):
  651. """
  652. Get this object's name.
  653. """
  654. return self._key.name
  655. @property
  656. def size(self):
  657. """
  658. Get this object's size.
  659. """
  660. return self._key.size
  661. @property
  662. def last_modified(self):
  663. """
  664. Get the date and time this object was last modified.
  665. """
  666. lm = datetime.strptime(self._key.last_modified,
  667. "%Y-%m-%dT%H:%M:%S.%fZ")
  668. return lm.strftime("%Y-%m-%dT%H:%M:%S.%f")
  669. def iter_content(self):
  670. """
  671. Returns this object's content as an
  672. iterable.
  673. """
  674. return self._key
  675. def upload(self, data):
  676. """
  677. Set the contents of this object to the data read from the source
  678. string.
  679. """
  680. self._key.set_contents_from_string(data)
  681. def delete(self):
  682. """
  683. Delete this object.
  684. :rtype: bool
  685. :return: True if successful
  686. """
  687. self._key.delete()
  688. class AWSBucket(BaseBucket):
  689. def __init__(self, provider, bucket):
  690. super(AWSBucket, self).__init__(provider)
  691. self._bucket = bucket
  692. @property
  693. def id(self):
  694. return self._bucket.name
  695. @property
  696. def name(self):
  697. """
  698. Get this bucket's name.
  699. """
  700. return self._bucket.name
  701. def get(self, key):
  702. """
  703. Retrieve a given object from this bucket.
  704. """
  705. key = Key(self._bucket, key)
  706. if key.exists():
  707. return AWSBucketObject(self._provider, key)
  708. return None
  709. def list(self, limit=None, marker=None):
  710. """
  711. List all objects within this bucket.
  712. :rtype: BucketObject
  713. :return: List of all available BucketObjects within this bucket.
  714. """
  715. objects = [AWSBucketObject(self._provider, obj)
  716. for obj in self._bucket.list()]
  717. return ClientPagedResultList(self._provider, objects,
  718. limit=limit, marker=marker)
  719. def delete(self, delete_contents=False):
  720. """
  721. Delete this bucket.
  722. """
  723. self._bucket.delete()
  724. def create_object(self, name):
  725. key = Key(self._bucket, name)
  726. return AWSBucketObject(self._provider, key)
  727. class AWSRegion(BaseRegion):
  728. def __init__(self, provider, aws_region):
  729. super(AWSRegion, self).__init__(provider)
  730. self._aws_region = aws_region
  731. @property
  732. def id(self):
  733. return self._aws_region.name
  734. @property
  735. def name(self):
  736. return self._aws_region.name
  737. @property
  738. def zones(self):
  739. """
  740. Accesss information about placement zones within this region.
  741. """
  742. if self.name == self._provider.region_name: # optimisation
  743. zones = self._provider.ec2_conn.get_all_zones()
  744. return [AWSPlacementZone(self._provider, zone.name,
  745. self._provider.region_name)
  746. for zone in zones]
  747. else:
  748. region = [region for region in
  749. self._provider.ec2_conn.get_all_regions()
  750. if self.name == region.name][0]
  751. conn = self._provider._conect_ec2_region(region)
  752. zones = conn.get_all_zones()
  753. return [AWSPlacementZone(self._provider, zone.name, region.name)
  754. for zone in zones]
  755. class AWSNetwork(BaseNetwork):
  756. # Ref:
  757. # docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeVpcs.html
  758. _NETWORK_STATE_MAP = {
  759. 'pending': NetworkState.PENDING,
  760. 'available': NetworkState.AVAILABLE,
  761. }
  762. def __init__(self, provider, network):
  763. super(AWSNetwork, self).__init__(provider)
  764. self._vpc = network
  765. @property
  766. def id(self):
  767. return self._vpc.id
  768. @property
  769. def name(self):
  770. """
  771. Get the network name.
  772. .. note:: the network must have a (case sensitive) tag ``Name``
  773. """
  774. return self._vpc.tags.get('Name')
  775. @name.setter
  776. # pylint:disable=arguments-differ
  777. def name(self, value):
  778. """
  779. Set the network name.
  780. """
  781. self._vpc.add_tag('Name', value)
  782. @property
  783. def external(self):
  784. """
  785. For AWS, all VPC networks can be connected to the Internet so always
  786. return ``True``.
  787. """
  788. return True
  789. @property
  790. def state(self):
  791. return AWSNetwork._NETWORK_STATE_MAP.get(
  792. self._vpc.update(), NetworkState.UNKNOWN)
  793. @property
  794. def cidr_block(self):
  795. return self._vpc.cidr_block
  796. def delete(self):
  797. return self._vpc.delete()
  798. def subnets(self):
  799. flter = {'vpc-id': self.id}
  800. subnets = self._provider.vpc_conn.get_all_subnets(filters=flter)
  801. return [AWSSubnet(self._provider, subnet) for subnet in subnets]
  802. def create_subnet(self, cidr_block, name=None):
  803. subnet = self._provider.vpc_conn.create_subnet(self.id, cidr_block)
  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. def delete(self):
  842. return self._provider.vpc_conn.delete_subnet(subnet_id=self.id)
  843. class AWSFloatingIP(BaseFloatingIP):
  844. def __init__(self, provider, floating_ip):
  845. super(AWSFloatingIP, self).__init__(provider)
  846. self._ip = floating_ip
  847. @property
  848. def id(self):
  849. return self._ip.allocation_id
  850. @property
  851. def public_ip(self):
  852. return self._ip.public_ip
  853. @property
  854. def private_ip(self):
  855. return self._ip.private_ip_address
  856. def in_use(self):
  857. return True if self._ip.instance_id else False
  858. def delete(self):
  859. return self._ip.delete()
  860. class AWSRouter(BaseRouter):
  861. def __init__(self, provider, router):
  862. super(AWSRouter, self).__init__(provider)
  863. self._router = router
  864. self._ROUTE_CIDR = '0.0.0.0/0'
  865. def _route_table(self, subnet_id):
  866. """
  867. Get the route table for the VPC to which the supplied subnet belongs.
  868. Note that only the first route table will be returned in case more
  869. exist.
  870. :type subnet_id: ``str``
  871. :param subnet_id: Filter the route table by the network in which the
  872. given subnet belongs.
  873. :rtype: :class:`boto.vpc.routetable.RouteTable`
  874. :return: A RouteTable object.
  875. """
  876. sn = self._provider.vpc_conn.get_all_subnets([subnet_id])[0]
  877. return self._provider.vpc_conn.get_all_route_tables(
  878. filters={'vpc-id': sn.vpc_id})[0]
  879. @property
  880. def id(self):
  881. return self._router.id
  882. @property
  883. def name(self):
  884. """
  885. Get the router name.
  886. .. note:: the router must have a (case sensitive) tag ``Name``
  887. """
  888. return self._router.tags.get('Name')
  889. @name.setter
  890. # pylint:disable=arguments-differ
  891. def name(self, value):
  892. """
  893. Set the router name.
  894. """
  895. self._router.add_tag('Name', value)
  896. def refresh(self):
  897. self._router = self._provider.vpc_conn.get_all_internet_gateways(
  898. [self.id])[0]
  899. @property
  900. def state(self):
  901. self.refresh() # Explicitly refresh the local object
  902. if self._router.attachments and \
  903. self._router.attachments[0].state == 'available':
  904. return RouterState.ATTACHED
  905. return RouterState.DETACHED
  906. @property
  907. def network_id(self):
  908. if self.state == RouterState.ATTACHED:
  909. return self._router.attachments[0].vpc_id
  910. return None
  911. def delete(self):
  912. return self._provider._vpc_conn.delete_internet_gateway(self.id)
  913. def attach_network(self, network_id):
  914. return self._provider.vpc_conn.attach_internet_gateway(
  915. self.id, network_id)
  916. def detach_network(self):
  917. return self._provider.vpc_conn.detach_internet_gateway(
  918. self.id, self.network_id)
  919. def add_route(self, subnet_id):
  920. """
  921. Add a default route to this router.
  922. For AWS, routes are added to a route table. A route table is assoc.
  923. with a network vs. a subnet so we retrieve the network via the subnet.
  924. Note that the subnet must belong to the same network as the router
  925. is attached to.
  926. Further, only a single route can be added, targeting the Internet
  927. (i.e., destination CIDR block ``0.0.0.0/0``).
  928. """
  929. rt = self._route_table(subnet_id)
  930. return self._provider.vpc_conn.create_route(
  931. rt.id, self._ROUTE_CIDR, self.id)
  932. def remove_route(self, subnet_id):
  933. """
  934. Remove the default Internet route from this router.
  935. .. seealso:: ``add_route`` method
  936. """
  937. rt = self._route_table(subnet_id)
  938. return self._provider.vpc_conn.delete_route(rt.id, self._ROUTE_CIDR)
  939. class AWSLaunchConfig(BaseLaunchConfig):
  940. def __init__(self, provider):
  941. super(AWSLaunchConfig, self).__init__(provider)
  942. def add_network_interface(self, net_id):
  943. """
  944. Extract a subnet within the network identified by ``net_id``.
  945. AWS requires a subnet ID to be supplied vs. a network (i.e., VPC) ID
  946. so just pull out one subnet within the network (currently, the first
  947. one).
  948. """
  949. net = self.provider.network.get(net_id)
  950. sns = net.subnets()
  951. if sns:
  952. sn = sns[0]
  953. self.network_interfaces.append(sn.id)