@@ -0,0 +1,2 @@ | |||
[default] | |||
region = __REGION__ |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -106,11 +106,10 @@ | |||
<artifactId>jetty-proxy</artifactId> | |||
<version>${jetty.version}</version> | |||
</dependency> | |||
<!-- https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-ec2 --> | |||
<dependency> | |||
<groupId>com.amazonaws</groupId> | |||
<artifactId>aws-java-sdk-ec2</artifactId> | |||
<version>1.11.762</version> | |||
<version>1.11.797</version> | |||
</dependency> | |||
<!--<!– https://mvnrepository.com/artifact/org.atmosphere/atmosphere-jersey –>--> | |||
@@ -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<PackerImage> getPackerImages() { return Collections.emptyList(); } | |||
@Override public List<PackerImage> getAllPackerImages() { return null; } | |||
@Override public List<PackerImage> 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 + ")"); | |||
@@ -0,0 +1,7 @@ | |||
package bubble.cloud.compute; | |||
public enum ComputeDiskType { | |||
ssd, hdd, ebs_gp2, ebs_magnetic; | |||
} |
@@ -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; | |||
@@ -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<PackerImage> getPackerImages(); | |||
default List<PackerImage> finalizeIncompletePackerRun(CommandResult commandResult, AnsibleInstallType installType, String jarSha) { return null; } | |||
List<PackerImage> getAllPackerImages(); | |||
List<PackerImage> getPackerImagesForRegion(String region); | |||
default List<PackerImage> finalizeIncompletePackerRun(CommandResult commandResult, AnsibleInstallType installType) { return null; } | |||
default Map<String, Object> getPackerRegionContext(CloudRegion region) { return null; } | |||
default int getPackerParallelBuilds() { return 1; } | |||
} |
@@ -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<ComputeConfig> | |||
implements ComputeServiceDriver { | |||
public static final long PACKER_TIMEOUT = MINUTES.toMillis(60); | |||
private final AtomicReference<NodeReaper> 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<List<PackerImage>> 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<PackerImage> images = getPackerImagesForRegion(region); | |||
return images == null ? null : images.stream() | |||
.filter(i -> i.getName().contains("_"+installType.name()+"_")) | |||
.findFirst() | |||
.orElse(null); | |||
} | |||
} |
@@ -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; | |||
} |
@@ -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; | |||
@@ -6,19 +6,16 @@ public abstract class PackerImageParserBase extends ListResourceParser<PackerIma | |||
private String bubbleVersion; | |||
private String keyHash; | |||
private String jarSha; | |||
public PackerImageParserBase(String bubbleVersion, String keyHash, String jarSha) { | |||
public PackerImageParserBase(String bubbleVersion, String keyHash) { | |||
this.bubbleVersion = bubbleVersion; | |||
this.keyHash = keyHash; | |||
this.jarSha = jarSha; | |||
} | |||
public boolean isValidPackerImage(String name) { | |||
if (!name.startsWith(PACKER_IMAGE_PREFIX)) return false; | |||
if (!name.contains("_"+bubbleVersion+"_")) return false; | |||
if (!name.contains("_"+keyHash+"_")) return false; | |||
// if (!name.endsWith("_"+jarSha)) return false; | |||
return true; | |||
} | |||
@@ -75,6 +75,8 @@ public class DelegatedComputeDriver extends DelegatedCloudServiceDriverBase impl | |||
return notificationService.notifySync(delegate, compute_driver_status, notification(node)); | |||
} | |||
@Override public List<PackerImage> getPackerImages() { return notSupported("getPackerImages"); } | |||
@Override public List<PackerImage> getAllPackerImages() { return notSupported("getPackerImages"); } | |||
@Override public List<PackerImage> getPackerImagesForRegion(String region) { return notSupported("getPackerImagesForRegion"); } | |||
} |
@@ -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<Comput | |||
.setInternalName(item.get("slug").textValue()) | |||
.setVcpu(item.get("vcpus").intValue()) | |||
.setMemoryMB(item.get("memory").intValue()) | |||
.setSsdGB(item.get("disk").intValue()) | |||
.setDiskGB(item.get("disk").intValue()) | |||
.setDiskType(ComputeDiskType.ssd) | |||
.setTransferGB(1024 * item.get("transfer").intValue()); | |||
} | |||
@@ -170,14 +170,13 @@ public class DigitalOceanDriver extends ComputeServiceDriverBase { | |||
final ComputeNodeSize size = config.getSize(node.getSize()); | |||
// todo: lookup image based on node installType and region | |||
final String os = getOs().getName(); | |||
final PackerImage packerImage = getPackerImage(node); | |||
final CreateDropletRequest createRequest = new CreateDropletRequest() | |||
.setName(node.getFqdn()) | |||
.setRegion(region.getInternalName()) | |||
.setSize(size.getInternalName()) | |||
.setImage(os) | |||
.setImage(packerImage.getId()) | |||
.setIpv6(true) | |||
.setBackups(false) | |||
.setMonitoring(false) | |||
@@ -232,8 +231,11 @@ public class DigitalOceanDriver extends ComputeServiceDriverBase { | |||
return node.setState(found.get(0).getState()); | |||
} | |||
@Override public List<PackerImage> getPackerImages() { | |||
final List<PackerImage> images = getResources(PACKER_IMAGES_URI, new DigitalOceanPackerImageParser(configuration.getVersion(), packerService.getPackerPublicKeyHash(), configuration.getJarSha())); | |||
@Override public List<PackerImage> getAllPackerImages() { return getPackerImages(); } | |||
@Override public List<PackerImage> getPackerImagesForRegion(String region) { return getPackerImages(); } | |||
public List<PackerImage> getPackerImages () { | |||
final List<PackerImage> images = getResources(PACKER_IMAGES_URI, new DigitalOceanPackerImageParser(configuration.getVersion(), packerService.getPackerPublicKeyHash())); | |||
return images == null ? Collections.emptyList() : images; | |||
} | |||
@@ -13,7 +13,7 @@ public class DigitalOceanOsImageParser extends ListResourceParser<OsImage> { | |||
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<OsImage> { | |||
} | |||
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"); | |||
} | |||
@@ -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; } | |||
@@ -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<String, AmazonEC2> 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<CloudRegion> 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<String, AmazonEC2> ec2ClientMap = initClientMap(); | |||
private Map<String, AmazonEC2> initClientMap() { | |||
final Map<String, AmazonEC2> 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<ComputeNodeSize> getCloudSizes() { | |||
// todo | |||
return null; | |||
@Getter(lazy=true) private final List<CloudRegion> cloudRegions = driverConfig("regions"); | |||
@Getter(lazy=true) private final List<ComputeNodeSize> cloudSizes = driverConfig("sizes"); | |||
private <T> List<T> 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<OsImage> cloudOsImages = initImages(); | |||
private List<OsImage> initImages() { | |||
final ArrayList<Filter> 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<Future<?>> 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<Image> 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<Object> 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<OsImage> getCloudOsImages() { | |||
// todo | |||
return null; | |||
@Getter(lazy=true) private final Map<String, OsImage> imagesByRegion = getCloudOsImages().stream() | |||
.collect(Collectors.toMap(OsImage::getRegion, Function.identity())); | |||
public Map<String, Object> getPackerRegionContext(CloudRegion region) { | |||
final Map<String, Object> ctx = new HashMap<>(); | |||
final String internalName = region.getInternalName(); | |||
final Map<String, OsImage> 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<String, Vpc> 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<String, Map<String, Subnet>> subnetsByRegion = getSubnetsByRegion(); | |||
if (empty(subnetsByRegion)) return die("getPackerRegionContext: getSubnetsByRegion returned empty map"); | |||
final Map<String, Subnet> 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<String> 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<String, AmazonEC2> 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<PackerImage> getAllPackerImages() { | |||
final List<Future<?>> futures = new ArrayList<>(); | |||
for (CloudRegion region : getRegions()) { | |||
futures.add(getPerRegionExecutor().submit(() -> getPackerImagesForRegion(region.getInternalName()))); | |||
} | |||
ec2client = ec2ClientMap.get(regionName); | |||
return ec2client; | |||
final AwaitResult<Object> awaitResult = awaitAll(futures, PARALLEL_TIMEOUT); | |||
if (!awaitResult.allSucceeded()) return die("initImages: "+awaitResult.getFailures().values()); | |||
final List<PackerImage> images = new ArrayList<>(); | |||
for (Object o : awaitResult.getSuccesses().values()) images.addAll((List<PackerImage>) o); | |||
return images; | |||
} | |||
@Override public List<BubbleNode> listNodes() throws IOException { | |||
List<Future<?>> listNodeJobs = new ArrayList<>(); | |||
for (final String regionName : getEc2ClientMap().keySet()) { | |||
listNodeJobs.add(perRegionExecutor.submit(new listNodesHelper(regionName))); | |||
@Override public List<PackerImage> getPackerImagesForRegion(String region) { | |||
final ArrayList<Filter> 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<String, Vpc> vpcCache = new ExpirationMap<>(DAYS.toMillis(1)); | |||
public Map<String, Vpc> getVpcsByRegion() { | |||
final List<Future<?>> futures = new ArrayList<>(); | |||
final Map<String, Vpc> 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<Vpc> 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<BubbleNode> nodes = new ArrayList<>(); | |||
return vpcsByRegion; | |||
} | |||
nodes.addAll((Collection<? extends BubbleNode>) awaitResult.getSuccesses().values()); | |||
private final ExpirationMap<String, List<String>> azCache = new ExpirationMap<>(DAYS.toMillis(30)); | |||
private final ExpirationMap<String, Map<String, Subnet>> subnetCache = new ExpirationMap<>(DAYS.toMillis(1)); | |||
public Map<String, Map<String, Subnet>> getSubnetsByRegion() { | |||
final Map<String, Vpc> vpcsByRegion = getVpcsByRegion(); | |||
final List<Future<?>> futures = new ArrayList<>(); | |||
final Map<String, Map<String, Subnet>> subnetsByRegion = new ConcurrentHashMap<>(); | |||
for (CloudRegion region : getRegions()) { | |||
final String internalName = region.getInternalName(); | |||
final AmazonEC2 ec2 = getEc2Client(internalName); | |||
futures.add(getPerRegionExecutor().submit(() -> { | |||
final List<String> 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<String, Subnet> 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<RouteTableAssociation> 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<List<BubbleNode>> { | |||
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<BubbleNode> listNodes() throws IOException { | |||
final List<Future<?>> 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<BubbleNode> nodes = new ArrayList<>(); | |||
for (Object o : awaitResult.getSuccesses().values()) nodes.addAll((List<BubbleNode>) o); | |||
return nodes; | |||
} | |||
@AllArgsConstructor | |||
private class ListNodesHelper implements Callable<List<BubbleNode>> { | |||
private AmazonEC2 ec2; | |||
public List<BubbleNode> listNodesForSingleRegion(String regionName) { | |||
AmazonEC2 ec2Client = getEC2Client(regionName); | |||
@Override public List<BubbleNode> call() { | |||
final List<BubbleNode> 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<BubbleNode> 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<Tag> 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<PackerImage> getPackerImages() { return notSupported("getPackerImages"); } | |||
} |
@@ -27,6 +27,7 @@ public class LocalComputeDriver extends CloudServiceDriverBase<ComputeConfig> 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<PackerImage> getPackerImages() { return notSupported("getPackerImages"); } | |||
@Override public List<PackerImage> getAllPackerImages() { return notSupported("getPackerImages"); } | |||
@Override public List<PackerImage> getPackerImagesForRegion(String region) { return notSupported("getPackerImagesForRegion"); } | |||
} |
@@ -62,6 +62,7 @@ public class MockComputeDriver extends ComputeServiceDriverBase { | |||
return node; | |||
} | |||
@Override public List<PackerImage> getPackerImages() { return Collections.emptyList(); } | |||
@Override public List<PackerImage> getAllPackerImages() { return Collections.emptyList(); } | |||
@Override public List<PackerImage> getPackerImagesForRegion(String region) { return Collections.emptyList(); } | |||
} |
@@ -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<ComputeNodeSi | |||
.setInternalName(item.get("name").textValue()) | |||
.setVcpu(item.get("vcpu_count").asInt()) | |||
.setMemoryMB(item.get("ram").asInt()) | |||
.setSsdGB(item.get("disk").asInt()) | |||
.setDiskGB(item.get("disk").asInt()) | |||
.setDiskType(ComputeDiskType.ssd) | |||
.setTransferGB(item.get("bandwidth_gb").asInt()); | |||
} | |||
@@ -24,7 +24,6 @@ import java.util.ArrayList; | |||
import java.util.Collections; | |||
import java.util.Iterator; | |||
import java.util.List; | |||
import java.util.concurrent.atomic.AtomicReference; | |||
import java.util.function.Function; | |||
import static bubble.model.cloud.BubbleNode.TAG_INSTANCE_ID; | |||
@@ -56,8 +55,6 @@ public class VultrDriver extends ComputeServiceDriverBase { | |||
public static final String VULTR_V4_IP = "main_ip"; | |||
public static final String VULTR_V6_IP = "v6_main_ip"; | |||
public static final String CREATE_SSH_KEY_URL = VULTR_API_BASE + "sshkey/create"; | |||
public static final String DESTROY_SSH_KEY_URL = VULTR_API_BASE + "sshkey/destroy"; | |||
public static final String CREATE_SERVER_URL = VULTR_API_BASE + "server/create"; | |||
public static final String DESTROY_SERVER_URL = VULTR_API_BASE + "server/destroy"; | |||
public static final String LIST_SERVERS_URL = VULTR_API_BASE + "server/list"; | |||
@@ -73,7 +70,7 @@ public class VultrDriver extends ComputeServiceDriverBase { | |||
.filter(i -> 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<List<PackerImage>> 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<PackerImage> getPackerImages() { | |||
final List<PackerImage> images = loadCloudResources(SNAPSHOT_URL, new VultrPackerImageParser(configuration.getVersion(), packerService.getPackerPublicKeyHash(), configuration.getJarSha())); | |||
return images == null ? Collections.emptyList() : images; | |||
} | |||
@Override public List<PackerImage> getAllPackerImages() { return getPackerImages(); } | |||
@Override public List<PackerImage> getPackerImagesForRegion(String region) { return getPackerImages(); } | |||
public PackerImage getPackerImage(AnsibleInstallType installType) { | |||
final List<PackerImage> images = getPackerImages(); | |||
return images == null ? null : images.stream() | |||
.filter(i -> i.getName().contains("_"+installType.name()+"_")) | |||
.findFirst() | |||
.orElse(null); | |||
public List<PackerImage> getPackerImages () { | |||
final List<PackerImage> 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<PackerImage> finalizeIncompletePackerRun(CommandResult commandResult, AnsibleInstallType installType, String jarSha) { | |||
@Override public List<PackerImage> 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<BubbleNode> 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); | |||
@@ -12,8 +12,8 @@ public class VultrOsImageParser extends ListResourceParser<OsImage> { | |||
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()); | |||
} | |||
} |
@@ -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) { | |||
@@ -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; | |||
} | |||
} | |||
@@ -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); } | |||
} |
@@ -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}") | |||
@@ -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<List<PackerImage>> { | |||
@@ -54,15 +53,15 @@ public class PackerJob implements Callable<List<PackerImage>> { | |||
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<List<PackerImage>> { | |||
} | |||
public List<PackerImage> _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<List<PackerImage>> { | |||
copyScripts(bubbleFilesDir); | |||
// check to see if we have packer images for all regions | |||
final List<PackerImage> existingImages = computeDriver.getPackerImages(); | |||
final List<PackerImage> existingImages = computeDriver.getAllPackerImages(); | |||
if (!empty(existingImages)) { | |||
final List<PackerImage> existingForInstallType = existingImages.stream() | |||
.filter(i -> i.getName().startsWith(PACKER_IMAGE_PREFIX+installType.name())) | |||
@@ -190,10 +187,11 @@ public class PackerJob implements Callable<List<PackerImage>> { | |||
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<List<PackerImage>> { | |||
if (packerConfig.iterateRegions()) { | |||
for (CloudRegion region : computeDriver.getRegions()) { | |||
ctx.put("region", region); | |||
final Map<String, Object> 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<List<PackerImage>> { | |||
// 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<List<PackerImage>> { | |||
images.addAll(Arrays.stream(builds).map(b -> b.toPackerImage(imageName)).collect(Collectors.toList())); | |||
} else { | |||
final List<PackerImage> finalizedImages = computeDriver.finalizeIncompletePackerRun(commandResult, installType, jarSha); | |||
final List<PackerImage> finalizedImages = computeDriver.finalizeIncompletePackerRun(commandResult, installType); | |||
if (empty(finalizedImages)) { | |||
return die("Error executing packer: exit status " + commandResult.getExitStatus()); | |||
} | |||
@@ -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}} |
@@ -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 <a href="https://aws.amazon.com/ec2/">Elastic Compute Cloud (EC2)</a> 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 <a href="https://www.godaddy.com/">GoDaddy</a> to manage DNS records for Bubbles | |||
driver_credential_GODADDY_API_KEY_bubble.cloud.dns.godaddy.GoDaddyDnsDriver=GoDaddy API Key | |||
@@ -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-<<region.internalName>>", | |||
"access_key": "[[user `AWS_ACCESS_KEY_ID`]]", | |||
"secret_key": "[[user `AWS_SECRET_KEY`]]", | |||
"region": "<<region.internalName>>", | |||
"ami_name": "<<packerImageName>>", | |||
"ssh_username": "ubuntu", | |||
"source_ami": "<<imageForRegion.id>>", | |||
"instance_type": "<<sizes.small.internalName>>", | |||
"vpc_id": "<<vpcForRegion.vpcId>>", | |||
"availability_zone": "<<availabilityZoneForRegion>>", | |||
"subnet_id": "<<subnetForRegion.subnetId>>", | |||
"associate_public_ip_address": true | |||
} | |||
} | |||
}, | |||
"credentials": { | |||
"params": [ | |||
@@ -326,4 +393,4 @@ | |||
}, | |||
"template": true | |||
} | |||
] | |||
] |
@@ -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: | |||
@@ -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" | |||
] | |||
}, | |||
@@ -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 | |||