diff --git a/java/com.sap.sailing.landscape.test/src/com/sap/sailing/landscape/test/TestProcedures.java b/java/com.sap.sailing.landscape.test/src/com/sap/sailing/landscape/test/TestProcedures.java index f9a5a081901..200df973c60 100755 --- a/java/com.sap.sailing.landscape.test/src/com/sap/sailing/landscape/test/TestProcedures.java +++ b/java/com.sap.sailing.landscape.test/src/com/sap/sailing/landscape/test/TestProcedures.java @@ -24,6 +24,7 @@ import com.sap.sailing.landscape.SailingAnalyticsMetrics; import com.sap.sailing.landscape.SailingAnalyticsReplica; import com.sap.sailing.landscape.impl.BearerTokenReplicationCredentials; import com.sap.sailing.landscape.procedures.StartSailingAnalyticsMaster; +import com.sap.sailing.landscape.procedures.UpgradeAmi; import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; import com.sap.sse.landscape.InboundReplicationConfiguration; @@ -67,6 +68,20 @@ public class TestProcedures { securityServiceReplicationBearerToken = System.getProperty(SECURITY_SERVICE_REPLICATION_BEARER_TOKEN); } + @Test + public void testImageUpgrade() throws Exception { + final com.sap.sailing.landscape.procedures.UpgradeAmi.Builder, SailingAnalyticsReplica> imageUpgradeProcedureBuilder = UpgradeAmi.builder(); + final UpgradeAmi, SailingAnalyticsReplica> imageUpgradeProcedure = + imageUpgradeProcedureBuilder.build(); + try { + imageUpgradeProcedure.run(); + int continueHere; + // TODO add test verdicts + } finally { + imageUpgradeProcedure.getUpgradedAmi().delete(); + } + } + @Test public void testMongoReplica() throws Exception { final Builder startMongoDBServerProcedureBuilder = StartMongoDBServer.builder(); diff --git a/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/StartEmptyServer.java b/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/StartEmptyServer.java index 455ed978976..5b46065714c 100755 --- a/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/StartEmptyServer.java +++ b/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/StartEmptyServer.java @@ -21,7 +21,8 @@ import com.sap.sse.landscape.orchestration.Procedure; * @param * @param */ -public abstract class StartEmptyServer, +ShardingKey, MetricsT extends ApplicationProcessMetrics, MasterProcessT extends ApplicationMasterProcess, ReplicaProcessT extends ApplicationReplicaProcess, HostT extends AwsInstance> @@ -43,42 +44,40 @@ implements Procedure { * * @author Axel Uhl (D043530) */ - public static interface Builder, + ShardingKey, MetricsT extends ApplicationProcessMetrics, MasterProcessT extends ApplicationMasterProcess, ReplicaProcessT extends ApplicationReplicaProcess, HostT extends AwsInstance> - extends StartAwsHost.Builder, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> { - boolean isNoShutdown(); - - Builder setNoShutdown(boolean noShutdown); + extends StartAwsHost.Builder { + Builder setNoShutdown(boolean noShutdown); } - protected abstract static class BuilderImpl, + ShardingKey, MetricsT extends ApplicationProcessMetrics, MasterProcessT extends ApplicationMasterProcess, ReplicaProcessT extends ApplicationReplicaProcess, HostT extends AwsInstance> - extends StartAwsHost.BuilderImpl, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> - implements Builder { + extends StartAwsHost.BuilderImpl + implements Builder { private boolean noShutdown = true; - @Override - public boolean isNoShutdown() { + protected boolean isNoShutdown() { return noShutdown; } @Override - public Builder setNoShutdown(boolean noShutdown) { + public Builder setNoShutdown(boolean noShutdown) { this.noShutdown = noShutdown; return this; } - @Override - public String getInstanceName() { + protected String getInstanceName() { return super.getInstanceName() == null ? IMAGE_UPGRADE_USER_DATA+" for "+getMachineImage().getId() : super.getInstanceName(); } } - public StartEmptyServer(Builder builder) { + public StartEmptyServer(BuilderImpl builder) { super(builder); addUserData(Collections.singleton(IMAGE_UPGRADE_USER_DATA)); if (builder.isNoShutdown()) { diff --git a/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/StartMultiServer.java b/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/StartMultiServer.java index 5f34a9d2c48..272248d0f89 100755 --- a/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/StartMultiServer.java +++ b/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/StartMultiServer.java @@ -6,12 +6,11 @@ import java.util.logging.Logger; import com.sap.sailing.landscape.SailingAnalyticsHost; import com.sap.sailing.landscape.SailingAnalyticsMetrics; -import com.sap.sailing.landscape.impl.SailingAnalyticsHostImpl; import com.sap.sse.common.Duration; import com.sap.sse.landscape.application.ApplicationMasterProcess; -import com.sap.sse.landscape.application.ApplicationProcessMetrics; import com.sap.sse.landscape.application.ApplicationReplicaProcess; -import com.sap.sse.landscape.aws.AwsInstance; +import com.sap.sse.landscape.aws.AwsAvailabilityZone; +import com.sap.sse.landscape.aws.AwsLandscape; import com.sap.sse.landscape.aws.HostSupplier; import com.sap.sse.landscape.ssh.SshCommandChannel; @@ -30,13 +29,12 @@ import software.amazon.awssdk.services.ec2.model.InstanceType; * @author Axel Uhl (D043530) * * @param - * @param + * @param > */ -public class StartMultiServer, -ReplicaProcessT extends ApplicationReplicaProcess, -HostT extends AwsInstance> -extends StartEmptyServer { +public class StartMultiServer, +ReplicaProcessT extends ApplicationReplicaProcess> +extends StartEmptyServer, ShardingKey, SailingAnalyticsMetrics, MasterProcessT, ReplicaProcessT, SailingAnalyticsHost> { private static final Logger logger = Logger.getLogger(StartMultiServer.class.getName()); private Optional optionalTimeout; @@ -46,41 +44,48 @@ extends StartEmptyServer, - ReplicaProcessT extends ApplicationReplicaProcess, - HostT extends AwsInstance> - extends StartEmptyServer.Builder { - @Override - default boolean isNoShutdown() { - return true; - } + public static interface Builder, + ReplicaProcessT extends ApplicationReplicaProcess> + extends StartEmptyServer.Builder, ShardingKey, SailingAnalyticsMetrics, MasterProcessT, ReplicaProcessT, SailingAnalyticsHost> { } protected static class BuilderImpl, ReplicaProcessT extends ApplicationReplicaProcess> - extends StartEmptyServer.BuilderImpl> - implements Builder> { + extends StartEmptyServer.BuilderImpl, + ShardingKey, SailingAnalyticsMetrics, MasterProcessT, ReplicaProcessT, SailingAnalyticsHost> + implements Builder { @Override - public StartMultiServer> build() { + public StartMultiServer build() { return new StartMultiServer<>(this); } + @Override + protected boolean isNoShutdown() { + return true; + } + @Override public HostSupplier> getHostSupplier() { - return SailingAnalyticsHostImpl::new; + return new HostSupplier>() { + @Override + public SailingAnalyticsHost supply(String instanceId, AwsAvailabilityZone az, + AwsLandscape landscape) { + return null; + } + }; } } public static , - ReplicaProcessT extends ApplicationReplicaProcess, - HostT extends AwsInstance> Builder> builder() { + ReplicaProcessT extends ApplicationReplicaProcess> + Builder builder() { return new BuilderImpl<>(); } - protected StartMultiServer(Builder builder) { + protected StartMultiServer(BuilderImpl builder) { super(builder); this.optionalTimeout = builder.getOptionalTimeout(); } diff --git a/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/StartSailingAnalyticsHost.java b/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/StartSailingAnalyticsHost.java index aa804097db8..b8270097cba 100755 --- a/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/StartSailingAnalyticsHost.java +++ b/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/StartSailingAnalyticsHost.java @@ -147,7 +147,7 @@ implements Procedure, ShardingKey, ProcessT> builder) { + protected StartSailingAnalyticsHost(BuilderImpl, ShardingKey, ProcessT> builder) { super(builder); // remember the port we need in order to hand out the process this.port = builder.getPort(); diff --git a/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/StartSailingAnalyticsMaster.java b/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/StartSailingAnalyticsMaster.java index d0911f7f5c8..582d22ccbe8 100755 --- a/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/StartSailingAnalyticsMaster.java +++ b/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/StartSailingAnalyticsMaster.java @@ -46,7 +46,7 @@ public class StartSailingAnalyticsMaster return new BuilderImpl<>(); } - protected StartSailingAnalyticsMaster(Builder builder) { + protected StartSailingAnalyticsMaster(BuilderImpl builder) { super(builder); addUserData(ProcessConfigurationVariable.USE_ENVIRONMENT, "live-master-server"); // TODO maybe this should be handled by this procedure adding the correct defaults, e.g., for replicating security/sharedsailing? } diff --git a/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/StartSailingAnalyticsReplica.java b/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/StartSailingAnalyticsReplica.java index 16a9540829a..75f5e11a13e 100755 --- a/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/StartSailingAnalyticsReplica.java +++ b/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/StartSailingAnalyticsReplica.java @@ -47,7 +47,7 @@ public class StartSailingAnalyticsReplica return new BuilderImpl<>(); } - protected StartSailingAnalyticsReplica(Builder builder) { + protected StartSailingAnalyticsReplica(BuilderImpl builder) { super(builder); addUserData(ProcessConfigurationVariable.USE_ENVIRONMENT, "live-replica-server"); // TODO maybe this should be handled by this procedure adding the correct defaults, e.g., for replicating security/sharedsailing? } diff --git a/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/UpgradeAmi.java b/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/UpgradeAmi.java index 835a3690a9f..55142c03319 100755 --- a/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/UpgradeAmi.java +++ b/java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/UpgradeAmi.java @@ -1,22 +1,29 @@ package com.sap.sailing.landscape.procedures; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.sap.sailing.landscape.SailingAnalyticsHost; import com.sap.sailing.landscape.SailingAnalyticsMetrics; import com.sap.sailing.landscape.impl.SailingAnalyticsHostImpl; -import com.sap.sailing.landscape.procedures.UpgradeAmi.Builder; -import com.sap.sailing.landscape.procedures.UpgradeAmi.Builder.VersionPart; -import com.sap.sse.landscape.MachineImage; +import com.sap.sse.common.Duration; +import com.sap.sse.common.TimePoint; import com.sap.sse.landscape.application.ApplicationMasterProcess; import com.sap.sse.landscape.application.ApplicationReplicaProcess; +import com.sap.sse.landscape.aws.AmazonMachineImage; import com.sap.sse.landscape.aws.AwsInstance; import com.sap.sse.landscape.aws.HostSupplier; import com.sap.sse.landscape.aws.orchestration.StartAwsHost; import com.sap.sse.landscape.orchestration.Procedure; +import software.amazon.awssdk.services.ec2.model.BlockDeviceMapping; +import software.amazon.awssdk.services.ec2.model.Instance; +import software.amazon.awssdk.services.ec2.model.InstanceStateName; + /** * Upgrades an existing Amazon Machine Image that is expected to be prepared for such an upgrade, by * invoking it with very specific user data that trigger the automatic upgrade. The resulting AMI can @@ -41,14 +48,18 @@ import com.sap.sse.landscape.orchestration.Procedure; public class UpgradeAmi, ReplicaProcessT extends ApplicationReplicaProcess> -extends StartEmptyServer> +extends StartEmptyServer, ShardingKey, SailingAnalyticsMetrics, MasterProcessT, ReplicaProcessT, SailingAnalyticsHost> implements Procedure { + private static final Logger logger = Logger.getLogger(UpgradeAmi.class.getName()); private static final String IMAGE_UPGRADE_USER_DATA = "image-upgrade"; private static final String NO_SHUTDOWN_USER_DATA = "no-shutdown"; private static final Pattern imageNamePattern = Pattern.compile("^(.*) ([0-9]+)\\.([0-9]+)(\\.([0-9]+))?$"); private final String upgradedImageName; - private MachineImage upgradedAmi; + private final Duration timeout; + private final boolean waitForShutdown; // no need to wait if no shutdown was requested + private final Map deviceNamesToSnapshotBaseNames; + private AmazonMachineImage upgradedAmi; /** * Additional default rules in addition to what the {@link StartAwsHost.Builder parent builder} defines: @@ -71,8 +82,7 @@ implements Procedure, ReplicaProcessT extends ApplicationReplicaProcess> - extends - StartEmptyServer.Builder> { + extends StartEmptyServer.Builder, ShardingKey, SailingAnalyticsMetrics, MasterProcessT, ReplicaProcessT, SailingAnalyticsHost> { enum VersionPart { MAJOR, MINOR, MICRO } @@ -80,18 +90,35 @@ implements Procedure setUpgradedImageName(String upgradedImageName); Builder setVersionPartToIncrement(VersionPart versionPartToIncrement); + + /** + * An optional timeout when waiting for the upgraded instance to shut down for image creation. + */ + Builder setTimeout(Duration timeout); + + /** + * It is possible to assign base names for snapshots based on their device name in the AMI. For example, "/dev/sdc" may + * be the "Swap" device, and "/dev/xvda" may be the "System" partition. The full name for the snapshot is then assembled from + * the AMI's name including its version and this base name. If no such basename is provided for a device name for which + * a block device mapping to a snapshot exists, the snapshot will only be named after the AMI's name, so when multiple snapshots + * are connected to the AMI then their names will not be discernible. + */ + Builder setSnapshotBaseName(String deviceName, String snapshotBaseName); } protected static class BuilderImpl, ReplicaProcessT extends ApplicationReplicaProcess> - extends StartEmptyServer.BuilderImpl> + extends StartEmptyServer.BuilderImpl, ShardingKey, SailingAnalyticsMetrics, MasterProcessT, ReplicaProcessT, SailingAnalyticsHost> implements Builder { private String upgradedImageName; private VersionPart versionPartToIncrement; + private Duration timeout; + private final Map deviceNamesToSnapshotBaseNames; private BuilderImpl() { super(); + deviceNamesToSnapshotBaseNames = new HashMap<>(); setNoShutdown(false); } @@ -101,22 +128,36 @@ implements Procedure setTimeout(Duration timeout) { + this.timeout = timeout; + return this; + } + private String increaseVersionNumber(String imageName) { final String result; final Matcher versionNumberMatcher = imageNamePattern.matcher(imageName); if (versionNumberMatcher.matches()) { final String imageBaseName = versionNumberMatcher.group(1); - final String majorVersion = versionNumberMatcher.group(2); - final String minorVersion = versionNumberMatcher.group(3); - final String microVersion = versionNumberMatcher.group(5); + final Integer oldMajorVersion = Integer.valueOf(versionNumberMatcher.group(2)); + final Integer oldMinorVersion = Integer.valueOf(versionNumberMatcher.group(3)); + final Integer oldMicroVersion = versionNumberMatcher.group(5) == null ? null : Integer.valueOf(versionNumberMatcher.group(5)); + final VersionPart partToEffectivelyIncrement = versionPartToIncrement == null + ? oldMicroVersion == null ? VersionPart.MINOR : VersionPart.MICRO + : versionPartToIncrement; + final Integer newMajorVersion = partToEffectivelyIncrement == VersionPart.MAJOR ? oldMajorVersion + 1 : oldMajorVersion; + final Integer newMinorVersion = partToEffectivelyIncrement == VersionPart.MINOR ? oldMinorVersion + 1 : oldMinorVersion; + final Integer newMicroVersion = oldMajorVersion == null ? + partToEffectivelyIncrement == VersionPart.MICRO ? 0 : null : + partToEffectivelyIncrement == VersionPart.MICRO ? oldMajorVersion + 1 : oldMajorVersion; final StringBuilder sb = new StringBuilder(imageBaseName); sb.append(' '); - sb.append(majorVersion); + sb.append(newMajorVersion); sb.append('.'); - sb.append(minorVersion); - if (microVersion != null || versionPartToIncrement == VersionPart.MICRO) { + sb.append(newMinorVersion); + if (newMicroVersion != null) { sb.append('.'); - sb.append(microVersion); + sb.append(newMicroVersion); } result = sb.toString(); } else { @@ -131,6 +172,12 @@ implements Procedure setSnapshotBaseName(String deviceName, String snapshotBaseName) { + deviceNamesToSnapshotBaseNames.put(deviceName, snapshotBaseName); + return this; + } + @Override public HostSupplier> getHostSupplier() { return SailingAnalyticsHostImpl::new; @@ -139,6 +186,17 @@ implements Procedure getDeviceNamesToSnapshotBaseNames() { + return Collections.unmodifiableMap(deviceNamesToSnapshotBaseNames); + } @Override public UpgradeAmi build() { @@ -159,6 +217,9 @@ implements Procedure builder) { super(builder); upgradedImageName = builder.getUpgradedImageName(); + timeout = builder.getTimeout(); + waitForShutdown = !builder.isNoShutdown(); + deviceNamesToSnapshotBaseNames = builder.getDeviceNamesToSnapshotBaseNames(); addUserData(Collections.singleton(IMAGE_UPGRADE_USER_DATA)); if (builder.isNoShutdown()) { addUserData(Collections.singleton(NO_SHUTDOWN_USER_DATA)); @@ -168,16 +229,45 @@ implements Procedure getUpgradedAmi() { return upgradedAmi; } } diff --git a/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/AwsLandscape.java b/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/AwsLandscape.java index 8f583b3dcd4..3149e2a84ee 100755 --- a/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/AwsLandscape.java +++ b/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/AwsLandscape.java @@ -122,6 +122,10 @@ extends Landscape { AmazonMachineImage getLatestImageWithTag(Region region, String tagName, String tagValue); + void setSnapshotName(Region region, String snapshotId, String snapshotName); + + void deleteSnapshot(Region region, String snapshotId); + /** * Finds EC2 instances in the {@code region} that have a tag named {@code tagName} with value {@code tagValue}. */ diff --git a/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/impl/AmazonMachineImageImpl.java b/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/impl/AmazonMachineImageImpl.java index 4c19115035e..be1ac984703 100755 --- a/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/impl/AmazonMachineImageImpl.java +++ b/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/impl/AmazonMachineImageImpl.java @@ -61,6 +61,12 @@ public class AmazonMachineImageImpl createImage(AwsInstance instance, String imageName) { + logger.info("Creating Amazon Machine Image (AMI) named "+imageName+" for instance "+instance.getInstanceId()); final Ec2Client client = getEc2Client(getRegion(instance.getRegion())); final String imageId = client.createImage(b->b .instanceId(instance.getInstanceId()) @@ -469,6 +470,16 @@ ReplicaProcessT extends ApplicationReplicaProcess(response.images().stream().max(getMachineImageCreationDateComparator()).get(), region); } + @Override + public void setSnapshotName(com.sap.sse.landscape.Region region, String snapshotId, String snapshotName) { + getEc2Client(getRegion(region)).createTags(b->b.tags(Tag.builder().key("Name").value(snapshotName).build())); + } + + @Override + public void deleteSnapshot(com.sap.sse.landscape.Region region, String snapshotId) { + getEc2Client(getRegion(region)).deleteSnapshot(b->b.snapshotId(snapshotId)); + } + @Override public Iterable> getHostsWithTagValue(com.sap.sse.landscape.Region region, String tagName, String tagValue) { diff --git a/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/orchestration/StartAwsHost.java b/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/orchestration/StartAwsHost.java index bdea50c0d01..e9eccfcd053 100755 --- a/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/orchestration/StartAwsHost.java +++ b/java/com.sap.sse.landscape.aws/src/com/sap/sse/landscape/aws/orchestration/StartAwsHost.java @@ -48,17 +48,7 @@ public abstract class StartAwsHost, HostT extends AwsInstance> extends StartHost { - /** - * The {@link AwsLandscape#getLatestImageWithTag(com.sap.sse.landscape.Region, String, String)} method is - * used to obtain default images for specific AWS host starting procedures that subclass this class. The - * Amazon Machine Images (AMIs) for this are then expected to be tagged with a tag named as specified by this - * constant ("image-type"). The tag value then must match what the subclass wants. - * - * @see #getLatestImageOfType(String) - */ - private final static String IMAGE_TYPE_TAG_NAME = "image-type"; - - private static final String NAME_TAG_NAME = "Name"; + protected static final String NAME_TAG_NAME = "Name"; private final List userData; private final InstanceType instanceType; @@ -68,7 +58,6 @@ extends StartHost private final Optional tags; private final HostSupplier hostSupplier; private HostT host; - /** * A builder that helps building an instance of type {@link StartAwsHost} or any subclass thereof (then using @@ -110,110 +99,58 @@ extends StartHost ReplicaProcessT extends ApplicationReplicaProcess, HostT extends AwsInstance> extends StartHost.Builder { - @Override - AmazonMachineImage getMachineImage(); - Builder setMachineImage(AmazonMachineImage machineImage); - /** - * When not {@code null}, the newest {@link AmazonMachineImage} tagged with a tag named as specified by the constant {@link #IMAGE_TYPE_TAG_NAME} - * with the value provided by the result of this method will be searched and will be used as the default for {@link #getMachineImage()}. - */ - String getImageType(); - - Builder setImageType(String imageType); + Builder setImageType(String imageType); - /** - * By default, the release pre-deployed in the image will be used, represented by an empty {@link Optional} - * returned by this default method implementation. - */ - Optional getRelease(); - Builder setRelease(Optional release); - @Override - AwsLandscape getLandscape(); - Builder setLandscape(AwsLandscape landscape); - InstanceType getInstanceType(); - Builder setInstanceType(InstanceType instanceType); - - AwsAvailabilityZone getAvailabilityZone(); Builder setAvailabilityZone(AwsAvailabilityZone availabilityZone); - String getKeyName(); - Builder setKeyName(String keyName); - Iterable getSecurityGroups(); - Builder setSecurityGroups(Iterable securityGroups); - - Optional getTags(); Builder setTags(Optional tags); - Iterable getUserData(); - Builder setUserData(String[] userData); - AwsRegion getRegion(); - Builder setRegion(AwsRegion region); - String getInstanceName(); - Builder setInstanceName(String name); - String getServerName(); - Builder setServerName(String serverName); - - String getDatabaseName(); Builder setDatabaseName(String databaseName); - Optional getInboundReplicationConfiguration(); - Builder setReplicationConfiguration( InboundReplicationConfiguration replicationConfiguration); Builder setOutboundReplicationConfiguration(OutboundReplicationConfiguration outboundReplicationConfiguration); - RabbitMQEndpoint getRabbitConfiguration(); - Builder setRabbitConfiguration(RabbitMQEndpoint rabbitConfiguration); - Database getDatabaseConfiguration(); - Builder setDatabaseConfiguration(Database databaseConfiguration); - String getCommaSeparatedEmailAddressesToNotifyOfStartup(); - Builder setCommaSeparatedEmailAddressesToNotifyOfStartup( String commaSeparatedEmailAddressesToNotifyOfStartup); - /** - * A timeout for interacting with the instance, such as when creating an SSH / SFTP connection or waiting for its - * public IP address. - */ - Optional getOptionalTimeout(); - Builder setOptionalTimeout(Optional optionalTimeout); - HostSupplier getHostSupplier(); - - OutboundReplicationConfiguration getOutboundReplicationConfiguration(); + Builder setHostSupplier(HostSupplier hostSupplier); } protected abstract static class BuilderImpl, ShardingKey, MetricsT extends ApplicationProcessMetrics, MasterProcessT extends ApplicationMasterProcess, ReplicaProcessT extends ApplicationReplicaProcess, - HostT extends AwsInstance> implements Builder { + HostT extends AwsInstance> + extends StartHost.BuilderImpl + implements Builder { private AmazonMachineImage machineImage; private String imageType; private Optional release = Optional.empty(); @@ -234,10 +171,10 @@ extends StartHost private OutboundReplicationConfiguration outboundReplicationConfiguration; private String commaSeparatedEmailAddressesToNotifyOfStartup; private Optional optionalTimeout; + private HostSupplier hostSupplier; - @Override - public AmazonMachineImage getMachineImage() { - return machineImage == null ? getLatestImageOfType(getImageType(), getLandscape(), getRegion()) : machineImage; + protected AmazonMachineImage getMachineImage() { + return machineImage == null ? getLandscape().getLatestImageWithTag((Region) getRegion(), IMAGE_TYPE_TAG_NAME, getImageType()) : machineImage; } @Override @@ -247,8 +184,11 @@ extends StartHost return this; } - @Override - public String getImageType() { + /** + * When not {@code null}, the newest {@link AmazonMachineImage} tagged with a tag named as specified by the constant {@link #IMAGE_TYPE_TAG_NAME} + * with the value provided by the result of this method will be searched and will be used as the default for {@link #getMachineImage()}. + */ + protected String getImageType() { return imageType; } @@ -259,8 +199,11 @@ extends StartHost return this; } - @Override - public Optional getRelease() { + /** + * By default, the release pre-deployed in the image will be used, represented by an empty {@link Optional} + * returned by this default method implementation. + */ + protected Optional getRelease() { return release; } @@ -270,8 +213,7 @@ extends StartHost return this; } - @Override - public AwsLandscape getLandscape() { + protected AwsLandscape getLandscape() { return landscape; } @@ -282,8 +224,7 @@ extends StartHost return this; } - @Override - public InstanceType getInstanceType() { + protected InstanceType getInstanceType() { return instanceType; } @@ -294,8 +235,7 @@ extends StartHost return this; } - @Override - public AwsAvailabilityZone getAvailabilityZone() { + protected AwsAvailabilityZone getAvailabilityZone() { return availabilityZone == null ? getRandomAvailabilityZone(getRegion(), getLandscape()) : availabilityZone; } @@ -306,8 +246,7 @@ extends StartHost return this; } - @Override - public String getKeyName() { + protected String getKeyName() { return keyName; } @@ -317,8 +256,7 @@ extends StartHost return this; } - @Override - public Iterable getSecurityGroups() { + protected Iterable getSecurityGroups() { return securityGroups == null ? Collections.singleton(getLandscape().getDefaultSecurityGroupForApplicationHosts(getRegion())) : securityGroups; } @@ -329,8 +267,7 @@ extends StartHost return this; } - @Override - public Optional getTags() { + protected Optional getTags() { return tags; } @@ -340,8 +277,7 @@ extends StartHost return this; } - @Override - public Iterable getUserData() { + protected Iterable getUserData() { return Collections.unmodifiableList(userData); } @@ -355,8 +291,7 @@ extends StartHost return this; } - @Override - public AwsRegion getRegion() { + protected AwsRegion getRegion() { return region == null ? getAvailabilityZone().getRegion() : region; } @@ -366,8 +301,7 @@ extends StartHost return this; } - @Override - public String getInstanceName() { + protected String getInstanceName() { return instanceName; } @@ -381,8 +315,7 @@ extends StartHost return this; } - @Override - public String getServerName() { + protected String getServerName() { return serverName; } @@ -392,8 +325,7 @@ extends StartHost return this; } - @Override - public String getDatabaseName() { + protected String getDatabaseName() { return databaseName == null ? getServerName() : databaseName; } @@ -403,8 +335,7 @@ extends StartHost return this; } - @Override - public Database getDatabaseConfiguration() { + protected Database getDatabaseConfiguration() { return databaseConfiguration == null ? getLandscape().getDatabase(getRegion(), getDatabaseName()) : databaseConfiguration; } @@ -414,8 +345,7 @@ extends StartHost return this; } - @Override - public RabbitMQEndpoint getRabbitConfiguration() { + protected RabbitMQEndpoint getRabbitConfiguration() { return rabbitConfiguration; } @@ -438,8 +368,7 @@ extends StartHost return outboundReplicationConfiguration != null && outboundReplicationConfiguration.getOutboundRabbitMQEndpoint() != null; } - @Override - public OutboundReplicationConfiguration getOutboundReplicationConfiguration() { + protected OutboundReplicationConfiguration getOutboundReplicationConfiguration() { final OutboundReplicationConfiguration.Builder resultBuilder; if (outboundReplicationConfiguration != null) { resultBuilder = OutboundReplicationConfiguration.copy(outboundReplicationConfiguration); @@ -461,8 +390,7 @@ extends StartHost return this; } - @Override - public Optional getInboundReplicationConfiguration() { + protected Optional getInboundReplicationConfiguration() { final InboundReplicationConfiguration.Builder resultBuilder; if (inboundReplicationConfiguration == null || !inboundReplicationConfiguration.isPresent()) { resultBuilder = InboundReplicationConfiguration.builder(); @@ -482,8 +410,7 @@ extends StartHost return this; } - @Override - public String getCommaSeparatedEmailAddressesToNotifyOfStartup() { + protected String getCommaSeparatedEmailAddressesToNotifyOfStartup() { return commaSeparatedEmailAddressesToNotifyOfStartup; } @@ -493,7 +420,10 @@ extends StartHost return this; } - @Override + /** + * A timeout for interacting with the instance, such as when creating an SSH / SFTP connection or waiting for its + * public IP address. + */ public Optional getOptionalTimeout() { return optionalTimeout == null ? Optional.empty() : optionalTimeout; } @@ -504,9 +434,19 @@ extends StartHost this.optionalTimeout = optionalTimeout; return this; } + + protected HostSupplier getHostSupplier() { + return hostSupplier; + } + + @Override + public Builder setHostSupplier(HostSupplier hostSupplier) { + this.hostSupplier = hostSupplier; + return this; + } } - protected StartAwsHost(Builder, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> builder) { + protected StartAwsHost(BuilderImpl, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> builder) { super(builder); this.userData = new ArrayList<>(); for (final String ud : builder.getUserData()) { diff --git a/java/com.sap.sse.landscape/src/com/sap/sse/landscape/Landscape.java b/java/com.sap.sse.landscape/src/com/sap/sse/landscape/Landscape.java index 33c2a3c5a4b..48d0ae279cb 100755 --- a/java/com.sap.sse.landscape/src/com/sap/sse/landscape/Landscape.java +++ b/java/com.sap.sse.landscape/src/com/sap/sse/landscape/Landscape.java @@ -41,5 +41,5 @@ ReplicaProcessT extends ApplicationReplicaProcess getRegions(); - + MachineImage getLatestImageWithTag(Region region, String tagName, String tagValue); } diff --git a/java/com.sap.sse.landscape/src/com/sap/sse/landscape/orchestration/StartHost.java b/java/com.sap.sse.landscape/src/com/sap/sse/landscape/orchestration/StartHost.java index 975b66964a8..03b52c72991 100755 --- a/java/com.sap.sse.landscape/src/com/sap/sse/landscape/orchestration/StartHost.java +++ b/java/com.sap.sse.landscape/src/com/sap/sse/landscape/orchestration/StartHost.java @@ -3,6 +3,7 @@ package com.sap.sse.landscape.orchestration; import com.sap.sse.landscape.Host; import com.sap.sse.landscape.Landscape; import com.sap.sse.landscape.MachineImage; +import com.sap.sse.landscape.Region; import com.sap.sse.landscape.application.ApplicationMasterProcess; import com.sap.sse.landscape.application.ApplicationProcessMetrics; import com.sap.sse.landscape.application.ApplicationReplicaProcess; @@ -14,6 +15,16 @@ public abstract class StartHost extends AbstractProcedureImpl implements Procedure { + /** + * The {@link AwsLandscape#getLatestImageWithTag(com.sap.sse.landscape.Region, String, String)} method is + * used to obtain default images for specific AWS host starting procedures that subclass this class. The + * Amazon Machine Images (AMIs) for this are then expected to be tagged with a tag named as specified by this + * constant ("image-type"). The tag value then must match what the subclass wants. + * + * @see #getLatestImageOfType(String) + */ + protected final static String IMAGE_TYPE_TAG_NAME = "image-type"; + private final MachineImage machineImage; /** @@ -28,13 +39,57 @@ implements Procedure { ReplicaProcessT extends ApplicationReplicaProcess, HostT extends Host> { T build(); - - MachineImage getMachineImage(); - - Landscape getLandscape(); } - protected StartHost(Builder, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> builder) { + protected abstract static class BuilderImpl, ShardingKey, + MetricsT extends ApplicationProcessMetrics, + MasterProcessT extends ApplicationMasterProcess, + ReplicaProcessT extends ApplicationReplicaProcess, + HostT extends Host> + implements Builder { + private MachineImage machineImage; + private Landscape landscape; + private Region region; + private String imageType; + + protected MachineImage getMachineImage() { + return machineImage == null ? getLandscape().getLatestImageWithTag(getRegion(), IMAGE_TYPE_TAG_NAME, getImageType()) : machineImage; + } + + protected Region getRegion() { + return region; + } + + protected Builder setRegion(Region region) { + this.region = region; + return this; + } + + protected Builder setMachineImage(MachineImage machineImage) { + this.machineImage = machineImage; + return this; + } + + protected String getImageType() { + return imageType; + } + + protected Builder setImageType(String imageType) { + this.imageType = imageType; + return this; + } + + protected Landscape getLandscape() { + return landscape; + } + + protected Builder setLandscape(Landscape landscape) { + this.landscape = landscape; + return this; + } + } + + protected StartHost(BuilderImpl, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> builder) { super(builder.getLandscape()); this.machineImage = builder.getMachineImage(); }