resources.py 39 KB

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