diff --git a/bin/aws/config.template b/bin/aws/config.template new file mode 100644 index 00000000..720f4f85 --- /dev/null +++ b/bin/aws/config.template @@ -0,0 +1,2 @@ +[default] +region = __REGION__ diff --git a/bin/aws/delete_subnets.sh b/bin/aws/delete_subnets.sh new file mode 100755 index 00000000..4a9f0fb9 --- /dev/null +++ b/bin/aws/delete_subnets.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +function die { + echo 1>&2 "${1}" + exit 1 +} + +THISDIR=$(cd $(dirname ${0}) && pwd) +for region in $(${THISDIR}/list_regions.sh) ; do + echo "Deleting subnets in region ${region}" + ${THISDIR}/set_aws_region.sh ${region} || die "Error setting aws region ${region}" + for subnet in $(aws ec2 describe-subnets --filters "Name=default-for-az,Values=false" | grep SubnetId | cut -d\" -f4) ; do + echo "Deleting subnet ${subnet} in region ${region}" + aws ec2 delete-subnet --subnet-id ${subnet} || echo "WARNING: Error deleting subnet ${subnet} in region ${region}" + done +done diff --git a/bin/compute/ec2/delete_test_instances.sh b/bin/aws/delete_test_instances.sh similarity index 100% rename from bin/compute/ec2/delete_test_instances.sh rename to bin/aws/delete_test_instances.sh diff --git a/bin/aws/init_aws_configs.sh b/bin/aws/init_aws_configs.sh new file mode 100755 index 00000000..703d4550 --- /dev/null +++ b/bin/aws/init_aws_configs.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +THISDIR=$(cd $(dirname ${0}) && pwd) +for region in $(${THISDIR}/list_regions.sh) ; do + cat ${THISDIR}/config.template > ~/.aws/config.${region} && echo "created config for region ${region}" +done diff --git a/bin/aws/list_regions.sh b/bin/aws/list_regions.sh new file mode 100755 index 00000000..1826ede9 --- /dev/null +++ b/bin/aws/list_regions.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +aws ec2 describe-regions --filters "Name=opt-in-status,Values=opt-in-not-required" | grep RegionName | cut -d\" -f4 | sort diff --git a/bin/compute/ec2/list_test_instances.sh b/bin/aws/list_test_instances.sh similarity index 100% rename from bin/compute/ec2/list_test_instances.sh rename to bin/aws/list_test_instances.sh diff --git a/bin/aws/set_aws_region.sh b/bin/aws/set_aws_region.sh new file mode 100755 index 00000000..ad4574a1 --- /dev/null +++ b/bin/aws/set_aws_region.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +function die { + echo 1>&2 "${1}" + exit 1 +} + +region=${1:?no region specified} +if [[ ! -f ~/.aws/config.${region} ]] ; then + die "Region not found: ${region}" +fi +rm -f ~/.aws/config && ln -s ~/.aws/config.${region} ~/.aws/config diff --git a/bubble-server/pom.xml b/bubble-server/pom.xml index f711b6a4..387ed25c 100644 --- a/bubble-server/pom.xml +++ b/bubble-server/pom.xml @@ -106,11 +106,10 @@ jetty-proxy ${jetty.version} - com.amazonaws aws-java-sdk-ec2 - 1.11.762 + 1.11.797 diff --git a/bubble-server/src/main/java/bubble/cloud/NoopCloud.java b/bubble-server/src/main/java/bubble/cloud/NoopCloud.java index 06a15837..f6a7de16 100644 --- a/bubble-server/src/main/java/bubble/cloud/NoopCloud.java +++ b/bubble-server/src/main/java/bubble/cloud/NoopCloud.java @@ -36,7 +36,6 @@ import org.cobbzilla.util.dns.DnsRecordMatch; import java.io.IOException; import java.io.InputStream; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -69,7 +68,9 @@ public class NoopCloud implements return false; } - @Override public List getPackerImages() { return Collections.emptyList(); } + @Override public List getAllPackerImages() { return null; } + + @Override public List getPackerImagesForRegion(String region) { return null; } @Override public boolean _write(String fromNode, String key, InputStream data, StorageMetadata metadata, String requestId) throws IOException { if (log.isDebugEnabled()) log.debug("_write(fromNode=" + fromNode + ")"); diff --git a/bubble-server/src/main/java/bubble/cloud/compute/ComputeDiskType.java b/bubble-server/src/main/java/bubble/cloud/compute/ComputeDiskType.java new file mode 100644 index 00000000..ed4857ba --- /dev/null +++ b/bubble-server/src/main/java/bubble/cloud/compute/ComputeDiskType.java @@ -0,0 +1,7 @@ +package bubble.cloud.compute; + +public enum ComputeDiskType { + + ssd, hdd, ebs_gp2, ebs_magnetic; + +} diff --git a/bubble-server/src/main/java/bubble/cloud/compute/ComputeNodeSize.java b/bubble-server/src/main/java/bubble/cloud/compute/ComputeNodeSize.java index e16b1d34..75a4ad4e 100644 --- a/bubble-server/src/main/java/bubble/cloud/compute/ComputeNodeSize.java +++ b/bubble-server/src/main/java/bubble/cloud/compute/ComputeNodeSize.java @@ -23,8 +23,8 @@ public class ComputeNodeSize { @Getter @Setter private String description; @Getter @Setter private int vcpu; @Getter @Setter private int memoryMB; - @Getter @Setter private int ssdGB; - @Getter @Setter private int hddGB; + @Getter @Setter private int diskGB; + @Getter @Setter private ComputeDiskType diskType; @Getter @Setter private Integer networkMbps; @Getter @Setter private Integer transferGB; diff --git a/bubble-server/src/main/java/bubble/cloud/compute/ComputeServiceDriver.java b/bubble-server/src/main/java/bubble/cloud/compute/ComputeServiceDriver.java index c9eec8dd..a54f988a 100644 --- a/bubble-server/src/main/java/bubble/cloud/compute/ComputeServiceDriver.java +++ b/bubble-server/src/main/java/bubble/cloud/compute/ComputeServiceDriver.java @@ -4,6 +4,7 @@ */ package bubble.cloud.compute; +import bubble.cloud.CloudRegion; import bubble.cloud.CloudServiceDriver; import bubble.cloud.CloudServiceType; import bubble.model.cloud.AnsibleInstallType; @@ -35,7 +36,12 @@ public interface ComputeServiceDriver extends CloudServiceDriver, RegionalServic @Override default boolean test () { return true; } - List getPackerImages(); - default List finalizeIncompletePackerRun(CommandResult commandResult, AnsibleInstallType installType, String jarSha) { return null; } + List getAllPackerImages(); + List getPackerImagesForRegion(String region); + default List finalizeIncompletePackerRun(CommandResult commandResult, AnsibleInstallType installType) { return null; } + + default Map getPackerRegionContext(CloudRegion region) { return null; } + + default int getPackerParallelBuilds() { return 1; } } diff --git a/bubble-server/src/main/java/bubble/cloud/compute/ComputeServiceDriverBase.java b/bubble-server/src/main/java/bubble/cloud/compute/ComputeServiceDriverBase.java index 3b0e1055..53e0b60a 100644 --- a/bubble-server/src/main/java/bubble/cloud/compute/ComputeServiceDriverBase.java +++ b/bubble-server/src/main/java/bubble/cloud/compute/ComputeServiceDriverBase.java @@ -7,6 +7,7 @@ package bubble.cloud.compute; import bubble.cloud.CloudRegion; import bubble.cloud.CloudServiceDriverBase; import bubble.dao.cloud.BubbleNodeDAO; +import bubble.model.cloud.AnsibleInstallType; import bubble.model.cloud.BubbleNode; import bubble.service.packer.PackerService; import lombok.Getter; @@ -18,13 +19,19 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.SECONDS; import static org.cobbzilla.util.daemon.ZillaRuntime.die; +import static org.cobbzilla.util.daemon.ZillaRuntime.now; +import static org.cobbzilla.util.system.Sleep.sleep; @Slf4j public abstract class ComputeServiceDriverBase extends CloudServiceDriverBase implements ComputeServiceDriver { + public static final long PACKER_TIMEOUT = MINUTES.toMillis(60); + private final AtomicReference reaper = new AtomicReference<>(); @Override public void postSetup() { @@ -83,7 +90,7 @@ public abstract class ComputeServiceDriverBase } @Getter(lazy=true) private final OsImage os = initOs(); - private OsImage initOs() { + protected OsImage initOs() { final OsImage os = getCloudOsImages().stream() .filter(s -> s.getName().equals(config.getOs())) .findFirst() @@ -104,4 +111,32 @@ public abstract class ComputeServiceDriverBase .findAny().orElse(null); } + public PackerImage getPackerImage(BubbleNode node) { + PackerImage packerImage = getPackerImage(node.getInstallType(), node.getRegion()); + if (packerImage == null) { + final AtomicReference> imagesRef = new AtomicReference<>(); + packerService.writePackerImages(cloud, node.getInstallType(), imagesRef); + long start = now(); + while (imagesRef.get() == null && now() - start < PACKER_TIMEOUT) { + sleep(SECONDS.toMillis(1), "getPackerImage: waiting for packer image creation"); + } + if (imagesRef.get() == null) { + return die("getPackerImage: timeout creating packer image"); + } + packerImage = getPackerImage(node.getInstallType(), node.getRegion()); + if (packerImage == null) { + return die("getPackerImage: error creating packer image"); + } + } + return packerImage; + } + + public PackerImage getPackerImage(AnsibleInstallType installType, String region) { + final List images = getPackerImagesForRegion(region); + return images == null ? null : images.stream() + .filter(i -> i.getName().contains("_"+installType.name()+"_")) + .findFirst() + .orElse(null); + } + } diff --git a/bubble-server/src/main/java/bubble/cloud/compute/OsImage.java b/bubble-server/src/main/java/bubble/cloud/compute/OsImage.java index afc9cc70..274e24e1 100644 --- a/bubble-server/src/main/java/bubble/cloud/compute/OsImage.java +++ b/bubble-server/src/main/java/bubble/cloud/compute/OsImage.java @@ -1,12 +1,14 @@ package bubble.cloud.compute; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.*; import lombok.experimental.Accessors; @NoArgsConstructor @Accessors(chain=true) public class OsImage { - @Getter @Setter private Long id; + @Getter @Setter private String id; @Getter @Setter private String name; + @Getter @Setter private String region; } diff --git a/bubble-server/src/main/java/bubble/cloud/compute/PackerImage.java b/bubble-server/src/main/java/bubble/cloud/compute/PackerImage.java index 2c60a9ef..e456e245 100644 --- a/bubble-server/src/main/java/bubble/cloud/compute/PackerImage.java +++ b/bubble-server/src/main/java/bubble/cloud/compute/PackerImage.java @@ -7,7 +7,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import lombok.experimental.Accessors; -@NoArgsConstructor @Accessors(chain=true) @EqualsAndHashCode(of={"id"}) +@NoArgsConstructor @Accessors(chain=true) @EqualsAndHashCode(of={"id", "regions"}) public class PackerImage { @Getter @Setter private String id; diff --git a/bubble-server/src/main/java/bubble/cloud/compute/PackerImageParserBase.java b/bubble-server/src/main/java/bubble/cloud/compute/PackerImageParserBase.java index 73f9b568..0e1c41c1 100644 --- a/bubble-server/src/main/java/bubble/cloud/compute/PackerImageParserBase.java +++ b/bubble-server/src/main/java/bubble/cloud/compute/PackerImageParserBase.java @@ -6,19 +6,16 @@ public abstract class PackerImageParserBase extends ListResourceParser getPackerImages() { return notSupported("getPackerImages"); } + @Override public List getAllPackerImages() { return notSupported("getPackerImages"); } + @Override public List getPackerImagesForRegion(String region) { return notSupported("getPackerImagesForRegion"); } + } diff --git a/bubble-server/src/main/java/bubble/cloud/compute/digitalocean/DigitalOceanComputeNodeSizeParser.java b/bubble-server/src/main/java/bubble/cloud/compute/digitalocean/DigitalOceanComputeNodeSizeParser.java index ddaaed3f..50dcaf7b 100644 --- a/bubble-server/src/main/java/bubble/cloud/compute/digitalocean/DigitalOceanComputeNodeSizeParser.java +++ b/bubble-server/src/main/java/bubble/cloud/compute/digitalocean/DigitalOceanComputeNodeSizeParser.java @@ -1,5 +1,6 @@ package bubble.cloud.compute.digitalocean; +import bubble.cloud.compute.ComputeDiskType; import bubble.cloud.compute.ComputeNodeSize; import bubble.cloud.compute.ListResourceParser; import com.fasterxml.jackson.databind.JsonNode; @@ -18,7 +19,8 @@ public class DigitalOceanComputeNodeSizeParser extends ListResourceParser getPackerImages() { - final List images = getResources(PACKER_IMAGES_URI, new DigitalOceanPackerImageParser(configuration.getVersion(), packerService.getPackerPublicKeyHash(), configuration.getJarSha())); + @Override public List getAllPackerImages() { return getPackerImages(); } + @Override public List getPackerImagesForRegion(String region) { return getPackerImages(); } + + public List getPackerImages () { + final List images = getResources(PACKER_IMAGES_URI, new DigitalOceanPackerImageParser(configuration.getVersion(), packerService.getPackerPublicKeyHash())); return images == null ? Collections.emptyList() : images; } diff --git a/bubble-server/src/main/java/bubble/cloud/compute/digitalocean/DigitalOceanOsImageParser.java b/bubble-server/src/main/java/bubble/cloud/compute/digitalocean/DigitalOceanOsImageParser.java index ae200794..e87b2c32 100644 --- a/bubble-server/src/main/java/bubble/cloud/compute/digitalocean/DigitalOceanOsImageParser.java +++ b/bubble-server/src/main/java/bubble/cloud/compute/digitalocean/DigitalOceanOsImageParser.java @@ -13,7 +13,7 @@ public class DigitalOceanOsImageParser extends ListResourceParser { if (item.has("id")) { final JsonNode id = item.get("id"); if (id.isNumber()) { - image.setId(id.numberValue().longValue()); + image.setId(id.asText()); } else { return die("parse: id was not numeric"); } @@ -22,7 +22,7 @@ public class DigitalOceanOsImageParser extends ListResourceParser { } if (item.has("name")) { final JsonNode name = item.get("slug"); - image.setName(name.textValue()); + image.setName(name.asText()); } else { return die("parse: name not found"); } diff --git a/bubble-server/src/main/java/bubble/cloud/compute/digitalocean/DigitalOceanPackerImageParser.java b/bubble-server/src/main/java/bubble/cloud/compute/digitalocean/DigitalOceanPackerImageParser.java index 93c58ee0..d28782d4 100644 --- a/bubble-server/src/main/java/bubble/cloud/compute/digitalocean/DigitalOceanPackerImageParser.java +++ b/bubble-server/src/main/java/bubble/cloud/compute/digitalocean/DigitalOceanPackerImageParser.java @@ -10,8 +10,8 @@ import java.util.List; public class DigitalOceanPackerImageParser extends PackerImageParserBase { - public DigitalOceanPackerImageParser (String bubbleVersion, String keyHash, String jarSha) { - super(bubbleVersion, keyHash, jarSha); + public DigitalOceanPackerImageParser (String bubbleVersion, String keyHash) { + super(bubbleVersion, keyHash); } @Override public boolean allowEmpty() { return true; } diff --git a/bubble-server/src/main/java/bubble/cloud/compute/ec2/AmazonEC2Driver.java b/bubble-server/src/main/java/bubble/cloud/compute/ec2/AmazonEC2Driver.java index 5d8d2130..e34701cc 100644 --- a/bubble-server/src/main/java/bubble/cloud/compute/ec2/AmazonEC2Driver.java +++ b/bubble-server/src/main/java/bubble/cloud/compute/ec2/AmazonEC2Driver.java @@ -18,109 +18,391 @@ import com.amazonaws.regions.Regions; import com.amazonaws.services.ec2.AmazonEC2; import com.amazonaws.services.ec2.AmazonEC2ClientBuilder; import com.amazonaws.services.ec2.model.*; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.cobbzilla.util.collection.ExpirationMap; +import org.cobbzilla.util.collection.SingletonList; import org.cobbzilla.util.daemon.AwaitResult; +import org.cobbzilla.util.reflect.ReflectionUtil; +import org.cobbzilla.wizard.cache.redis.RedisService; +import org.springframework.beans.factory.annotation.Autowired; import java.io.IOException; import java.util.*; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; +import java.util.function.Function; +import java.util.stream.Collectors; import static bubble.model.cloud.BubbleNode.TAG_INSTANCE_ID; import static bubble.model.cloud.BubbleNode.TAG_TEST; +import static java.util.Comparator.comparing; +import static java.util.concurrent.TimeUnit.DAYS; import static org.cobbzilla.util.daemon.Await.awaitAll; import static org.cobbzilla.util.daemon.DaemonThreadFactory.fixedPool; import static org.cobbzilla.util.daemon.ZillaRuntime.*; import static org.cobbzilla.util.http.HttpStatusCodes.OK; +import static org.cobbzilla.util.json.JsonUtil.json; +import static org.cobbzilla.wizard.cache.redis.RedisService.EX; import static org.cobbzilla.wizard.resources.ResourceUtil.notFoundEx; @Slf4j public class AmazonEC2Driver extends ComputeServiceDriverBase { - public static final long TIMEOUT = TimeUnit.SECONDS.toMillis(10); + public static final long PARALLEL_TIMEOUT = TimeUnit.SECONDS.toMillis(20); public static final String TAG_CLOUD_UUID = "cloudUUID"; public static final String TAG_NODE_UUID = "nodeUUID"; public static final String KEY_NAME_PREFIX = "keyName_"; - public static final int MIN_COUNT = 1; - public static final int MAX_COUNT = 1; - @Getter(lazy=true) private final AWSCredentialsProvider ec2credentials = new BubbleAwsCredentialsProvider(cloud, getCredentials()); - @Getter(lazy=true) private final Map ec2ClientMap = new HashMap<>(); + public static final String VPC_CIDR_BLOCK = "10.0.0.0/16"; + public static final String VPC_IPV6_CIDR_BLOCK = "fdb0:bb00::/48"; + public static final String TAG_BUBBLE_CLASS = "BUBBLE_CLASS"; + public static final String TAG_BUBBLE_CLASS_PACKER_VPC = "BUBBLE_PACKER_VPC"; + public static final String TAG_BUBBLE_CLASS_PACKER_SUBNET = "BUBBLE_PACKER_SUBNET"; + + public static final Filter[] VPC_FILTERS = new Filter[]{ + new Filter("tag:" + TAG_BUBBLE_CLASS, new SingletonList<>(TAG_BUBBLE_CLASS_PACKER_VPC)) + }; + public static final Filter[] SUBNET_FILTERS = new Filter[]{ + new Filter("tag:" + TAG_BUBBLE_CLASS, new SingletonList<>(TAG_BUBBLE_CLASS_PACKER_SUBNET)) + }; + public static final String IP4_CIDR_ALL = "0.0.0.0/0"; + public static final String IP6_CIDR_ALL = "::/0"; + + @Autowired private RedisService redis; - @Override protected List getCloudRegions() { - // todo - return null; + @Getter(lazy=true) private final String securityGroup = config.getConfig("securityGroup"); + + @Getter(lazy=true) private final AWSCredentialsProvider ec2credentials = new BubbleAwsCredentialsProvider(cloud, getCredentials()); + @Getter(lazy=true) private final Map ec2ClientMap = initClientMap(); + + private Map initClientMap() { + final Map clients = new HashMap<>(); + final AWSCredentialsProvider ec2credentials = getEc2credentials(); + for (CloudRegion region : getCloudRegions()) { + final AmazonEC2 ec2 = AmazonEC2ClientBuilder.standard() + .withRegion(Regions.fromName(region.getInternalName())) + .withCredentials(ec2credentials).build(); + clients.put(region.getInternalName(), ec2); + } + return clients; } - @Override protected List getCloudSizes() { - // todo - return null; + @Getter(lazy=true) private final List cloudRegions = driverConfig("regions"); + @Getter(lazy=true) private final List cloudSizes = driverConfig("sizes"); + + private List driverConfig(String field) { return Arrays.asList((T[]) ReflectionUtil.get(config, field)); } + + @Getter(lazy=true) private final RedisService imageCache = redis.prefixNamespace(getClass().getSimpleName()+".ec2_ubuntu_image"); + public static final long IMAGE_CACHE_TIME = DAYS.toSeconds(30); + + @Getter(lazy=true) private final ExecutorService perRegionExecutor = fixedPool(getRegions().size()); + + @Getter(lazy=true) private final List cloudOsImages = initImages(); + private List initImages() { + final ArrayList filters = new ArrayList<>(); + filters.add(new Filter("root-device-type", new SingletonList<>("ebs"))); + filters.add(new Filter("state", new SingletonList<>("available"))); + filters.add(new Filter("name", new SingletonList<>(config.getOs()))); + final List> futures = new ArrayList<>(); + for (CloudRegion region : getCloudRegions()) { + futures.add(getPerRegionExecutor().submit(() -> { + final String internalName = region.getInternalName(); + final String cachedJson = getImageCache().get(internalName); + if (cachedJson != null) { + return json(cachedJson, OsImage.class); + } + final AmazonEC2 ec2 = getEc2Client(region); + final DescribeImagesRequest imageRequest = new DescribeImagesRequest().withFilters(filters); + final DescribeImagesResult imagesResult = ec2.describeImages(imageRequest); + if (empty(imagesResult.getImages())) die("no images found"); + final List sorted = new ArrayList<>(imagesResult.getImages()); + sorted.sort(comparing(Image::getCreationDate)); + final Image first = sorted.get(0); + final OsImage image = new OsImage() + .setName(first.getName()) + .setId(first.getImageId()) + .setRegion(internalName); + getImageCache().set(internalName, json(image), EX, IMAGE_CACHE_TIME); + return image; + })); + } + final AwaitResult awaitResult = awaitAll(futures, PARALLEL_TIMEOUT); + if (!awaitResult.allSucceeded()) return die("initImages: "+awaitResult.getFailures().values()); + return awaitResult.getSuccesses().values().stream().map(o -> (OsImage) o).collect(Collectors.toList()); } - @Override protected List getCloudOsImages() { - // todo - return null; + @Getter(lazy=true) private final Map imagesByRegion = getCloudOsImages().stream() + .collect(Collectors.toMap(OsImage::getRegion, Function.identity())); + + public Map getPackerRegionContext(CloudRegion region) { + final Map ctx = new HashMap<>(); + final String internalName = region.getInternalName(); + + final Map imagesByRegion = getImagesByRegion(); + if (empty(imagesByRegion)) return die("getPackerRegionContext: getImagesByRegion returned empty map"); + final OsImage imageForRegion = imagesByRegion.get(internalName); + if (imageForRegion == null) return die("getPackerRegionContext: no image found for region: "+internalName); + ctx.put("imageForRegion", imageForRegion); + + final Map vpcsByRegion = getVpcsByRegion(); + if (empty(vpcsByRegion)) return die("getPackerRegionContext: getVpcsByRegion returned empty map"); + final Vpc vpc = vpcsByRegion.get(internalName); + if (vpc == null) return die("getPackerRegionContext: no vpc found for region: "+internalName); + ctx.put("vpcForRegion", vpc); + + final Map> subnetsByRegion = getSubnetsByRegion(); + if (empty(subnetsByRegion)) return die("getPackerRegionContext: getSubnetsByRegion returned empty map"); + final Map subnets = subnetsByRegion.get(internalName); + if (subnets == null) return die("getPackerRegionContext: no subnets found for region: "+internalName); + + // use the last az/subnet in the region + final List azNames = new ArrayList<>(new TreeSet<>(subnets.keySet())); + final String az = azNames.get(azNames.size() - 1); + ctx.put("availabilityZoneForRegion", az); + ctx.put("subnetForRegion", subnets.get(az)); + + return ctx; } - private static final ExecutorService perRegionExecutor = fixedPool(8); - - private AmazonEC2 getEC2Client(final String regionName) { - Map ec2ClientMap = getEc2ClientMap(); - AmazonEC2 ec2client; - if (!ec2ClientMap.containsKey(regionName)) { - final Regions region; - try { - region = Regions.valueOf(regionName); - } catch (Exception e) { - return die("initEC2Client: invalid region: " + regionName); - } - ec2client = AmazonEC2ClientBuilder.standard() - .withRegion(region) - .withCredentials(getEc2credentials()) - .build(); - ec2ClientMap.put(regionName, ec2client); + @Override protected OsImage initOs() { return null; } + + @Override public List getAllPackerImages() { + final List> futures = new ArrayList<>(); + for (CloudRegion region : getRegions()) { + futures.add(getPerRegionExecutor().submit(() -> getPackerImagesForRegion(region.getInternalName()))); } - ec2client = ec2ClientMap.get(regionName); - return ec2client; + final AwaitResult awaitResult = awaitAll(futures, PARALLEL_TIMEOUT); + if (!awaitResult.allSucceeded()) return die("initImages: "+awaitResult.getFailures().values()); + + final List images = new ArrayList<>(); + for (Object o : awaitResult.getSuccesses().values()) images.addAll((List) o); + return images; } - @Override public List listNodes() throws IOException { - List> listNodeJobs = new ArrayList<>(); - for (final String regionName : getEc2ClientMap().keySet()) { - listNodeJobs.add(perRegionExecutor.submit(new listNodesHelper(regionName))); + @Override public List getPackerImagesForRegion(String region) { + final ArrayList filters = new ArrayList<>(); + filters.add(new Filter("root-device-type", new SingletonList<>("ebs"))); + filters.add(new Filter("state", new SingletonList<>("available"))); + filters.add(new Filter("name", new SingletonList<>("packer_*_"+packerService.getPackerPublicKeyHash()+"_"+configuration.getVersion()+"_*"))); + final AmazonEC2 ec2 = getEc2Client(region); + final DescribeImagesRequest imageRequest = new DescribeImagesRequest().withFilters(filters); + final DescribeImagesResult imagesResult = ec2.describeImages(imageRequest); + if (empty(imagesResult.getImages())) return Collections.emptyList(); + return imagesResult.getImages().stream().map(i -> new PackerImage() + .setName(i.getName()) + .setId(i.getImageId()) + .setRegions(new CloudRegion[]{getRegion(region)}) + ).collect(Collectors.toList()); + } + + @Override public int getPackerParallelBuilds() { return getRegions().size(); } + + private AmazonEC2 getEc2Client(CloudRegion region) { return getEc2Client(region.getInternalName()); } + + private AmazonEC2 getEc2Client(String internalName) { + final AmazonEC2 ec2 = getEc2ClientMap().get(internalName); + if (ec2 == null) return die("getEc2Client: invalid region: "+internalName); + return ec2; + } + + private final ExpirationMap vpcCache = new ExpirationMap<>(DAYS.toMillis(1)); + + public Map getVpcsByRegion() { + final List> futures = new ArrayList<>(); + final Map vpcsByRegion = new ConcurrentHashMap<>(); + for (CloudRegion region : getRegions()) { + final String internalName = region.getInternalName(); + final AmazonEC2 ec2 = getEc2Client(internalName); + futures.add(getPerRegionExecutor().submit(() -> { + Vpc vpc = vpcCache.get(internalName); + if (vpc != null) { + vpcsByRegion.put(internalName, vpc); + return; + } + final DescribeVpcsRequest describeRequest = new DescribeVpcsRequest().withFilters(VPC_FILTERS); + final DescribeVpcsResult describeVpcsResult = ec2.describeVpcs(describeRequest); + if (empty(describeVpcsResult.getVpcs())) { + final CreateVpcRequest createRequest = new CreateVpcRequest() + .withCidrBlock(VPC_CIDR_BLOCK) + .withAmazonProvidedIpv6CidrBlock(true); + final CreateVpcResult createVpcResult; + try { + createVpcResult = ec2.createVpc(createRequest); + } catch (Exception e) { + die("createVpcs("+internalName+"): "+e, e); + return; + } + + vpc = createVpcResult.getVpc(); + ec2.createTags(new CreateTagsRequest() + .withResources(vpc.getVpcId()) + .withTags(new Tag(TAG_BUBBLE_CLASS, TAG_BUBBLE_CLASS_PACKER_VPC))); + } else { + final List vpcs = describeVpcsResult.getVpcs(); + if (vpcs.size() > 1) die("createVpcs: more than 1 vpc found for region: "+internalName); + vpc = vpcs.get(0); + } + vpcsByRegion.put(internalName, vpc); + vpcCache.put(internalName, vpc); + })); } - AwaitResult awaitResult = awaitAll(listNodeJobs, TIMEOUT); + final AwaitResult awaitResult = awaitAll(futures, PARALLEL_TIMEOUT); if (!awaitResult.allSucceeded()) { - return die("listNodes: error listing nodes"); + return die("createVpcs: "+awaitResult.getFailures().values()); } - final List nodes = new ArrayList<>(); + return vpcsByRegion; + } - nodes.addAll((Collection) awaitResult.getSuccesses().values()); + private final ExpirationMap> azCache = new ExpirationMap<>(DAYS.toMillis(30)); + private final ExpirationMap> subnetCache = new ExpirationMap<>(DAYS.toMillis(1)); + + public Map> getSubnetsByRegion() { + final Map vpcsByRegion = getVpcsByRegion(); + final List> futures = new ArrayList<>(); + final Map> subnetsByRegion = new ConcurrentHashMap<>(); + for (CloudRegion region : getRegions()) { + final String internalName = region.getInternalName(); + final AmazonEC2 ec2 = getEc2Client(internalName); + futures.add(getPerRegionExecutor().submit(() -> { + final List availZones = azCache.computeIfAbsent(internalName, k -> { + final DescribeAvailabilityZonesResult azResult = ec2.describeAvailabilityZones(new DescribeAvailabilityZonesRequest().withFilters(new Filter().withName("region-name").withValues(k))); + return azResult.getAvailabilityZones().stream().map(AvailabilityZone::getZoneName).collect(Collectors.toList()); + }); + + Map subnets = subnetCache.get(internalName); + if (subnets == null) { + subnets = new HashMap<>(); + } else if (!empty(subnets) && subnets.size() == availZones.size()) { + subnetsByRegion.put(internalName, subnets); + return; + } - return nodes; + final DescribeSubnetsResult subnetsResult = ec2.describeSubnets(new DescribeSubnetsRequest().withFilters(SUBNET_FILTERS)); + for (String az : availZones) { + final Vpc vpc = vpcsByRegion.get(internalName); + Subnet subnet = subnetsResult.getSubnets().stream().filter(sn -> sn.getAvailabilityZone().equals(az)).findFirst().orElse(null); + final String subnetId = subnet.getSubnetId(); + if (subnet == null) { + final CreateSubnetResult createSubnetResult; + try { + final CreateSubnetRequest createRequest = new CreateSubnetRequest() + .withVpcId(vpc.getVpcId()) + .withAvailabilityZone(az) + .withCidrBlock(ip4Slash28(vpc, az)); + createSubnetResult = ec2.createSubnet(createRequest); + } catch (Exception e) { + die("createSubnets("+internalName+"/"+az+"): " + e, e); + return; + } + + subnet = createSubnetResult.getSubnet(); + ec2.createTags(new CreateTagsRequest() + .withResources(subnetId) + .withTags(new Tag(TAG_BUBBLE_CLASS, TAG_BUBBLE_CLASS_PACKER_SUBNET))); + } + + final DescribeInternetGatewaysResult gatewaysResult = ec2.describeInternetGateways(); + final InternetGateway gateway = gatewaysResult.getInternetGateways().stream() + .filter(g -> g.getAttachments().stream().anyMatch(a -> a.getVpcId().equals(vpc.getVpcId()))) + .findFirst() + .or(() -> Optional.ofNullable(ec2.createInternetGateway().getInternetGateway())) + .get(); + final String gatewayId = gateway.getInternetGatewayId(); + if (gateway.getAttachments().stream().noneMatch(a -> a.getVpcId().equals(vpc.getVpcId()))) { + ec2.attachInternetGateway(new AttachInternetGatewayRequest() + .withVpcId(vpc.getVpcId()) + .withInternetGatewayId(gatewayId)); + } + + final DescribeRouteTablesResult routes = ec2.describeRouteTables(); + final RouteTable routeTable = routes.getRouteTables().stream().filter(rt -> rt.getVpcId().equals(vpc.getVpcId())).findFirst().orElse(null); + if (routeTable == null) { + die("createSubnets("+internalName+"/"+az+"): no route table found for vpc "+vpc.getVpcId()); + return; + } + final List associations = routeTable.getAssociations(); + try { +// if (empty(associations) || associations.stream() +// .noneMatch(a -> a.getGatewayId() != null && a.getGatewayId().equals(gatewayId))) { +// ec2.associateRouteTable(new AssociateRouteTableRequest() +// .withGatewayId(gatewayId) +// .withRouteTableId(routeTable.getRouteTableId())); +// } + if (empty(associations) || associations.stream() + .noneMatch(a -> a.getSubnetId() != null && a.getSubnetId().equals(subnetId))) { + ec2.associateRouteTable(new AssociateRouteTableRequest() + .withSubnetId(subnetId) + .withRouteTableId(routeTable.getRouteTableId())); + } + if (routeTable.getRoutes().stream() + .noneMatch(r -> r.getDestinationCidrBlock() != null && r.getDestinationCidrBlock().equals(IP4_CIDR_ALL))) { + ec2.createRoute(new CreateRouteRequest() + .withDestinationCidrBlock(IP4_CIDR_ALL) + .withGatewayId(gatewayId) + .withRouteTableId(routeTable.getRouteTableId())); + } + if (routeTable.getRoutes().stream() + .noneMatch(r -> r.getDestinationIpv6CidrBlock() != null && r.getDestinationIpv6CidrBlock().equals(IP6_CIDR_ALL))) { + ec2.createRoute(new CreateRouteRequest() + .withDestinationIpv6CidrBlock(IP6_CIDR_ALL) + .withGatewayId(gatewayId) + .withRouteTableId(routeTable.getRouteTableId())); + } + } catch (Exception e) { + die("createSubnets("+internalName+"/"+az+"): error adding gateway route vpc "+vpc.getVpcId()); + return; + } + + subnets.put(az, subnet); + } + subnetsByRegion.put(internalName, subnets); + subnetCache.put(internalName, subnets); + })); + } + final AwaitResult awaitResult = awaitAll(futures, PARALLEL_TIMEOUT); + if (!awaitResult.allSucceeded()) { + return die("createSubnets: "+awaitResult.getFailures().values()); + } + return subnetsByRegion; } - private class listNodesHelper implements Callable> { - private String regionName; + private String ip4Slash28(Vpc vpc, String az) { + final char zoneId = az.toLowerCase().charAt(az.length()-1); + final int subnetNumber = zoneId - 'a'; + final String[] parts = vpc.getCidrBlock().split("[./]"); + final String subnet = parts[0] + "." + parts[1] + "." + parts[2] + "." + (subnetNumber * 16) + "/28"; + return subnet; + } - public listNodesHelper(String regionName) { - this.regionName = regionName; + @Override public List listNodes() throws IOException { + final List> listNodeJobs = new ArrayList<>(); + for (final AmazonEC2 ec2 : getEc2ClientMap().values()) { + listNodeJobs.add(getPerRegionExecutor().submit(new ListNodesHelper(ec2))); + } + final AwaitResult awaitResult = awaitAll(listNodeJobs, PARALLEL_TIMEOUT); + if (!awaitResult.allSucceeded()) { + return die("listNodes: error listing nodes: "+awaitResult.getFailures().values()); } + final List nodes = new ArrayList<>(); + for (Object o : awaitResult.getSuccesses().values()) nodes.addAll((List) o); + return nodes; + } + + @AllArgsConstructor + private class ListNodesHelper implements Callable> { + private AmazonEC2 ec2; - public List listNodesForSingleRegion(String regionName) { - AmazonEC2 ec2Client = getEC2Client(regionName); + @Override public List call() { final List nodes = new ArrayList<>(); final DescribeInstancesRequest describeInstancesRequest = new DescribeInstancesRequest() .withFilters( - new Filter("instance.group-id").withValues(config.getConfig("group")), + new Filter("instance.group-id").withValues(getSecurityGroup()), new Filter("tag:" + TAG_CLOUD_UUID).withValues(cloud.getUuid()), new Filter("instance-state-name").withValues("running", "pending") ); - final DescribeInstancesResult result = ec2Client.describeInstances(describeInstancesRequest); + final DescribeInstancesResult result = ec2.describeInstances(describeInstancesRequest); if (result.getSdkHttpMetadata().getHttpStatusCode() == OK) { for (Reservation reservation : result.getReservations()) { for (Instance instance : reservation.getInstances()) { @@ -131,50 +413,38 @@ public class AmazonEC2Driver extends ComputeServiceDriverBase { } } } else { - return die("listNodesForRegion: error describe EC2 instances for region " + regionName + return die("list: error describe EC2 instances: " + result.getSdkHttpMetadata().getHttpStatusCode() + ": " + result.getSdkHttpMetadata().getAllHttpHeaders()); } return nodes; } - - @Override public List call() throws Exception { - try { - return listNodesForSingleRegion(this.regionName); - } catch (Exception e) { - log.error("listNodesHelper.call: " + e, e); - throw e; - } - } } @Override public BubbleNode start(BubbleNode node) throws Exception { final ComputeNodeSize size = config.getSize(node.getSize()); - final AmazonEC2 ec2Client = getEC2Client(node.getRegion()); - - final DescribeSubnetsRequest describeSubnetsRequest = new DescribeSubnetsRequest() - .withFilters(new Filter().withName("state").withValues("available")); - - final String subnetId = ec2Client.describeSubnets(describeSubnetsRequest).getSubnets().stream() - .filter(s -> s.getAvailableIpAddressCount() != 0) - .findAny().orElseThrow(() -> new NoSuchElementException("Subnet not found")).getSubnetId(); + final AmazonEC2 ec2Client = getEc2Client(node.getRegion()); + final PackerImage packerImage = getPackerImage(node); + final EbsBlockDevice ebs = null; // todo final RunInstancesRequest runInstancesRequest = new RunInstancesRequest().withImageId(config.getConfig("imageId")) .withInstanceType(size.getInternalName()) - .withMinCount(MIN_COUNT) - .withMaxCount(MAX_COUNT) + .withImageId(packerImage.getId()) + .withMinCount(1) + .withMaxCount(1) .withKeyName(KEY_NAME_PREFIX+node.getUuid()) + .withBlockDeviceMappings(new BlockDeviceMapping().withEbs(ebs)) .withNetworkInterfaces(new InstanceNetworkInterfaceSpecification() .withAssociatePublicIpAddress(true) .withDeviceIndex(0) - .withSubnetId(subnetId) - .withGroups(config.getConfig("group"))); + .withIpv6AddressCount(1) + .withGroups(getSecurityGroup())); final RunInstancesResult runInstancesResult = ec2Client.runInstances(runInstancesRequest); - if (runInstancesResult.getSdkHttpMetadata().getHttpStatusCode() != OK) - return die("start: error running instance: " - + runInstancesResult.getSdkHttpMetadata().getAllHttpHeaders()); + if (runInstancesResult.getSdkHttpMetadata().getHttpStatusCode() != OK) { + return die("start: error running instance"); + } final String instanceId = runInstancesResult.getReservation().getInstances().get(0).getInstanceId(); @@ -185,52 +455,47 @@ public class AmazonEC2Driver extends ComputeServiceDriverBase { final DescribeInstancesResult result = ec2Client.describeInstances( new DescribeInstancesRequest().withInstanceIds(instanceId)); - if (result.getSdkHttpMetadata().getHttpStatusCode() == OK) { - for (final Reservation reservation : result.getReservations()) { - for (final Instance i : reservation.getInstances()) { - if (i.getInstanceId().equals(instanceId)) { - final String ip4 = i.getPrivateIpAddress(); - if (ip4 != null && ip4.length() > 0 && !ip4.equals("0.0.0.0")) { - node.setIp4(ip4); - nodeDAO.update(node); - } - final String ip6 = i.getPublicIpAddress(); - if (ip6 != null && ip6.length() > 0) { - node.setIp6(ip6); - nodeDAO.update(node); - } - break; + if (result.getSdkHttpMetadata().getHttpStatusCode() != OK) { + return die("start: error describing instance"); + } + for (final Reservation reservation : result.getReservations()) { + for (final Instance i : reservation.getInstances()) { + if (i.getInstanceId().equals(instanceId)) { + final String ip4 = i.getPublicIpAddress(); + if (ip4 != null && ip4.length() > 0 && !ip4.equals("0.0.0.0")) { + node.setIp4(ip4); + nodeDAO.update(node); + } + final String ip6 = i.getPublicIpAddress(); + if (ip6 != null && ip6.length() > 0) { + node.setIp6(ip6); + nodeDAO.update(node); } + break; } } - } else { - log.error("start: error describe instance, status: " + result.getSdkHttpMetadata().getHttpStatusCode() - + result.getSdkHttpMetadata().getAllHttpHeaders()); } // Setting up the tags for the instance - try { - ec2Client.createTags(new CreateTagsRequest() - .withResources(instanceId) - .withTags(new Tag(TAG_NODE_UUID, node.getUuid()), - new Tag(TAG_CLOUD_UUID, cloud.getUuid()))); - } catch (AmazonServiceException e) { - log.warn("start: error creating tags: " + e.getErrorMessage() + e.getErrorCode()); - } - - // Setting up the tag for the test instance + final List tags = new ArrayList<>(); + tags.add(new Tag(TAG_NODE_UUID, node.getUuid())); + tags.add(new Tag(TAG_CLOUD_UUID, cloud.getUuid())); if (configuration.testMode()) { final String testTag = configuration.getEnvironment().get("TEST_TAG_CLOUD"); if (empty(testTag)) return die("TEST_TAG_CLOUD env var is not defined or is empty"); - - try { - ec2Client.createTags(new CreateTagsRequest() - .withResources(instanceId) - .withTags(new Tag(TAG_TEST, configuration.getEnvironment().get("TEST_TAG_CLOUD")))); - } catch (AmazonServiceException e) { - log.warn("start: error creating test tag: " + e.getErrorMessage() + e.getErrorCode()); + tags.add(new Tag(TAG_TEST, testTag)); + } + try { + final CreateTagsResult tagsResult = ec2Client.createTags(new CreateTagsRequest() + .withResources(instanceId) + .withTags(tags)); + if (tagsResult.getSdkHttpMetadata().getHttpStatusCode() != OK) { + return die("start: error setting tags on instance"); } + } catch (AmazonServiceException e) { + return die("start: error setting tags on instance: "+shortError(e), e); } + return node; } @@ -246,8 +511,7 @@ public class AmazonEC2Driver extends ComputeServiceDriverBase { final StopInstancesRequest stopInstancesRequest = new StopInstancesRequest() .withInstanceIds(instanceID); - final AmazonEC2 ec2Client = getEC2Client(node.getRegion()); - + final AmazonEC2 ec2Client = getEc2Client(node.getRegion()); try { ec2Client.stopInstances(stopInstancesRequest); } catch (AmazonServiceException e) { @@ -274,7 +538,4 @@ public class AmazonEC2Driver extends ComputeServiceDriverBase { return node; } - // todo - @Override public List getPackerImages() { return notSupported("getPackerImages"); } - } diff --git a/bubble-server/src/main/java/bubble/cloud/compute/local/LocalComputeDriver.java b/bubble-server/src/main/java/bubble/cloud/compute/local/LocalComputeDriver.java index 056010c6..ccffd551 100644 --- a/bubble-server/src/main/java/bubble/cloud/compute/local/LocalComputeDriver.java +++ b/bubble-server/src/main/java/bubble/cloud/compute/local/LocalComputeDriver.java @@ -27,6 +27,7 @@ public class LocalComputeDriver extends CloudServiceDriverBase im @Override public BubbleNode stop(BubbleNode node) throws Exception { return notSupported("stop"); } @Override public BubbleNode status(BubbleNode node) throws Exception { return notSupported("status"); } - @Override public List getPackerImages() { return notSupported("getPackerImages"); } + @Override public List getAllPackerImages() { return notSupported("getPackerImages"); } + @Override public List getPackerImagesForRegion(String region) { return notSupported("getPackerImagesForRegion"); } } diff --git a/bubble-server/src/main/java/bubble/cloud/compute/mock/MockComputeDriver.java b/bubble-server/src/main/java/bubble/cloud/compute/mock/MockComputeDriver.java index 97a98edd..85a59c09 100644 --- a/bubble-server/src/main/java/bubble/cloud/compute/mock/MockComputeDriver.java +++ b/bubble-server/src/main/java/bubble/cloud/compute/mock/MockComputeDriver.java @@ -62,6 +62,7 @@ public class MockComputeDriver extends ComputeServiceDriverBase { return node; } - @Override public List getPackerImages() { return Collections.emptyList(); } + @Override public List getAllPackerImages() { return Collections.emptyList(); } + @Override public List getPackerImagesForRegion(String region) { return Collections.emptyList(); } } diff --git a/bubble-server/src/main/java/bubble/cloud/compute/vultr/VultrComputeNodeSizeParser.java b/bubble-server/src/main/java/bubble/cloud/compute/vultr/VultrComputeNodeSizeParser.java index a2775788..7902f903 100644 --- a/bubble-server/src/main/java/bubble/cloud/compute/vultr/VultrComputeNodeSizeParser.java +++ b/bubble-server/src/main/java/bubble/cloud/compute/vultr/VultrComputeNodeSizeParser.java @@ -1,5 +1,6 @@ package bubble.cloud.compute.vultr; +import bubble.cloud.compute.ComputeDiskType; import bubble.cloud.compute.ComputeNodeSize; import bubble.cloud.compute.ListResourceParser; import com.fasterxml.jackson.databind.JsonNode; @@ -19,7 +20,8 @@ public class VultrComputeNodeSizeParser extends ListResourceParser i.getName().equals("Snapshot")) .findFirst() .orElse(null); - return snapshot == null ? die("initSnapshotOsId: no snapshot OS found") : snapshot.getId().intValue(); + return snapshot == null ? die("initSnapshotOsId: no snapshot OS found") : Integer.parseInt(snapshot.getId()); } public static final long SERVER_START_INITIAL_INTERVAL = SECONDS.toMillis(30); @@ -108,8 +105,6 @@ public class VultrDriver extends ComputeServiceDriverBase { super.postSetup(); } - public static final long PACKER_TIMEOUT = MINUTES.toMillis(60); - @Override public BubbleNode start(BubbleNode node) throws Exception { final CloudRegion region = config.getRegion(node.getRegion()); @@ -121,20 +116,7 @@ public class VultrDriver extends ComputeServiceDriverBase { final Long planId = getSize(size.getType()).getId(); if (planId == null) return die("start: plan not found: "+size.getInternalName()); - PackerImage packerImage = getPackerImage(node.getInstallType()); - if (packerImage == null) { - final AtomicReference> imagesRef = new AtomicReference<>(); - packerService.writePackerImages(cloud, node.getInstallType(), imagesRef); - long start = now(); - while (imagesRef.get() == null && now() - start < PACKER_TIMEOUT) { - sleep(SECONDS.toMillis(1), "start: waiting for packer image creation"); - } - if (imagesRef.get() == null) return die("start: timeout creating packer image"); - packerImage = getPackerImage(node.getInstallType()); - if (packerImage == null) { - return die("start: error creating packer image"); - } - } + final PackerImage packerImage = getPackerImage(node); // prepare to create server final String data = "DCID=" + regionId + @@ -392,56 +374,55 @@ public class VultrDriver extends ComputeServiceDriverBase { } } - @Override public List getPackerImages() { - final List images = loadCloudResources(SNAPSHOT_URL, new VultrPackerImageParser(configuration.getVersion(), packerService.getPackerPublicKeyHash(), configuration.getJarSha())); - return images == null ? Collections.emptyList() : images; - } + @Override public List getAllPackerImages() { return getPackerImages(); } + @Override public List getPackerImagesForRegion(String region) { return getPackerImages(); } - public PackerImage getPackerImage(AnsibleInstallType installType) { - final List images = getPackerImages(); - return images == null ? null : images.stream() - .filter(i -> i.getName().contains("_"+installType.name()+"_")) - .findFirst() - .orElse(null); + public List getPackerImages () { + final List images = loadCloudResources(SNAPSHOT_URL, new VultrPackerImageParser(configuration.getVersion(), packerService.getPackerPublicKeyHash())); + return images == null ? Collections.emptyList() : images; } public static final long SNAPSHOT_TIMEOUT = MINUTES.toMillis(60); - @Override public List finalizeIncompletePackerRun(CommandResult commandResult, AnsibleInstallType installType, String jarSha) { + @Override public List finalizeIncompletePackerRun(CommandResult commandResult, AnsibleInstallType installType) { if (!commandResult.getStdout().contains("Waiting 300s for snapshot") || !commandResult.getStdout().contains("Error waiting for snapshot") || !commandResult.getStdout().contains("Unable to destroy server: Unable to remove VM: Server is currently locked")) { - stopImageServer(installType, jarSha); + stopImageServer(installType); return null; } // wait longer for the snapshot... + final String keyHash = packerService.getPackerPublicKeyHash(); final long start = now(); PackerImage snapshot = null; while (now() - start < SNAPSHOT_TIMEOUT) { snapshot = getPackerImages().stream() - .filter(i -> i.getName().contains("_"+installType.name()+"_") && i.getName().endsWith(jarSha)) + .filter(i -> i.getName().contains("_"+installType.name()+"_") && i.getName().contains(keyHash)) .findFirst() .orElse(null); if (snapshot != null) break; - sleep(SECONDS.toMillis(20), "finalizeIncompletePackerRun: waiting for snapshot: "+jarSha); + sleep(SECONDS.toMillis(20), "finalizeIncompletePackerRun: waiting for snapshot: "+keyHash); } if (snapshot == null) { log.error("finalizeIncompletePackerRun: timeout waiting for snapshot"); } - if (!stopImageServer(installType, jarSha)) return null; + if (!stopImageServer(installType)) return null; if (snapshot == null) return null; return new SingletonList<>(snapshot); } - public boolean stopImageServer(AnsibleInstallType installType, String jarSha) { - // find the server + public boolean stopImageServer(AnsibleInstallType installType) { + + final String keyHash = packerService.getPackerPublicKeyHash(); final List servers; + + // find the server(s) try { servers = listNodes(server -> { final String tag = server.has("tag") ? server.get("tag").textValue() : null; - return tag != null && tag.contains("_"+installType.name()+"_") && tag.endsWith(jarSha); + return tag != null && tag.contains("_"+installType.name()+"_") && tag.contains(keyHash); }); } catch (IOException e) { log.error("finalizeIncompletePackerRun: error listing servers: "+shortError(e), e); diff --git a/bubble-server/src/main/java/bubble/cloud/compute/vultr/VultrOsImageParser.java b/bubble-server/src/main/java/bubble/cloud/compute/vultr/VultrOsImageParser.java index 4052825d..1764f34c 100644 --- a/bubble-server/src/main/java/bubble/cloud/compute/vultr/VultrOsImageParser.java +++ b/bubble-server/src/main/java/bubble/cloud/compute/vultr/VultrOsImageParser.java @@ -12,8 +12,8 @@ public class VultrOsImageParser extends ListResourceParser { if (!item.has("OSID")) return die("parse: OSID not found"); if (!item.has("name")) return die("parse: name not found"); return new OsImage() - .setId(item.get("OSID").asLong()) - .setName(item.get("name").textValue()); + .setId(item.get("OSID").asText()) + .setName(item.get("name").asText()); } } diff --git a/bubble-server/src/main/java/bubble/cloud/compute/vultr/VultrPackerImageParser.java b/bubble-server/src/main/java/bubble/cloud/compute/vultr/VultrPackerImageParser.java index fddafe22..a12afd9f 100644 --- a/bubble-server/src/main/java/bubble/cloud/compute/vultr/VultrPackerImageParser.java +++ b/bubble-server/src/main/java/bubble/cloud/compute/vultr/VultrPackerImageParser.java @@ -8,8 +8,8 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.die; public class VultrPackerImageParser extends PackerImageParserBase { - public VultrPackerImageParser(String bubbleVersion, String keyHash, String jarSha) { - super(bubbleVersion, keyHash, jarSha); + public VultrPackerImageParser(String bubbleVersion, String keyHash) { + super(bubbleVersion, keyHash); } @Override public PackerImage parse(JsonNode item) { diff --git a/bubble-server/src/main/java/bubble/cloud/shared/aws/BubbleAwsCredentialsProvider.java b/bubble-server/src/main/java/bubble/cloud/shared/aws/BubbleAwsCredentialsProvider.java index b78e0708..1be64720 100644 --- a/bubble-server/src/main/java/bubble/cloud/shared/aws/BubbleAwsCredentialsProvider.java +++ b/bubble-server/src/main/java/bubble/cloud/shared/aws/BubbleAwsCredentialsProvider.java @@ -15,23 +15,17 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.empty; public class BubbleAwsCredentialsProvider implements AWSCredentialsProvider { + public static final String AWS_ACCESS_KEY_ID = "AWS_ACCESS_KEY_ID"; + public static final String AWS_SECRET_KEY = "AWS_SECRET_KEY"; + private final CloudService cloud; private final CloudCredentials awsCredentials; - private String accessKeyParam = "AWS_ACCESS_KEY_ID"; - private String secretKeyParam = "AWS_SECRET_KEY"; public BubbleAwsCredentialsProvider (CloudService cloud, CloudCredentials credentials) { this.cloud = cloud; this.awsCredentials = credentials; } - public BubbleAwsCredentialsProvider (CloudService cloud, CloudCredentials credentials, String accessKeyParam, String secretKeyParam) { - this.cloud = cloud; - this.awsCredentials = credentials; - this.accessKeyParam = accessKeyParam; - this.secretKeyParam = secretKeyParam; - } - @Getter(lazy=true) private final AWSCredentials credentials = new BubbleAwsCredentials(); @Override public void refresh() {} @@ -39,17 +33,17 @@ public class BubbleAwsCredentialsProvider implements AWSCredentialsProvider { private class BubbleAwsCredentials implements AWSCredentials { @Override public String getAWSAccessKeyId() { - final String key = awsCredentials.getParam(accessKeyParam); + final String key = awsCredentials.getParam(AWS_ACCESS_KEY_ID); return empty(key) - ? die("getAWSAccessKeyId: no accessKeyParam ("+accessKeyParam+") defined in credentials for cloud: "+cloud.getUuid()) + ? die("getAWSAccessKeyId: no AWS_ACCESS_KEY_ID defined in credentials for cloud: "+cloud.getUuid()) : key; } @Override public String getAWSSecretKey() { - final String key = awsCredentials.getParam(secretKeyParam); + final String key = awsCredentials.getParam(AWS_SECRET_KEY); return empty(key) - ? die("getAWSSecretKey: no secretKeyParam ("+secretKeyParam+") defined in credentials for cloud: "+cloud.getUuid()) + ? die("getAWSSecretKey: no AWS_SECRET_KEY defined in credentials for cloud: "+cloud.getUuid()) : key; } } diff --git a/bubble-server/src/main/java/bubble/main/http/BubbleHttpEntityOptions.java b/bubble-server/src/main/java/bubble/main/http/BubbleHttpEntityOptions.java index bc5ee396..07b95c8c 100644 --- a/bubble-server/src/main/java/bubble/main/http/BubbleHttpEntityOptions.java +++ b/bubble-server/src/main/java/bubble/main/http/BubbleHttpEntityOptions.java @@ -14,9 +14,9 @@ import org.kohsuke.args4j.Option; import java.io.InputStream; import static org.cobbzilla.util.daemon.ZillaRuntime.*; -import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON; -import static org.cobbzilla.util.http.HttpContentTypes.MULTIPART_FORM_DATA; +import static org.cobbzilla.util.http.HttpContentTypes.*; import static org.cobbzilla.util.json.JsonUtil.*; +import static org.cobbzilla.util.string.StringUtil.UTF8cs; @Slf4j public class BubbleHttpEntityOptions extends BubbleHttpOptions { @@ -57,6 +57,6 @@ public class BubbleHttpEntityOptions extends BubbleHttpOptions { return APPLICATION_JSON; } - public ContentType contentType() { return ContentType.create(getContentType()); } + public ContentType contentType() { return ContentType.create(getContentType(), UTF8cs); } } diff --git a/bubble-server/src/main/java/bubble/resources/cloud/ComputePackerResource.java b/bubble-server/src/main/java/bubble/resources/cloud/ComputePackerResource.java index ad637a63..a9390f71 100644 --- a/bubble-server/src/main/java/bubble/resources/cloud/ComputePackerResource.java +++ b/bubble-server/src/main/java/bubble/resources/cloud/ComputePackerResource.java @@ -36,7 +36,7 @@ public class ComputePackerResource { public Response listImages(@Context Request req, @Context ContainerRequest ctx) { final ComputeServiceDriver driver = cloud.getComputeDriver(configuration); - return ok(driver.getPackerImages()); + return ok(driver.getAllPackerImages()); } @PUT @Path("/{type}") diff --git a/bubble-server/src/main/java/bubble/service/packer/PackerJob.java b/bubble-server/src/main/java/bubble/service/packer/PackerJob.java index 7554d0f2..8fe1d593 100644 --- a/bubble-server/src/main/java/bubble/service/packer/PackerJob.java +++ b/bubble-server/src/main/java/bubble/service/packer/PackerJob.java @@ -2,10 +2,7 @@ package bubble.service.packer; import bubble.cloud.CloudRegion; import bubble.cloud.CloudRegionRelative; -import bubble.cloud.compute.ComputeConfig; -import bubble.cloud.compute.ComputeServiceDriver; -import bubble.cloud.compute.PackerConfig; -import bubble.cloud.compute.PackerImage; +import bubble.cloud.compute.*; import bubble.cloud.geoLocation.GeoLocation; import bubble.dao.account.AccountDAO; import bubble.model.account.Account; @@ -25,6 +22,7 @@ import org.cobbzilla.util.io.TempDir; import org.cobbzilla.util.system.Command; import org.cobbzilla.util.system.CommandResult; import org.cobbzilla.util.system.CommandShell; +import org.cobbzilla.util.time.TimeUtil; import org.springframework.beans.factory.annotation.Autowired; import java.io.File; @@ -36,14 +34,15 @@ import java.util.stream.Collectors; import static bubble.ApiConstants.copyScripts; import static bubble.model.cloud.RegionalServiceDriver.findClosestRegions; import static bubble.service.packer.PackerService.*; -import static org.cobbzilla.util.daemon.ZillaRuntime.die; -import static org.cobbzilla.util.daemon.ZillaRuntime.empty; +import static org.cobbzilla.util.daemon.ZillaRuntime.*; import static org.cobbzilla.util.io.FileUtil.*; import static org.cobbzilla.util.io.StreamUtil.copyClasspathDirectory; import static org.cobbzilla.util.io.StreamUtil.stream2string; import static org.cobbzilla.util.json.JsonUtil.json; import static org.cobbzilla.util.network.NetworkUtil.getExternalIp; +import static org.cobbzilla.util.string.StringUtil.truncate; import static org.cobbzilla.util.system.CommandShell.hostname; +import static org.cobbzilla.util.time.TimeUtil.DATE_FORMAT_YYYYMMDDHHMMSS; @Slf4j public class PackerJob implements Callable> { @@ -54,15 +53,15 @@ public class PackerJob implements Callable> { public static final String PACKER_IMAGE_PREFIX = "packer_bubble_"; public static final String INSTALL_TYPE_VAR = "@@TYPE@@"; - public static final String BUBBLE_VERSION_VAR = "@@BUBBLE_VERSION@@"; public static final String SAGE_HOST_VAR = "@@SAGE_HOST@@"; public static final String PACKER_KEY_VAR = "@@PACKER_KEY_HASH@@"; - public static final String JAR_SHA_VAR = "@@JAR_SHA256@@"; + public static final String BUBBLE_VERSION_VAR = "@@BUBBLE_VERSION@@"; + public static final String TIMESTAMP_VAR = "@@TIMESTAMP@@"; public static final String PACKER_IMAGE_NAME_TEMPLATE = PACKER_IMAGE_PREFIX + INSTALL_TYPE_VAR + "_" + SAGE_HOST_VAR + "_" + PACKER_KEY_VAR + "_" + BUBBLE_VERSION_VAR - + "_" + JAR_SHA_VAR; + + "_" + TIMESTAMP_VAR; public static final String VARIABLES_VAR = "packerVariables"; public static final String BUILD_REGION_VAR = "buildRegion"; @@ -103,8 +102,6 @@ public class PackerJob implements Callable> { } public List _call() throws Exception { - final String jarSha = configuration.getJarSha(); - final ComputeConfig computeConfig = json(cloud.getDriverConfigJson(), ComputeConfig.class); final ComputeServiceDriver computeDriver = cloud.getComputeDriver(configuration); final PackerConfig packerConfig = computeConfig.getPacker(); @@ -146,7 +143,7 @@ public class PackerJob implements Callable> { copyScripts(bubbleFilesDir); // check to see if we have packer images for all regions - final List existingImages = computeDriver.getPackerImages(); + final List existingImages = computeDriver.getAllPackerImages(); if (!empty(existingImages)) { final List existingForInstallType = existingImages.stream() .filter(i -> i.getName().startsWith(PACKER_IMAGE_PREFIX+installType.name())) @@ -190,10 +187,11 @@ public class PackerJob implements Callable> { final String imageName = PACKER_IMAGE_NAME_TEMPLATE .replace(INSTALL_TYPE_VAR, installType.name()) - .replace(BUBBLE_VERSION_VAR, configuration.getVersion()) + .replace(SAGE_HOST_VAR, truncate(hostname(), 19)) .replace(PACKER_KEY_VAR, packerService.getPackerPublicKeyHash()) - .replace(SAGE_HOST_VAR, hostname()) - .replace(JAR_SHA_VAR, jarSha); + .replace(BUBBLE_VERSION_VAR, configuration.getVersion()) + .replace(TIMESTAMP_VAR, TimeUtil.format(now(), DATE_FORMAT_YYYYMMDDHHMMSS)); + if (imageName.length() > 128) return die("imageName.length > 128: "+imageName); // sanity check ctx.put(PACKER_IMAGE_NAME_VAR, imageName); final String packerConfigTemplate = stream2string(PACKER_TEMPLATE); @@ -204,6 +202,8 @@ public class PackerJob implements Callable> { if (packerConfig.iterateRegions()) { for (CloudRegion region : computeDriver.getRegions()) { ctx.put("region", region); + final Map perRegionCtx = computeDriver.getPackerRegionContext(region); + if (perRegionCtx != null) ctx.putAll(perRegionCtx); builderJsons.add(generateBuilder(packerConfig, ctx)); } } else { @@ -221,9 +221,10 @@ public class PackerJob implements Callable> { // run packer, return handle to running packer log.info("running packer for " + installType + "..."); + final int packerParallelBuilds = computeDriver.getPackerParallelBuilds(); final CommandResult commandResult = CommandShell.exec(new Command(new CommandLine(PACKER_BINARY) .addArgument("build") - .addArgument("-parallel-builds=2") + .addArgument("-parallel-builds="+packerParallelBuilds) .addArgument("-color=false") .addArgument("packer.json")) .setDir(tempDir) @@ -244,7 +245,7 @@ public class PackerJob implements Callable> { images.addAll(Arrays.stream(builds).map(b -> b.toPackerImage(imageName)).collect(Collectors.toList())); } else { - final List finalizedImages = computeDriver.finalizeIncompletePackerRun(commandResult, installType, jarSha); + final List finalizedImages = computeDriver.finalizeIncompletePackerRun(commandResult, installType); if (empty(finalizedImages)) { return die("Error executing packer: exit status " + commandResult.getExitStatus()); } diff --git a/bubble-server/src/main/resources/ansible/playbook.yml.hbs b/bubble-server/src/main/resources/ansible/playbook.yml.hbs index 03c92e07..34b9ade1 100644 --- a/bubble-server/src/main/resources/ansible/playbook.yml.hbs +++ b/bubble-server/src/main/resources/ansible/playbook.yml.hbs @@ -1,8 +1,8 @@ --- -- name: Create new bubble node +- name: Configure new bubble node hosts: bubble - remote_user: root + become: yes roles: {{#each roles}} - {{this}} {{/each}} diff --git a/bubble-server/src/main/resources/message_templates/en_US/server/pre_auth/ResourceMessages.properties b/bubble-server/src/main/resources/message_templates/en_US/server/pre_auth/ResourceMessages.properties index fae45a26..f44d2b39 100644 --- a/bubble-server/src/main/resources/message_templates/en_US/server/pre_auth/ResourceMessages.properties +++ b/bubble-server/src/main/resources/message_templates/en_US/server/pre_auth/ResourceMessages.properties @@ -400,6 +400,17 @@ driver_config_description_sizes_bubble.cloud.compute.digitalocean.DigitalOceanDr driver_config_config_bubble.cloud.compute.digitalocean.DigitalOceanDriver=Additional Config JSON driver_config_description_config_bubble.cloud.compute.digitalocean.DigitalOceanDriver=Array of configuration options in the form [ {name: "...", "value": ...} ] There must be one option named 'os' that contains the Operating System image to use when launching Bubbles. +driver_bubble.cloud.compute.ec2.AmazonEC2Driver=Amazon EC2 +description_bubble.cloud.compute.ec2.AmazonEC2Driver=Use the Elastic Compute Cloud (EC2) from Amazon AWS to launch new Bubbles +driver_credential_AWS_ACCESS_KEY_ID.bubble.cloud.compute.ec2.AmazonEC2Driver=Amazon Access Key +driver_credential_AWS_SECRET_KEY.bubble.cloud.compute.ec2.AmazonEC2Driver=Amazon Secret Key +driver_config_regions_bubble.cloud.compute.ec2.AmazonEC2Driver=Regions JSON +driver_config_description_regions_bubble.cloud.compute.ec2.AmazonEC2Driver=Array of bubble.cloud.CloudRegion objects. Determines which regions are supported, and defines latitude/longitude to enable geo-aware decisions. +driver_config_sizes_bubble.cloud.compute.ec2.AmazonEC2Driver=Instance Sizes JSON +driver_config_description_sizes_bubble.cloud.compute.ec2.AmazonEC2Driver=Array of bubble.cloud.compute.ComputeNodeSize objects. Determines which instance sizes are supported. +driver_config_config_bubble.cloud.compute.ec2.AmazonEC2Driver=Additional Config JSON +driver_config_description_config_bubble.cloud.compute.ec2.AmazonEC2Driver=Array of configuration options in the form [ {name: "...", "value": ...} ] There must be one option named 'os' that contains the Operating System image to use when launching Bubbles. + driver_bubble.cloud.dns.godaddy.GoDaddyDnsDriver=GoDaddy DNS description_bubble.cloud.dns.godaddy.GoDaddyDnsDriver=Use GoDaddy to manage DNS records for Bubbles driver_credential_GODADDY_API_KEY_bubble.cloud.dns.godaddy.GoDaddyDnsDriver=GoDaddy API Key diff --git a/bubble-server/src/main/resources/models/defaults/cloudService.json b/bubble-server/src/main/resources/models/defaults/cloudService.json index 616588bb..a61861b5 100644 --- a/bubble-server/src/main/resources/models/defaults/cloudService.json +++ b/bubble-server/src/main/resources/models/defaults/cloudService.json @@ -300,23 +300,90 @@ }, { - "name": "AmazonEC2Driver", + "name": "AmazonEC2Compute", "type": "compute", "driverClass": "bubble.cloud.compute.ec2.AmazonEC2Driver", "driverConfig": { "regions": [{ - "name": "US_WEST_2", "description": "US West (Oregon)", - "location": {"country": "US", "lat": "45.5272", "lon": "122.9361"} + "name": "Amazon - N. Virginia", "description": "US East 1 (N. Virginia)", "internalName": "us-east-1", + "location": {"city": "Arlington", "region": "VA", "country": "US", "lat": "38.880278", "lon": "-77.108333"} + }, { + "name": "Amazon - Ohio", "description": "US East 2 (Ohio)", "internalName": "us-east-2", + "location": {"region": "OH", "country": "US", "lat": "40.3416167", "lon": "-84.9180579"} + }, { + "name": "Amazon - N. California", "description": "US West 1 (N. California)", "internalName": "us-west-1", + "location": {"city": "San Jose", "country": "US", "region": "CA", "lat": "37.333333", "lon": "-121.9"} + }, { + "name": "Amazon - Oregon", "description": "US West 2 (Oregon)", "internalName": "us-west-2", + "location": {"city": "Hermiston", "region": "OR", "country": "US", "lat": "45.841111", "lon": "-119.291667"} + }, { + "name": "Amazon - Canada", "description": "Canada (Central)", "internalName": "ca-central-1", + "location": {"region": "QC", "country": "CA", "lat": "46.813889", "lon": "-71.208056"} + }, { + "name": "Amazon - Stockholm", "description": "EU (Stockholm)", "internalName": "eu-north-1", + "location": {"city": "Stockholm", "region": "Södermanland", "country": "SE", "lat": "59.329444", "lon": "18.068611"} + }, { + "name": "Amazon - Ireland", "description": "EU (Ireland)", "internalName": "eu-west-1", + "location": {"city": "Dublin", "region": "Leinster", "country": "IE", "lat": "53.35", "lon": "-6.266667"} + }, { + "name": "Amazon - London", "description": "EU (London)", "internalName": "eu-west-2", + "location": {"city": "London", "country": "GB", "region": "London", "lat": "51.507222", "lon": "-0.1275"} + }, { + "name": "Amazon - Milan", "description": "EU (Milan)", "internalName": "eu-west-3", + "location": {"city": "Milan", "country": "IT", "region": "Lombardy", "lat": "45.466944", "lon": "9.19"} + }, { + "name": "Amazon - Frankfurt", "description": "EU (Frankfurt)", "internalName": "eu-central-1", + "location": {"city": "Frankfurt", "country": "DE", "region": "Hesse", "lat": "50.116667", "lon": "8.683333"} + }, { + "name": "Amazon - Tokyo", "description": "Asia Pacific (Tokyo)", "internalName": "ap-northeast-1", + "location": {"city": "Tokyo", "country": "JP", "region": "Kantō", "lat": "35.689722", "lon": "139.692222"} + }, { + "name": "Amazon - Seoul", "description": "Asia Pacific (Seoul)", "internalName": "ap-northeast-2", + "location": {"city": "Seoul", "country": "KR", "region": "Sudogwon", "lat": "37.566667", "lon": "126.966667"} + }, { + "name": "Amazon - Singapore", "description": "Asia Pacific (Singapore)", "internalName": "ap-southeast-1", + "location": {"city": "Singapore", "country": "SG", "region": "Singapore", "lat": "1.283333", "lon": "103.833333"} + }, { + "name": "Amazon - Sydney", "description": "Asia Pacific (Sydney)", "internalName": "ap-southeast-2", + "location": {"city": "Sydney", "country": "AU", "region": "NSW", "lat": "-33.865", "lon": "151.209444"} + }, { + "name": "Amazon - Mumbai", "description": "Asia Pacific (Mumbai)", "internalName": "ap-south-1", + "location": {"city": "Mumbai", "country": "IN", "region": "Konkan", "lat": "18.975", "lon": "72.825833"} + }, { + "name": "Amazon - São Paulo", "description": "South America (São Paulo)", "internalName": "sa-east-1", + "location": {"city": "São Paulo", "country": "BR", "region": "São Paulo", "lat": "-23.55", "lon": "-46.633333"} }], "sizes": [ - {"name": "small", "type": "small", "internalName": "t2.micro", "vcpu": 1, "memoryMB": 1024, "ssdGB": 0}, - {"name": "medium", "type": "medium", "internalName": "t2.small", "vcpu": 1, "memoryMB": 2048, "ssdGB": 0}, - {"name": "large", "type": "large", "internalName": "t2.medium", "vcpu": 2, "memoryMB": 4096, "ssdGB": 80} + {"name": "small", "type": "small", "internalName": "t2.micro", "vcpu": 1, "memoryMB": 1024, "diskGB": 10, "diskType": "ebs_magnetic"}, + {"name": "medium", "type": "medium", "internalName": "t2.small", "vcpu": 1, "memoryMB": 2048, "diskGB": 20, "diskType": "ebs_magnetic"}, + {"name": "large", "type": "large", "internalName": "t2.medium", "vcpu": 2, "memoryMB": 4096, "diskGB": 40, "diskType": "ebs_magnetic"} ], + "os": "ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-????????", "config": [ - {"name": "imageId", "value": "{{AWS_EC2_IMAGE_ID}}"}, - {"name": "group", "value": "{{EC2_SECURITY_GROUP}}"} - ] + {"name": "securityGroup", "value": "{{AWS_EC2_SECURITY_GROUP}}"} + ], + "packer": { + "vars": [ + {"name": "AWS_ACCESS_KEY_ID", "value": "[[credentials.AWS_ACCESS_KEY_ID]]"}, + {"name": "AWS_SECRET_KEY", "value": "[[credentials.AWS_SECRET_KEY]]"} + ], + "iterateRegions": true, + "builder": { + "type": "amazon-ebs", + "name": "amazon-ebs-<>", + "access_key": "[[user `AWS_ACCESS_KEY_ID`]]", + "secret_key": "[[user `AWS_SECRET_KEY`]]", + "region": "<>", + "ami_name": "<>", + "ssh_username": "ubuntu", + "source_ami": "<>", + "instance_type": "<>", + "vpc_id": "<>", + "availability_zone": "<>", + "subnet_id": "<>", + "associate_public_ip_address": true + } + } }, "credentials": { "params": [ @@ -326,4 +393,4 @@ }, "template": true } -] \ No newline at end of file +] diff --git a/bubble-server/src/main/resources/packer/packer-playbook.yml.hbs b/bubble-server/src/main/resources/packer/packer-playbook.yml.hbs index 6a6d6ac5..99b2400c 100644 --- a/bubble-server/src/main/resources/packer/packer-playbook.yml.hbs +++ b/bubble-server/src/main/resources/packer/packer-playbook.yml.hbs @@ -1,8 +1,8 @@ --- -- name: Create new bubble sage node +- name: Create new bubble [[installType]] hosts: bubble - remote_user: root + become: yes vars: install_type: [[installType]] roles: diff --git a/bubble-server/src/main/resources/packer/packer.json.hbs b/bubble-server/src/main/resources/packer/packer.json.hbs index b42ecf07..bf9b66a5 100644 --- a/bubble-server/src/main/resources/packer/packer.json.hbs +++ b/bubble-server/src/main/resources/packer/packer.json.hbs @@ -12,9 +12,9 @@ "type": "shell", "inline": [ "sleep 30", - "sudo apt-get -y update", - "sudo apt-get -y upgrade", - "sudo apt-get -y install python3 python3-pip virtualenv", + "sudo bash -c 'DEBIAN_FRONTEND=noninteractive apt-get -y update'", + "sudo bash -c 'DEBIAN_FRONTEND=noninteractive apt-get -y upgrade'", + "sudo bash -c 'DEBIAN_FRONTEND=noninteractive apt-get -y install python3 python3-pip virtualenv'", "sudo pip3 install setuptools psycopg2-binary ansible" ] }, diff --git a/bubble-server/src/main/resources/packer/roles/algo/files/config.cfg.hbs b/bubble-server/src/main/resources/packer/roles/algo/files/config.cfg.hbs index 6ad35264..4592ebf6 100644 --- a/bubble-server/src/main/resources/packer/roles/algo/files/config.cfg.hbs +++ b/bubble-server/src/main/resources/packer/roles/algo/files/config.cfg.hbs @@ -26,7 +26,7 @@ strongswan_log_level: 2 # rightsourceip for ipsec # ipv4 -strongswan_network: 10.19.48.0/24 +strongswan_network: 172.16.0.0/16 # ipv6 strongswan_network_ipv6: 'fd9d:bc11:4020::/48' @@ -42,7 +42,7 @@ wireguard_port: 51820 wireguard_PersistentKeepalive: 0 # WireGuard network configuration -wireguard_network_ipv4: 10.19.49.0/24 +wireguard_network_ipv4: 172.16.1.0/16 wireguard_network_ipv6: fd9d:bc11:4021::/48 # Reduce the MTU of the VPN tunnel