bug4811: fixed StartMultiServer and added a test case

This commit is contained in:
Axel Uhl
2020-11-10 12:00:08 +01:00
parent 767b6c1e87
commit 51217e497b
8 changed files with 122 additions and 81 deletions
@@ -7,6 +7,8 @@ import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.Optional;
import java.util.Random;
@@ -26,6 +28,7 @@ import com.sap.sailing.landscape.SailingAnalyticsMaster;
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.StartMultiServer;
import com.sap.sailing.landscape.procedures.StartSailingAnalyticsMaster;
import com.sap.sailing.landscape.procedures.UpgradeAmi;
import com.sap.sse.common.Duration;
@@ -75,6 +78,38 @@ public class TestProcedures {
securityServiceReplicationBearerToken = System.getProperty(SECURITY_SERVICE_REPLICATION_BEARER_TOKEN);
}
@Test
public void testStartupEmptyMultiServer() throws Exception {
final String keyName = "MyKey-"+UUID.randomUUID();
landscape.createKeyPair(region, keyName);
final StartMultiServer.Builder<String, SailingAnalyticsMetrics, SailingAnalyticsMaster<String>, SailingAnalyticsReplica<String>> builder = StartMultiServer.builder();
final StartMultiServer<String, SailingAnalyticsMetrics, SailingAnalyticsMaster<String>, SailingAnalyticsReplica<String>> startEmptyMultiServer = builder
.setLandscape(landscape)
.setKeyName(keyName)
.setOptionalTimeout(optionalTimeout)
.build();
try {
// this is expected to have connected to the default "live" replica set.
startEmptyMultiServer.run();
final AwsInstance<String, SailingAnalyticsMetrics> host = startEmptyMultiServer.getHost();
final SshCommandChannel sshChannel = host.createRootSshChannel(optionalTimeout);
sshChannel.sendCommandLineSynchronously("ls "+SailingAnalyticsHost.DEFAULT_SERVERS_PATH, new ByteArrayOutputStream());
final String result = sshChannel.getStreamContentsAsString();
assertTrue(result.isEmpty());
final HttpURLConnection connection = (HttpURLConnection) new URL("http", host.getPublicAddress().getCanonicalHostName(), 80, "/").openConnection();
assertEquals(200, connection.getResponseCode());
connection.disconnect();
} catch (Exception e) {
logger.log(Level.SEVERE, "Exception while trying to create a MongoDB replica", e);
throw e;
} finally {
if (startEmptyMultiServer.getHost() != null) {
startEmptyMultiServer.getHost().terminate();
}
landscape.deleteKeyPair(region, keyName);
}
}
@Test
public void testAddMongoReplica() throws Exception {
final String keyName = "MyKey-"+UUID.randomUUID();
@@ -6,6 +6,12 @@ import com.sap.sse.landscape.aws.AwsInstance;
import com.sap.sse.landscape.aws.ReverseProxy;
public interface SailingAnalyticsHost<ShardingKey> extends AwsInstance<ShardingKey, SailingAnalyticsMetrics> {
String DEFAULT_SERVERS_PATH = "/home/sailing/servers";
String DEFAULT_SERVER_DIRECTORY_NAME = "server";
String DEFAULT_SERVER_PATH = DEFAULT_SERVERS_PATH+"/"+DEFAULT_SERVER_DIRECTORY_NAME;
/**
* Obtains an object through which an Apache reverse proxy running on this sailing analytics host can be configured.
* It is mainly used to decide how to route based on a URL's hostname or other {@link Scope} identification, and to
@@ -24,7 +24,7 @@ SailingAnalyticsMaster<ShardingKey>, SailingAnalyticsReplica<ShardingKey>> imple
private static final String HEALTH_CHECK_PATH = "/gwt/status";
public SailingAnalyticsProcessImpl(int port, Host host, String serverDirectory) {
super(port, host, "/home/sailing/servers/server");
super(port, host, serverDirectory);
}
@Override
@@ -5,13 +5,13 @@ import java.util.Optional;
import java.util.logging.Logger;
import com.sap.sailing.landscape.SailingAnalyticsHost;
import com.sap.sailing.landscape.SailingAnalyticsMetrics;
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.AwsAvailabilityZone;
import com.sap.sse.landscape.aws.AwsLandscape;
import com.sap.sse.landscape.aws.AwsInstance;
import com.sap.sse.landscape.aws.HostSupplier;
import com.sap.sse.landscape.aws.impl.AwsInstanceImpl;
import com.sap.sse.landscape.ssh.SshCommandChannel;
import software.amazon.awssdk.services.ec2.model.InstanceType;
@@ -31,34 +31,37 @@ import software.amazon.awssdk.services.ec2.model.InstanceType;
* @param <ShardingKey>
* @param <SailingAnalyticsHost<ShardingKey>>
*/
public class StartMultiServer<ShardingKey,
MasterProcessT extends ApplicationMasterProcess<ShardingKey, SailingAnalyticsMetrics, MasterProcessT, ReplicaProcessT>,
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, SailingAnalyticsMetrics, MasterProcessT, ReplicaProcessT>>
extends StartEmptyServer<StartMultiServer<ShardingKey, MasterProcessT, ReplicaProcessT>, ShardingKey, SailingAnalyticsMetrics, MasterProcessT, ReplicaProcessT, SailingAnalyticsHost<ShardingKey>>
public class StartMultiServer<ShardingKey, MetricsT extends ApplicationProcessMetrics,
MasterProcessT extends ApplicationMasterProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>>
extends StartEmptyServer<StartMultiServer<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, AwsInstance<ShardingKey, MetricsT>>
implements StartFromSailingAnalyticsImage {
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.
* that after the upgrade progress the server does not try to re-boot. Defaults:<ul>
* <li>The instance name defaults to "Multi-Server"</li>
* <li>The instance type defaults to {@link InstanceType#C5_D_4_XLARGE}</li>
* </ul>
*
* @author Axel Uhl (D043530)
*/
public static interface Builder<ShardingKey,
MasterProcessT extends ApplicationMasterProcess<ShardingKey, SailingAnalyticsMetrics, MasterProcessT, ReplicaProcessT>,
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, SailingAnalyticsMetrics, MasterProcessT, ReplicaProcessT>>
extends StartEmptyServer.Builder<StartMultiServer<ShardingKey, MasterProcessT, ReplicaProcessT>, ShardingKey, SailingAnalyticsMetrics, MasterProcessT, ReplicaProcessT, SailingAnalyticsHost<ShardingKey>> {
public static interface Builder<ShardingKey, MetricsT extends ApplicationProcessMetrics,
MasterProcessT extends ApplicationMasterProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>>
extends StartEmptyServer.Builder<StartMultiServer<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, AwsInstance<ShardingKey, MetricsT>> {
}
protected static class BuilderImpl<ShardingKey,
MasterProcessT extends ApplicationMasterProcess<ShardingKey, SailingAnalyticsMetrics, MasterProcessT, ReplicaProcessT>,
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, SailingAnalyticsMetrics, MasterProcessT, ReplicaProcessT>>
extends StartEmptyServer.BuilderImpl<StartMultiServer<ShardingKey, MasterProcessT, ReplicaProcessT>,
ShardingKey, SailingAnalyticsMetrics, MasterProcessT, ReplicaProcessT, SailingAnalyticsHost<ShardingKey>>
implements Builder<ShardingKey, MasterProcessT, ReplicaProcessT> {
protected static class BuilderImpl<ShardingKey, MetricsT extends ApplicationProcessMetrics,
MasterProcessT extends ApplicationMasterProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>>
extends StartEmptyServer.BuilderImpl<StartMultiServer<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, AwsInstance<ShardingKey, MetricsT>>
implements Builder<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> {
@Override
public StartMultiServer<ShardingKey, MasterProcessT, ReplicaProcessT> build() {
public StartMultiServer<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> build() {
return new StartMultiServer<>(this);
}
@@ -71,27 +74,49 @@ implements StartFromSailingAnalyticsImage {
protected String getImageType() {
return super.getImageType() == null ? IMAGE_TYPE_TAG_VALUE_SAILING : super.getImageType();
}
@Override
public HostSupplier<ShardingKey, SailingAnalyticsMetrics, MasterProcessT, ReplicaProcessT, SailingAnalyticsHost<ShardingKey>> getHostSupplier() {
return new HostSupplier<ShardingKey, SailingAnalyticsMetrics, MasterProcessT, ReplicaProcessT, SailingAnalyticsHost<ShardingKey>>() {
@Override
public SailingAnalyticsHost<ShardingKey> supply(String instanceId, AwsAvailabilityZone az,
AwsLandscape<ShardingKey, SailingAnalyticsMetrics, MasterProcessT, ReplicaProcessT> landscape) {
return null;
}
};
protected String getInstanceName() {
final String result;
if (isInstanceNameSet()) {
result = super.getInstanceName();
} else {
result = "Multi-Server";
}
return result;
}
@Override
protected HostSupplier<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, AwsInstance<ShardingKey, MetricsT>> getHostSupplier() {
final HostSupplier<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, AwsInstance<ShardingKey, MetricsT>> result;
if (super.getHostSupplier() == null) {
result = AwsInstanceImpl::new;
} else {
result = super.getHostSupplier();
}
return result;
}
@Override
protected InstanceType getInstanceType() {
final InstanceType result;
if (super.getInstanceType() == null) {
result = InstanceType.C5_D_4_XLARGE;
} else {
result = super.getInstanceType();
}
return result;
}
}
public static <ShardingKey,
MasterProcessT extends ApplicationMasterProcess<ShardingKey, SailingAnalyticsMetrics, MasterProcessT, ReplicaProcessT>,
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, SailingAnalyticsMetrics, MasterProcessT, ReplicaProcessT>>
Builder<ShardingKey, MasterProcessT, ReplicaProcessT> builder() {
public static <ShardingKey, MetricsT extends ApplicationProcessMetrics,
MasterProcessT extends ApplicationMasterProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>>
Builder<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> builder() {
return new BuilderImpl<>();
}
protected StartMultiServer(BuilderImpl<ShardingKey, MasterProcessT, ReplicaProcessT> builder) {
protected StartMultiServer(BuilderImpl<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> builder) {
super(builder);
this.optionalTimeout = builder.getOptionalTimeout();
}
@@ -99,12 +124,12 @@ implements StartFromSailingAnalyticsImage {
@Override
public void run() throws Exception {
super.run();
final String instanceId = getHost().getInstanceId();
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());
sshCommandChannel.sendCommandLineSynchronously("rm -rf "+SailingAnalyticsHost.DEFAULT_SERVER_PATH+"; service httpd start", stderr);
logger.info("stdout for removing "+SailingAnalyticsHost.DEFAULT_SERVER_PATH+" and starting httpd service on instance "+instanceId+": "+sshCommandChannel.getStreamContentsAsString());
logger.info("stderr for removing "+SailingAnalyticsHost.DEFAULT_SERVER_PATH+" and starting httpd service on instance \"+instanceId+\": "+stderr.toString());
logger.info("exit status for removing "+SailingAnalyticsHost.DEFAULT_SERVER_PATH+" and starting httpd service on instance \"+instanceId+\": "+sshCommandChannel.getExitStatus());
}
}
@@ -42,15 +42,17 @@ implements Procedure<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsMaste
* 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>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>
* {@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>
* <li>The {@link #getDefaultServerDirectory() server directory} defaults to {@link /home/sailing/servers/server}</li>
* <li>The {@link #getDefaultServerDirectory() server directory} defaults to {code /home/sailing/servers/server}
* (see {@link SailingAnalyticsHost#DEFAULT_SERVER_PATH})</li>
* </ul>
*
* @author Axel Uhl (D043530)
@@ -126,7 +128,7 @@ implements Procedure<ShardingKey, SailingAnalyticsMetrics, SailingAnalyticsMaste
// TODO the host start-up should ideally be separated from the process installation/startup
public String getDefaultServerDirectory() {
return defaultServerDirectory == null ? "/home/sailing/servers/server" : defaultServerDirectory;
return defaultServerDirectory == null ? SailingAnalyticsHost.DEFAULT_SERVER_PATH : defaultServerDirectory;
}
@Override
@@ -7,14 +7,16 @@ import com.sap.sse.landscape.ProcessConfigurationVariable;
/**
* 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>
* (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} (see
* {@link SailingAnalyticsHost#DEFAULT_SERVER_PATH}).
* <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
* (which then should be renamed to {@code DeployApplicationProcessOnServer}). That procedure then would have the
*
* @author Axel Uhl (D043530)
*/
@@ -88,10 +88,6 @@ extends StartHost<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT>
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
HostT extends AwsInstance<ShardingKey, MetricsT>>
extends StartHost.Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> {
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setMachineImage(AmazonMachineImage<ShardingKey, MetricsT> machineImage);
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setImageType(String imageType);
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setRelease(Optional<Release> release);
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setLandscape(AwsLandscape<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> landscape);
@@ -138,8 +134,6 @@ extends StartHost<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT>
HostT extends AwsInstance<ShardingKey, MetricsT>>
extends StartHost.BuilderImpl<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT>
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;
@@ -160,32 +154,6 @@ extends StartHost<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT>
private Optional<Duration> optionalTimeout;
private HostSupplier<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> hostSupplier;
protected AmazonMachineImage<ShardingKey, MetricsT> getMachineImage() {
return machineImage == null ? getLandscape().getLatestImageWithTag((Region) getRegion(), IMAGE_TYPE_TAG_NAME, getImageType()) : machineImage;
}
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setMachineImage(
AmazonMachineImage<ShardingKey, MetricsT> machineImage) {
this.machineImage = machineImage;
return this;
}
/**
* 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;
}
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setImageType(
String imageType) {
this.imageType = imageType;
return this;
}
/**
* By default, the release pre-deployed in the image will be used, represented by an empty {@link Optional}
* returned by this default method implementation.
@@ -43,6 +43,8 @@ implements Procedure<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> {
MasterProcessT extends ApplicationMasterProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
ReplicaProcessT extends ApplicationReplicaProcess<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT>,
HostT extends Host> {
Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setImageType(String imageType);
T build() throws UnknownHostException, URISyntaxException, JSchException, IOException, InterruptedException;
}
@@ -79,7 +81,8 @@ implements Procedure<ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT> {
return imageType;
}
protected Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setImageType(String imageType) {
@Override
public Builder<T, ShardingKey, MetricsT, MasterProcessT, ReplicaProcessT, HostT> setImageType(String imageType) {
this.imageType = imageType;
return this;
}