152 lines
5.2 KiB
Python
152 lines
5.2 KiB
Python
# Copyright 2018 Red Hat
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import logging
|
|
import boto3
|
|
|
|
from nodepool.driver import Provider
|
|
from nodepool.driver.aws.handler import AwsNodeRequestHandler
|
|
|
|
|
|
class AwsInstance:
|
|
def __init__(self, name, metadatas, provider):
|
|
self.id = name
|
|
self.name = name
|
|
self.metadata = {}
|
|
if metadatas:
|
|
for metadata in metadatas:
|
|
if metadata["Key"] == "nodepool_id":
|
|
self.metadata = {
|
|
'nodepool_provider_name': provider.name,
|
|
'nodepool_node_id': metadata["Value"],
|
|
}
|
|
break
|
|
|
|
def get(self, name, default=None):
|
|
return getattr(self, name, default)
|
|
|
|
|
|
class AwsProvider(Provider):
|
|
log = logging.getLogger("nodepool.driver.aws.AwsProvider")
|
|
|
|
def __init__(self, provider, *args):
|
|
self.provider = provider
|
|
self.ec2 = None
|
|
|
|
def getRequestHandler(self, poolworker, request):
|
|
return AwsNodeRequestHandler(poolworker, request)
|
|
|
|
def start(self, zk_conn):
|
|
if self.ec2 is not None:
|
|
return True
|
|
self.log.debug("Starting")
|
|
self.aws = boto3.Session(
|
|
region_name=self.provider.region_name,
|
|
profile_name=self.provider.profile_name)
|
|
self.ec2 = self.aws.resource('ec2')
|
|
|
|
def stop(self):
|
|
self.log.debug("Stopping")
|
|
|
|
def listNodes(self):
|
|
servers = []
|
|
|
|
for instance in self.ec2.instances.all():
|
|
if instance.state["Name"].lower() == "terminated":
|
|
continue
|
|
servers.append(AwsInstance(
|
|
instance.id, instance.tags, self.provider))
|
|
return servers
|
|
|
|
def getImage(self, image_id):
|
|
return self.ec2.Image(image_id)
|
|
|
|
def labelReady(self, label):
|
|
if not label.cloud_image:
|
|
msg = "A cloud-image (AMI) must be supplied with the AWS driver."
|
|
raise Exception(msg)
|
|
|
|
image = self.getImage(label.cloud_image.external_name)
|
|
# Image loading is deferred, check if it's really there
|
|
if image.state != 'available':
|
|
self.log.warning(
|
|
"Provider %s is configured to use %s as the AMI for"
|
|
" label %s and that AMI is there but unavailable in the"
|
|
" cloud." % (self.provider.name,
|
|
label.cloud_image.external_name,
|
|
label.name))
|
|
return False
|
|
return True
|
|
|
|
def join(self):
|
|
return True
|
|
|
|
def cleanupLeakedResources(self):
|
|
# TODO: remove leaked resources if any
|
|
pass
|
|
|
|
def cleanupNode(self, server_id):
|
|
if self.ec2 is None:
|
|
return False
|
|
instance = self.ec2.Instance(server_id)
|
|
instance.terminate()
|
|
|
|
def waitForNodeCleanup(self, server_id):
|
|
# TODO: track instance deletion
|
|
return True
|
|
|
|
def createInstance(self, label):
|
|
image_id = label.cloud_image.external_name
|
|
args = dict(
|
|
ImageId=image_id,
|
|
MinCount=1,
|
|
MaxCount=1,
|
|
KeyName=label.key_name,
|
|
InstanceType=label.instance_type,
|
|
NetworkInterfaces=[{
|
|
'AssociatePublicIpAddress': True,
|
|
'DeviceIndex': 0}])
|
|
|
|
if label.pool.security_group_id:
|
|
args['NetworkInterfaces'][0]['Groups'] = [
|
|
label.pool.security_group_id
|
|
]
|
|
if label.pool.subnet_id:
|
|
args['NetworkInterfaces'][0]['SubnetId'] = label.pool.subnet_id
|
|
|
|
# Default block device mapping parameters are embedded in AMIs.
|
|
# We might need to supply our own mapping before lauching the instance.
|
|
# We basically want to make sure DeleteOnTermination is true and be
|
|
# able to set the volume type and size.
|
|
image = self.getImage(image_id)
|
|
# TODO: Flavors can also influence whether or not the VM spawns with a
|
|
# volume -- we basically need to ensure DeleteOnTermination is true
|
|
if hasattr(image, 'block_device_mappings'):
|
|
bdm = image.block_device_mappings
|
|
mapping = bdm[0]
|
|
if 'Ebs' in mapping:
|
|
mapping['Ebs']['DeleteOnTermination'] = True
|
|
if label.volume_size:
|
|
mapping['Ebs']['VolumeSize'] = label.volume_size
|
|
if label.volume_type:
|
|
mapping['Ebs']['VolumeType'] = label.volume_type
|
|
# If the AMI is a snapshot, we cannot supply an "encrypted"
|
|
# parameter
|
|
if 'Encrypted' in mapping['Ebs']:
|
|
del mapping['Ebs']['Encrypted']
|
|
args['BlockDeviceMappings'] = [mapping]
|
|
|
|
instances = self.ec2.create_instances(**args)
|
|
return self.ec2.Instance(instances[0].id)
|