resources.py 35 KB

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