package core.aws.task.ec2; import com.amazonaws.services.ec2.model.BlockDeviceMapping; import com.amazonaws.services.ec2.model.EbsBlockDevice; import com.amazonaws.services.ec2.model.IamInstanceProfileSpecification; import com.amazonaws.services.ec2.model.RunInstancesRequest; import com.amazonaws.services.ec2.model.Subnet; import com.amazonaws.services.ec2.model.Tag; import core.aws.client.AWS; import core.aws.env.Context; import core.aws.env.Environment; import core.aws.resource.ec2.EBS; import core.aws.resource.ec2.Instance; import core.aws.resource.vpc.SubnetType; import core.aws.util.Maps; import core.aws.util.Strings; import core.aws.util.ToStringHelper; import core.aws.workflow.Action; import core.aws.workflow.Task; import org.apache.commons.codec.binary.Base64; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * @author neo */ @Action("create-instance") public class CreateInstanceTask extends Task<Instance> { private final int count; private final boolean waitUntilInService; public CreateInstanceTask(Instance instance, int count, boolean waitUntilInService) { super(instance); this.count = count; this.waitUntilInService = waitUntilInService; } @Override public void execute(Context context) throws Exception { Map<String, Integer> addedInstanceCount = planAddedCountBySubnet(); for (Map.Entry<String, Integer> entry : addedInstanceCount.entrySet()) { String subnetId = entry.getKey(); int count = entry.getValue(); if (count > 0) { createInstance(context, count, subnetId); } } } private Map<String, Integer> planAddedCountBySubnet() { Map<String, Integer> instanceCount = Maps.newHashMap(); for (Subnet remoteSubnet : resource.subnet.remoteSubnets) { instanceCount.put(remoteSubnet.getSubnetId(), 0); } for (com.amazonaws.services.ec2.model.Instance remoteInstance : resource.remoteInstances) { instanceCount.compute(remoteInstance.getSubnetId(), (key, oldValue) -> oldValue + 1); } Map<String, Integer> addedInstanceCount = Maps.newHashMap(); for (String subnetId : instanceCount.keySet()) { addedInstanceCount.put(subnetId, 0); } for (int i = 0; i < count; i++) { String targetSubnet = findSubnetHasMinimalInstances(instanceCount); instanceCount.compute(targetSubnet, (key, oldValue) -> oldValue + 1); addedInstanceCount.compute(targetSubnet, (key, oldValue) -> oldValue + 1); } return addedInstanceCount; } private String findSubnetHasMinimalInstances(Map<String, Integer> instanceCount) { String subnetId = null; int minCount = Integer.MAX_VALUE; for (Map.Entry<String, Integer> entry : instanceCount.entrySet()) { int count = entry.getValue(); if (count < minCount) { minCount = count; subnetId = entry.getKey(); } } return subnetId; } private void createInstance(Context context, int count, String subnetId) throws Exception { String sgId = resource.securityGroup.remoteSecurityGroup.getGroupId(); RunInstancesRequest request = new RunInstancesRequest() .withKeyName(resource.keyPair.remoteKeyPair.getKeyName()) .withInstanceType(resource.instanceType) .withImageId(resource.ami.imageId()) .withSubnetId(subnetId) .withSecurityGroupIds(sgId) .withMinCount(count) .withMaxCount(count) .withUserData(Base64.encodeBase64String(Strings.bytes(userData(context.env)))); if (EBS.enableEBSOptimized(resource.instanceType)) { request.withEbsOptimized(true); } if (resource.instanceProfile != null) request.withIamInstanceProfile(new IamInstanceProfileSpecification() .withName(resource.instanceProfile.remoteInstanceProfile.getInstanceProfileName())); if (resource.ebs.rootVolumeSize != null) { request.getBlockDeviceMappings().add(new BlockDeviceMapping() .withDeviceName("/dev/sda1") .withEbs(new EbsBlockDevice().withVolumeSize(resource.ebs.rootVolumeSize).withVolumeType(resource.ebs.type))); } List<com.amazonaws.services.ec2.model.Instance> remoteInstances = AWS.ec2.runInstances(request, tags(context.env)); resource.remoteInstances.addAll(remoteInstances); for (com.amazonaws.services.ec2.model.Instance remoteInstance : remoteInstances) { String key = String.format("instance/%s/%s", resource.id, remoteInstance.getInstanceId()); StringBuilder builder = new StringBuilder(); builder.append("privateIP=").append(remoteInstance.getPrivateIpAddress()); if (resource.subnet == null || resource.subnet.type == SubnetType.PUBLIC) { builder.append(", publicDNS=").append(remoteInstance.getPublicDnsName()); } context.output(key, builder.toString()); } if (resource.elb != null) { List<String> instanceIds = remoteInstances.stream().map(com.amazonaws.services.ec2.model.Instance::getInstanceId).collect(Collectors.toList()); AWS.elb.attachInstances(resource.elb.remoteELB.getLoadBalancerName(), instanceIds, waitUntilInService); } } private Tag[] tags(Environment env) { EC2TagHelper tagHelper = new EC2TagHelper(env); String name = name(env); return new Tag[]{tagHelper.env(), new Tag("Name", name), tagHelper.resourceId(resource.id)}; } private String name(Environment env) { StringBuilder name = new StringBuilder().append(env.name).append(':').append(resource.id); if (!resource.id.equals(resource.ami.id())) { name.append(':').append(resource.ami.id()); } resource.ami.version() .ifPresent(version -> name.append(":v").append(version)); return name.toString(); } private String userData(Environment env) { return "env=" + env.name + '&' + "id=" + resource.id + '&' + "name=" + name(env); } @Override public String toString() { return new ToStringHelper(this) .add(resource) .add("count", count) .toString(); } }