/* * Copyright 2015 herd contributors * * 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. */ package org.finra.herd.dao.impl; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import com.amazonaws.AmazonServiceException; import com.amazonaws.services.ec2.AmazonEC2Client; import com.amazonaws.services.ec2.model.AvailabilityZone; import com.amazonaws.services.ec2.model.DescribeAvailabilityZonesRequest; import com.amazonaws.services.ec2.model.DescribeAvailabilityZonesResult; import com.amazonaws.services.ec2.model.DescribeInstanceAttributeRequest; import com.amazonaws.services.ec2.model.DescribeInstanceAttributeResult; import com.amazonaws.services.ec2.model.DescribeSpotPriceHistoryRequest; import com.amazonaws.services.ec2.model.DescribeSpotPriceHistoryResult; import com.amazonaws.services.ec2.model.DescribeSubnetsRequest; import com.amazonaws.services.ec2.model.DescribeSubnetsResult; import com.amazonaws.services.ec2.model.GroupIdentifier; import com.amazonaws.services.ec2.model.InstanceAttributeName; import com.amazonaws.services.ec2.model.ModifyInstanceAttributeRequest; import com.amazonaws.services.ec2.model.SpotPrice; import com.amazonaws.services.ec2.model.Subnet; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import org.finra.herd.dao.AwsClientFactory; import org.finra.herd.dao.Ec2Dao; import org.finra.herd.dao.Ec2Operations; import org.finra.herd.model.ObjectNotFoundException; import org.finra.herd.model.dto.AwsParamsDto; /** * The EC2 DAO implementation. */ @Repository public class Ec2DaoImpl implements Ec2Dao { /** * http://docs.aws.amazon.com/AWSEC2/latest/APIReference/errors-overview.html */ public static final String ERROR_CODE_SUBNET_ID_NOT_FOUND = "InvalidSubnetID.NotFound"; @Autowired private Ec2Operations ec2Operations; @Autowired private AwsClientFactory awsClientFactory; /** * Adds the security groups to an EC2 instance. * * @param ec2InstanceId the ec2 instance id. * @param securityGroups security groups to be added. * @param awsParams awsParamsDto object * * @return updated security groups. */ @Override public List<String> addSecurityGroupsToEc2Instance(String ec2InstanceId, List<String> securityGroups, AwsParamsDto awsParams) { Set<String> updatedSecurityGroups = new HashSet<>(); for (String securityGroup : securityGroups) { updatedSecurityGroups.add(securityGroup); } // Get existing security groups DescribeInstanceAttributeRequest describeInstanceAttributeRequest = new DescribeInstanceAttributeRequest().withInstanceId(ec2InstanceId).withAttribute(InstanceAttributeName.GroupSet); DescribeInstanceAttributeResult describeInstanceAttributeResult = ec2Operations.describeInstanceAttribute(getEc2Client(awsParams), describeInstanceAttributeRequest); List<GroupIdentifier> groups = describeInstanceAttributeResult.getInstanceAttribute().getGroups(); for (GroupIdentifier groupIdentifier : groups) { updatedSecurityGroups.add(groupIdentifier.getGroupId()); } // Add security group on master EC2 instance ModifyInstanceAttributeRequest modifyInstanceAttributeRequest = new ModifyInstanceAttributeRequest().withInstanceId(ec2InstanceId).withGroups(updatedSecurityGroups); ec2Operations.modifyInstanceAttribute(getEc2Client(awsParams), modifyInstanceAttributeRequest); return new ArrayList<>(updatedSecurityGroups); } /** * Create the EC2 client with the given proxy and access key details This is the main AmazonEC2Client object * * @param awsParamsDto AWS related parameters for access/secret keys and proxy details * * @return the AmazonEC2Client object */ @Override public AmazonEC2Client getEc2Client(AwsParamsDto awsParamsDto) { return awsClientFactory.getEc2Client(awsParamsDto); } /** * This implementation uses DescribeSpotPriceHistory API which returns the latest spot price history for the specified AZ and instance types. This method * then filters the returned list to only contain the latest spot price for each instance type. */ @Override public List<SpotPrice> getLatestSpotPrices(String availabilityZone, Collection<String> instanceTypes, Collection<String> productDescriptions, AwsParamsDto awsParamsDto) { AmazonEC2Client ec2Client = getEc2Client(awsParamsDto); DescribeSpotPriceHistoryRequest describeSpotPriceHistoryRequest = new DescribeSpotPriceHistoryRequest(); describeSpotPriceHistoryRequest.setAvailabilityZone(availabilityZone); describeSpotPriceHistoryRequest.setInstanceTypes(instanceTypes); describeSpotPriceHistoryRequest.setProductDescriptions(productDescriptions); DescribeSpotPriceHistoryResult describeSpotPriceHistoryResult = ec2Operations.describeSpotPriceHistory(ec2Client, describeSpotPriceHistoryRequest); List<SpotPrice> spotPrices = new ArrayList<>(); Set<String> instanceTypesFound = new HashSet<>(); for (SpotPrice spotPriceHistoryEntry : describeSpotPriceHistoryResult.getSpotPriceHistory()) { if (instanceTypesFound.add(spotPriceHistoryEntry.getInstanceType())) { spotPrices.add(spotPriceHistoryEntry); } } return spotPrices; } /** * This implementation uses the DescribeAvailabilityZones API to get the list of AZs. */ @Override public List<AvailabilityZone> getAvailabilityZonesForSubnetIds(Collection<Subnet> subnets, AwsParamsDto awsParamsDto) { Set<String> zoneNames = new HashSet<>(); for (Subnet subnet : subnets) { zoneNames.add(subnet.getAvailabilityZone()); } AmazonEC2Client ec2Client = getEc2Client(awsParamsDto); DescribeAvailabilityZonesRequest describeAvailabilityZonesRequest = new DescribeAvailabilityZonesRequest(); describeAvailabilityZonesRequest.setZoneNames(zoneNames); DescribeAvailabilityZonesResult describeAvailabilityZonesResult = ec2Operations.describeAvailabilityZones(ec2Client, describeAvailabilityZonesRequest); return describeAvailabilityZonesResult.getAvailabilityZones(); } /** * This implementation uses the DescribeSubnets API. */ @Override public List<Subnet> getSubnets(Collection<String> subnetIds, AwsParamsDto awsParamsDto) { AmazonEC2Client ec2Client = getEc2Client(awsParamsDto); DescribeSubnetsRequest describeSubnetsRequest = new DescribeSubnetsRequest(); describeSubnetsRequest.setSubnetIds(subnetIds); try { DescribeSubnetsResult describeSubnetsResult = ec2Operations.describeSubnets(ec2Client, describeSubnetsRequest); return describeSubnetsResult.getSubnets(); } catch (AmazonServiceException amazonServiceException) { /* * AWS throws a 400 error when any one of the specified subnet ID is not found. * We want to catch it and throw as an handled herd error as a 404 not found. */ if (ERROR_CODE_SUBNET_ID_NOT_FOUND.equals(amazonServiceException.getErrorCode())) { throw new ObjectNotFoundException(amazonServiceException.getErrorMessage(), amazonServiceException); } // Any other type of error we throw as is because they are unexpected. else { throw amazonServiceException; } } } /** * Sets implementation of the EC2 operations. This method is required for unit tests that use Mockito framework. * * @param ec2Operations the implementation of the EC2 operations */ public void setEc2Operations(Ec2Operations ec2Operations) { this.ec2Operations = ec2Operations; } }