2
0

resources.py 41 KB

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