@@ -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> | <artifactId>jetty-proxy</artifactId> | ||||
<version>${jetty.version}</version> | <version>${jetty.version}</version> | ||||
</dependency> | </dependency> | ||||
<!-- https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-ec2 --> | |||||
<dependency> | <dependency> | ||||
<groupId>com.amazonaws</groupId> | <groupId>com.amazonaws</groupId> | ||||
<artifactId>aws-java-sdk-ec2</artifactId> | <artifactId>aws-java-sdk-ec2</artifactId> | ||||
<version>1.11.762</version> | |||||
<version>1.11.797</version> | |||||
</dependency> | </dependency> | ||||
<!--<!– https://mvnrepository.com/artifact/org.atmosphere/atmosphere-jersey –>--> | <!--<!– 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.IOException; | ||||
import java.io.InputStream; | import java.io.InputStream; | ||||
import java.util.Collection; | import java.util.Collection; | ||||
import java.util.Collections; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
@@ -69,7 +68,9 @@ public class NoopCloud implements | |||||
return false; | 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 { | @Override public boolean _write(String fromNode, String key, InputStream data, StorageMetadata metadata, String requestId) throws IOException { | ||||
if (log.isDebugEnabled()) log.debug("_write(fromNode=" + fromNode + ")"); | 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 String description; | ||||
@Getter @Setter private int vcpu; | @Getter @Setter private int vcpu; | ||||
@Getter @Setter private int memoryMB; | @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 networkMbps; | ||||
@Getter @Setter private Integer transferGB; | @Getter @Setter private Integer transferGB; | ||||
@@ -4,6 +4,7 @@ | |||||
*/ | */ | ||||
package bubble.cloud.compute; | package bubble.cloud.compute; | ||||
import bubble.cloud.CloudRegion; | |||||
import bubble.cloud.CloudServiceDriver; | import bubble.cloud.CloudServiceDriver; | ||||
import bubble.cloud.CloudServiceType; | import bubble.cloud.CloudServiceType; | ||||
import bubble.model.cloud.AnsibleInstallType; | import bubble.model.cloud.AnsibleInstallType; | ||||
@@ -35,7 +36,12 @@ public interface ComputeServiceDriver extends CloudServiceDriver, RegionalServic | |||||
@Override default boolean test () { return true; } | @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.CloudRegion; | ||||
import bubble.cloud.CloudServiceDriverBase; | import bubble.cloud.CloudServiceDriverBase; | ||||
import bubble.dao.cloud.BubbleNodeDAO; | import bubble.dao.cloud.BubbleNodeDAO; | ||||
import bubble.model.cloud.AnsibleInstallType; | |||||
import bubble.model.cloud.BubbleNode; | import bubble.model.cloud.BubbleNode; | ||||
import bubble.service.packer.PackerService; | import bubble.service.packer.PackerService; | ||||
import lombok.Getter; | import lombok.Getter; | ||||
@@ -18,13 +19,19 @@ import java.util.ArrayList; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.concurrent.atomic.AtomicReference; | 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.die; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.now; | |||||
import static org.cobbzilla.util.system.Sleep.sleep; | |||||
@Slf4j | @Slf4j | ||||
public abstract class ComputeServiceDriverBase | public abstract class ComputeServiceDriverBase | ||||
extends CloudServiceDriverBase<ComputeConfig> | extends CloudServiceDriverBase<ComputeConfig> | ||||
implements ComputeServiceDriver { | implements ComputeServiceDriver { | ||||
public static final long PACKER_TIMEOUT = MINUTES.toMillis(60); | |||||
private final AtomicReference<NodeReaper> reaper = new AtomicReference<>(); | private final AtomicReference<NodeReaper> reaper = new AtomicReference<>(); | ||||
@Override public void postSetup() { | @Override public void postSetup() { | ||||
@@ -83,7 +90,7 @@ public abstract class ComputeServiceDriverBase | |||||
} | } | ||||
@Getter(lazy=true) private final OsImage os = initOs(); | @Getter(lazy=true) private final OsImage os = initOs(); | ||||
private OsImage initOs() { | |||||
protected OsImage initOs() { | |||||
final OsImage os = getCloudOsImages().stream() | final OsImage os = getCloudOsImages().stream() | ||||
.filter(s -> s.getName().equals(config.getOs())) | .filter(s -> s.getName().equals(config.getOs())) | ||||
.findFirst() | .findFirst() | ||||
@@ -104,4 +111,32 @@ public abstract class ComputeServiceDriverBase | |||||
.findAny().orElse(null); | .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; | package bubble.cloud.compute; | ||||
import com.fasterxml.jackson.annotation.JsonIgnore; | |||||
import lombok.*; | import lombok.*; | ||||
import lombok.experimental.Accessors; | import lombok.experimental.Accessors; | ||||
@NoArgsConstructor @Accessors(chain=true) | @NoArgsConstructor @Accessors(chain=true) | ||||
public class OsImage { | public class OsImage { | ||||
@Getter @Setter private Long id; | |||||
@Getter @Setter private String id; | |||||
@Getter @Setter private String name; | @Getter @Setter private String name; | ||||
@Getter @Setter private String region; | |||||
} | } |
@@ -7,7 +7,7 @@ import lombok.NoArgsConstructor; | |||||
import lombok.Setter; | import lombok.Setter; | ||||
import lombok.experimental.Accessors; | import lombok.experimental.Accessors; | ||||
@NoArgsConstructor @Accessors(chain=true) @EqualsAndHashCode(of={"id"}) | |||||
@NoArgsConstructor @Accessors(chain=true) @EqualsAndHashCode(of={"id", "regions"}) | |||||
public class PackerImage { | public class PackerImage { | ||||
@Getter @Setter private String id; | @Getter @Setter private String id; | ||||
@@ -6,19 +6,16 @@ public abstract class PackerImageParserBase extends ListResourceParser<PackerIma | |||||
private String bubbleVersion; | private String bubbleVersion; | ||||
private String keyHash; | private String keyHash; | ||||
private String jarSha; | |||||
public PackerImageParserBase(String bubbleVersion, String keyHash, String jarSha) { | |||||
public PackerImageParserBase(String bubbleVersion, String keyHash) { | |||||
this.bubbleVersion = bubbleVersion; | this.bubbleVersion = bubbleVersion; | ||||
this.keyHash = keyHash; | this.keyHash = keyHash; | ||||
this.jarSha = jarSha; | |||||
} | } | ||||
public boolean isValidPackerImage(String name) { | public boolean isValidPackerImage(String name) { | ||||
if (!name.startsWith(PACKER_IMAGE_PREFIX)) return false; | if (!name.startsWith(PACKER_IMAGE_PREFIX)) return false; | ||||
if (!name.contains("_"+bubbleVersion+"_")) return false; | if (!name.contains("_"+bubbleVersion+"_")) return false; | ||||
if (!name.contains("_"+keyHash+"_")) return false; | if (!name.contains("_"+keyHash+"_")) return false; | ||||
// if (!name.endsWith("_"+jarSha)) return false; | |||||
return true; | return true; | ||||
} | } | ||||
@@ -75,6 +75,8 @@ public class DelegatedComputeDriver extends DelegatedCloudServiceDriverBase impl | |||||
return notificationService.notifySync(delegate, compute_driver_status, notification(node)); | 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; | package bubble.cloud.compute.digitalocean; | ||||
import bubble.cloud.compute.ComputeDiskType; | |||||
import bubble.cloud.compute.ComputeNodeSize; | import bubble.cloud.compute.ComputeNodeSize; | ||||
import bubble.cloud.compute.ListResourceParser; | import bubble.cloud.compute.ListResourceParser; | ||||
import com.fasterxml.jackson.databind.JsonNode; | import com.fasterxml.jackson.databind.JsonNode; | ||||
@@ -18,7 +19,8 @@ public class DigitalOceanComputeNodeSizeParser extends ListResourceParser<Comput | |||||
.setInternalName(item.get("slug").textValue()) | .setInternalName(item.get("slug").textValue()) | ||||
.setVcpu(item.get("vcpus").intValue()) | .setVcpu(item.get("vcpus").intValue()) | ||||
.setMemoryMB(item.get("memory").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()); | .setTransferGB(1024 * item.get("transfer").intValue()); | ||||
} | } | ||||
@@ -170,14 +170,13 @@ public class DigitalOceanDriver extends ComputeServiceDriverBase { | |||||
final ComputeNodeSize size = config.getSize(node.getSize()); | 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() | final CreateDropletRequest createRequest = new CreateDropletRequest() | ||||
.setName(node.getFqdn()) | .setName(node.getFqdn()) | ||||
.setRegion(region.getInternalName()) | .setRegion(region.getInternalName()) | ||||
.setSize(size.getInternalName()) | .setSize(size.getInternalName()) | ||||
.setImage(os) | |||||
.setImage(packerImage.getId()) | |||||
.setIpv6(true) | .setIpv6(true) | ||||
.setBackups(false) | .setBackups(false) | ||||
.setMonitoring(false) | .setMonitoring(false) | ||||
@@ -232,8 +231,11 @@ public class DigitalOceanDriver extends ComputeServiceDriverBase { | |||||
return node.setState(found.get(0).getState()); | 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; | return images == null ? Collections.emptyList() : images; | ||||
} | } | ||||
@@ -13,7 +13,7 @@ public class DigitalOceanOsImageParser extends ListResourceParser<OsImage> { | |||||
if (item.has("id")) { | if (item.has("id")) { | ||||
final JsonNode id = item.get("id"); | final JsonNode id = item.get("id"); | ||||
if (id.isNumber()) { | if (id.isNumber()) { | ||||
image.setId(id.numberValue().longValue()); | |||||
image.setId(id.asText()); | |||||
} else { | } else { | ||||
return die("parse: id was not numeric"); | return die("parse: id was not numeric"); | ||||
} | } | ||||
@@ -22,7 +22,7 @@ public class DigitalOceanOsImageParser extends ListResourceParser<OsImage> { | |||||
} | } | ||||
if (item.has("name")) { | if (item.has("name")) { | ||||
final JsonNode name = item.get("slug"); | final JsonNode name = item.get("slug"); | ||||
image.setName(name.textValue()); | |||||
image.setName(name.asText()); | |||||
} else { | } else { | ||||
return die("parse: name not found"); | return die("parse: name not found"); | ||||
} | } | ||||
@@ -10,8 +10,8 @@ import java.util.List; | |||||
public class DigitalOceanPackerImageParser extends PackerImageParserBase { | 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; } | @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.AmazonEC2; | ||||
import com.amazonaws.services.ec2.AmazonEC2ClientBuilder; | import com.amazonaws.services.ec2.AmazonEC2ClientBuilder; | ||||
import com.amazonaws.services.ec2.model.*; | import com.amazonaws.services.ec2.model.*; | ||||
import lombok.AllArgsConstructor; | |||||
import lombok.Getter; | import lombok.Getter; | ||||
import lombok.extern.slf4j.Slf4j; | 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.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.io.IOException; | ||||
import java.util.*; | 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_INSTANCE_ID; | ||||
import static bubble.model.cloud.BubbleNode.TAG_TEST; | 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.Await.awaitAll; | ||||
import static org.cobbzilla.util.daemon.DaemonThreadFactory.fixedPool; | import static org.cobbzilla.util.daemon.DaemonThreadFactory.fixedPool; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.*; | import static org.cobbzilla.util.daemon.ZillaRuntime.*; | ||||
import static org.cobbzilla.util.http.HttpStatusCodes.OK; | 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; | import static org.cobbzilla.wizard.resources.ResourceUtil.notFoundEx; | ||||
@Slf4j | @Slf4j | ||||
public class AmazonEC2Driver extends ComputeServiceDriverBase { | 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_CLOUD_UUID = "cloudUUID"; | ||||
public static final String TAG_NODE_UUID = "nodeUUID"; | public static final String TAG_NODE_UUID = "nodeUUID"; | ||||
public static final String KEY_NAME_PREFIX = "keyName_"; | 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()) { | 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 List<BubbleNode> nodes = new ArrayList<>(); | ||||
final DescribeInstancesRequest describeInstancesRequest = new DescribeInstancesRequest() | final DescribeInstancesRequest describeInstancesRequest = new DescribeInstancesRequest() | ||||
.withFilters( | .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("tag:" + TAG_CLOUD_UUID).withValues(cloud.getUuid()), | ||||
new Filter("instance-state-name").withValues("running", "pending") | 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) { | if (result.getSdkHttpMetadata().getHttpStatusCode() == OK) { | ||||
for (Reservation reservation : result.getReservations()) { | for (Reservation reservation : result.getReservations()) { | ||||
for (Instance instance : reservation.getInstances()) { | for (Instance instance : reservation.getInstances()) { | ||||
@@ -131,50 +413,38 @@ public class AmazonEC2Driver extends ComputeServiceDriverBase { | |||||
} | } | ||||
} | } | ||||
} else { | } else { | ||||
return die("listNodesForRegion: error describe EC2 instances for region " + regionName | |||||
return die("list: error describe EC2 instances: " | |||||
+ result.getSdkHttpMetadata().getHttpStatusCode() | + result.getSdkHttpMetadata().getHttpStatusCode() | ||||
+ ": " + result.getSdkHttpMetadata().getAllHttpHeaders()); | + ": " + result.getSdkHttpMetadata().getAllHttpHeaders()); | ||||
} | } | ||||
return nodes; | 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 { | @Override public BubbleNode start(BubbleNode node) throws Exception { | ||||
final ComputeNodeSize size = config.getSize(node.getSize()); | 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")) | final RunInstancesRequest runInstancesRequest = new RunInstancesRequest().withImageId(config.getConfig("imageId")) | ||||
.withInstanceType(size.getInternalName()) | .withInstanceType(size.getInternalName()) | ||||
.withMinCount(MIN_COUNT) | |||||
.withMaxCount(MAX_COUNT) | |||||
.withImageId(packerImage.getId()) | |||||
.withMinCount(1) | |||||
.withMaxCount(1) | |||||
.withKeyName(KEY_NAME_PREFIX+node.getUuid()) | .withKeyName(KEY_NAME_PREFIX+node.getUuid()) | ||||
.withBlockDeviceMappings(new BlockDeviceMapping().withEbs(ebs)) | |||||
.withNetworkInterfaces(new InstanceNetworkInterfaceSpecification() | .withNetworkInterfaces(new InstanceNetworkInterfaceSpecification() | ||||
.withAssociatePublicIpAddress(true) | .withAssociatePublicIpAddress(true) | ||||
.withDeviceIndex(0) | .withDeviceIndex(0) | ||||
.withSubnetId(subnetId) | |||||
.withGroups(config.getConfig("group"))); | |||||
.withIpv6AddressCount(1) | |||||
.withGroups(getSecurityGroup())); | |||||
final RunInstancesResult runInstancesResult = ec2Client.runInstances(runInstancesRequest); | 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(); | final String instanceId = runInstancesResult.getReservation().getInstances().get(0).getInstanceId(); | ||||
@@ -185,52 +455,47 @@ public class AmazonEC2Driver extends ComputeServiceDriverBase { | |||||
final DescribeInstancesResult result = ec2Client.describeInstances( | final DescribeInstancesResult result = ec2Client.describeInstances( | ||||
new DescribeInstancesRequest().withInstanceIds(instanceId)); | 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 | // 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()) { | if (configuration.testMode()) { | ||||
final String testTag = configuration.getEnvironment().get("TEST_TAG_CLOUD"); | 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"); | 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; | return node; | ||||
} | } | ||||
@@ -246,8 +511,7 @@ public class AmazonEC2Driver extends ComputeServiceDriverBase { | |||||
final StopInstancesRequest stopInstancesRequest = new StopInstancesRequest() | final StopInstancesRequest stopInstancesRequest = new StopInstancesRequest() | ||||
.withInstanceIds(instanceID); | .withInstanceIds(instanceID); | ||||
final AmazonEC2 ec2Client = getEC2Client(node.getRegion()); | |||||
final AmazonEC2 ec2Client = getEc2Client(node.getRegion()); | |||||
try { | try { | ||||
ec2Client.stopInstances(stopInstancesRequest); | ec2Client.stopInstances(stopInstancesRequest); | ||||
} catch (AmazonServiceException e) { | } catch (AmazonServiceException e) { | ||||
@@ -274,7 +538,4 @@ public class AmazonEC2Driver extends ComputeServiceDriverBase { | |||||
return node; | 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 stop(BubbleNode node) throws Exception { return notSupported("stop"); } | ||||
@Override public BubbleNode status(BubbleNode node) throws Exception { return notSupported("status"); } | @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; | 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; | package bubble.cloud.compute.vultr; | ||||
import bubble.cloud.compute.ComputeDiskType; | |||||
import bubble.cloud.compute.ComputeNodeSize; | import bubble.cloud.compute.ComputeNodeSize; | ||||
import bubble.cloud.compute.ListResourceParser; | import bubble.cloud.compute.ListResourceParser; | ||||
import com.fasterxml.jackson.databind.JsonNode; | import com.fasterxml.jackson.databind.JsonNode; | ||||
@@ -19,7 +20,8 @@ public class VultrComputeNodeSizeParser extends ListResourceParser<ComputeNodeSi | |||||
.setInternalName(item.get("name").textValue()) | .setInternalName(item.get("name").textValue()) | ||||
.setVcpu(item.get("vcpu_count").asInt()) | .setVcpu(item.get("vcpu_count").asInt()) | ||||
.setMemoryMB(item.get("ram").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()); | .setTransferGB(item.get("bandwidth_gb").asInt()); | ||||
} | } | ||||
@@ -24,7 +24,6 @@ import java.util.ArrayList; | |||||
import java.util.Collections; | import java.util.Collections; | ||||
import java.util.Iterator; | import java.util.Iterator; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.concurrent.atomic.AtomicReference; | |||||
import java.util.function.Function; | import java.util.function.Function; | ||||
import static bubble.model.cloud.BubbleNode.TAG_INSTANCE_ID; | 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_V4_IP = "main_ip"; | ||||
public static final String VULTR_V6_IP = "v6_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 CREATE_SERVER_URL = VULTR_API_BASE + "server/create"; | ||||
public static final String DESTROY_SERVER_URL = VULTR_API_BASE + "server/destroy"; | public static final String DESTROY_SERVER_URL = VULTR_API_BASE + "server/destroy"; | ||||
public static final String LIST_SERVERS_URL = VULTR_API_BASE + "server/list"; | 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")) | .filter(i -> i.getName().equals("Snapshot")) | ||||
.findFirst() | .findFirst() | ||||
.orElse(null); | .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); | public static final long SERVER_START_INITIAL_INTERVAL = SECONDS.toMillis(30); | ||||
@@ -108,8 +105,6 @@ public class VultrDriver extends ComputeServiceDriverBase { | |||||
super.postSetup(); | super.postSetup(); | ||||
} | } | ||||
public static final long PACKER_TIMEOUT = MINUTES.toMillis(60); | |||||
@Override public BubbleNode start(BubbleNode node) throws Exception { | @Override public BubbleNode start(BubbleNode node) throws Exception { | ||||
final CloudRegion region = config.getRegion(node.getRegion()); | final CloudRegion region = config.getRegion(node.getRegion()); | ||||
@@ -121,20 +116,7 @@ public class VultrDriver extends ComputeServiceDriverBase { | |||||
final Long planId = getSize(size.getType()).getId(); | final Long planId = getSize(size.getType()).getId(); | ||||
if (planId == null) return die("start: plan not found: "+size.getInternalName()); | 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 | // prepare to create server | ||||
final String data = "DCID=" + regionId + | 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); | 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") | if (!commandResult.getStdout().contains("Waiting 300s for snapshot") | ||||
|| !commandResult.getStdout().contains("Error waiting for snapshot") | || !commandResult.getStdout().contains("Error waiting for snapshot") | ||||
|| !commandResult.getStdout().contains("Unable to destroy server: Unable to remove VM: Server is currently locked")) { | || !commandResult.getStdout().contains("Unable to destroy server: Unable to remove VM: Server is currently locked")) { | ||||
stopImageServer(installType, jarSha); | |||||
stopImageServer(installType); | |||||
return null; | return null; | ||||
} | } | ||||
// wait longer for the snapshot... | // wait longer for the snapshot... | ||||
final String keyHash = packerService.getPackerPublicKeyHash(); | |||||
final long start = now(); | final long start = now(); | ||||
PackerImage snapshot = null; | PackerImage snapshot = null; | ||||
while (now() - start < SNAPSHOT_TIMEOUT) { | while (now() - start < SNAPSHOT_TIMEOUT) { | ||||
snapshot = getPackerImages().stream() | 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() | .findFirst() | ||||
.orElse(null); | .orElse(null); | ||||
if (snapshot != null) break; | 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) { | if (snapshot == null) { | ||||
log.error("finalizeIncompletePackerRun: timeout waiting for snapshot"); | log.error("finalizeIncompletePackerRun: timeout waiting for snapshot"); | ||||
} | } | ||||
if (!stopImageServer(installType, jarSha)) return null; | |||||
if (!stopImageServer(installType)) return null; | |||||
if (snapshot == null) return null; | if (snapshot == null) return null; | ||||
return new SingletonList<>(snapshot); | 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; | final List<BubbleNode> servers; | ||||
// find the server(s) | |||||
try { | try { | ||||
servers = listNodes(server -> { | servers = listNodes(server -> { | ||||
final String tag = server.has("tag") ? server.get("tag").textValue() : null; | 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) { | } catch (IOException e) { | ||||
log.error("finalizeIncompletePackerRun: error listing servers: "+shortError(e), 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("OSID")) return die("parse: OSID not found"); | ||||
if (!item.has("name")) return die("parse: name not found"); | if (!item.has("name")) return die("parse: name not found"); | ||||
return new OsImage() | 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 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) { | @Override public PackerImage parse(JsonNode item) { | ||||
@@ -15,23 +15,17 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||||
public class BubbleAwsCredentialsProvider implements AWSCredentialsProvider { | 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 CloudService cloud; | ||||
private final CloudCredentials awsCredentials; | private final CloudCredentials awsCredentials; | ||||
private String accessKeyParam = "AWS_ACCESS_KEY_ID"; | |||||
private String secretKeyParam = "AWS_SECRET_KEY"; | |||||
public BubbleAwsCredentialsProvider (CloudService cloud, CloudCredentials credentials) { | public BubbleAwsCredentialsProvider (CloudService cloud, CloudCredentials credentials) { | ||||
this.cloud = cloud; | this.cloud = cloud; | ||||
this.awsCredentials = credentials; | 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(); | @Getter(lazy=true) private final AWSCredentials credentials = new BubbleAwsCredentials(); | ||||
@Override public void refresh() {} | @Override public void refresh() {} | ||||
@@ -39,17 +33,17 @@ public class BubbleAwsCredentialsProvider implements AWSCredentialsProvider { | |||||
private class BubbleAwsCredentials implements AWSCredentials { | private class BubbleAwsCredentials implements AWSCredentials { | ||||
@Override public String getAWSAccessKeyId() { | @Override public String getAWSAccessKeyId() { | ||||
final String key = awsCredentials.getParam(accessKeyParam); | |||||
final String key = awsCredentials.getParam(AWS_ACCESS_KEY_ID); | |||||
return empty(key) | 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; | : key; | ||||
} | } | ||||
@Override public String getAWSSecretKey() { | @Override public String getAWSSecretKey() { | ||||
final String key = awsCredentials.getParam(secretKeyParam); | |||||
final String key = awsCredentials.getParam(AWS_SECRET_KEY); | |||||
return empty(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; | : key; | ||||
} | } | ||||
} | } | ||||
@@ -14,9 +14,9 @@ import org.kohsuke.args4j.Option; | |||||
import java.io.InputStream; | import java.io.InputStream; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.*; | 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.json.JsonUtil.*; | ||||
import static org.cobbzilla.util.string.StringUtil.UTF8cs; | |||||
@Slf4j | @Slf4j | ||||
public class BubbleHttpEntityOptions extends BubbleHttpOptions { | public class BubbleHttpEntityOptions extends BubbleHttpOptions { | ||||
@@ -57,6 +57,6 @@ public class BubbleHttpEntityOptions extends BubbleHttpOptions { | |||||
return APPLICATION_JSON; | 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, | public Response listImages(@Context Request req, | ||||
@Context ContainerRequest ctx) { | @Context ContainerRequest ctx) { | ||||
final ComputeServiceDriver driver = cloud.getComputeDriver(configuration); | final ComputeServiceDriver driver = cloud.getComputeDriver(configuration); | ||||
return ok(driver.getPackerImages()); | |||||
return ok(driver.getAllPackerImages()); | |||||
} | } | ||||
@PUT @Path("/{type}") | @PUT @Path("/{type}") | ||||
@@ -2,10 +2,7 @@ package bubble.service.packer; | |||||
import bubble.cloud.CloudRegion; | import bubble.cloud.CloudRegion; | ||||
import bubble.cloud.CloudRegionRelative; | 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.cloud.geoLocation.GeoLocation; | ||||
import bubble.dao.account.AccountDAO; | import bubble.dao.account.AccountDAO; | ||||
import bubble.model.account.Account; | 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.Command; | ||||
import org.cobbzilla.util.system.CommandResult; | import org.cobbzilla.util.system.CommandResult; | ||||
import org.cobbzilla.util.system.CommandShell; | import org.cobbzilla.util.system.CommandShell; | ||||
import org.cobbzilla.util.time.TimeUtil; | |||||
import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
import java.io.File; | import java.io.File; | ||||
@@ -36,14 +34,15 @@ import java.util.stream.Collectors; | |||||
import static bubble.ApiConstants.copyScripts; | import static bubble.ApiConstants.copyScripts; | ||||
import static bubble.model.cloud.RegionalServiceDriver.findClosestRegions; | import static bubble.model.cloud.RegionalServiceDriver.findClosestRegions; | ||||
import static bubble.service.packer.PackerService.*; | 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.FileUtil.*; | ||||
import static org.cobbzilla.util.io.StreamUtil.copyClasspathDirectory; | import static org.cobbzilla.util.io.StreamUtil.copyClasspathDirectory; | ||||
import static org.cobbzilla.util.io.StreamUtil.stream2string; | import static org.cobbzilla.util.io.StreamUtil.stream2string; | ||||
import static org.cobbzilla.util.json.JsonUtil.json; | import static org.cobbzilla.util.json.JsonUtil.json; | ||||
import static org.cobbzilla.util.network.NetworkUtil.getExternalIp; | 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.system.CommandShell.hostname; | ||||
import static org.cobbzilla.util.time.TimeUtil.DATE_FORMAT_YYYYMMDDHHMMSS; | |||||
@Slf4j | @Slf4j | ||||
public class PackerJob implements Callable<List<PackerImage>> { | 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 PACKER_IMAGE_PREFIX = "packer_bubble_"; | ||||
public static final String INSTALL_TYPE_VAR = "@@TYPE@@"; | 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 SAGE_HOST_VAR = "@@SAGE_HOST@@"; | ||||
public static final String PACKER_KEY_VAR = "@@PACKER_KEY_HASH@@"; | 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 | public static final String PACKER_IMAGE_NAME_TEMPLATE = PACKER_IMAGE_PREFIX + INSTALL_TYPE_VAR | ||||
+ "_" + SAGE_HOST_VAR | + "_" + SAGE_HOST_VAR | ||||
+ "_" + PACKER_KEY_VAR | + "_" + PACKER_KEY_VAR | ||||
+ "_" + BUBBLE_VERSION_VAR | + "_" + BUBBLE_VERSION_VAR | ||||
+ "_" + JAR_SHA_VAR; | |||||
+ "_" + TIMESTAMP_VAR; | |||||
public static final String VARIABLES_VAR = "packerVariables"; | public static final String VARIABLES_VAR = "packerVariables"; | ||||
public static final String BUILD_REGION_VAR = "buildRegion"; | 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 { | public List<PackerImage> _call() throws Exception { | ||||
final String jarSha = configuration.getJarSha(); | |||||
final ComputeConfig computeConfig = json(cloud.getDriverConfigJson(), ComputeConfig.class); | final ComputeConfig computeConfig = json(cloud.getDriverConfigJson(), ComputeConfig.class); | ||||
final ComputeServiceDriver computeDriver = cloud.getComputeDriver(configuration); | final ComputeServiceDriver computeDriver = cloud.getComputeDriver(configuration); | ||||
final PackerConfig packerConfig = computeConfig.getPacker(); | final PackerConfig packerConfig = computeConfig.getPacker(); | ||||
@@ -146,7 +143,7 @@ public class PackerJob implements Callable<List<PackerImage>> { | |||||
copyScripts(bubbleFilesDir); | copyScripts(bubbleFilesDir); | ||||
// check to see if we have packer images for all regions | // 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)) { | if (!empty(existingImages)) { | ||||
final List<PackerImage> existingForInstallType = existingImages.stream() | final List<PackerImage> existingForInstallType = existingImages.stream() | ||||
.filter(i -> i.getName().startsWith(PACKER_IMAGE_PREFIX+installType.name())) | .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 | final String imageName = PACKER_IMAGE_NAME_TEMPLATE | ||||
.replace(INSTALL_TYPE_VAR, installType.name()) | .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(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); | ctx.put(PACKER_IMAGE_NAME_VAR, imageName); | ||||
final String packerConfigTemplate = stream2string(PACKER_TEMPLATE); | final String packerConfigTemplate = stream2string(PACKER_TEMPLATE); | ||||
@@ -204,6 +202,8 @@ public class PackerJob implements Callable<List<PackerImage>> { | |||||
if (packerConfig.iterateRegions()) { | if (packerConfig.iterateRegions()) { | ||||
for (CloudRegion region : computeDriver.getRegions()) { | for (CloudRegion region : computeDriver.getRegions()) { | ||||
ctx.put("region", region); | ctx.put("region", region); | ||||
final Map<String, Object> perRegionCtx = computeDriver.getPackerRegionContext(region); | |||||
if (perRegionCtx != null) ctx.putAll(perRegionCtx); | |||||
builderJsons.add(generateBuilder(packerConfig, ctx)); | builderJsons.add(generateBuilder(packerConfig, ctx)); | ||||
} | } | ||||
} else { | } else { | ||||
@@ -221,9 +221,10 @@ public class PackerJob implements Callable<List<PackerImage>> { | |||||
// run packer, return handle to running packer | // run packer, return handle to running packer | ||||
log.info("running packer for " + installType + "..."); | log.info("running packer for " + installType + "..."); | ||||
final int packerParallelBuilds = computeDriver.getPackerParallelBuilds(); | |||||
final CommandResult commandResult = CommandShell.exec(new Command(new CommandLine(PACKER_BINARY) | final CommandResult commandResult = CommandShell.exec(new Command(new CommandLine(PACKER_BINARY) | ||||
.addArgument("build") | .addArgument("build") | ||||
.addArgument("-parallel-builds=2") | |||||
.addArgument("-parallel-builds="+packerParallelBuilds) | |||||
.addArgument("-color=false") | .addArgument("-color=false") | ||||
.addArgument("packer.json")) | .addArgument("packer.json")) | ||||
.setDir(tempDir) | .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())); | images.addAll(Arrays.stream(builds).map(b -> b.toPackerImage(imageName)).collect(Collectors.toList())); | ||||
} else { | } else { | ||||
final List<PackerImage> finalizedImages = computeDriver.finalizeIncompletePackerRun(commandResult, installType, jarSha); | |||||
final List<PackerImage> finalizedImages = computeDriver.finalizeIncompletePackerRun(commandResult, installType); | |||||
if (empty(finalizedImages)) { | if (empty(finalizedImages)) { | ||||
return die("Error executing packer: exit status " + commandResult.getExitStatus()); | 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 | hosts: bubble | ||||
remote_user: root | |||||
become: yes | |||||
roles: | roles: | ||||
{{#each roles}} - {{this}} | {{#each roles}} - {{this}} | ||||
{{/each}} | {{/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_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_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 | 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 | 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 | driver_credential_GODADDY_API_KEY_bubble.cloud.dns.godaddy.GoDaddyDnsDriver=GoDaddy API Key | ||||
@@ -300,23 +300,90 @@ | |||||
}, | }, | ||||
{ | { | ||||
"name": "AmazonEC2Driver", | |||||
"name": "AmazonEC2Compute", | |||||
"type": "compute", | "type": "compute", | ||||
"driverClass": "bubble.cloud.compute.ec2.AmazonEC2Driver", | "driverClass": "bubble.cloud.compute.ec2.AmazonEC2Driver", | ||||
"driverConfig": { | "driverConfig": { | ||||
"regions": [{ | "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": [ | "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": [ | "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": { | "credentials": { | ||||
"params": [ | "params": [ | ||||
@@ -326,4 +393,4 @@ | |||||
}, | }, | ||||
"template": true | "template": true | ||||
} | } | ||||
] | |||||
] |
@@ -1,8 +1,8 @@ | |||||
--- | --- | ||||
- name: Create new bubble sage node | |||||
- name: Create new bubble [[installType]] | |||||
hosts: bubble | hosts: bubble | ||||
remote_user: root | |||||
become: yes | |||||
vars: | vars: | ||||
install_type: [[installType]] | install_type: [[installType]] | ||||
roles: | roles: | ||||
@@ -12,9 +12,9 @@ | |||||
"type": "shell", | "type": "shell", | ||||
"inline": [ | "inline": [ | ||||
"sleep 30", | "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" | "sudo pip3 install setuptools psycopg2-binary ansible" | ||||
] | ] | ||||
}, | }, | ||||
@@ -26,7 +26,7 @@ strongswan_log_level: 2 | |||||
# rightsourceip for ipsec | # rightsourceip for ipsec | ||||
# ipv4 | # ipv4 | ||||
strongswan_network: 10.19.48.0/24 | |||||
strongswan_network: 172.16.0.0/16 | |||||
# ipv6 | # ipv6 | ||||
strongswan_network_ipv6: 'fd9d:bc11:4020::/48' | strongswan_network_ipv6: 'fd9d:bc11:4020::/48' | ||||
@@ -42,7 +42,7 @@ wireguard_port: 51820 | |||||
wireguard_PersistentKeepalive: 0 | wireguard_PersistentKeepalive: 0 | ||||
# WireGuard network configuration | # 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 | wireguard_network_ipv6: fd9d:bc11:4021::/48 | ||||
# Reduce the MTU of the VPN tunnel | # Reduce the MTU of the VPN tunnel | ||||