Manage a pool of nodes for a distributed test infrastructure
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

provider.py 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. # Copyright 2018 Red Hat
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  4. # not use this file except in compliance with the License. You may obtain
  5. # a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  11. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  12. # License for the specific language governing permissions and limitations
  13. # under the License.
  14. import logging
  15. import boto3
  16. from nodepool.driver import Provider
  17. from nodepool.driver.aws.handler import AwsNodeRequestHandler
  18. class AwsInstance:
  19. def __init__(self, name, metadatas, provider):
  20. self.id = name
  21. self.name = name
  22. self.metadata = {}
  23. if metadatas:
  24. for metadata in metadatas:
  25. if metadata["Key"] == "nodepool_id":
  26. self.metadata = {
  27. 'nodepool_provider_name': provider.name,
  28. 'nodepool_node_id': metadata["Value"],
  29. }
  30. break
  31. def get(self, name, default=None):
  32. return getattr(self, name, default)
  33. class AwsProvider(Provider):
  34. log = logging.getLogger("nodepool.driver.aws.AwsProvider")
  35. def __init__(self, provider, *args):
  36. self.provider = provider
  37. self.ec2 = None
  38. def getRequestHandler(self, poolworker, request):
  39. return AwsNodeRequestHandler(poolworker, request)
  40. def start(self, zk_conn):
  41. if self.ec2 is not None:
  42. return True
  43. self.log.debug("Starting")
  44. self.aws = boto3.Session(
  45. region_name=self.provider.region_name,
  46. profile_name=self.provider.profile_name)
  47. self.ec2 = self.aws.resource('ec2')
  48. def stop(self):
  49. self.log.debug("Stopping")
  50. def listNodes(self):
  51. servers = []
  52. for instance in self.ec2.instances.all():
  53. if instance.state["Name"].lower() == "terminated":
  54. continue
  55. servers.append(AwsInstance(
  56. instance.id, instance.tags, self.provider))
  57. return servers
  58. def getImage(self, image_id):
  59. return self.ec2.Image(image_id)
  60. def labelReady(self, label):
  61. if not label.cloud_image:
  62. msg = "A cloud-image (AMI) must be supplied with the AWS driver."
  63. raise Exception(msg)
  64. image = self.getImage(label.cloud_image.external_name)
  65. # Image loading is deferred, check if it's really there
  66. if image.state != 'available':
  67. self.log.warning(
  68. "Provider %s is configured to use %s as the AMI for"
  69. " label %s and that AMI is there but unavailable in the"
  70. " cloud." % (self.provider.name,
  71. label.cloud_image.external_name,
  72. label.name))
  73. return False
  74. return True
  75. def join(self):
  76. return True
  77. def cleanupLeakedResources(self):
  78. # TODO: remove leaked resources if any
  79. pass
  80. def cleanupNode(self, server_id):
  81. if self.ec2 is None:
  82. return False
  83. instance = self.ec2.Instance(server_id)
  84. instance.terminate()
  85. def waitForNodeCleanup(self, server_id):
  86. # TODO: track instance deletion
  87. return True
  88. def createInstance(self, label):
  89. image_name = label.cloud_image.external_name
  90. args = dict(
  91. ImageId=image_name,
  92. MinCount=1,
  93. MaxCount=1,
  94. KeyName=label.key_name,
  95. InstanceType=label.flavor_name,
  96. NetworkInterfaces=[{
  97. 'AssociatePublicIpAddress': True,
  98. 'DeviceIndex': 0}])
  99. if label.pool.security_group_id:
  100. args['NetworkInterfaces'][0]['Groups'] = [
  101. label.pool.security_group_id
  102. ]
  103. if label.pool.subnet_id:
  104. args['NetworkInterfaces'][0]['SubnetId'] = label.pool.subnet_id
  105. # Default block device mapping parameters are embedded in AMIs.
  106. # We might need to supply our own mapping before lauching the instance.
  107. # We basically want to make sure DeleteOnTermination is true and be
  108. # able to set the volume type and size.
  109. image = self.getImage(image_name)
  110. # TODO: Flavors can also influence whether or not the VM spawns with a
  111. # volume -- we basically need to ensure DeleteOnTermination is true
  112. if hasattr(image, 'block_device_mappings'):
  113. bdm = image.block_device_mappings
  114. mapping = bdm[0]
  115. if 'Ebs' in mapping:
  116. mapping['Ebs']['DeleteOnTermination'] = True
  117. if label.volume_size:
  118. mapping['Ebs']['VolumeSize'] = label.volume_size
  119. if label.volume_type:
  120. mapping['Ebs']['VolumeType'] = label.volume_type
  121. # If the AMI is a snapshot, we cannot supply an "encrypted"
  122. # parameter
  123. if 'Encrypted' in mapping['Ebs']:
  124. del mapping['Ebs']['Encrypted']
  125. args['BlockDeviceMappings'] = [mapping]
  126. instances = self.ec2.create_instances(**args)
  127. return self.ec2.Instance(instances[0].id)