resources.py 40 KB

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