resources.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125
  1. """
  2. DataTypes used by this provider
  3. """
  4. import hashlib
  5. import inspect
  6. import logging
  7. from botocore.exceptions import ClientError
  8. from cloudbridge.cloud.base.resources import BaseAttachmentInfo
  9. from cloudbridge.cloud.base.resources import BaseBucket
  10. from cloudbridge.cloud.base.resources import BaseBucketObject
  11. from cloudbridge.cloud.base.resources import BaseFloatingIP
  12. from cloudbridge.cloud.base.resources import BaseInstance
  13. from cloudbridge.cloud.base.resources import BaseInternetGateway
  14. from cloudbridge.cloud.base.resources import BaseKeyPair
  15. from cloudbridge.cloud.base.resources import BaseLaunchConfig
  16. from cloudbridge.cloud.base.resources import BaseMachineImage
  17. from cloudbridge.cloud.base.resources import BaseNetwork
  18. from cloudbridge.cloud.base.resources import BasePlacementZone
  19. from cloudbridge.cloud.base.resources import BaseRegion
  20. from cloudbridge.cloud.base.resources import BaseRouter
  21. from cloudbridge.cloud.base.resources import BaseSnapshot
  22. from cloudbridge.cloud.base.resources import BaseSubnet
  23. from cloudbridge.cloud.base.resources import BaseVMFirewall
  24. from cloudbridge.cloud.base.resources import BaseVMFirewallRule
  25. from cloudbridge.cloud.base.resources import BaseVMType
  26. from cloudbridge.cloud.base.resources import BaseVolume
  27. from cloudbridge.cloud.interfaces.resources import GatewayState
  28. from cloudbridge.cloud.interfaces.resources import InstanceState
  29. from cloudbridge.cloud.interfaces.resources import MachineImageState
  30. from cloudbridge.cloud.interfaces.resources import NetworkState
  31. from cloudbridge.cloud.interfaces.resources import RouterState
  32. from cloudbridge.cloud.interfaces.resources import SnapshotState
  33. from cloudbridge.cloud.interfaces.resources import SubnetState
  34. from cloudbridge.cloud.interfaces.resources import VolumeState
  35. from .helpers import find_tag_value
  36. from .helpers import trim_empty_params
  37. from .subservices import AWSBucketObjectSubService
  38. from .subservices import AWSFloatingIPSubService
  39. from .subservices import AWSGatewaySubService
  40. from .subservices import AWSVMFirewallRuleSubService
  41. log = logging.getLogger(__name__)
  42. class AWSMachineImage(BaseMachineImage):
  43. IMAGE_STATE_MAP = {
  44. 'pending': MachineImageState.PENDING,
  45. 'transient': MachineImageState.PENDING,
  46. 'available': MachineImageState.AVAILABLE,
  47. 'deregistered': MachineImageState.PENDING,
  48. 'failed': MachineImageState.ERROR,
  49. 'error': MachineImageState.ERROR,
  50. 'invalid': MachineImageState.ERROR
  51. }
  52. def __init__(self, provider, image):
  53. super(AWSMachineImage, self).__init__(provider)
  54. if isinstance(image, AWSMachineImage):
  55. # pylint:disable=protected-access
  56. self._ec2_image = image._ec2_image
  57. else:
  58. self._ec2_image = image
  59. @property
  60. def id(self):
  61. return self._ec2_image.id
  62. @property
  63. def name(self):
  64. try:
  65. return self._ec2_image.name
  66. except (AttributeError, ClientError) as e:
  67. log.warn("Cannot get name for image {0}: {1}".format(self.id, e))
  68. @property
  69. # pylint:disable=arguments-differ
  70. def label(self):
  71. """
  72. .. note:: an instance must have a (case sensitive) tag ``Name``
  73. """
  74. return find_tag_value(self._ec2_image.tags, 'Name')
  75. @label.setter
  76. # pylint:disable=arguments-differ
  77. def label(self, value):
  78. self.assert_valid_resource_label(value)
  79. self._ec2_image.create_tags(Tags=[{'Key': 'Name',
  80. 'Value': value or ""}])
  81. @property
  82. def description(self):
  83. try:
  84. return self._ec2_image.description
  85. except AttributeError:
  86. return None
  87. @property
  88. def min_disk(self):
  89. vols = [bdm.get('Ebs', {}) for bdm in
  90. self._ec2_image.block_device_mappings if
  91. bdm.get('DeviceName') == self._ec2_image.root_device_name]
  92. if vols:
  93. return vols[0].get('VolumeSize')
  94. else:
  95. return None
  96. def delete(self):
  97. snapshot_id = [
  98. bdm.get('Ebs', {}).get('SnapshotId') for bdm in
  99. self._ec2_image.block_device_mappings if
  100. bdm.get('DeviceName') == self._ec2_image.root_device_name]
  101. self._ec2_image.deregister()
  102. self.wait_for([MachineImageState.UNKNOWN, MachineImageState.ERROR])
  103. snapshot = self._provider.storage.snapshots.get(snapshot_id[0])
  104. if snapshot:
  105. snapshot.delete()
  106. @property
  107. def state(self):
  108. try:
  109. return AWSMachineImage.IMAGE_STATE_MAP.get(
  110. self._ec2_image.state, MachineImageState.UNKNOWN)
  111. except Exception:
  112. # Ignore all exceptions when querying state
  113. return MachineImageState.UNKNOWN
  114. def refresh(self):
  115. self._ec2_image.reload()
  116. class AWSPlacementZone(BasePlacementZone):
  117. def __init__(self, provider, zone, region):
  118. super(AWSPlacementZone, self).__init__(provider)
  119. if isinstance(zone, AWSPlacementZone):
  120. # pylint:disable=protected-access
  121. self._aws_zone = zone._aws_zone
  122. self._aws_region = zone._aws_region
  123. else:
  124. self._aws_zone = zone
  125. self._aws_region = region
  126. @property
  127. def id(self):
  128. return self._aws_zone
  129. @property
  130. def name(self):
  131. return self.id
  132. @property
  133. def region_name(self):
  134. return self._aws_region
  135. class AWSVMType(BaseVMType):
  136. def __init__(self, provider, instance_dict):
  137. super(AWSVMType, self).__init__(provider)
  138. self._inst_dict = instance_dict
  139. @property
  140. def id(self):
  141. return str(self._inst_dict['instance_type'])
  142. @property
  143. def name(self):
  144. return self.id
  145. @property
  146. def family(self):
  147. return self._inst_dict.get('family')
  148. @property
  149. def vcpus(self):
  150. vcpus = self._inst_dict.get('vCPU')
  151. if vcpus == 'N/A':
  152. return None
  153. return vcpus
  154. @property
  155. def ram(self):
  156. return self._inst_dict.get('memory')
  157. @property
  158. def size_root_disk(self):
  159. return 0
  160. @property
  161. def size_ephemeral_disks(self):
  162. storage = self._inst_dict.get('storage')
  163. if storage:
  164. return storage.get('size') * storage.get("devices")
  165. else:
  166. return 0
  167. @property
  168. def num_ephemeral_disks(self):
  169. storage = self._inst_dict.get('storage')
  170. if storage:
  171. return storage.get("devices")
  172. else:
  173. return 0
  174. @property
  175. def extra_data(self):
  176. return {key: val for key, val in self._inst_dict.items()
  177. if key not in ["instance_type", "family", "vCPU", "memory"]}
  178. class AWSInstance(BaseInstance):
  179. # ref:
  180. # http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-lifecycle.html
  181. INSTANCE_STATE_MAP = {
  182. 'pending': InstanceState.PENDING,
  183. 'running': InstanceState.RUNNING,
  184. 'shutting-down': InstanceState.CONFIGURING,
  185. 'terminated': InstanceState.DELETED,
  186. 'stopping': InstanceState.CONFIGURING,
  187. 'stopped': InstanceState.STOPPED
  188. }
  189. def __init__(self, provider, ec2_instance):
  190. super(AWSInstance, self).__init__(provider)
  191. self._ec2_instance = ec2_instance
  192. self._unknown_state = False
  193. @property
  194. def id(self):
  195. return self._ec2_instance.id
  196. @property
  197. def name(self):
  198. return self.id
  199. @property
  200. # pylint:disable=arguments-differ
  201. def label(self):
  202. """
  203. .. note:: an instance must have a (case sensitive) tag ``Name``
  204. """
  205. return find_tag_value(self._ec2_instance.tags, 'Name')
  206. @label.setter
  207. # pylint:disable=arguments-differ
  208. def label(self, value):
  209. self.assert_valid_resource_label(value)
  210. self._ec2_instance.create_tags(Tags=[{'Key': 'Name',
  211. 'Value': value or ""}])
  212. @property
  213. def public_ips(self):
  214. return ([self._ec2_instance.public_ip_address]
  215. if self._ec2_instance.public_ip_address else [])
  216. @property
  217. def private_ips(self):
  218. return ([self._ec2_instance.private_ip_address]
  219. if self._ec2_instance.private_ip_address else [])
  220. @property
  221. def vm_type_id(self):
  222. return self._ec2_instance.instance_type
  223. @property
  224. def vm_type(self):
  225. return self._provider.compute.vm_types.find(
  226. name=self._ec2_instance.instance_type)[0]
  227. def reboot(self):
  228. self._ec2_instance.reboot()
  229. @property
  230. def image_id(self):
  231. return self._ec2_instance.image_id
  232. @property
  233. def zone_id(self):
  234. return self._ec2_instance.placement.get('AvailabilityZone')
  235. @property
  236. def subnet_id(self):
  237. return self._ec2_instance.subnet_id
  238. @property
  239. def vm_firewalls(self):
  240. return [
  241. self._provider.security.vm_firewalls.get(fw_id)
  242. for fw_id in self.vm_firewall_ids
  243. ]
  244. @property
  245. def vm_firewall_ids(self):
  246. return list(set([
  247. group.get('GroupId') for group in
  248. self._ec2_instance.security_groups
  249. ]))
  250. @property
  251. def key_pair_id(self):
  252. return self._ec2_instance.key_name
  253. def create_image(self, label):
  254. self.assert_valid_resource_label(label)
  255. name = self._generate_name_from_label(label, 'cb-img')
  256. image = AWSMachineImage(self._provider,
  257. self._ec2_instance.create_image(Name=name))
  258. # Wait for the image to exist
  259. self._provider.ec2_conn.meta.client.get_waiter('image_exists').wait(
  260. ImageIds=[image.id])
  261. # Add image label
  262. image.label = label
  263. # Return the image
  264. image.refresh()
  265. return image
  266. def _get_fip(self, floating_ip):
  267. """Get a floating IP object based on the supplied allocation ID."""
  268. return self._provider.networking._floating_ips.get(None, floating_ip)
  269. def add_floating_ip(self, floating_ip):
  270. fip = (floating_ip if isinstance(floating_ip, AWSFloatingIP)
  271. else self._get_fip(floating_ip))
  272. params = trim_empty_params({
  273. 'InstanceId': self.id,
  274. 'PublicIp': None if self._ec2_instance.vpc_id else
  275. fip.public_ip,
  276. # pylint:disable=protected-access
  277. 'AllocationId': fip._ip.allocation_id})
  278. self._provider.ec2_conn.meta.client.associate_address(**params)
  279. self.refresh()
  280. def remove_floating_ip(self, floating_ip):
  281. fip = (floating_ip if isinstance(floating_ip, AWSFloatingIP)
  282. else self._get_fip(floating_ip))
  283. params = trim_empty_params({
  284. 'PublicIp': None if self._ec2_instance.vpc_id else
  285. fip.public_ip,
  286. # pylint:disable=protected-access
  287. 'AssociationId': fip._ip.association_id})
  288. self._provider.ec2_conn.meta.client.disassociate_address(**params)
  289. self.refresh()
  290. def add_vm_firewall(self, firewall):
  291. self._ec2_instance.modify_attribute(
  292. Groups=self.vm_firewall_ids + [firewall.id])
  293. def remove_vm_firewall(self, firewall):
  294. self._ec2_instance.modify_attribute(
  295. Groups=([fw_id for fw_id in self.vm_firewall_ids
  296. if fw_id != firewall.id]))
  297. @property
  298. def state(self):
  299. if self._unknown_state:
  300. return InstanceState.UNKNOWN
  301. try:
  302. return AWSInstance.INSTANCE_STATE_MAP.get(
  303. self._ec2_instance.state['Name'], InstanceState.UNKNOWN)
  304. except Exception:
  305. # Ignore all exceptions when querying state
  306. return InstanceState.UNKNOWN
  307. def refresh(self):
  308. try:
  309. self._ec2_instance.reload()
  310. self._unknown_state = False
  311. except ClientError:
  312. # The instance no longer exists and cannot be refreshed.
  313. # set the state to unknown
  314. self._unknown_state = True
  315. # pylint:disable=unused-argument
  316. def _wait_till_exists(self, timeout=None, interval=None):
  317. self._ec2_instance.wait_until_exists()
  318. class AWSVolume(BaseVolume):
  319. # Ref:
  320. # http://docs.aws.amazon.com/AWSEC2/latest/CommandLineReference/
  321. # ApiReference-cmd-DescribeVolumes.html
  322. VOLUME_STATE_MAP = {
  323. 'creating': VolumeState.CREATING,
  324. 'available': VolumeState.AVAILABLE,
  325. 'in-use': VolumeState.IN_USE,
  326. 'deleting': VolumeState.CONFIGURING,
  327. 'deleted': VolumeState.DELETED,
  328. 'error': VolumeState.ERROR
  329. }
  330. def __init__(self, provider, volume):
  331. super(AWSVolume, self).__init__(provider)
  332. self._volume = volume
  333. self._unknown_state = False
  334. @property
  335. def id(self):
  336. return self._volume.id
  337. @property
  338. def name(self):
  339. return self.id
  340. @property
  341. # pylint:disable=arguments-differ
  342. def label(self):
  343. try:
  344. return find_tag_value(self._volume.tags, 'Name')
  345. except ClientError as e:
  346. log.warn("Cannot get label for volume {0}: {1}".format(self.id, e))
  347. @label.setter
  348. # pylint:disable=arguments-differ
  349. def label(self, value):
  350. self.assert_valid_resource_label(value)
  351. self._volume.create_tags(Tags=[{'Key': 'Name', 'Value': value or ""}])
  352. @property
  353. def description(self):
  354. return find_tag_value(self._volume.tags, 'Description')
  355. @description.setter
  356. def description(self, value):
  357. self._volume.create_tags(Tags=[{'Key': 'Description',
  358. 'Value': value or ""}])
  359. @property
  360. def size(self):
  361. return self._volume.size
  362. @property
  363. def create_time(self):
  364. return self._volume.create_time
  365. @property
  366. def zone_id(self):
  367. return self._volume.availability_zone
  368. @property
  369. def source(self):
  370. if self._volume.snapshot_id:
  371. return self._provider.storage.snapshots.get(
  372. self._volume.snapshot_id)
  373. return None
  374. @property
  375. def attachments(self):
  376. return [
  377. BaseAttachmentInfo(self,
  378. a.get('InstanceId'),
  379. a.get('Device'))
  380. for a in self._volume.attachments
  381. ][0] if self._volume.attachments else None
  382. def attach(self, instance, device):
  383. instance_id = instance.id if isinstance(
  384. instance,
  385. AWSInstance) else instance
  386. self._volume.attach_to_instance(InstanceId=instance_id,
  387. Device=device)
  388. def detach(self, force=False):
  389. a = self.attachments
  390. if a:
  391. self._volume.detach_from_instance(
  392. InstanceId=a.instance_id,
  393. Device=a.device,
  394. Force=force)
  395. def create_snapshot(self, label, description=None):
  396. self.assert_valid_resource_label(label)
  397. snap = AWSSnapshot(
  398. self._provider,
  399. self._volume.create_snapshot(
  400. TagSpecifications=[{'ResourceType': 'snapshot',
  401. 'Tags': [{'Key': 'Name',
  402. 'Value': label}]}],
  403. Description=description or ""))
  404. snap.wait_till_ready()
  405. return snap
  406. @property
  407. def state(self):
  408. if self._unknown_state:
  409. return VolumeState.UNKNOWN
  410. try:
  411. return AWSVolume.VOLUME_STATE_MAP.get(
  412. self._volume.state, VolumeState.UNKNOWN)
  413. except Exception:
  414. # Ignore all exceptions when querying state
  415. return VolumeState.UNKNOWN
  416. def refresh(self):
  417. try:
  418. self._volume.reload()
  419. self._unknown_state = False
  420. except ClientError:
  421. # The volume no longer exists and cannot be refreshed.
  422. # set the status to unknown
  423. self._unknown_state = True
  424. class AWSSnapshot(BaseSnapshot):
  425. # Ref: http://docs.aws.amazon.com/AWSEC2/latest/CommandLineReference/
  426. # ApiReference-cmd-DescribeSnapshots.html
  427. SNAPSHOT_STATE_MAP = {
  428. 'pending': SnapshotState.PENDING,
  429. 'deleting': SnapshotState.PENDING,
  430. 'completed': SnapshotState.AVAILABLE,
  431. 'error': SnapshotState.ERROR
  432. }
  433. def __init__(self, provider, snapshot):
  434. super(AWSSnapshot, self).__init__(provider)
  435. self._snapshot = snapshot
  436. self._unknown_state = False
  437. @property
  438. def id(self):
  439. return self._snapshot.id
  440. @property
  441. def name(self):
  442. return self.id
  443. @property
  444. # pylint:disable=arguments-differ
  445. def label(self):
  446. try:
  447. return find_tag_value(self._snapshot.tags, 'Name')
  448. except ClientError as e:
  449. log.warn("Cannot get label for snap {0}: {1}".format(self.id, e))
  450. @label.setter
  451. # pylint:disable=arguments-differ
  452. def label(self, value):
  453. self.assert_valid_resource_label(value)
  454. self._snapshot.create_tags(Tags=[{'Key': 'Name',
  455. 'Value': value or ""}])
  456. @property
  457. def description(self):
  458. return find_tag_value(self._snapshot.tags, 'Description')
  459. @description.setter
  460. def description(self, value):
  461. self._snapshot.create_tags(Tags=[{
  462. 'Key': 'Description', 'Value': value or ""}])
  463. @property
  464. def size(self):
  465. return self._snapshot.volume_size
  466. @property
  467. def volume_id(self):
  468. return self._snapshot.volume_id
  469. @property
  470. def create_time(self):
  471. return self._snapshot.start_time
  472. @property
  473. def state(self):
  474. if self._unknown_state:
  475. return SnapshotState.UNKNOWN
  476. try:
  477. return AWSSnapshot.SNAPSHOT_STATE_MAP.get(
  478. self._snapshot.state, SnapshotState.UNKNOWN)
  479. except Exception:
  480. # Ignore all exceptions when querying state
  481. return SnapshotState.UNKNOWN
  482. def refresh(self):
  483. try:
  484. self._snapshot.reload()
  485. self._unknown_state = False
  486. except ClientError:
  487. # The snapshot no longer exists and cannot be refreshed.
  488. # set the status to unknown
  489. self._unknown_state = True
  490. def create_volume(self, placement, size=None, volume_type=None, iops=None):
  491. label = "from-snap-{0}".format(self.label or self.id)
  492. cb_vol = self._provider.storage.volumes.create(
  493. label=label,
  494. size=size,
  495. zone=placement,
  496. snapshot=self.id)
  497. cb_vol.wait_till_ready()
  498. return cb_vol
  499. class AWSKeyPair(BaseKeyPair):
  500. def __init__(self, provider, key_pair):
  501. super(AWSKeyPair, self).__init__(provider, key_pair)
  502. class AWSVMFirewall(BaseVMFirewall):
  503. def __init__(self, provider, _vm_firewall):
  504. super(AWSVMFirewall, self).__init__(provider, _vm_firewall)
  505. self._rule_container = AWSVMFirewallRuleSubService(provider, self)
  506. @property
  507. def name(self):
  508. """
  509. Return the name of this VM firewall.
  510. """
  511. return self._vm_firewall.group_name
  512. @property
  513. def label(self):
  514. try:
  515. return find_tag_value(self._vm_firewall.tags, 'Name')
  516. except ClientError:
  517. return None
  518. @label.setter
  519. # pylint:disable=arguments-differ
  520. def label(self, value):
  521. self.assert_valid_resource_label(value)
  522. self._vm_firewall.create_tags(Tags=[{'Key': 'Name',
  523. 'Value': value or ""}])
  524. @property
  525. def network_id(self):
  526. return self._vm_firewall.vpc_id
  527. @property
  528. def rules(self):
  529. return self._rule_container
  530. def refresh(self):
  531. self._vm_firewall.reload()
  532. def to_json(self):
  533. attr = inspect.getmembers(self, lambda a: not inspect.isroutine(a))
  534. js = {k: v for (k, v) in attr if not k.startswith('_')}
  535. json_rules = [r.to_json() for r in self.rules]
  536. js['rules'] = json_rules
  537. if js.get('network_id'):
  538. js.pop('network_id') # Omit for consistency across cloud providers
  539. return js
  540. class AWSVMFirewallRule(BaseVMFirewallRule):
  541. def __init__(self, parent_fw, direction, rule):
  542. self._direction = direction
  543. super(AWSVMFirewallRule, self).__init__(parent_fw, rule)
  544. # cache id
  545. md5 = hashlib.md5()
  546. md5.update(self._name.encode('ascii'))
  547. self._id = md5.hexdigest()
  548. @property
  549. def id(self):
  550. return self._id
  551. @property
  552. def direction(self):
  553. return self._direction
  554. @property
  555. def protocol(self):
  556. return self._rule.get('IpProtocol')
  557. @property
  558. def from_port(self):
  559. return self._rule.get('FromPort')
  560. @property
  561. def to_port(self):
  562. return self._rule.get('ToPort')
  563. @property
  564. def cidr(self):
  565. if len(self._rule.get('IpRanges') or []) > 0:
  566. return self._rule['IpRanges'][0].get('CidrIp')
  567. return None
  568. @property
  569. def src_dest_fw_id(self):
  570. if len(self._rule.get('UserIdGroupPairs') or []) > 0:
  571. return self._rule['UserIdGroupPairs'][0]['GroupId']
  572. else:
  573. return None
  574. @property
  575. def src_dest_fw(self):
  576. if self.src_dest_fw_id:
  577. return AWSVMFirewall(
  578. self._provider,
  579. self._provider.ec2_conn.SecurityGroup(self.src_dest_fw_id))
  580. else:
  581. return None
  582. @staticmethod
  583. def _construct_ip_perms(protocol, from_port, to_port, cidr,
  584. src_dest_fw_id):
  585. return {
  586. 'IpProtocol': protocol,
  587. 'FromPort': from_port,
  588. 'ToPort': to_port,
  589. 'IpRanges': [{'CidrIp': cidr}] if cidr else None,
  590. 'UserIdGroupPairs': [{
  591. 'GroupId': src_dest_fw_id}
  592. ] if src_dest_fw_id else None
  593. }
  594. class AWSBucketObject(BaseBucketObject):
  595. class BucketObjIterator():
  596. CHUNK_SIZE = 4096
  597. def __init__(self, body):
  598. self.body = body
  599. def __iter__(self):
  600. while True:
  601. data = self.read(self.CHUNK_SIZE)
  602. if data:
  603. yield data
  604. else:
  605. break
  606. def read(self, length):
  607. return self.body.read(amt=length)
  608. def close(self):
  609. return self.body.close()
  610. def __init__(self, provider, obj):
  611. super(AWSBucketObject, self).__init__(provider)
  612. self._obj = obj
  613. @property
  614. def id(self):
  615. return self._obj.key
  616. @property
  617. def name(self):
  618. return self.id
  619. @property
  620. def size(self):
  621. try:
  622. return self._obj.content_length
  623. except AttributeError: # we're dealing with s3.ObjectSummary
  624. return self._obj.size
  625. @property
  626. def last_modified(self):
  627. return self._obj.last_modified.strftime("%Y-%m-%dT%H:%M:%S.%f")
  628. def iter_content(self):
  629. return self.BucketObjIterator(self._obj.get().get('Body'))
  630. def upload(self, data):
  631. self._obj.put(Body=data)
  632. def upload_from_file(self, path):
  633. self._obj.upload_file(path)
  634. def delete(self):
  635. self._obj.delete()
  636. def generate_url(self, expires_in):
  637. return self._provider.s3_conn.meta.client.generate_presigned_url(
  638. 'get_object',
  639. Params={'Bucket': self._obj.bucket_name, 'Key': self.id},
  640. ExpiresIn=expires_in)
  641. def refresh(self):
  642. self._obj.load()
  643. class AWSBucket(BaseBucket):
  644. def __init__(self, provider, bucket):
  645. super(AWSBucket, self).__init__(provider)
  646. self._bucket = bucket
  647. self._object_container = AWSBucketObjectSubService(provider, self)
  648. @property
  649. def id(self):
  650. return self._bucket.name
  651. @property
  652. def name(self):
  653. return self.id
  654. @property
  655. def objects(self):
  656. return self._object_container
  657. class AWSRegion(BaseRegion):
  658. def __init__(self, provider, aws_region):
  659. super(AWSRegion, self).__init__(provider)
  660. self._aws_region = aws_region
  661. @property
  662. def id(self):
  663. return self._aws_region.get('RegionName')
  664. @property
  665. def name(self):
  666. return self.id
  667. @property
  668. def zones(self):
  669. if self.id == self._provider.region_name: # optimisation
  670. conn = self._provider.ec2_conn
  671. else:
  672. # pylint:disable=protected-access
  673. conn = self._provider._connect_ec2_region(region_name=self.id)
  674. zones = (conn.meta.client.describe_availability_zones()
  675. .get('AvailabilityZones', []))
  676. return [AWSPlacementZone(self._provider, zone.get('ZoneName'),
  677. self.id)
  678. for zone in zones]
  679. class AWSNetwork(BaseNetwork):
  680. # Ref:
  681. # docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeVpcs.html
  682. _NETWORK_STATE_MAP = {
  683. 'pending': NetworkState.PENDING,
  684. 'available': NetworkState.AVAILABLE,
  685. }
  686. def __init__(self, provider, network):
  687. super(AWSNetwork, self).__init__(provider)
  688. self._vpc = network
  689. self._gtw_container = AWSGatewaySubService(provider, self)
  690. self._unknown_state = False
  691. @property
  692. def id(self):
  693. return self._vpc.id
  694. @property
  695. def name(self):
  696. return self.id
  697. @property
  698. def label(self):
  699. return find_tag_value(self._vpc.tags, 'Name')
  700. @label.setter
  701. # pylint:disable=arguments-differ
  702. def label(self, value):
  703. self.assert_valid_resource_label(value)
  704. self._vpc.create_tags(Tags=[{'Key': 'Name', 'Value': value or ""}])
  705. @property
  706. def external(self):
  707. """
  708. For AWS, all VPC networks can be connected to the Internet so always
  709. return ``True``.
  710. """
  711. return True
  712. @property
  713. def state(self):
  714. if self._unknown_state:
  715. return NetworkState.UNKNOWN
  716. try:
  717. return AWSNetwork._NETWORK_STATE_MAP.get(
  718. self._vpc.state, NetworkState.UNKNOWN)
  719. except Exception:
  720. # Ignore all exceptions when querying state
  721. return NetworkState.UNKNOWN
  722. @property
  723. def cidr_block(self):
  724. return self._vpc.cidr_block
  725. @property
  726. def subnets(self):
  727. return [AWSSubnet(self._provider, s) for s in self._vpc.subnets.all()]
  728. def refresh(self):
  729. try:
  730. self._vpc.reload()
  731. self._unknown_state = False
  732. except ClientError:
  733. # The network no longer exists and cannot be refreshed.
  734. # set the status to unknown
  735. self._unknown_state = True
  736. def wait_till_ready(self, timeout=None, interval=None):
  737. self._provider.ec2_conn.meta.client.get_waiter('vpc_available').wait(
  738. VpcIds=[self.id])
  739. self.refresh()
  740. @property
  741. def gateways(self):
  742. return self._gtw_container
  743. class AWSSubnet(BaseSubnet):
  744. # http://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSubnets.html
  745. _SUBNET_STATE_MAP = {
  746. 'pending': SubnetState.PENDING,
  747. 'available': SubnetState.AVAILABLE,
  748. }
  749. def __init__(self, provider, subnet):
  750. super(AWSSubnet, self).__init__(provider)
  751. self._subnet = subnet
  752. self._unknown_state = False
  753. @property
  754. def id(self):
  755. return self._subnet.id
  756. @property
  757. def name(self):
  758. return self.id
  759. @property
  760. def label(self):
  761. return find_tag_value(self._subnet.tags, 'Name')
  762. @label.setter
  763. # pylint:disable=arguments-differ
  764. def label(self, value):
  765. self.assert_valid_resource_label(value)
  766. self._subnet.create_tags(Tags=[{'Key': 'Name', 'Value': value or ""}])
  767. @property
  768. def cidr_block(self):
  769. return self._subnet.cidr_block
  770. @property
  771. def network_id(self):
  772. return self._subnet.vpc_id
  773. @property
  774. def zone(self):
  775. return AWSPlacementZone(self._provider, self._subnet.availability_zone,
  776. self._provider.region_name)
  777. @property
  778. def state(self):
  779. if self._unknown_state:
  780. return SubnetState.UNKNOWN
  781. try:
  782. return self._SUBNET_STATE_MAP.get(
  783. self._subnet.state, SubnetState.UNKNOWN)
  784. except Exception:
  785. # Ignore all exceptions when querying state
  786. return SubnetState.UNKNOWN
  787. def refresh(self):
  788. try:
  789. self._subnet.reload()
  790. self._unknown_state = False
  791. except ClientError:
  792. # subnet no longer exists
  793. self._unknown_state = True
  794. class AWSFloatingIP(BaseFloatingIP):
  795. def __init__(self, provider, floating_ip):
  796. super(AWSFloatingIP, self).__init__(provider)
  797. self._ip = floating_ip
  798. @property
  799. def id(self):
  800. return self._ip.allocation_id
  801. @property
  802. def public_ip(self):
  803. return self._ip.public_ip
  804. @property
  805. def private_ip(self):
  806. return self._ip.private_ip_address
  807. @property
  808. def in_use(self):
  809. return True if self._ip.association_id else False
  810. def refresh(self):
  811. self._ip.reload()
  812. class AWSRouter(BaseRouter):
  813. def __init__(self, provider, route_table):
  814. super(AWSRouter, self).__init__(provider)
  815. self._route_table = route_table
  816. @property
  817. def id(self):
  818. return self._route_table.id
  819. @property
  820. def name(self):
  821. return self.id
  822. @property
  823. def label(self):
  824. return find_tag_value(self._route_table.tags, 'Name')
  825. @label.setter
  826. # pylint:disable=arguments-differ
  827. def label(self, value):
  828. self.assert_valid_resource_label(value)
  829. self._route_table.create_tags(Tags=[{'Key': 'Name',
  830. 'Value': value or ""}])
  831. def refresh(self):
  832. try:
  833. self._route_table.reload()
  834. except ClientError:
  835. self._route_table.associations = None
  836. @property
  837. def state(self):
  838. if self._route_table.associations:
  839. return RouterState.ATTACHED
  840. return RouterState.DETACHED
  841. @property
  842. def network_id(self):
  843. return self._route_table.vpc_id
  844. def attach_subnet(self, subnet):
  845. subnet_id = subnet.id if isinstance(subnet, AWSSubnet) else subnet
  846. self._route_table.associate_with_subnet(SubnetId=subnet_id)
  847. self.refresh()
  848. def detach_subnet(self, subnet):
  849. subnet_id = subnet.id if isinstance(subnet, AWSSubnet) else subnet
  850. associations = [a for a in self._route_table.associations
  851. if a.subnet_id == subnet_id]
  852. for a in associations:
  853. a.delete()
  854. self.refresh()
  855. @property
  856. def subnets(self):
  857. return [AWSSubnet(self._provider, rta.subnet)
  858. for rta in self._route_table.associations if rta.subnet]
  859. def attach_gateway(self, gateway):
  860. gw_id = (gateway.id if isinstance(gateway, AWSInternetGateway)
  861. else gateway)
  862. if self._route_table.create_route(
  863. DestinationCidrBlock='0.0.0.0/0', GatewayId=gw_id):
  864. return True
  865. return False
  866. def detach_gateway(self, gateway):
  867. gw_id = (gateway.id if isinstance(gateway, AWSInternetGateway)
  868. else gateway)
  869. return self._provider.ec2_conn.meta.client.detach_internet_gateway(
  870. InternetGatewayId=gw_id, VpcId=self._route_table.vpc_id)
  871. class AWSInternetGateway(BaseInternetGateway):
  872. def __init__(self, provider, gateway):
  873. super(AWSInternetGateway, self).__init__(provider)
  874. self._gateway = gateway
  875. self._gateway.state = ''
  876. self._fips_container = AWSFloatingIPSubService(provider, self)
  877. @property
  878. def id(self):
  879. return self._gateway.id
  880. @property
  881. def name(self):
  882. return find_tag_value(self._gateway.tags, 'Name')
  883. def refresh(self):
  884. try:
  885. self._gateway.reload()
  886. except ClientError:
  887. self._gateway.state = GatewayState.UNKNOWN
  888. @property
  889. def state(self):
  890. if self._gateway.state == GatewayState.UNKNOWN:
  891. return GatewayState.UNKNOWN
  892. else:
  893. return GatewayState.AVAILABLE
  894. @property
  895. def network_id(self):
  896. if self._gateway.attachments:
  897. return self._gateway.attachments[0].get('VpcId')
  898. return None
  899. @property
  900. def floating_ips(self):
  901. return self._fips_container
  902. class AWSLaunchConfig(BaseLaunchConfig):
  903. def __init__(self, provider):
  904. super(AWSLaunchConfig, self).__init__(provider)