bug4811: a compilable version with massively refactored procedure support, using the Builder pattern

This commit is contained in:
Axel Uhl
2020-10-30 18:04:39 +01:00
parent 7617ea490c
commit 94c1a32cb9
15 changed files with 690 additions and 347 deletions
@@ -5,9 +5,6 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.Optional;
import java.util.Random;
import java.util.UUID;
@@ -15,30 +12,22 @@ import java.util.UUID;
import org.junit.Before;
import org.junit.Test;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.SftpException;
import com.sap.sailing.landscape.SailingAnalyticsHost;
import com.sap.sailing.landscape.SailingAnalyticsMaster;
import com.sap.sailing.landscape.SailingAnalyticsMetrics;
import com.sap.sailing.landscape.SailingAnalyticsReplica;
import com.sap.sailing.landscape.SailingReleaseRepository;
import com.sap.sailing.landscape.procedures.StartSailingAnalyticsMaster;
import com.sap.sse.common.Duration;
import com.sap.sse.landscape.Release;
import com.sap.sse.landscape.application.ApplicationProcess;
import com.sap.sse.landscape.application.impl.ApplicationProcessImpl;
import com.sap.sse.landscape.aws.AwsInstance;
import com.sap.sse.landscape.aws.AwsLandscape;
import com.sap.sse.landscape.aws.Tags;
import com.sap.sse.landscape.aws.impl.AwsRegion;
import com.sap.sse.landscape.aws.orchestration.CreateDNSBasedLoadBalancerMapping;
import com.sap.sse.landscape.aws.orchestration.StartAwsHost;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.model.Instance;
import software.amazon.awssdk.services.ec2.model.InstanceType;
import software.amazon.awssdk.services.ec2.model.Tag;
import software.amazon.awssdk.services.route53.model.RRType;
/**
* Tests for the AWS SDK landscape wrapper in bundle {@code com.sap.sse.landscape.aws}. To run these tests
@@ -61,20 +50,22 @@ public class TestProcedures {
}
@Test
public void testConnectivity() throws JSchException, IOException, SftpException, NumberFormatException, InterruptedException, URISyntaxException {
final String TARGET_GROUP_NAME_PREFIX = "S-test-";
final String hostedZoneName = "wiesen-weg.de";
final String hostname = "S-test-"+new Random().nextInt()+"."+hostedZoneName;
public void testConnectivity() throws Exception {
final String serverName = "test"+new Random().nextInt();
final String keyName = "MyKey-"+UUID.randomUUID();
final Release release = SailingReleaseRepository.INSTANCE.getLatestMasterRelease();
final StartAwsHost<String, SailingAnalyticsMetrics, SailingAnalyticsMaster<String>, SailingAnalyticsReplica<String>, SailingAnalyticsHost<String>> startSailingAnalyticsMaster = new StartSailingAnalyticsMaster<String>(
hostname, region, landscape, InstanceType.T2_SMALL, keyName, Optional.of(release), /* databaseConfiguration */ null,
/* rabbitConfiguration */ null, /* replicationConfiguration */ null, "axel.uhl@sap.com", Optional.empty());
final StartSailingAnalyticsMaster.Builder<String> builder = StartSailingAnalyticsMaster.builder();
final StartSailingAnalyticsMaster<String> startSailingAnalyticsMaster = builder
.setServerName(serverName)
.setLandscape(landscape)
.setRegion(region)
.setInstanceType(InstanceType.T3_SMALL)
.setKeyName(keyName)
.setCommaSeparatedEmailAddressesToNotifyOfStartup("axel.uhl@sap.com")
.setTags(Optional.of(Tags.with("Hello", "World")))
.setOptionalTimeout(optionalTimeout)
.build();
startSailingAnalyticsMaster.run();
final AwsInstance<String, SailingAnalyticsMetrics> host = landscape.launchHost(landscape
.getImage(region, "ami-01b4b27a5699e33e6"),
InstanceType.T3_SMALL, landscape.getAvailabilityZoneByName(region, "eu-west-2b"), keyName, Collections.singleton(()->"sg-0b2afd48960251280"),
Optional.of(Tags.with("Name", "MyHost").and("Hello", "World")));
final SailingAnalyticsHost<String> host = startSailingAnalyticsMaster.getHost();
try {
assertNotNull(host);
final Instance instance = landscape.getInstance(host.getInstanceId(), region);
@@ -94,24 +85,8 @@ public class TestProcedures {
final ApplicationProcess<String, SailingAnalyticsMetrics, SailingAnalyticsMaster<String>, SailingAnalyticsReplica<String>> process = new ApplicationProcessImpl<>(8888, host, "/home/sailing/servers/server");
final String envSh = process.getEnvSh(optionalTimeout);
assertFalse(envSh.isEmpty());
assertTrue(envSh.contains("SERVER_NAME="));
assertTrue(envSh.contains("SERVER_NAME="+serverName));
assertEquals(14888, process.getTelnetPortToOSGiConsole(optionalTimeout));
final AwsLandscape<String, SailingAnalyticsMetrics, SailingAnalyticsMaster<String>, SailingAnalyticsReplica<String>> castLandscape = (AwsLandscape<String, SailingAnalyticsMetrics, SailingAnalyticsMaster<String>, SailingAnalyticsReplica<String>>) landscape;
final CreateDNSBasedLoadBalancerMapping<String, SailingAnalyticsMetrics, SailingAnalyticsMaster<String>, SailingAnalyticsReplica<String>, AwsInstance<String, SailingAnalyticsMetrics>> createDNSBasedLoadBalancerMappingProcedure =
new CreateDNSBasedLoadBalancerMapping<String, SailingAnalyticsMetrics, SailingAnalyticsMaster<String>, SailingAnalyticsReplica<String>, AwsInstance<String, SailingAnalyticsMetrics>>(
process, hostname, TARGET_GROUP_NAME_PREFIX, castLandscape, optionalTimeout);
final String wiesenWegId = landscape.getDNSHostedZoneId(hostedZoneName);
try {
createDNSBasedLoadBalancerMappingProcedure.run();
assertNotNull(createDNSBasedLoadBalancerMappingProcedure.getLoadBalancerUsed());
assertNotNull(createDNSBasedLoadBalancerMappingProcedure.getMasterTargetGroupCreated());
assertEquals(TARGET_GROUP_NAME_PREFIX+process.getServerName(optionalTimeout), createDNSBasedLoadBalancerMappingProcedure.getPublicTargetGroupCreated().getName());
} finally {
if (createDNSBasedLoadBalancerMappingProcedure.getLoadBalancerUsed() != null) {
createDNSBasedLoadBalancerMappingProcedure.getLoadBalancerUsed().delete();
landscape.removeDNSRecord(wiesenWegId, hostname, RRType.CNAME, createDNSBasedLoadBalancerMappingProcedure.getLoadBalancerUsed().getDNSName());
}
}
} finally {
landscape.terminate(host);
landscape.deleteKeyPair(region, keyName);
@@ -3,8 +3,8 @@ package com.sap.sailing.landscape.impl;
import java.util.HashMap;
import java.util.Map;
import com.sap.sailing.landscape.ReplicationCredentials;
import com.sap.sse.landscape.ProcessConfigurationVariable;
import com.sap.sse.landscape.ReplicationCredentials;
public class BearerTokenReplicationCredentials implements ReplicationCredentials {
private final String bearerToken;
@@ -3,8 +3,8 @@ package com.sap.sailing.landscape.impl;
import java.util.HashMap;
import java.util.Map;
import com.sap.sailing.landscape.ReplicationCredentials;
import com.sap.sse.landscape.ProcessConfigurationVariable;
import com.sap.sse.landscape.ReplicationCredentials;
public class UsernamePasswordReplicationCredentials implements ReplicationCredentials {
private final String username;
@@ -1,55 +1,91 @@
package com.sap.sailing.landscape.procedures;
import java.net.URISyntaxException;
import java.io.ByteArrayOutputStream;
import java.util.Optional;
import java.util.logging.Logger;
import com.sap.sailing.landscape.ReplicationConfiguration;
import com.sap.sailing.landscape.SailingAnalyticsHost;
import com.sap.sailing.landscape.SailingAnalyticsMaster;
import com.sap.sailing.landscape.SailingAnalyticsMetrics;
import com.sap.sailing.landscape.SailingAnalyticsReplica;
import com.sap.sse.landscape.Release;
import com.sap.sse.landscape.SecurityGroup;
import com.sap.sse.landscape.aws.AwsAvailabilityZone;
import com.sap.sse.landscape.aws.AwsLandscape;
import com.sap.sse.landscape.aws.Tags;
import com.sap.sse.landscape.aws.impl.AmazonMachineImage;
import com.sap.sse.landscape.mongodb.Database;
import com.sap.sse.landscape.rabbitmq.RabbitMQEndpoint;
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.ssh.SshCommandChannel;
import software.amazon.awssdk.services.ec2.model.InstanceType;
/**
* Starts an empty multi-server. The image will cause a {@code /home/sailing/servers/server} directory to exist, but
* after successfully launching, that directory will be removed. A {@link DeployProcessOnMultiServer} procedure needs to
* be run with the {@link #getHost()} of this procedure telling the host on which to deploy the process.
* be run with the {@link #getHost()} of this procedure telling the host on which to deploy the process.<p>
*
* The implementation specializes the {@link UpgradeAmi} procedure in {@link Builder#setNoShutdown(boolean) no-shutdown} mode.
* After running that part, the {@code httpd} service is launched.<p>
*
* You want to at least specify an {@link Builder#setInstanceName(String) instance name} and {@link Builder#setInstanceType(InstanceType)}.
*
* @author Axel Uhl (D043530)
*
* @param <ShardingKey>
* @param <HostT>
*/
public class StartMultiServer<ShardingKey, HostT extends SailingAnalyticsHost<ShardingKey>>
extends StartSailingAnalyticsHost<ShardingKey, SailingAnalyticsHost<ShardingKey>> {
public StartMultiServer(String name, AmazonMachineImage<ShardingKey, SailingAnalyticsMetrics> machineImage,
AwsLandscape<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsMaster<ShardingKey>, SailingAnalyticsReplica<ShardingKey>> landscape,
InstanceType instanceType, AwsAvailabilityZone availabilityZone, String keyName,
Iterable<SecurityGroup> securityGroups, Optional<Release> release, Database databaseConfiguration,
RabbitMQEndpoint rabbitConfiguration, String outputReplicationExchangeName,
Optional<ReplicationConfiguration> replicationConfiguration,
String commaSeparatedEmailAddressesToNotifyOfStartup, Optional<Tags> tags, String[] userData)
throws URISyntaxException {
super(name, machineImage, landscape, instanceType, availabilityZone, keyName, securityGroups, release,
databaseConfiguration, rabbitConfiguration, outputReplicationExchangeName, replicationConfiguration,
commaSeparatedEmailAddressesToNotifyOfStartup, tags, userData);
// TODO Auto-generated constructor stub
public class StartMultiServer<ShardingKey, MetricsT extends ApplicationProcessMetrics,
MasterProcessT extends ApplicationMasterProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
HostT extends AwsInstance<ShardingKey, MetricsT>>
extends UpgradeAmi<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> {
private static final Logger logger = Logger.getLogger(StartMultiServer.class.getName());
private Optional<Duration> optionalTimeout;
/**
* Under all circumstances, this builder will return {@code true} for {@link #isNoShutdown()}, making sure
* that after the upgrade progress the server does not try to re-boot.
*
* @author Axel Uhl (D043530)
*/
public static interface Builder<ShardingKey, MetricsT extends ApplicationProcessMetrics,
MasterProcessT extends ApplicationMasterProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
HostT extends AwsInstance<ShardingKey, MetricsT>>
extends UpgradeAmi.Builder<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> {
@Override
default boolean isNoShutdown() {
return true;
}
}
protected static class BuilderImpl<ShardingKey, MetricsT extends ApplicationProcessMetrics,
MasterProcessT extends ApplicationMasterProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
HostT extends AwsInstance<ShardingKey, MetricsT>>
extends UpgradeAmi.BuilderImpl<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT>
implements Builder<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> {
@Override
public StartMultiServer<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> build() {
return new StartMultiServer<>(this);
}
}
public static <ShardingKey, MetricsT extends ApplicationProcessMetrics,
MasterProcessT extends ApplicationMasterProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
HostT extends AwsInstance<ShardingKey, MetricsT>> Builder<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> builder() {
return new BuilderImpl<>();
}
@Override
public void run() {
super.run();
// TODO clean up the default /home/sailing/servers/server directory
protected StartMultiServer(Builder<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> builder) {
super(builder);
this.optionalTimeout = builder.getOptionalTimeout();
}
@Override
public void run() throws Exception {
super.run();
final SshCommandChannel sshCommandChannel = getHost().createRootSshChannel(optionalTimeout);
final ByteArrayOutputStream stderr = new ByteArrayOutputStream();
sshCommandChannel.sendCommandLineSynchronously("service httpd start", stderr);
final String instanceId = getHost().getInstanceId();
logger.info("stdout for starting httpd service on instance "+instanceId+": "+sshCommandChannel.getStreamContentsAsString());
logger.info("stderr for starting httpd service on instance \"+instanceId+\": "+stderr.toString());
logger.info("exit status for starting httpd service on instance \"+instanceId+\": "+sshCommandChannel.getExitStatus());
}
}
@@ -1,29 +1,16 @@
package com.sap.sailing.landscape.procedures;
import java.net.URISyntaxException;
import java.util.Optional;
import com.sap.sailing.landscape.ReplicationConfiguration;
import com.sap.sailing.landscape.SailingAnalyticsHost;
import com.sap.sailing.landscape.SailingAnalyticsMaster;
import com.sap.sailing.landscape.SailingAnalyticsMetrics;
import com.sap.sailing.landscape.SailingAnalyticsReplica;
import com.sap.sailing.landscape.SailingReleaseRepository;
import com.sap.sse.landscape.MachineImage;
import com.sap.sse.landscape.ProcessConfigurationVariable;
import com.sap.sse.landscape.Release;
import com.sap.sse.landscape.SecurityGroup;
import com.sap.sse.landscape.aws.AwsAvailabilityZone;
import com.sap.sse.landscape.aws.AwsLandscape;
import com.sap.sse.landscape.aws.Tags;
import com.sap.sse.landscape.aws.impl.AmazonMachineImage;
import com.sap.sse.landscape.aws.impl.AwsRegion;
import com.sap.sse.landscape.aws.orchestration.StartAwsHost;
import com.sap.sse.landscape.mongodb.Database;
import com.sap.sse.landscape.orchestration.Procedure;
import com.sap.sse.landscape.rabbitmq.RabbitMQEndpoint;
import software.amazon.awssdk.services.ec2.model.InstanceType;
/**
* TODO handle the naming problem: base name drives instance "Name" tag generation ("SL ... (Master)"), exchange name,
@@ -36,86 +23,51 @@ import software.amazon.awssdk.services.ec2.model.InstanceType;
*/
public abstract class StartSailingAnalyticsHost<ShardingKey,
HostT extends SailingAnalyticsHost<ShardingKey>>
extends StartAwsHost<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsMaster<ShardingKey>, SailingAnalyticsReplica<ShardingKey>, SailingAnalyticsHost<ShardingKey>>
extends StartAwsHost<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsMaster<ShardingKey>, SailingAnalyticsReplica<ShardingKey>, HostT>
implements Procedure<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsMaster<ShardingKey>, SailingAnalyticsReplica<ShardingKey>> {
private final static String IMAGE_TYPE_TAG_VALUE_SAILING = "sailing-analytics-server";
private final static String INSTANCE_NAME_DEFAULT_PREFIX = "SL ";
/**
* Configures the start host procedure using the latest default machine image for the Sailing Analytics application,
* tagged with the value {@link #IMAGE_TYPE_TAG_VALUE_SAILING} for the image type tag key. A random availability
* zone is picked from the {@code region} specified, and the
* {@link SailingReleaseRepository#getLatestMasterRelease() latest master build release} is chosen as the release to
* deploy.
* The following defaults, in addition to the defaults implemented by the more general {@link StartAwsHost.Builder},
* are:
* <ul>
* <li>If no {@link #setInstanceName(String) instance name} is provided, the instance name is constructed from the {@link #getServerName() server name}
* by pre-pending the prefix "SL ".</li>
* <li>Uses the latest machine image of the type described by
* {@link StartSailingAnalyticsHost#IMAGE_TYPE_TAG_VALUE_SAILING} if no explicit {@link #setMachineImage(AmazonMachineImage) machine image is set}
* and no {@link #setImageType(String) image type is set} of which the latest version would be used otherwise.</li>
* <li>If no {@link Release} is explicitly {@link #setRelease set}, or that {@link Optional} is empty,
* {@link SailingReleaseRepository#INSTANCE}{@link SailingReleaseRepository#getLatestMasterRelease()
* getLatestMasterRelease()} will be used instead.</li>
* </ul>
*
* @param name
* the name is used as the server name, is the basis for the server-group's name
* @author Axel Uhl (D043530)
*/
public StartSailingAnalyticsHost(String name,
AwsLandscape<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsMaster<ShardingKey>, SailingAnalyticsReplica<ShardingKey>> landscape,
AwsRegion region,
InstanceType instanceType, String keyName, Database databaseConfiguration,
RabbitMQEndpoint rabbitConfiguration, String outputReplicationExchangeName,
Optional<ReplicationConfiguration> replicationConfiguration,
String commaSeparatedEmailAddressesToNotifyOfStartup, Optional<Tags> tags, String... additionalUserData) throws URISyntaxException {
this(name, getLatestSailingMachineImage(landscape, region), landscape, instanceType,
getRandomAvailabilityZone(region, landscape), keyName,
Optional.of(SailingReleaseRepository.INSTANCE.getLatestMasterRelease()), databaseConfiguration, rabbitConfiguration,
outputReplicationExchangeName, replicationConfiguration, commaSeparatedEmailAddressesToNotifyOfStartup,
tags, additionalUserData);
}
protected static <ShardingKey> AmazonMachineImage<ShardingKey, SailingAnalyticsMetrics> getLatestSailingMachineImage(
AwsLandscape<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsMaster<ShardingKey>, SailingAnalyticsReplica<ShardingKey>> landscape,
AwsRegion region) {
return getLatestImageOfType(IMAGE_TYPE_TAG_VALUE_SAILING, landscape, region);
}
public StartSailingAnalyticsHost(String name, Optional<Release> release,
AwsLandscape<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsMaster<ShardingKey>, SailingAnalyticsReplica<ShardingKey>> landscape,
AwsRegion region,
InstanceType instanceType, String keyName, Database databaseConfiguration,
RabbitMQEndpoint rabbitConfiguration, String outputReplicationExchangeName,
Optional<ReplicationConfiguration> replicationConfiguration,
String commaSeparatedEmailAddressesToNotifyOfStartup, Optional<Tags> tags, String... additionalUserData) throws URISyntaxException {
this(name, getLatestSailingMachineImage(landscape, region), landscape, instanceType,
getRandomAvailabilityZone(region, landscape), keyName,
release, databaseConfiguration, rabbitConfiguration,
outputReplicationExchangeName, replicationConfiguration, commaSeparatedEmailAddressesToNotifyOfStartup,
tags, additionalUserData);
}
/**
* @param name
* the name is used as the server name, is the basis for the server-group's name
*/
public StartSailingAnalyticsHost(String name,
AmazonMachineImage<ShardingKey, SailingAnalyticsMetrics> machineImage,
AwsLandscape<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsMaster<ShardingKey>, SailingAnalyticsReplica<ShardingKey>> landscape,
InstanceType instanceType, AwsAvailabilityZone availabilityZone, String keyName,
Optional<Release> release, Database databaseConfiguration, RabbitMQEndpoint rabbitConfiguration,
String outputReplicationExchangeName, Optional<ReplicationConfiguration> replicationConfiguration,
String commaSeparatedEmailAddressesToNotifyOfStartup, Optional<Tags> tags, String... userData) {
super(machineImage, release, landscape, instanceType, availabilityZone, keyName, addNameTag(name, tags), userData);
public static interface Builder<T extends StartSailingAnalyticsHost<ShardingKey, HostT>, ShardingKey, HostT extends SailingAnalyticsHost<ShardingKey>>
extends StartAwsHost.Builder<T, ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsMaster<ShardingKey>, SailingAnalyticsReplica<ShardingKey>, HostT> {
}
/**
* @param name
* the name is used as the server name, is the basis for the server-group's name
*/
public StartSailingAnalyticsHost(String name, MachineImage machineImage,
AwsLandscape<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsMaster<ShardingKey>, SailingAnalyticsReplica<ShardingKey>> landscape,
InstanceType instanceType, AwsAvailabilityZone availabilityZone, String keyName,
Iterable<SecurityGroup> securityGroups, Optional<Release> release, Database databaseConfiguration,
RabbitMQEndpoint rabbitConfiguration,
String outputReplicationExchangeName,
Optional<ReplicationConfiguration> replicationConfiguration,
String commaSeparatedEmailAddressesToNotifyOfStartup, Optional<Tags> tags, String... additionalUserData) throws URISyntaxException {
super(machineImage, release, landscape, instanceType, availabilityZone, keyName, securityGroups, addNameTag(name, tags), additionalUserData);
addUserData(databaseConfiguration);
addUserData(rabbitConfiguration);
addUserData(ProcessConfigurationVariable.SERVER_NAME, name);
addUserData(ProcessConfigurationVariable.REPLICATION_CHANNEL, outputReplicationExchangeName);
addUserData(ProcessConfigurationVariable.SERVER_STARTUP_NOTIFY, commaSeparatedEmailAddressesToNotifyOfStartup);
replicationConfiguration.ifPresent(this::addUserData);
protected abstract static class BuilderImpl<T extends StartSailingAnalyticsHost<ShardingKey, HostT>, ShardingKey, HostT extends SailingAnalyticsHost<ShardingKey>>
extends StartAwsHost.BuilderImpl<T, ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsMaster<ShardingKey>, SailingAnalyticsReplica<ShardingKey>, HostT>
implements Builder<T, ShardingKey, HostT> {
@Override
public String getImageType() {
return super.getImageType() == null ? StartSailingAnalyticsHost.IMAGE_TYPE_TAG_VALUE_SAILING : super.getImageType();
}
@Override
public Optional<Release> getRelease() {
return Optional.of(super.getRelease().orElse(SailingReleaseRepository.INSTANCE.getLatestMasterRelease()));
}
@Override
public String getInstanceName() {
return super.getInstanceName() == null ? INSTANCE_NAME_DEFAULT_PREFIX+getServerName() : super.getInstanceName();
}
}
protected StartSailingAnalyticsHost(Builder<? extends StartSailingAnalyticsHost<ShardingKey,HostT>, ShardingKey, HostT> builder) {
super(builder);
}
}
@@ -1,70 +1,44 @@
package com.sap.sailing.landscape.procedures;
import java.net.URISyntaxException;
import java.util.Optional;
import com.sap.sailing.landscape.ReplicationConfiguration;
import com.sap.sailing.landscape.SailingAnalyticsHost;
import com.sap.sailing.landscape.SailingAnalyticsMaster;
import com.sap.sailing.landscape.SailingAnalyticsMetrics;
import com.sap.sailing.landscape.SailingAnalyticsReplica;
import com.sap.sse.landscape.MachineImage;
import com.sap.sse.landscape.ProcessConfigurationVariable;
import com.sap.sse.landscape.Release;
import com.sap.sse.landscape.SecurityGroup;
import com.sap.sse.landscape.aws.AwsAvailabilityZone;
import com.sap.sse.landscape.aws.AwsLandscape;
import com.sap.sse.landscape.aws.Tags;
import com.sap.sse.landscape.aws.impl.AwsRegion;
import com.sap.sse.landscape.mongodb.Database;
import com.sap.sse.landscape.rabbitmq.RabbitMQReplicaSet;
import software.amazon.awssdk.services.ec2.model.InstanceType;
/**
* TODO handle the naming problem: base name drives instance "Name" tag generation ("SL ... (Master)"), exchange name,
* database name and SERVER_NAME. When moving up the inheritance hierarchy, name is interpreted in some places as the instance name
* which obviously doesn't equal the "Name" tag value. So, we have to clearly distinguish these.
* This procedure does two things: it {@link StartSailingAnalyticsHost starts} a {@link SailingAnalyticsHost}, and
* (currently implicitly, based on the way the /etc/init.d/sailing script works) also starts a "master" process
* that is expected to have the default working directory {@code /home/sailing/servers/server}.<p>
*
* TODO What we should probably be doing instead is harmonize the way the set-up / launching of a regular default master
* works with how a {@link StartMultiServer multi-server is started}. We could start both empty, with only the default
* reverse proxy mappings for {@code internal-server-status} and the plain access through the {@code ec2-...} hostname.
* From there on, all process launching and stopping would work through the {@link DeployProcessOnMultiServer} procedure
* (which then should be renamed to {@code DeployApplicationProcessOnServer}). That procedure then would have the
*
* @author Axel Uhl (D043530)
*
* @param <ShardingKey>
*/
public class StartSailingAnalyticsMaster<ShardingKey>
extends StartSailingAnalyticsHost<ShardingKey, SailingAnalyticsHost<ShardingKey>> {
public StartSailingAnalyticsMaster(String name, AwsRegion region,
AwsLandscape<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsMaster<ShardingKey>, SailingAnalyticsReplica<ShardingKey>> landscape,
InstanceType instanceType, String keyName, Optional<Release> release, Database databaseConfiguration,
RabbitMQReplicaSet rabbitConfiguration, Optional<ReplicationConfiguration> replicationConfiguration,
String commaSeparatedEmailAddressesToNotifyOfStartup, Optional<Tags> tags) throws URISyntaxException {
this(name,
getLatestSailingMachineImage(landscape, region), landscape, instanceType, getRandomAvailabilityZone(region, landscape), keyName,
getDefaultSecurityGroupForApplicationHosts(landscape, region),
release, databaseConfiguration, rabbitConfiguration, replicationConfiguration, commaSeparatedEmailAddressesToNotifyOfStartup, tags);
public static interface Builder<ShardingKey>
extends StartSailingAnalyticsHost.Builder<StartSailingAnalyticsMaster<ShardingKey>, ShardingKey, SailingAnalyticsHost<ShardingKey>> {
}
public StartSailingAnalyticsMaster(String name, MachineImage machineImage,
AwsRegion region,
AwsLandscape<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsMaster<ShardingKey>, SailingAnalyticsReplica<ShardingKey>> landscape,
InstanceType instanceType, String keyName, Optional<Release> release,
Database databaseConfiguration, RabbitMQReplicaSet rabbitConfiguration,
Optional<ReplicationConfiguration> replicationConfiguration,
String commaSeparatedEmailAddressesToNotifyOfStartup, Optional<Tags> tags) throws URISyntaxException {
this(name, machineImage, landscape, instanceType, getRandomAvailabilityZone(region, landscape), keyName,
getDefaultSecurityGroupForApplicationHosts(landscape, region),
release, databaseConfiguration, rabbitConfiguration, replicationConfiguration, commaSeparatedEmailAddressesToNotifyOfStartup, tags);
// TODO model an AwsLandscape subclass describing the specifics of the Sailing landscape, with a central security service that a master replicates by default
protected static class BuilderImpl<ShardingKey>
extends StartSailingAnalyticsHost.BuilderImpl<StartSailingAnalyticsMaster<ShardingKey>, ShardingKey, SailingAnalyticsHost<ShardingKey>>
implements Builder<ShardingKey> {
@Override
public StartSailingAnalyticsMaster<ShardingKey> build() {
return new StartSailingAnalyticsMaster<ShardingKey>(this);
}
}
public StartSailingAnalyticsMaster(String name, MachineImage machineImage,
AwsLandscape<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsMaster<ShardingKey>, SailingAnalyticsReplica<ShardingKey>> landscape,
InstanceType instanceType, AwsAvailabilityZone availabilityZone, String keyName,
Iterable<SecurityGroup> securityGroups, Optional<Release> release, Database databaseConfiguration,
RabbitMQReplicaSet rabbitConfiguration, Optional<ReplicationConfiguration> replicationConfiguration,
String commaSeparatedEmailAddressesToNotifyOfStartup, Optional<Tags> tags) throws URISyntaxException {
super(name, machineImage, landscape, instanceType, availabilityZone, keyName, securityGroups, release,
databaseConfiguration, rabbitConfiguration,
/* use {name} as outbound replication exchange name for master */ name, replicationConfiguration,
commaSeparatedEmailAddressesToNotifyOfStartup, tags);
addUserData(ProcessConfigurationVariable.USE_ENVIRONMENT, "live-master-server");
public static <ShardingKey> Builder<ShardingKey> builder() {
return new BuilderImpl<>();
}
protected StartSailingAnalyticsMaster(Builder<ShardingKey> 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?
}
}
@@ -1,62 +1,29 @@
package com.sap.sailing.landscape.procedures;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.Optional;
import com.sap.sailing.landscape.ReplicationConfiguration;
import com.sap.sailing.landscape.SailingAnalyticsHost;
import com.sap.sailing.landscape.SailingAnalyticsMaster;
import com.sap.sailing.landscape.SailingAnalyticsMetrics;
import com.sap.sailing.landscape.SailingAnalyticsReplica;
import com.sap.sse.landscape.ProcessConfigurationVariable;
import com.sap.sse.landscape.Release;
import com.sap.sse.landscape.SecurityGroup;
import com.sap.sse.landscape.aws.AwsAvailabilityZone;
import com.sap.sse.landscape.aws.AwsLandscape;
import com.sap.sse.landscape.aws.Tags;
import com.sap.sse.landscape.aws.impl.AmazonMachineImage;
import com.sap.sse.landscape.mongodb.Database;
import com.sap.sse.landscape.rabbitmq.RabbitMQEndpoint;
import software.amazon.awssdk.services.ec2.model.InstanceType;
/**
* TODO handle the naming problem: base name drives instance "Name" tag generation ("SL ... (Master)"), exchange name,
* database name and SERVER_NAME. When moving up the inheritance hierarchy, name is interpreted in some places as the instance name
* which obviously doesn't equal the "Name" tag value. So, we have to clearly distinguish these.
*
* @author Axel Uhl (D043530)
*
* @param <ShardingKey>
*/
public class StartSailingAnalyticsReplica<ShardingKey>
extends StartSailingAnalyticsHost<ShardingKey, SailingAnalyticsHost<ShardingKey>> {
public StartSailingAnalyticsReplica(String name, AmazonMachineImage<ShardingKey, SailingAnalyticsMetrics> machineImage,
AwsLandscape<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsMaster<ShardingKey>, SailingAnalyticsReplica<ShardingKey>> landscape,
InstanceType instanceType, AwsAvailabilityZone availabilityZone, String keyName, Optional<Release> release,
Database databaseConfiguration, RabbitMQEndpoint rabbitConfiguration,
Optional<ReplicationConfiguration> replicationConfiguration, SailingAnalyticsMaster<ShardingKey> master,
String commaSeparatedEmailAddressesToNotifyOfStartup, Optional<Tags> tags, String[] userData)
throws URISyntaxException {
this(name, machineImage, landscape, instanceType, availabilityZone, keyName,
Collections
.singleton(landscape.getDefaultSecurityGroupForApplicationHosts(availabilityZone.getRegion())),
release, databaseConfiguration, rabbitConfiguration, replicationConfiguration, master,
commaSeparatedEmailAddressesToNotifyOfStartup, tags, userData);
public static interface Builder<ShardingKey>
extends StartSailingAnalyticsHost.Builder<StartSailingAnalyticsReplica<ShardingKey>, ShardingKey, SailingAnalyticsHost<ShardingKey>> {
}
public StartSailingAnalyticsReplica(String name, AmazonMachineImage<ShardingKey, SailingAnalyticsMetrics> machineImage,
AwsLandscape<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsMaster<ShardingKey>, SailingAnalyticsReplica<ShardingKey>> landscape,
InstanceType instanceType, AwsAvailabilityZone availabilityZone, String keyName,
Iterable<SecurityGroup> securityGroups, Optional<Release> release, Database databaseConfiguration,
RabbitMQEndpoint rabbitConfiguration, Optional<ReplicationConfiguration> replicationConfiguration,
SailingAnalyticsMaster<ShardingKey> master, String commaSeparatedEmailAddressesToNotifyOfStartup,
Optional<Tags> tags, String... userData) throws URISyntaxException {
super(name, machineImage, landscape, instanceType, availabilityZone, keyName, securityGroups, release,
databaseConfiguration, rabbitConfiguration,
/* use {name}-replica as outbound replication exchange name for master */ name + "-replica",
replicationConfiguration, commaSeparatedEmailAddressesToNotifyOfStartup, tags, userData);
addUserData(ProcessConfigurationVariable.USE_ENVIRONMENT, "live-replica-server");
protected static class BuilderImpl<ShardingKey>
extends StartSailingAnalyticsHost.BuilderImpl<StartSailingAnalyticsReplica<ShardingKey>, ShardingKey, SailingAnalyticsHost<ShardingKey>>
implements Builder<ShardingKey> {
@Override
public StartSailingAnalyticsReplica<ShardingKey> build() {
return new StartSailingAnalyticsReplica<ShardingKey>(this);
}
}
public static <ShardingKey> Builder<ShardingKey> builder() {
return new BuilderImpl<>();
}
protected StartSailingAnalyticsReplica(Builder<ShardingKey> 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?
}
}
@@ -1,22 +1,15 @@
package com.sap.sailing.landscape.procedures;
import java.util.Optional;
import java.util.Collections;
import com.sap.sse.landscape.MachineImage;
import com.sap.sse.landscape.SecurityGroup;
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.AwsAvailabilityZone;
import com.sap.sse.landscape.aws.AwsInstance;
import com.sap.sse.landscape.aws.AwsLandscape;
import com.sap.sse.landscape.aws.Tags;
import com.sap.sse.landscape.aws.impl.AmazonMachineImage;
import com.sap.sse.landscape.aws.orchestration.StartAwsHost;
import com.sap.sse.landscape.orchestration.Procedure;
import software.amazon.awssdk.services.ec2.model.InstanceType;
/**
* 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
@@ -31,23 +24,81 @@ public class UpgradeAmi<ShardingKey, MetricsT extends ApplicationProcessMetrics,
MasterProcessT extends ApplicationMasterProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
HostT extends AwsInstance<ShardingKey, MetricsT>>
extends
StartAwsHost<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> implements
Procedure<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> {
private static final String IMAGE_UPGRADE = "image-upgrade";
extends StartAwsHost<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT>
implements Procedure<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> {
private static final String IMAGE_UPGRADE_USER_DATA = "image-upgrade";
private static final String NO_SHUTDOWN_USER_DATA = "no-shutdown";
private MachineImage upgradedAmi;
public UpgradeAmi(AmazonMachineImage<ShardingKey, MetricsT> machineImage,
AwsLandscape<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> landscape,
InstanceType instanceType, AwsAvailabilityZone availabilityZone, String keyName,
Iterable<SecurityGroup> securityGroups) {
super(machineImage, Optional.empty(), landscape, instanceType, availabilityZone, keyName,
securityGroups, Optional.of(Tags.with("Name", IMAGE_UPGRADE+" for "+machineImage.getId())), /* user data */ IMAGE_UPGRADE);
/**
* Additional default rules in addition to what the {@link StartAwsHost.Builder parent builder} defines:
*
* <ul>
* <li>If no {@link #getInstanceName() instance name} is set, the default instance name will be constructed as
* {@code IMAGE_UPGRADE+" for "+machineImage.getId()}</li>
* <li>The user data are set to the string defined by {@link UpgradeAmi#IMAGE_UPGRADE_USER_DATA}, forcing the image to
* boot without trying to launch a process instance.</li>
* </ul>
* @author Axel Uhl (D043530)
*/
public static interface Builder<ShardingKey, MetricsT extends ApplicationProcessMetrics,
MasterProcessT extends ApplicationMasterProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
HostT extends AwsInstance<ShardingKey, MetricsT>>
extends StartAwsHost.Builder<UpgradeAmi<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT>, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> {
boolean isNoShutdown();
Builder<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setNoShutdown(boolean noShutdown);
}
protected static class BuilderImpl<ShardingKey, MetricsT extends ApplicationProcessMetrics,
MasterProcessT extends ApplicationMasterProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
HostT extends AwsInstance<ShardingKey, MetricsT>>
extends StartAwsHost.BuilderImpl<UpgradeAmi<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT>, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT>
implements Builder<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> {
private boolean noShutdown;
@Override
public UpgradeAmi<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> build() {
return new UpgradeAmi<>(this);
}
@Override
public boolean isNoShutdown() {
return noShutdown;
}
@Override
public Builder<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setNoShutdown(boolean noShutdown) {
this.noShutdown = noShutdown;
return this;
}
@Override
public String getInstanceName() {
return super.getInstanceName() == null ? IMAGE_UPGRADE_USER_DATA+" for "+getMachineImage().getId() : super.getInstanceName();
}
}
public static <ShardingKey, MetricsT extends ApplicationProcessMetrics,
MasterProcessT extends ApplicationMasterProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
HostT extends AwsInstance<ShardingKey, MetricsT>> Builder<ShardingKey, MetricsT, MasterProcessT,ReplicaProcessT, HostT> builder() {
return new BuilderImpl<>();
}
public UpgradeAmi(Builder<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> builder) {
super(builder);
addUserData(Collections.singleton(IMAGE_UPGRADE_USER_DATA));
if (builder.isNoShutdown()) {
addUserData(Collections.singleton(NO_SHUTDOWN_USER_DATA));
}
}
@Override
public void run() {
public void run() throws Exception {
super.run(); // launches the machine in upgrade mode and shuts it down again, preparing for AMI creation
// TODO now comes the waiting for the shutdown and initiating the creation of an AMI for the instance
// TODO then comes the tagging of the volume snapshots created
@@ -18,6 +18,7 @@ import com.sap.sse.landscape.application.ApplicationReplicaProcess;
import com.sap.sse.landscape.aws.impl.AmazonMachineImage;
import com.sap.sse.landscape.aws.impl.AwsLandscapeImpl;
import com.sap.sse.landscape.aws.impl.AwsTargetGroupImpl;
import com.sap.sse.landscape.mongodb.Database;
import com.sap.sse.landscape.mongodb.MongoEndpoint;
import com.sap.sse.landscape.ssh.SSHKeyPair;
@@ -280,6 +281,8 @@ extends Landscape<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> {
*/
MongoEndpoint getDatabaseConfigurationForDefaultCluster(Region region);
Database getDatabase(Region region, String databaseName);
/**
* The region to use as the default region for instance creation, DB connectivity, reverse proxy config, ...
*/
@@ -40,7 +40,9 @@ import com.sap.sse.landscape.aws.TargetGroup;
import com.sap.sse.landscape.aws.persistence.DomainObjectFactory;
import com.sap.sse.landscape.aws.persistence.MongoObjectFactory;
import com.sap.sse.landscape.aws.persistence.PersistenceFactory;
import com.sap.sse.landscape.mongodb.Database;
import com.sap.sse.landscape.mongodb.MongoEndpoint;
import com.sap.sse.landscape.mongodb.impl.DatabaseImpl;
import com.sap.sse.landscape.rabbitmq.RabbitMQEndpoint;
import com.sap.sse.landscape.ssh.SSHKeyPair;
import com.sap.sse.mongodb.MongoDBService;
@@ -710,6 +712,11 @@ ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, MetricsT, MasterP
return null;
}
@Override
public Database getDatabase(com.sap.sse.landscape.Region region, String databaseName) {
return new DatabaseImpl(getDatabaseConfigurationForDefaultCluster(region), databaseName);
}
@Override
public RabbitMQEndpoint getMessagingConfigurationForDefaultCluster(com.sap.sse.landscape.Region region) {
final RabbitMQEndpoint result;
@@ -8,16 +8,15 @@ import java.util.Map.Entry;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sse.common.Duration;
import com.sap.sse.common.Util;
import com.sap.sse.landscape.AvailabilityZone;
import com.sap.sse.landscape.Landscape;
import com.sap.sse.landscape.MachineImage;
import com.sap.sse.landscape.ProcessConfigurationVariable;
import com.sap.sse.landscape.Region;
import com.sap.sse.landscape.Release;
import com.sap.sse.landscape.ReplicationConfiguration;
import com.sap.sse.landscape.SecurityGroup;
import com.sap.sse.landscape.UserDataProvider;
import com.sap.sse.landscape.application.ApplicationMasterProcess;
@@ -29,7 +28,9 @@ import com.sap.sse.landscape.aws.AwsLandscape;
import com.sap.sse.landscape.aws.Tags;
import com.sap.sse.landscape.aws.impl.AmazonMachineImage;
import com.sap.sse.landscape.aws.impl.AwsRegion;
import com.sap.sse.landscape.mongodb.Database;
import com.sap.sse.landscape.orchestration.StartHost;
import com.sap.sse.landscape.rabbitmq.RabbitMQEndpoint;
import software.amazon.awssdk.services.ec2.model.InstanceType;
@@ -38,12 +39,6 @@ import software.amazon.awssdk.services.ec2.model.InstanceType;
* calling {@link #getHost()}.
*
* @author Axel Uhl (D043530)
*
* @param <ShardingKey>
* @param <MetricsT>
* @param <MasterProcessT>
* @param <ReplicaProcessT>
* @param <HostT>
*/
public abstract class StartAwsHost<ShardingKey,
MetricsT extends ApplicationProcessMetrics,
@@ -51,8 +46,6 @@ public abstract class StartAwsHost<ShardingKey,
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
HostT extends AwsInstance<ShardingKey, MetricsT>>
extends StartHost<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> {
private static final Logger logger = Logger.getLogger(StartAwsHost.class.getName());
/**
* 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
@@ -69,34 +62,413 @@ extends StartHost<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT>
private final String keyName;
private final Iterable<SecurityGroup> securityGroups;
private final Optional<Tags> tags;
private AwsInstance<ShardingKey, MetricsT> host;
private HostT host;
public StartAwsHost(MachineImage machineImage, Optional<Release> release,
AwsLandscape<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> landscape,
InstanceType instanceType, AwsAvailabilityZone availabilityZone, String keyName,
Iterable<SecurityGroup> securityGroups, Optional<Tags> tags, String... userData) {
super(machineImage, landscape);
/**
* A builder that helps building an instance of type {@link StartAwsHost} or any subclass thereof (then using
* specialized builders). The following default rules apply:
* <ul>
* <li>If an {@link #getImageType() image type} has been specified, it serves as the default for looking up the
* {@link #getMachineImage() image} to launch from. However, an image type set explicitly, or subclasses overriding
* {@link #getMachineImage()} may take precedence.</li>
* <li>If no {@link #setRelease(Release) release is set}, an empty {@link Optional} will be returned by
* {@link #getRelease()}, indicating to use the default release pre-deployed in the image launched.</li>
* <li>If no {@link #setAvailabilityZone(AwsAvailabilityZone) availability zone} is specified, this builder will try
* to obtain the {@link #getRegion()} which in this case must have been {@link #setRegion(AwsRegion)} or otherwise
* be returned from an overridden {@link #getRegion()} in a specialized builder, and will
* {@link StartAwsHost#getRandomAvailabilityZone(AwsRegion, AwsLandscape) pick an availability zone randomly} within
* that region.</li>
* <li>Conversely, if an {@link #setAvailabilityZone(AwsAvailabilityZone) availability zone has been set}, its
* {@link AwsAvailabilityZone#getRegion() region} will be the default answer of {@link #getRegion()}.</li>
* <li>If no {@link #setSecurityGroups(Iterable) security group} has been set, the {@link #getLandscape() landscape}
* is asked to provide its {@link AwsLandscape#getDefaultSecurityGroupForApplicationHosts(Region) default security
* group for application hosts} in the {@link #getRegion() region} used by this builder.</li>
* <li>If no {@link #setDatabaseName(String) database name is set explicitly}, it defaults to the
* {@link #getServerName() server name}.</li>
* <li>The {@link #getOptionalTimeout() optional timeout} defaults to an {@link Optional#empty() empty optional}, meaning
* that waiting for the instance won't timeout by default.</li>
* </ul>
*
* @author Axel Uhl (D043530)
*/
public static interface Builder<T extends StartAwsHost<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT>, ShardingKey,
MetricsT extends ApplicationProcessMetrics,
MasterProcessT extends ApplicationMasterProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
HostT extends AwsInstance<ShardingKey, MetricsT>>
extends StartHost.Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> {
@Override
AmazonMachineImage<ShardingKey, MetricsT> getMachineImage();
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setMachineImage(AmazonMachineImage<ShardingKey, MetricsT> 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<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> 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<Release> getRelease();
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setRelease(Optional<Release> release);
@Override
AwsLandscape<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> getLandscape();
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setLandscape(AwsLandscape<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> landscape);
InstanceType getInstanceType();
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setInstanceType(InstanceType instanceType);
AwsAvailabilityZone getAvailabilityZone();
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setAvailabilityZone(AwsAvailabilityZone availabilityZone);
String getKeyName();
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setKeyName(String keyName);
Iterable<SecurityGroup> getSecurityGroups();
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setSecurityGroups(Iterable<SecurityGroup> securityGroups);
Optional<Tags> getTags();
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setTags(Optional<Tags> tags);
String[] getUserData();
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setUserData(String[] userData);
AwsRegion getRegion();
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setRegion(AwsRegion region);
String getInstanceName();
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setInstanceName(String name);
String getServerName();
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setServerName(String serverName);
String getDatabaseName();
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setDatabaseName(String databaseName);
Optional<ReplicationConfiguration> getReplicationConfiguration();
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setReplicationConfiguration(
Optional<ReplicationConfiguration> replicationConfiguration);
String getOutputReplicationExchangeName();
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setOutputReplicationExchangeName(String outputReplicationExchangeName);
RabbitMQEndpoint getRabbitConfiguration();
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setRabbitConfiguration(RabbitMQEndpoint rabbitConfiguration);
Database getDatabaseConfiguration();
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setDatabaseConfiguration(Database databaseConfiguration);
String getCommaSeparatedEmailAddressesToNotifyOfStartup();
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> 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<Duration> getOptionalTimeout();
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setOptionalTimeout(Optional<Duration> optionalTimeout);
}
protected abstract static class BuilderImpl<T extends StartAwsHost<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT>, ShardingKey,
MetricsT extends ApplicationProcessMetrics,
MasterProcessT extends ApplicationMasterProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
HostT extends AwsInstance<ShardingKey, MetricsT>> implements Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> {
private AmazonMachineImage<ShardingKey, MetricsT> machineImage;
private String imageType;
private Optional<Release> release = Optional.empty();
private AwsLandscape<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> landscape;
private InstanceType instanceType;
private AwsAvailabilityZone availabilityZone;
private String keyName;
private Iterable<SecurityGroup> securityGroups;
private Optional<Tags> tags = Optional.empty();
private String[] userData;
private AwsRegion region;
private String instanceName;
private String serverName;
private String databaseName;
private Database databaseConfiguration;
private RabbitMQEndpoint rabbitConfiguration;
private String outputReplicationExchangeName;
private Optional<ReplicationConfiguration> replicationConfiguration;
private String commaSeparatedEmailAddressesToNotifyOfStartup;
private Optional<Duration> optionalTimeout;
@Override
public AmazonMachineImage<ShardingKey, MetricsT> getMachineImage() {
return machineImage == null ? getLatestImageOfType(getImageType(), getLandscape(), getRegion()) : machineImage;
}
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setMachineImage(
AmazonMachineImage<ShardingKey, MetricsT> machineImage) {
this.machineImage = machineImage;
return this;
}
@Override
public String getImageType() {
return imageType;
}
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setImageType(
String imageType) {
this.imageType = imageType;
return this;
}
@Override
public Optional<Release> getRelease() {
return release;
}
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setRelease(Optional<Release> release) {
this.release = release;
return this;
}
@Override
public AwsLandscape<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> getLandscape() {
return landscape;
}
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setLandscape(
AwsLandscape<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> landscape) {
this.landscape = landscape;
return this;
}
@Override
public InstanceType getInstanceType() {
return instanceType;
}
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setInstanceType(
InstanceType instanceType) {
this.instanceType = instanceType;
return this;
}
@Override
public AwsAvailabilityZone getAvailabilityZone() {
return availabilityZone == null ? getRandomAvailabilityZone(getRegion(), getLandscape()) : availabilityZone;
}
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setAvailabilityZone(
AwsAvailabilityZone availabilityZone) {
this.availabilityZone = availabilityZone;
return this;
}
@Override
public String getKeyName() {
return keyName;
}
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setKeyName(String keyName) {
this.keyName = keyName;
return this;
}
@Override
public Iterable<SecurityGroup> getSecurityGroups() {
return securityGroups == null ? Collections.singleton(getLandscape().getDefaultSecurityGroupForApplicationHosts(getRegion())) : securityGroups;
}
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setSecurityGroups(
Iterable<SecurityGroup> securityGroups) {
this.securityGroups = securityGroups;
return this;
}
@Override
public Optional<Tags> getTags() {
return tags;
}
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setTags(Optional<Tags> tags) {
this.tags = tags;
return this;
}
@Override
public String[] getUserData() {
return userData;
}
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setUserData(
String[] userData) {
this.userData = userData;
return this;
}
@Override
public AwsRegion getRegion() {
return region == null ? getAvailabilityZone().getRegion() : region;
}
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setRegion(AwsRegion region) {
this.region = region;
return this;
}
@Override
public String getInstanceName() {
return instanceName;
}
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setInstanceName(String instanceName) {
this.instanceName = instanceName;
return this;
}
@Override
public String getServerName() {
return serverName;
}
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setServerName(String serverName) {
this.serverName = serverName;
return this;
}
@Override
public String getDatabaseName() {
return databaseName == null ? getServerName() : databaseName;
}
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setDatabaseName(String databaseName) {
this.databaseName = databaseName;
return this;
}
@Override
public Database getDatabaseConfiguration() {
return databaseConfiguration == null ? getLandscape().getDatabase(getRegion(), getDatabaseName()) : databaseConfiguration;
}
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setDatabaseConfiguration(Database databaseConfiguration) {
this.databaseConfiguration = databaseConfiguration;
return this;
}
@Override
public RabbitMQEndpoint getRabbitConfiguration() {
return rabbitConfiguration;
}
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setRabbitConfiguration(RabbitMQEndpoint rabbitConfiguration) {
this.rabbitConfiguration = rabbitConfiguration;
return this;
}
@Override
public String getOutputReplicationExchangeName() {
return outputReplicationExchangeName;
}
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setOutputReplicationExchangeName(String outputReplicationExchangeName) {
this.outputReplicationExchangeName = outputReplicationExchangeName;
return this;
}
@Override
public Optional<ReplicationConfiguration> getReplicationConfiguration() {
return replicationConfiguration;
}
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setReplicationConfiguration(Optional<ReplicationConfiguration> replicationConfiguration) {
this.replicationConfiguration = replicationConfiguration;
return this;
}
@Override
public String getCommaSeparatedEmailAddressesToNotifyOfStartup() {
return commaSeparatedEmailAddressesToNotifyOfStartup;
}
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setCommaSeparatedEmailAddressesToNotifyOfStartup(String commaSeparatedEmailAddressesToNotifyOfStartup) {
this.commaSeparatedEmailAddressesToNotifyOfStartup = commaSeparatedEmailAddressesToNotifyOfStartup;
return this;
}
@Override
public Optional<Duration> getOptionalTimeout() {
return optionalTimeout == null ? Optional.empty() : optionalTimeout;
}
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setOptionalTimeout(
Optional<Duration> optionalTimeout) {
this.optionalTimeout = optionalTimeout;
return this;
}
}
protected StartAwsHost(Builder<? extends StartAwsHost<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT>, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> builder) {
super(builder);
this.userData = new ArrayList<>();
for (final String ud : userData) {
for (final String ud : builder.getUserData()) {
this.userData.add(ud);
}
this.instanceType = instanceType;
this.availabilityZone = availabilityZone;
this.keyName = keyName;
this.tags = tags;
this.securityGroups = securityGroups;
release.ifPresent(this::addUserData);
this.instanceType = builder.getInstanceType();
this.availabilityZone = builder.getAvailabilityZone();
this.keyName = builder.getKeyName();
this.tags = builder.getTags();
this.securityGroups = builder.getSecurityGroups();
builder.getRelease().ifPresent(this::addUserData);
addUserData(builder.getDatabaseConfiguration());
addUserData(builder.getRabbitConfiguration());
addUserData(ProcessConfigurationVariable.SERVER_NAME, builder.getServerName());
addUserData(ProcessConfigurationVariable.REPLICATION_CHANNEL, builder.getOutputReplicationExchangeName());
addUserData(ProcessConfigurationVariable.SERVER_STARTUP_NOTIFY, builder.getCommaSeparatedEmailAddressesToNotifyOfStartup());
builder.getReplicationConfiguration().ifPresent(this::addUserData);
}
public StartAwsHost(AmazonMachineImage<ShardingKey, MetricsT> machineImage,
Optional<Release> release,
AwsLandscape<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> landscape, InstanceType instanceType, AwsAvailabilityZone availabilityZone, String keyName,
Optional<Tags> tags, String[] userData) {
this(machineImage, release, landscape, instanceType, availabilityZone,
keyName,
Collections.singleton(landscape.getDefaultSecurityGroupForApplicationHosts(availabilityZone.getRegion())), tags, userData);
}
protected static <ShardingKey,
MetricsT extends ApplicationProcessMetrics,
MasterProcessT extends ApplicationMasterProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
@@ -132,27 +504,20 @@ extends StartHost<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT>
}
@Override
public void run() {
try {
host = getLandscape().launchHost(getMachineImage(), getInstanceType(), getAvailabilityZone(), getKeyName(), getSecurityGroups(), getTags(),
Util.toArray(getUserData(), new String[0]));
} catch (URISyntaxException e) {
logger.log(Level.SEVERE, "Exception trying to launch host", e);
throw new RuntimeException(e);
}
public void run() throws Exception {
@SuppressWarnings({ "unchecked" })
HostT castHost = (HostT) getLandscape().launchHost(getMachineImage(), getInstanceType(), getAvailabilityZone(), getKeyName(), getSecurityGroups(), getTags(),
Util.toArray(getUserData(), new String[0]));
host = castHost;
}
/**
* @return {@code null} before {@link #run()} is called; the host launched afterwards
*/
public AwsInstance<ShardingKey, MetricsT> getHost() {
public HostT getHost() {
return host;
}
public void setHost(AwsInstance<ShardingKey, MetricsT> host) {
this.host = host;
}
private Optional<Tags> getTags() {
return tags;
}
@@ -1,11 +1,8 @@
package com.sap.sailing.landscape;
package com.sap.sse.landscape;
import java.util.HashMap;
import java.util.Map;
import com.sap.sse.landscape.Landscape;
import com.sap.sse.landscape.ProcessConfigurationVariable;
import com.sap.sse.landscape.UserDataProvider;
import com.sap.sse.landscape.rabbitmq.RabbitMQEndpoint;
/**
@@ -1,6 +1,4 @@
package com.sap.sailing.landscape;
import com.sap.sse.landscape.UserDataProvider;
package com.sap.sse.landscape;
/**
* Can be used to authenticate a replica to its master. Authentication may happen by
@@ -2,5 +2,6 @@ package com.sap.sse.landscape.application;
public interface ApplicationMasterProcess<ShardingKey, MetricsT extends ApplicationProcessMetrics,
MasterProcessT extends ApplicationMasterProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>> extends ApplicationProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> {
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>>
extends ApplicationProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> {
}
@@ -16,10 +16,27 @@ extends AbstractProcedureImpl<ShardingKey, MetricsT, MasterProcessT, ReplicaProc
implements Procedure<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> {
private final MachineImage machineImage;
public StartHost(MachineImage machineImage,
Landscape<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> landscape) {
super(landscape);
this.machineImage = machineImage;
/**
* A builder that helps building an instance of type {@link StartHost} or any subclass thereof (then using
* specialized builders).
*
* @author Axel Uhl (D043530)
*/
public static interface Builder<T extends StartHost<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT>, ShardingKey,
MetricsT extends ApplicationProcessMetrics,
MasterProcessT extends ApplicationMasterProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
HostT extends Host> {
T build();
MachineImage getMachineImage();
Landscape<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> getLandscape();
}
protected StartHost(Builder<? extends StartHost<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT>, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> builder) {
super(builder.getLandscape());
this.machineImage = builder.getMachineImage();
}
protected MachineImage getMachineImage() {