resources.py 38 KB

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