bug6288: protect live races during landscape operations

Add shared live-content conflict reporting, REST and GWT preflight support, operation-time guards, selective force handling, and landscape tests.

Assisted-By: GPT-5.6-sol
This commit is contained in:
Axel Uhl committed 2026-09-14 23:30:19 +02:00
1 parent 35068d7799
commit 418c8e5634
34 files changed
+1441 -201

No files matched your search

@@ -0,0 +1,47 @@
package com.sap.sailing.landscape.common;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** Live tracked races belonging to one event. */
public final class EventLiveContent implements Serializable {
private static final long serialVersionUID = 852529878882048466L;
private final String eventId;
private final String eventName;
private final Long eventStartMillis;
private final Long eventEndMillis;
private final List<RaceLiveContent> racesWithLiveContent;
public EventLiveContent(final String eventId, final String eventName, final Long eventStartMillis,
final Long eventEndMillis, final Iterable<RaceLiveContent> racesWithLiveContent) {
this.eventId = eventId;
this.eventName = eventName;
this.eventStartMillis = eventStartMillis;
this.eventEndMillis = eventEndMillis;
final List<RaceLiveContent> racesWithLiveContentCopy = new ArrayList<>();
racesWithLiveContent.forEach(racesWithLiveContentCopy::add);
this.racesWithLiveContent = Collections.unmodifiableList(racesWithLiveContentCopy);
}
public String getEventId() {
return eventId;
}
public String getEventName() {
return eventName;
}
public Long getEventStartMillis() {
return eventStartMillis;
}
public Long getEventEndMillis() {
return eventEndMillis;
}
public List<RaceLiveContent> getRacesWithLiveContent() {
return racesWithLiveContent;
}
}
@@ -0,0 +1,45 @@
package com.sap.sailing.landscape.common;
import java.io.Serializable;
/**
* The result of a landscape operation which either completed successfully or was prevented because live content was
* found. Technical failures continue to be reported as exceptions by the operation.
*/
public final class LiveContentAwareOperationResult<T> implements Serializable {
private static final long serialVersionUID = -4892697307147697172L;
private final boolean successful;
private final T successfulResult;
private final LiveContentCheckResult liveContentCheckResult;
private LiveContentAwareOperationResult(final boolean successful, final T successfulResult,
final LiveContentCheckResult liveContentCheckResult) {
if (successful == (liveContentCheckResult != null)) {
throw new IllegalArgumentException("A successful result must not have a live-content conflict and a failed result must have one");
}
this.successful = successful;
this.successfulResult = successfulResult;
this.liveContentCheckResult = liveContentCheckResult;
}
public static <T> LiveContentAwareOperationResult<T> success(final T successfulResult) {
return new LiveContentAwareOperationResult<>(true, successfulResult, null);
}
public static <T> LiveContentAwareOperationResult<T> liveContentConflict(
final LiveContentCheckResult liveContentCheckResult) {
return new LiveContentAwareOperationResult<>(false, null, liveContentCheckResult);
}
public boolean isSuccessful() {
return successful;
}
public T getSuccessfulResult() {
return successfulResult;
}
public LiveContentCheckResult getLiveContentCheckResult() {
return liveContentCheckResult;
}
}
@@ -0,0 +1,36 @@
package com.sap.sailing.landscape.common;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Describes live tracked races found in one or more application replica sets at one instant in time. An empty
* {@link #getReplicaSetsWithLiveContent() list} means that no live content was found.
*/
public final class LiveContentCheckResult implements Serializable {
private static final long serialVersionUID = 4259503344851123760L;
private final long checkedAtMillis;
private final List<ReplicaSetLiveContent> replicaSetsWithLiveContent;
public LiveContentCheckResult(final long checkedAtMillis,
final Iterable<ReplicaSetLiveContent> replicaSetsWithLiveContent) {
this.checkedAtMillis = checkedAtMillis;
final List<ReplicaSetLiveContent> replicaSetsWithLiveContentCopy = new ArrayList<>();
replicaSetsWithLiveContent.forEach(replicaSetsWithLiveContentCopy::add);
this.replicaSetsWithLiveContent = Collections.unmodifiableList(replicaSetsWithLiveContentCopy);
}
public long getCheckedAtMillis() {
return checkedAtMillis;
}
public List<ReplicaSetLiveContent> getReplicaSetsWithLiveContent() {
return replicaSetsWithLiveContent;
}
public boolean hasLiveContent() {
return !replicaSetsWithLiveContent.isEmpty();
}
}
@@ -0,0 +1,36 @@
package com.sap.sailing.landscape.common;
import java.io.Serializable;
/** The identifying and tracking-time information for a live tracked race. */
public final class RaceLiveContent implements Serializable {
private static final long serialVersionUID = 4664388720482818951L;
private final String regattaName;
private final String raceName;
private final long trackingStartMillis;
private final Long trackingEndMillis;
public RaceLiveContent(final String regattaName, final String raceName, final long trackingStartMillis,
final Long trackingEndMillis) {
this.regattaName = regattaName;
this.raceName = raceName;
this.trackingStartMillis = trackingStartMillis;
this.trackingEndMillis = trackingEndMillis;
}
public String getRegattaName() {
return regattaName;
}
public String getRaceName() {
return raceName;
}
public long getTrackingStartMillis() {
return trackingStartMillis;
}
public Long getTrackingEndMillis() {
return trackingEndMillis;
}
}
@@ -0,0 +1,29 @@
package com.sap.sailing.landscape.common;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** Live content found in one application replica set. */
public final class ReplicaSetLiveContent implements Serializable {
private static final long serialVersionUID = -8106650879852011910L;
private final String replicaSetName;
private final List<EventLiveContent> eventsWithLiveContent;
public ReplicaSetLiveContent(final String replicaSetName,
final Iterable<EventLiveContent> eventsWithLiveContent) {
this.replicaSetName = replicaSetName;
final List<EventLiveContent> eventsWithLiveContentCopy = new ArrayList<>();
eventsWithLiveContent.forEach(eventsWithLiveContentCopy::add);
this.eventsWithLiveContent = Collections.unmodifiableList(eventsWithLiveContentCopy);
}
public String getReplicaSetName() {
return replicaSetName;
}
public List<EventLiveContent> getEventsWithLiveContent() {
return eventsWithLiveContent;
}
}
@@ -22,6 +22,7 @@ Require-Bundle: com.sap.sailing.landscape,
com.sap.sse.landscape,
com.sap.sse.landscape.aws,
com.sap.sailing.landscape,
com.sap.sailing.landscape.common,
org.apache.shiro.core;bundle-version="1.7.1",
org.apache.shiro.ehcache;bundle-version="1.7.1",
org.apache.shiro.web;bundle-version="1.7.1",
@@ -1,9 +1,12 @@
package com.sap.sailing.landscape.gateway.jaxrs.api;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -27,6 +30,7 @@ import org.json.simple.JSONObject;
import com.sap.sailing.domain.common.DataImportProgress;
import com.sap.sailing.landscape.AwsSessionCredentialsWithExpiry;
import com.sap.sailing.landscape.LandscapeService;
import com.sap.sailing.landscape.LiveContentConflictException;
import com.sap.sailing.landscape.SailingAnalyticsHost;
import com.sap.sailing.landscape.SailingAnalyticsMetrics;
import com.sap.sailing.landscape.SailingAnalyticsProcess;
@@ -34,10 +38,12 @@ import com.sap.sailing.landscape.gateway.impl.AwsApplicationProcessJsonSerialize
import com.sap.sailing.landscape.gateway.impl.AwsApplicationReplicaSetJsonSerializer;
import com.sap.sailing.landscape.gateway.impl.HostJsonSerializer;
import com.sap.sailing.landscape.gateway.jaxrs.AbstractLandscapeResource;
import com.sap.sailing.landscape.common.LiveContentCheckResult;
import com.sap.sailing.landscape.procedures.SailingAnalyticsHostSupplier;
import com.sap.sailing.server.gateway.interfaces.CompareServersResult;
import com.sap.sailing.server.gateway.serialization.impl.CompareServersResultJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.DataImportProgressJsonSerializer;
import com.sap.sailing.server.gateway.serialization.impl.LiveContentCheckResultJsonSerializer;
import com.sap.sse.common.Duration;
import com.sap.sse.common.Util.Triple;
import com.sap.sse.landscape.Landscape;
@@ -100,6 +106,8 @@ public class SailingLandscapeResource extends AbstractLandscapeResource {
private static final String REPLICA_SET_NAME = "replicaSetName";
private static final String PORT = "port";
private static final String IGTIMI_RIOT_PORT = "igtimiRiotPort";
private static final String FORCE_FORM_PARAM = "force";
private static final String FORCE_REPLICA_SET_FORM_PARAM = "forceReplicaSet";
@Context
UriInfo uriInfo;
@@ -212,8 +220,7 @@ public class SailingLandscapeResource extends AbstractLandscapeResource {
dedicatedInstanceType, dynamicLoadBalancerMapping, release.getName(),
optionalKeyName, privateKeyEncryptionPassphrase == null ? null : privateKeyEncryptionPassphrase.getBytes(), masterReplicationBearerToken,
replicaReplicationBearerToken, domainName, optionalMemoryInMegabytesOrNull,
optionalMemoryTotalSizeFactorOrNull, optionalIgtimiRiotPort, Optional.ofNullable(optionalMinimumAutoScalingGroupSize),
Optional.ofNullable(optionalMaximumAutoScalingGroupSize));
optionalMemoryTotalSizeFactorOrNull, optionalIgtimiRiotPort, Optional.ofNullable(optionalMinimumAutoScalingGroupSize), Optional.ofNullable(optionalMaximumAutoScalingGroupSize));
final JSONObject result = new AwsApplicationReplicaSetJsonSerializer(release.getName()).serialize(replicaSet);
response = Response.ok(streamingOutput(result)).build();
} catch (Exception e) {
@@ -234,7 +241,8 @@ public class SailingLandscapeResource extends AbstractLandscapeResource {
@FormParam(KEY_NAME_FORM_PARAM) String optionalKeyName,
@FormParam(PRIVATE_KEY_ENCRYPTION_PASSPHRASE_FORM_PARAM) String privateKeyEncryptionPassphrase,
@FormParam(REPLICA_REPLICATION_BEARER_TOKEN_FORM_PARAM) String replicaReplicationBearerToken,
@FormParam(TIMEOUT_IN_MILLISECONDS_FORM_PARAM) Long optionalTimeoutInMilliseconds) {
@FormParam(TIMEOUT_IN_MILLISECONDS_FORM_PARAM) Long optionalTimeoutInMilliseconds,
@FormParam(FORCE_FORM_PARAM) @DefaultValue("false") boolean force) {
checkLandscapeManageAwsPermission();
ResponseBuilder responseBuilder;
final JSONObject result = new JSONObject();
@@ -245,19 +253,62 @@ public class SailingLandscapeResource extends AbstractLandscapeResource {
replicaSet = getLandscapeService().getApplicationReplicaSet(region, replicaSetName, optionalTimeoutInMilliseconds, optionalKeyName, passphraseForPrivateKeyDecryption);
final AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> upgradedReplicaSet = getLandscapeService().upgradeApplicationReplicaSet(region, replicaSet,
releaseNameOrNullForLatestMaster, optionalKeyName, passphraseForPrivateKeyDecryption,
replicaReplicationBearerToken);
replicaReplicationBearerToken, force);
responseBuilder = Response.ok();
result.put(RESPONSE_STATUS, "OK");
result.put(RESPONSE_RELEASE, upgradedReplicaSet.getVersion(Optional.ofNullable(optionalTimeoutInMilliseconds).map(Duration::ofMillis),
Optional.ofNullable(optionalKeyName), passphraseForPrivateKeyDecryption).getName());
} catch (LiveContentConflictException e) {
return liveContentConflict(e.getLiveContentCheckResult());
} catch (Exception e) {
result.put(RESPONSE_STATUS, "ERROR");
result.put(RESPONSE_ERROR_MESSAGE, e.getMessage());
responseBuilder = Response.serverError();
}
return responseBuilder.entity(streamingOutput(result)).build();
}
}
@Path("/stopreplicatingandremovemasterfromtargetgroups")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces("application/json;charset=UTF-8")
public Response ensureAtLeastOneReplicaExistsStopReplicatingAndRemoveMasterFromTargetGroups(
@FormParam(REGION_FORM_PARAM) final String regionId,
@FormParam(REPLICA_SET_NAME_FORM_PARAM) final String replicaSetName,
@FormParam(TIMEOUT_IN_MILLISECONDS_FORM_PARAM) final Long optionalTimeoutInMilliseconds,
@FormParam(KEY_NAME_FORM_PARAM) final String optionalKeyName,
@FormParam(PRIVATE_KEY_ENCRYPTION_PASSPHRASE_FORM_PARAM) final String privateKeyEncryptionPassphrase,
@FormParam(REPLICA_REPLICATION_BEARER_TOKEN_FORM_PARAM) final String replicaReplicationBearerToken,
@FormParam(FORCE_FORM_PARAM) @DefaultValue("false") final boolean force) {
checkLandscapeManageAwsPermission();
Response response;
try {
final AwsRegion region = new AwsRegion(regionId, getLandscapeService().getLandscape());
final byte[] passphrase = privateKeyEncryptionPassphrase == null ? null
: privateKeyEncryptionPassphrase.getBytes();
final AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> replicaSet =
getLandscapeService().getApplicationReplicaSet(region, replicaSetName,
optionalTimeoutInMilliseconds, optionalKeyName, passphrase);
if (replicaSet == null) {
response = badRequest("Application replica set with name " + replicaSetName + " not found in region "
+ regionId);
} else {
final SailingAnalyticsProcess<String> additionalReplicaStarted = getLandscapeService()
.ensureAtLeastOneReplicaExistsStopReplicatingAndRemoveMasterFromTargetGroups(replicaSet,
optionalKeyName, passphrase,
getLandscapeService().getEffectiveBearerToken(replicaReplicationBearerToken), force);
final JSONObject result = new JSONObject();
result.put("additionalReplicaStarted", additionalReplicaStarted != null);
response = Response.ok(streamingOutput(result)).build();
}
} catch (LiveContentConflictException e) {
response = liveContentConflict(e.getLiveContentCheckResult());
} catch (Exception e) {
response = badRequest(e.getMessage());
}
return response;
}
@Path("/movetoarchiveserver")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@@ -273,7 +324,8 @@ public class SailingLandscapeResource extends AbstractLandscapeResource {
@FormParam(DURATION_TO_WAIT_BEFORE_COMPARE_SERVERS_IN_MILLISECONDS_FORM_PARAM) @DefaultValue("300000") long durationToWaitBeforeCompareServersInMillis,
@FormParam(MAX_NUMBER_OF_COMPARE_SERVER_ATTEMPTS_FORM_PARAM) @DefaultValue("5") int maxNumberOfCompareServerAttempts,
@FormParam(REMOVE_APPLICATION_REPLICA_SET_FORM_PARAM) @DefaultValue("true") boolean removeApplicationReplicaSet,
@FormParam(MONGO_URI_TO_ARCHIVE_DB_TO_FORM_PARAM) String mongoUriToArchiveDbTo) {
@FormParam(MONGO_URI_TO_ARCHIVE_DB_TO_FORM_PARAM) String mongoUriToArchiveDbTo,
@FormParam(FORCE_FORM_PARAM) @DefaultValue("false") boolean force) {
checkLandscapeManageAwsPermission();
if (removeApplicationReplicaSet) {
getSecurityService().checkCurrentUserDeletePermission(SecuredSecurityTypes.SERVER.getQualifiedObjectIdentifier(
@@ -295,13 +347,15 @@ public class SailingLandscapeResource extends AbstractLandscapeResource {
final Triple<DataImportProgress, CompareServersResult, String> mdiProgressAndCompareServersResult = getLandscapeService().archiveReplicaSet(regionId, applicationReplicaSetToArchive,
bearerTokenOrNullForApplicationReplicaSetToArchive, bearerTokenOrNullForArchive,
Duration.ofMillis(durationToWaitBeforeCompareServersInMillis), maxNumberOfCompareServerAttempts, removeApplicationReplicaSet,
moveDatabaseHere, optionalKeyName, passphraseForPrivateKeyDecryption);
moveDatabaseHere, optionalKeyName, passphraseForPrivateKeyDecryption, force);
final JSONObject result = new JSONObject();
result.put(MDI_PROGRESS, mdiProgressAndCompareServersResult.getA()==null?null:new DataImportProgressJsonSerializer().serialize(mdiProgressAndCompareServersResult.getA()));
result.put(COMPARE_SERVERS_RESULT, mdiProgressAndCompareServersResult.getB()==null?null:new CompareServersResultJsonSerializer().serialize(mdiProgressAndCompareServersResult.getB()));
result.put(ARCHIVE_MONGODB_RESULT, mdiProgressAndCompareServersResult.getC());
response = Response.ok(streamingOutput(result)).build();
}
} catch (LiveContentConflictException e) {
response = liveContentConflict(e.getLiveContentCheckResult());
} catch (Exception e) {
logger.log(Level.SEVERE, "Error trying to archive replica set "+replicaSetName+" in region "+regionId+": "+e.getMessage(), e);
response = badRequest("Error trying to archive replica set "+replicaSetName+" in region "+regionId+": "+e.getMessage());
@@ -318,7 +372,8 @@ public class SailingLandscapeResource extends AbstractLandscapeResource {
@FormParam(INSTANCE_ID) String fromInstanceWithId,
@FormParam(INSTANCE_TYPE_FORM_PARAM) String optionalInstanceType,
@FormParam(KEY_NAME_FORM_PARAM) String optionalKeyName,
@FormParam(PRIVATE_KEY_ENCRYPTION_PASSPHRASE_FORM_PARAM) String privateKeyEncryptionPassphrase) {
@FormParam(PRIVATE_KEY_ENCRYPTION_PASSPHRASE_FORM_PARAM) String privateKeyEncryptionPassphrase,
@FormParam(FORCE_REPLICA_SET_FORM_PARAM) Set<String> forceMasterReplicaSetNames) {
checkLandscapeManageAwsPermission();
Response response;
try {
@@ -328,7 +383,9 @@ public class SailingLandscapeResource extends AbstractLandscapeResource {
final Triple<SailingAnalyticsHost<String>, Map<String, SailingAnalyticsProcess<String>>, Map<String, SailingAnalyticsProcess<String>>> result = getLandscapeService()
.moveAllApplicationProcessesAwayFrom(fromHost,
Optional.ofNullable(optionalInstanceType == null ? null : InstanceType.valueOf(optionalInstanceType)),
optionalKeyName, passphraseForPrivateKeyDecryption);
optionalKeyName, passphraseForPrivateKeyDecryption,
forceMasterReplicaSetNames == null ? Collections.emptySet()
: new HashSet<>(forceMasterReplicaSetNames));
final JSONObject jsonResult = new JSONObject();
jsonResult.put(NEW_HOST_ID, result.getA().getId());
final JSONArray masterProcessesMoved = getProcessesAndTheirReplicaSetNamesAsJSONArray(result.getB());
@@ -336,6 +393,8 @@ public class SailingLandscapeResource extends AbstractLandscapeResource {
final JSONArray replicaProcessesMoved = getProcessesAndTheirReplicaSetNamesAsJSONArray(result.getC());
jsonResult.put(REPLICA_PROCESSES_MOVED, replicaProcessesMoved);
response = Response.ok(streamingOutput(jsonResult)).build();
} catch (LiveContentConflictException e) {
response = liveContentConflict(e.getLiveContentCheckResult());
} catch (Exception e) {
final String msg = "Error trying to move processes away from instance with ID "+fromInstanceWithId+" in region "+regionId+": "+e.getMessage();
logger.log(Level.SEVERE, msg, e);
@@ -344,6 +403,12 @@ public class SailingLandscapeResource extends AbstractLandscapeResource {
return response;
}
private Response liveContentConflict(final LiveContentCheckResult liveContentCheckResult) {
return Response.status(Status.CONFLICT)
.entity(streamingOutput(new LiveContentCheckResultJsonSerializer().serialize(liveContentCheckResult)))
.build();
}
private JSONArray getProcessesAndTheirReplicaSetNamesAsJSONArray(Map<String, SailingAnalyticsProcess<String>> processesKeyedByTheirApplicationReplicaSetName) {
final JSONArray result = new JSONArray();
for (final Entry<String, SailingAnalyticsProcess<String>> e : processesKeyedByTheirApplicationReplicaSetName.entrySet()) {
@@ -364,7 +429,8 @@ public class SailingLandscapeResource extends AbstractLandscapeResource {
@FormParam(TIMEOUT_IN_MILLISECONDS_FORM_PARAM) Long optionalTimeoutInMilliseconds,
@FormParam(KEY_NAME_FORM_PARAM) String optionalKeyName,
@FormParam(PRIVATE_KEY_ENCRYPTION_PASSPHRASE_FORM_PARAM) String privateKeyEncryptionPassphrase,
@FormParam(MONGO_URI_TO_ARCHIVE_DB_TO_FORM_PARAM) String mongoUriToArchiveDbTo) {
@FormParam(MONGO_URI_TO_ARCHIVE_DB_TO_FORM_PARAM) String mongoUriToArchiveDbTo,
@FormParam(FORCE_FORM_PARAM) @DefaultValue("false") boolean force) {
checkLandscapeManageAwsPermission();
getSecurityService().checkCurrentUserDeletePermission(SecuredSecurityTypes.SERVER.getQualifiedObjectIdentifier(
new TypeRelativeObjectIdentifier(replicaSetName)));
@@ -382,9 +448,11 @@ public class SailingLandscapeResource extends AbstractLandscapeResource {
final MongoUriParser<String> mongoUriParser = new MongoUriParser<>(landscape, region);
final MongoEndpoint moveDatabaseHere = mongoUriToArchiveDbTo == null ? null : mongoUriParser.parseMongoUri(mongoUriToArchiveDbTo).getEndpoint();
getLandscapeService().removeApplicationReplicaSet(regionId, applicationReplicaSetToRemove, moveDatabaseHere,
optionalKeyName, passphraseForPrivateKeyDecryption);
optionalKeyName, passphraseForPrivateKeyDecryption, force);
response = Response.ok().build();
}
} catch (LiveContentConflictException e) {
response = liveContentConflict(e.getLiveContentCheckResult());
} catch (Exception e) {
logger.log(Level.SEVERE, "Error trying to archive replica set "+replicaSetName+" in region "+regionId+": "+e.getMessage(), e);
response = badRequest("Error trying to archive replica set "+replicaSetName+" in region "+regionId+": "+e.getMessage());
@@ -420,9 +488,10 @@ public class SailingLandscapeResource extends AbstractLandscapeResource {
} else {
final LandscapeService landscapeService = getLandscapeService();
final SailingAnalyticsHost<String> hostToDeployTo = landscapeService.getLandscape().getHostByInstanceId(region, hostId, new SailingAnalyticsHostSupplier<>());
if (landscapeService.isEligibleForDeployment(hostToDeployTo, replicaSet.getServerName(), replicaSet.getPort(),
optionalIgtimiRiotPort,
optionalTimeoutInMilliseconds == null ? Optional.empty() : Optional.of(Duration.ofMillis(optionalTimeoutInMilliseconds)),
if (landscapeService.isEligibleForDeployment(hostToDeployTo, replicaSet.getServerName(),
replicaSet.getPort(), optionalIgtimiRiotPort,
optionalTimeoutInMilliseconds == null ? Optional.empty()
: Optional.of(Duration.ofMillis(optionalTimeoutInMilliseconds)),
optionalKeyName, passphraseForPrivateKeyDecryption)) {
final SailingAnalyticsProcess<String> process = getLandscapeService().deployReplicaToExistingHost(replicaSet, hostToDeployTo, optionalKeyName,
passphraseForPrivateKeyDecryption, replicaReplicationBearerToken,
@@ -576,7 +645,8 @@ public class SailingLandscapeResource extends AbstractLandscapeResource {
@FormParam(MASTER_REPLICATION_BEARER_TOKEN_FORM_PARAM) String masterReplicationBearerToken,
@FormParam(REPLICA_REPLICATION_BEARER_TOKEN_FORM_PARAM) String replicaReplicationBearerToken,
@FormParam(MEMORY_IN_MEGABYTES_FORM_PARAM) Integer optionalMemoryInMegabytesOrNull,
@FormParam(MEMORY_TOTAL_SIZE_FACTOR_FORM_PARAM) Integer optionalMemoryTotalSizeFactorOrNull) {
@FormParam(MEMORY_TOTAL_SIZE_FACTOR_FORM_PARAM) Integer optionalMemoryTotalSizeFactorOrNull,
@FormParam(FORCE_FORM_PARAM) @DefaultValue("false") boolean force) {
checkLandscapeManageAwsPermission();
Response response;
final AwsRegion region = new AwsRegion(regionId, getLandscapeService().getLandscape());
@@ -596,7 +666,7 @@ public class SailingLandscapeResource extends AbstractLandscapeResource {
Optional.ofNullable(optionalInstanceType).map(instanceTypeName->InstanceType.valueOf(instanceTypeName)),
optionalPreferredInstanceToDeployTo, optionalKeyName, passphraseForPrivateKeyDecryption,
masterReplicationBearerToken, replicaReplicationBearerToken,
optionalMemoryInMegabytesOrNull, optionalMemoryTotalSizeFactorOrNull);
optionalMemoryInMegabytesOrNull, optionalMemoryTotalSizeFactorOrNull, force);
response = Response.ok()
.entity(streamingOutput(new AwsApplicationReplicaSetJsonSerializer(result.getVersion(
optionalTimeoutInMilliseconds == null ? Landscape.WAIT_FOR_PROCESS_TIMEOUT
@@ -604,6 +674,8 @@ public class SailingLandscapeResource extends AbstractLandscapeResource {
Optional.ofNullable(optionalKeyName), passphraseForPrivateKeyDecryption).getName()).serialize(result)))
.build();
}
} catch (LiveContentConflictException e) {
response = liveContentConflict(e.getLiveContentCheckResult());
} catch (Exception e) {
logger.log(Level.SEVERE, "Error trying to archive replica set "+replicaSetName+" in region "+regionId+": "+e.getMessage(), e);
response = badRequest("Error trying to archive replica set "+replicaSetName+" in region "+regionId+": "+e.getMessage());
@@ -611,6 +683,35 @@ public class SailingLandscapeResource extends AbstractLandscapeResource {
return response;
}
@Path("/livecontentcheck")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces("application/json;charset=UTF-8")
public Response checkForLiveContent(@FormParam(REGION_FORM_PARAM) final String regionId,
@FormParam(REPLICA_SET_NAME_FORM_PARAM) final Set<String> replicaSetNames,
@FormParam(TIMEOUT_IN_MILLISECONDS_FORM_PARAM) final Long optionalTimeoutInMilliseconds,
@FormParam(KEY_NAME_FORM_PARAM) final String optionalKeyName,
@FormParam(PRIVATE_KEY_ENCRYPTION_PASSPHRASE_FORM_PARAM) final String privateKeyEncryptionPassphrase,
@FormParam(MASTER_REPLICATION_BEARER_TOKEN_FORM_PARAM) final String bearerToken) {
checkLandscapeManageAwsPermission();
Response response;
try {
final AwsRegion region = new AwsRegion(regionId, getLandscapeService().getLandscape());
final byte[] passphrase = privateKeyEncryptionPassphrase == null ? null
: privateKeyEncryptionPassphrase.getBytes();
final Set<AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>>> replicaSets = new HashSet<>();
for (final String replicaSetName : replicaSetNames) {
replicaSets.add(getLandscapeService().getApplicationReplicaSet(region, replicaSetName,
optionalTimeoutInMilliseconds, optionalKeyName, passphrase));
}
response = Response.ok(streamingOutput(new LiveContentCheckResultJsonSerializer()
.serialize(getLandscapeService().checkForLiveContent(replicaSets, bearerToken)))).build();
} catch (Exception e) {
response = badRequest(e.getMessage());
}
return response;
}
@Path("/changeautoscalingreplicasinstancetype")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@@ -634,7 +735,8 @@ public class SailingLandscapeResource extends AbstractLandscapeResource {
} else {
final AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> result = getLandscapeService()
.changeAutoScalingReplicasInstanceType(replicaSet, InstanceType.valueOf(instanceType),
Landscape.WAIT_FOR_PROCESS_TIMEOUT, Optional.ofNullable(optionalKeyName), passphraseForPrivateKeyDecryption);
Landscape.WAIT_FOR_PROCESS_TIMEOUT, Optional.ofNullable(optionalKeyName),
passphraseForPrivateKeyDecryption);
response = Response.ok()
.entity(streamingOutput(new AwsApplicationReplicaSetJsonSerializer(result.getVersion(
Landscape.WAIT_FOR_PROCESS_TIMEOUT, Optional.ofNullable(optionalKeyName), passphraseForPrivateKeyDecryption).getName()).serialize(result)))
@@ -0,0 +1,48 @@
package com.sap.sailing.landscape.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import com.sap.sailing.landscape.common.EventLiveContent;
import com.sap.sailing.landscape.common.LiveContentAwareOperationResult;
import com.sap.sailing.landscape.common.LiveContentCheckResult;
import com.sap.sailing.landscape.common.RaceLiveContent;
import com.sap.sailing.landscape.common.ReplicaSetLiveContent;
public class TestLiveContentValueTypes {
@Test
public void testEmptyCheckResult() {
final LiveContentCheckResult result = new LiveContentCheckResult(42L, Collections.emptyList());
assertFalse(result.hasLiveContent());
assertEquals(42L, result.getCheckedAtMillis());
}
@Test
public void testNestedLiveContent() {
final RaceLiveContent race = new RaceLiveContent("regatta", "race", 10L, null);
final EventLiveContent event = new EventLiveContent("event-id", "event", 1L, 100L,
Collections.singleton(race));
final ReplicaSetLiveContent replicaSet = new ReplicaSetLiveContent("replica-set",
Collections.singleton(event));
final LiveContentCheckResult result = new LiveContentCheckResult(42L, Collections.singleton(replicaSet));
assertTrue(result.hasLiveContent());
assertEquals("race", result.getReplicaSetsWithLiveContent().get(0).getEventsWithLiveContent().get(0)
.getRacesWithLiveContent().get(0).getRaceName());
assertNull(race.getTrackingEndMillis());
}
@Test
public void testOperationResultInvariants() {
final LiveContentCheckResult conflict = new LiveContentCheckResult(42L, Collections.emptyList());
assertTrue(LiveContentAwareOperationResult.success("ok").isSuccessful());
assertFalse(LiveContentAwareOperationResult.liveContentConflict(conflict).isSuccessful());
assertThrows(IllegalArgumentException.class, () -> LiveContentAwareOperationResult.liveContentConflict(null));
}
}
@@ -28,7 +28,9 @@ Require-Bundle: com.sap.sse.gwt,
com.sap.sse,
com.sap.sse.shared.android,
com.sap.sailing.domain
Export-Package: com.google.gwt.user.client.rpc.core.com.sap.sse.landscape.aws.common.shared,
Export-Package: com.google.gwt.user.client.rpc.core.com.sap.sailing.landscape.common,
com.google.gwt.user.client.rpc.core.com.sap.sailing.landscape.ui.shared,
com.google.gwt.user.client.rpc.core.com.sap.sse.landscape.aws.common.shared,
com.google.gwt.user.client.rpc.core.com.sap.sse.landscape.common.shared,
com.sap.sailing.landscape.ui.client,
com.sap.sailing.landscape.ui.server,
@@ -0,0 +1,63 @@
package com.google.gwt.user.client.rpc.core.com.sap.sailing.landscape.common;
import java.util.ArrayList;
import java.util.List;
import com.google.gwt.user.client.rpc.CustomFieldSerializer;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamReader;
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
import com.sap.sailing.landscape.common.EventLiveContent;
import com.sap.sailing.landscape.common.RaceLiveContent;
public final class EventLiveContent_CustomFieldSerializer extends CustomFieldSerializer<EventLiveContent> {
public static void serialize(final SerializationStreamWriter writer, final EventLiveContent instance)
throws SerializationException {
writer.writeString(instance.getEventId());
writer.writeString(instance.getEventName());
writer.writeObject(instance.getEventStartMillis());
writer.writeObject(instance.getEventEndMillis());
writer.writeInt(instance.getRacesWithLiveContent().size());
for (final RaceLiveContent race : instance.getRacesWithLiveContent()) {
writer.writeObject(race);
}
}
public static EventLiveContent instantiate(final SerializationStreamReader reader) throws SerializationException {
final String eventId = reader.readString();
final String eventName = reader.readString();
final Long eventStartMillis = (Long) reader.readObject();
final Long eventEndMillis = (Long) reader.readObject();
final int raceCount = reader.readInt();
final List<RaceLiveContent> races = new ArrayList<>(raceCount);
for (int i = 0; i < raceCount; i++) {
races.add((RaceLiveContent) reader.readObject());
}
return new EventLiveContent(eventId, eventName, eventStartMillis, eventEndMillis, races);
}
public static void deserialize(final SerializationStreamReader reader, final EventLiveContent instance) {
// Fully initialized by instantiate(...)
}
@Override
public void serializeInstance(final SerializationStreamWriter writer, final EventLiveContent instance)
throws SerializationException {
serialize(writer, instance);
}
@Override
public boolean hasCustomInstantiateInstance() {
return true;
}
@Override
public EventLiveContent instantiateInstance(final SerializationStreamReader reader) throws SerializationException {
return instantiate(reader);
}
@Override
public void deserializeInstance(final SerializationStreamReader reader, final EventLiveContent instance) {
deserialize(reader, instance);
}
}
@@ -0,0 +1,60 @@
package com.google.gwt.user.client.rpc.core.com.sap.sailing.landscape.common;
import com.google.gwt.user.client.rpc.CustomFieldSerializer;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamReader;
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
import com.sap.sailing.landscape.common.LiveContentAwareOperationResult;
import com.sap.sailing.landscape.common.LiveContentCheckResult;
public final class LiveContentAwareOperationResult_CustomFieldSerializer
extends CustomFieldSerializer<LiveContentAwareOperationResult<?>> {
public static void serialize(final SerializationStreamWriter writer,
final LiveContentAwareOperationResult<?> instance) throws SerializationException {
writer.writeBoolean(instance.isSuccessful());
writer.writeObject(instance.getSuccessfulResult());
writer.writeObject(instance.getLiveContentCheckResult());
}
public static LiveContentAwareOperationResult<?> instantiate(final SerializationStreamReader reader)
throws SerializationException {
final boolean successful = reader.readBoolean();
final Object successfulResult = reader.readObject();
final LiveContentCheckResult liveContentCheckResult = (LiveContentCheckResult) reader.readObject();
final LiveContentAwareOperationResult<?> result;
if (successful) {
result = LiveContentAwareOperationResult.success(successfulResult);
} else {
result = LiveContentAwareOperationResult.liveContentConflict(liveContentCheckResult);
}
return result;
}
public static void deserialize(final SerializationStreamReader reader,
final LiveContentAwareOperationResult<?> instance) {
// Fully initialized by instantiate(...)
}
@Override
public void serializeInstance(final SerializationStreamWriter writer,
final LiveContentAwareOperationResult<?> instance) throws SerializationException {
serialize(writer, instance);
}
@Override
public boolean hasCustomInstantiateInstance() {
return true;
}
@Override
public LiveContentAwareOperationResult<?> instantiateInstance(final SerializationStreamReader reader)
throws SerializationException {
return instantiate(reader);
}
@Override
public void deserializeInstance(final SerializationStreamReader reader,
final LiveContentAwareOperationResult<?> instance) {
deserialize(reader, instance);
}
}
@@ -0,0 +1,59 @@
package com.google.gwt.user.client.rpc.core.com.sap.sailing.landscape.common;
import java.util.ArrayList;
import java.util.List;
import com.google.gwt.user.client.rpc.CustomFieldSerializer;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamReader;
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
import com.sap.sailing.landscape.common.LiveContentCheckResult;
import com.sap.sailing.landscape.common.ReplicaSetLiveContent;
public final class LiveContentCheckResult_CustomFieldSerializer extends CustomFieldSerializer<LiveContentCheckResult> {
public static void serialize(final SerializationStreamWriter writer, final LiveContentCheckResult instance)
throws SerializationException {
writer.writeLong(instance.getCheckedAtMillis());
writer.writeInt(instance.getReplicaSetsWithLiveContent().size());
for (final ReplicaSetLiveContent replicaSet : instance.getReplicaSetsWithLiveContent()) {
writer.writeObject(replicaSet);
}
}
public static LiveContentCheckResult instantiate(final SerializationStreamReader reader)
throws SerializationException {
final long checkedAtMillis = reader.readLong();
final int replicaSetCount = reader.readInt();
final List<ReplicaSetLiveContent> replicaSets = new ArrayList<>(replicaSetCount);
for (int i = 0; i < replicaSetCount; i++) {
replicaSets.add((ReplicaSetLiveContent) reader.readObject());
}
return new LiveContentCheckResult(checkedAtMillis, replicaSets);
}
public static void deserialize(final SerializationStreamReader reader, final LiveContentCheckResult instance) {
// Fully initialized by instantiate(...)
}
@Override
public void serializeInstance(final SerializationStreamWriter writer, final LiveContentCheckResult instance)
throws SerializationException {
serialize(writer, instance);
}
@Override
public boolean hasCustomInstantiateInstance() {
return true;
}
@Override
public LiveContentCheckResult instantiateInstance(final SerializationStreamReader reader)
throws SerializationException {
return instantiate(reader);
}
@Override
public void deserializeInstance(final SerializationStreamReader reader, final LiveContentCheckResult instance) {
deserialize(reader, instance);
}
}
@@ -0,0 +1,47 @@
package com.google.gwt.user.client.rpc.core.com.sap.sailing.landscape.common;
import com.google.gwt.user.client.rpc.CustomFieldSerializer;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamReader;
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
import com.sap.sailing.landscape.common.RaceLiveContent;
public final class RaceLiveContent_CustomFieldSerializer extends CustomFieldSerializer<RaceLiveContent> {
public static void serialize(final SerializationStreamWriter writer, final RaceLiveContent instance)
throws SerializationException {
writer.writeString(instance.getRegattaName());
writer.writeString(instance.getRaceName());
writer.writeLong(instance.getTrackingStartMillis());
writer.writeObject(instance.getTrackingEndMillis());
}
public static RaceLiveContent instantiate(final SerializationStreamReader reader) throws SerializationException {
return new RaceLiveContent(reader.readString(), reader.readString(), reader.readLong(),
(Long) reader.readObject());
}
public static void deserialize(final SerializationStreamReader reader, final RaceLiveContent instance) {
// Fully initialized by instantiate(...)
}
@Override
public void serializeInstance(final SerializationStreamWriter writer, final RaceLiveContent instance)
throws SerializationException {
serialize(writer, instance);
}
@Override
public boolean hasCustomInstantiateInstance() {
return true;
}
@Override
public RaceLiveContent instantiateInstance(final SerializationStreamReader reader) throws SerializationException {
return instantiate(reader);
}
@Override
public void deserializeInstance(final SerializationStreamReader reader, final RaceLiveContent instance) {
deserialize(reader, instance);
}
}
@@ -0,0 +1,59 @@
package com.google.gwt.user.client.rpc.core.com.sap.sailing.landscape.common;
import java.util.ArrayList;
import java.util.List;
import com.google.gwt.user.client.rpc.CustomFieldSerializer;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamReader;
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
import com.sap.sailing.landscape.common.EventLiveContent;
import com.sap.sailing.landscape.common.ReplicaSetLiveContent;
public final class ReplicaSetLiveContent_CustomFieldSerializer extends CustomFieldSerializer<ReplicaSetLiveContent> {
public static void serialize(final SerializationStreamWriter writer, final ReplicaSetLiveContent instance)
throws SerializationException {
writer.writeString(instance.getReplicaSetName());
writer.writeInt(instance.getEventsWithLiveContent().size());
for (final EventLiveContent event : instance.getEventsWithLiveContent()) {
writer.writeObject(event);
}
}
public static ReplicaSetLiveContent instantiate(final SerializationStreamReader reader)
throws SerializationException {
final String replicaSetName = reader.readString();
final int eventCount = reader.readInt();
final List<EventLiveContent> events = new ArrayList<>(eventCount);
for (int i = 0; i < eventCount; i++) {
events.add((EventLiveContent) reader.readObject());
}
return new ReplicaSetLiveContent(replicaSetName, events);
}
public static void deserialize(final SerializationStreamReader reader, final ReplicaSetLiveContent instance) {
// Fully initialized by instantiate(...)
}
@Override
public void serializeInstance(final SerializationStreamWriter writer, final ReplicaSetLiveContent instance)
throws SerializationException {
serialize(writer, instance);
}
@Override
public boolean hasCustomInstantiateInstance() {
return true;
}
@Override
public ReplicaSetLiveContent instantiateInstance(final SerializationStreamReader reader)
throws SerializationException {
return instantiate(reader);
}
@Override
public void deserializeInstance(final SerializationStreamReader reader, final ReplicaSetLiveContent instance) {
deserialize(reader, instance);
}
}
@@ -0,0 +1,48 @@
package com.google.gwt.user.client.rpc.core.com.sap.sailing.landscape.ui.shared;
import com.google.gwt.user.client.rpc.CustomFieldSerializer;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamReader;
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
import com.sap.sailing.landscape.ui.shared.MoveAllApplicationProcessesResultDTO;
public final class MoveAllApplicationProcessesResultDTO_CustomFieldSerializer
extends CustomFieldSerializer<MoveAllApplicationProcessesResultDTO> {
public static void serialize(final SerializationStreamWriter writer,
final MoveAllApplicationProcessesResultDTO instance) throws SerializationException {
writer.writeString(instance.getNewHostId());
}
public static MoveAllApplicationProcessesResultDTO instantiate(final SerializationStreamReader reader)
throws SerializationException {
return new MoveAllApplicationProcessesResultDTO(reader.readString());
}
public static void deserialize(final SerializationStreamReader reader,
final MoveAllApplicationProcessesResultDTO instance) {
// Fully initialized by instantiate(...)
}
@Override
public void serializeInstance(final SerializationStreamWriter writer,
final MoveAllApplicationProcessesResultDTO instance) throws SerializationException {
serialize(writer, instance);
}
@Override
public boolean hasCustomInstantiateInstance() {
return true;
}
@Override
public MoveAllApplicationProcessesResultDTO instantiateInstance(final SerializationStreamReader reader)
throws SerializationException {
return instantiate(reader);
}
@Override
public void deserializeInstance(final SerializationStreamReader reader,
final MoveAllApplicationProcessesResultDTO instance) {
deserialize(reader, instance);
}
}
@@ -10,6 +10,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import com.google.gwt.cell.client.SafeHtmlCell;
@@ -34,6 +35,11 @@ import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.DataImportProgress;
import com.sap.sailing.landscape.common.EventLiveContent;
import com.sap.sailing.landscape.common.LiveContentAwareOperationResult;
import com.sap.sailing.landscape.common.LiveContentCheckResult;
import com.sap.sailing.landscape.common.RaceLiveContent;
import com.sap.sailing.landscape.common.ReplicaSetLiveContent;
import com.sap.sailing.landscape.common.SharedLandscapeConstants;
import com.sap.sailing.landscape.ui.client.CreateApplicationReplicaSetDialog.CreateApplicationReplicaSetInstructions;
import com.sap.sailing.landscape.ui.client.MoveMasterProcessDialog.MoveMasterToOtherInstanceInstructions;
@@ -47,6 +53,7 @@ import com.sap.sailing.landscape.ui.shared.AwsInstanceDTO;
import com.sap.sailing.landscape.ui.shared.CompareServersResultDTO;
import com.sap.sailing.landscape.ui.shared.MongoEndpointDTO;
import com.sap.sailing.landscape.ui.shared.MongoScalingInstructionsDTO;
import com.sap.sailing.landscape.ui.shared.MoveAllApplicationProcessesResultDTO;
import com.sap.sailing.landscape.ui.shared.ProcessDTO;
import com.sap.sailing.landscape.ui.shared.ReleaseDTO;
import com.sap.sailing.landscape.ui.shared.ReverseProxyDTO;
@@ -70,6 +77,7 @@ import com.sap.sse.gwt.client.celltable.TableWrapperWithSingleSelectionAndFilter
import com.sap.sse.gwt.client.controls.IntegerBox;
import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator;
import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator;
import com.sap.sse.gwt.client.dialog.ConfirmationDialog;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
import com.sap.sse.landscape.aws.common.shared.RedirectDTO;
@@ -676,9 +684,32 @@ public class LandscapeManagementPanel extends SimplePanel {
sshKeyManagementPanel.getPassphraseForPrivateKeyDecryption() != null ? sshKeyManagementPanel.getPassphraseForPrivateKeyDecryption().getBytes() : null,
instructions.getMasterReplicationBearerToken(), instructions.getReplicaReplicationBearerToken(),
instructions.getOptionalMemoryInMegabytesOrNull(), instructions.getOptionalMemoryTotalSizeFactorOrNull(),
new ApplicationReplicaSetActionChainingCallback<MoveMasterToOtherInstanceInstructions>(
replicaSetIterator, replicaSet, (i, sri)->moveMasterToOtherInstance(instructions, replicaSetIterator, stringMessages), instructions,
replicaSetName->stringMessages.successfullyMovedMasterOfReplicaSet(replicaSetName)));
/* force */ false,
new AsyncCallback<LiveContentAwareOperationResult<SailingApplicationReplicaSetDTO<String>>>() {
@Override
public void onFailure(final Throwable caught) {
errorReporter.reportError(caught.getMessage());
applicationReplicaSetsBusy.setBusy(false);
}
@Override
public void onSuccess(final LiveContentAwareOperationResult<SailingApplicationReplicaSetDTO<String>> result) {
if (result.isSuccessful()) {
Notification.notify(stringMessages.successfullyMovedMasterOfReplicaSet(
replicaSet.getName()), NotificationType.SUCCESS);
applicationReplicaSetsTable.replaceBasedOnEntityIdentityComparator(
result.getSuccessfulResult());
if (replicaSetIterator.hasNext()) {
moveMasterToOtherInstance(instructions, replicaSetIterator, stringMessages);
} else {
applicationReplicaSetsBusy.setBusy(false);
}
} else {
applicationReplicaSetsBusy.setBusy(false);
showLiveContentWarning(result.getLiveContentCheckResult());
}
}
});
}
@Override
@@ -893,44 +924,132 @@ public class LandscapeManagementPanel extends SimplePanel {
final String selectedRegion = regionsTable.getSelectionModel().getSelectedObject();
new EnsureReplicaStopReplicatingRemoveMasterFromTargetGroupsDialog(stringMessages, errorReporter, new DialogCallback<String>() {
@Override
public void ok(String replicaReplicationBearerToken) {
public void ok(final String replicaReplicationBearerToken) {
final ArrayList<SailingApplicationReplicaSetDTO<String>> selectedReplicaSets = new ArrayList<>();
applicationReplicaSetsForWhichToEnsureAtLeastOneReplicaStopReplicatingAndRemoveMasterFromTargetGroups
.forEach(selectedReplicaSets::add);
applicationReplicaSetsBusy.setBusy(true);
final int[] howManyMoreToGo = new int[] { Util.size(applicationReplicaSetsForWhichToEnsureAtLeastOneReplicaStopReplicatingAndRemoveMasterFromTargetGroups) };
for (final SailingApplicationReplicaSetDTO<String> applicationReplicaSetForWhichToEnsureAtLeastOneReplicaStopReplicatingAndRemoveMasterFromTargetGroups :
applicationReplicaSetsForWhichToEnsureAtLeastOneReplicaStopReplicatingAndRemoveMasterFromTargetGroups) {
landscapeManagementService.ensureAtLeastOneReplicaExistsStopReplicatingAndRemoveMasterFromTargetGroups(
selectedRegion, applicationReplicaSetForWhichToEnsureAtLeastOneReplicaStopReplicatingAndRemoveMasterFromTargetGroups,
sshKeyManagementPanel.getSelectedKeyPair().getName(),
sshKeyManagementPanel.getPassphraseForPrivateKeyDecryption() != null
? sshKeyManagementPanel.getPassphraseForPrivateKeyDecryption().getBytes() : null,
replicaReplicationBearerToken, new AsyncCallback<Boolean>() {
@Override
public void onFailure(Throwable caught) {
decrementHowManyMoreToGoAndSetNonBusyIfDone(howManyMoreToGo);
errorReporter.reportError(caught.getMessage());
landscapeManagementService.checkForLiveContent(selectedRegion, selectedReplicaSets,
replicaReplicationBearerToken, sshKeyManagementPanel.getSelectedKeyPair().getName(),
sshKeyManagementPanel.getPassphraseForPrivateKeyDecryption() == null ? null
: sshKeyManagementPanel.getPassphraseForPrivateKeyDecryption().getBytes(),
new AsyncCallback<LiveContentCheckResult>() {
@Override
public void onFailure(final Throwable caught) {
applicationReplicaSetsBusy.setBusy(false);
errorReporter.reportError(caught.getMessage());
}
@Override
public void onSuccess(final LiveContentCheckResult preflightResult) {
final Runnable execute = () -> executeStopReplicating(selectedRegion, selectedReplicaSets,
replicaReplicationBearerToken, getConflictingReplicaSetNames(preflightResult),
stringMessages);
if (preflightResult.hasLiveContent()) {
applicationReplicaSetsBusy.setBusy(false);
showLiveContentWarning(preflightResult, confirmed -> {
if (confirmed) {
applicationReplicaSetsBusy.setBusy(true);
execute.run();
}
});
} else {
execute.run();
}
@Override
public void onSuccess(Boolean result) {
decrementHowManyMoreToGoAndSetNonBusyIfDone(howManyMoreToGo);
Notification.notify(stringMessages.successfullyStoppedReplicatingAndRemovedMasterFromTargetGroups(
applicationReplicaSetForWhichToEnsureAtLeastOneReplicaStopReplicatingAndRemoveMasterFromTargetGroups.getName()),
NotificationType.SUCCESS);
}
});
}
}
});
}
@Override public void cancel() {}
}).show();
}
private Set<String> getConflictingReplicaSetNames(final LiveContentCheckResult liveContentCheckResult) {
final Set<String> result = new HashSet<>();
for (final ReplicaSetLiveContent replicaSet : liveContentCheckResult.getReplicaSetsWithLiveContent()) {
result.add(replicaSet.getReplicaSetName());
}
return result;
}
private void executeStopReplicating(final String regionId,
final ArrayList<SailingApplicationReplicaSetDTO<String>> replicaSets,
final String replicaReplicationBearerToken, final Set<String> forceReplicaSetNames,
final StringMessages stringMessages) {
final int[] remaining = new int[] { replicaSets.size() };
for (final SailingApplicationReplicaSetDTO<String> replicaSet : replicaSets) {
landscapeManagementService.ensureAtLeastOneReplicaExistsStopReplicatingAndRemoveMasterFromTargetGroups(
regionId, replicaSet, sshKeyManagementPanel.getSelectedKeyPair().getName(),
sshKeyManagementPanel.getPassphraseForPrivateKeyDecryption() == null ? null
: sshKeyManagementPanel.getPassphraseForPrivateKeyDecryption().getBytes(),
replicaReplicationBearerToken, forceReplicaSetNames.contains(replicaSet.getName()),
new AsyncCallback<LiveContentAwareOperationResult<Boolean>>() {
@Override
public void onFailure(final Throwable caught) {
decrementHowManyMoreToGoAndSetNonBusyIfDone(remaining);
errorReporter.reportError(caught.getMessage());
}
@Override
public void onSuccess(final LiveContentAwareOperationResult<Boolean> result) {
decrementHowManyMoreToGoAndSetNonBusyIfDone(remaining);
if (result.isSuccessful()) {
Notification.notify(stringMessages
.successfullyStoppedReplicatingAndRemovedMasterFromTargetGroups(
replicaSet.getName()), NotificationType.SUCCESS);
} else {
showLiveContentWarning(result.getLiveContentCheckResult(), confirmed -> {
if (confirmed) {
final Set<String> additionalForceReplicaSetNames = new HashSet<>(
forceReplicaSetNames);
additionalForceReplicaSetNames.add(replicaSet.getName());
executeStopReplicating(regionId,
new ArrayList<>(Collections.singleton(replicaSet)),
replicaReplicationBearerToken, additionalForceReplicaSetNames,
stringMessages);
}
});
}
}
});
}
}
private void decrementHowManyMoreToGoAndSetNonBusyIfDone(int[] howManyMoreToGo) {
if (--howManyMoreToGo[0] <= 0) {
applicationReplicaSetsBusy.setBusy(false);
}
}
private void showLiveContentWarning(final LiveContentCheckResult liveContentCheckResult) {
showLiveContentWarning(liveContentCheckResult, /* confirmationCallback */ null);
}
private void showLiveContentWarning(final LiveContentCheckResult liveContentCheckResult,
final Consumer<Boolean> confirmationCallback) {
final StringBuilder details = new StringBuilder();
for (final ReplicaSetLiveContent replicaSet : liveContentCheckResult.getReplicaSetsWithLiveContent()) {
details.append(replicaSet.getReplicaSetName()).append(":\n");
for (final EventLiveContent event : replicaSet.getEventsWithLiveContent()) {
details.append(" ").append(event.getEventName()).append(":\n");
for (final RaceLiveContent race : event.getRacesWithLiveContent()) {
details.append(" ").append(race.getRegattaName()).append(" / ").append(race.getRaceName())
.append(" [").append(race.getTrackingStartMillis()).append(" - ")
.append(race.getTrackingEndMillis() == null ? "open" : race.getTrackingEndMillis())
.append("]\n");
}
}
}
if (confirmationCallback == null) {
Window.alert(StringMessages.INSTANCE.liveContentWarning(details.toString()));
} else {
ConfirmationDialog.create(StringMessages.INSTANCE.liveContentWarningTitle(),
StringMessages.INSTANCE.liveContentWarning(details.toString()),
StringMessages.INSTANCE.proceedDespiteLiveContent(), StringMessages.INSTANCE.cancel(),
confirmationCallback).center();
}
}
private void defineLandingPage(StringMessages stringMessages, String selectedRegion,
SailingApplicationReplicaSetDTO<String> applicationReplicaSetToDefineLandingPageFor) {
if (sshKeyManagementPanel.getSelectedKeyPair() == null) {
@@ -1041,7 +1160,8 @@ public class LandscapeManagementPanel extends SimplePanel {
sshKeyManagementPanel.getSelectedKeyPair()==null?null:sshKeyManagementPanel.getSelectedKeyPair().getName(),
sshKeyManagementPanel.getPassphraseForPrivateKeyDecryption() != null
? sshKeyManagementPanel.getPassphraseForPrivateKeyDecryption().getBytes() : null,
new AsyncCallback<Void>() {
Collections.emptySet(),
new AsyncCallback<LiveContentAwareOperationResult<MoveAllApplicationProcessesResultDTO>>() {
@Override
public void onFailure(Throwable caught) {
applicationReplicaSetsBusy.setBusy(false);
@@ -1049,10 +1169,15 @@ public class LandscapeManagementPanel extends SimplePanel {
}
@Override
public void onSuccess(Void result) {
public void onSuccess(final LiveContentAwareOperationResult<MoveAllApplicationProcessesResultDTO> result) {
applicationReplicaSetsBusy.setBusy(false);
Notification.notify(stringMessages.successfullyMovedAllProcessesAwayFromHost(fromHost.getInstanceId()), NotificationType.SUCCESS);
refreshApplicationReplicaSetsTable();
if (result.isSuccessful()) {
Notification.notify(stringMessages.successfullyMovedAllProcessesAwayFromHost(
fromHost.getInstanceId()), NotificationType.SUCCESS);
refreshApplicationReplicaSetsTable();
} else {
showLiveContentWarning(result.getLiveContentCheckResult());
}
}
});
}
@@ -1154,18 +1279,26 @@ public class LandscapeManagementPanel extends SimplePanel {
sshKeyManagementPanel.getSelectedKeyPair()==null?null:sshKeyManagementPanel.getSelectedKeyPair().getName(),
sshKeyManagementPanel.getPassphraseForPrivateKeyDecryption() != null
? sshKeyManagementPanel.getPassphraseForPrivateKeyDecryption().getBytes() : null,
new AsyncCallback<String>() {
/* force */ false, new AsyncCallback<LiveContentAwareOperationResult<String>>() {
@Override
public void onFailure(Throwable caught) {
public void onFailure(final Throwable caught) {
applicationReplicaSetActionChainingCallback.onFailure(caught);
}
@Override
public void onSuccess(String mongoDbArchivingErrorMessage) {
applicationReplicaSetActionChainingCallback.onSuccess(/* application replica set (removed) */ null);
if (mongoDbArchivingErrorMessage != null) {
errorReporter.reportError(stringMessages.errorArchivingMongoDBTo(
selectedMongoEndpointForDBArchiving.getReplicaSetName(), mongoDbArchivingErrorMessage));
public void onSuccess(final LiveContentAwareOperationResult<String> result) {
if (result.isSuccessful()) {
final String mongoDbArchivingErrorMessage = result.getSuccessfulResult();
applicationReplicaSetActionChainingCallback
.onSuccess(/* application replica set (removed) */ null);
if (mongoDbArchivingErrorMessage != null) {
errorReporter.reportError(stringMessages.errorArchivingMongoDBTo(
selectedMongoEndpointForDBArchiving.getReplicaSetName(),
mongoDbArchivingErrorMessage));
}
} else {
applicationReplicaSetsBusy.setBusy(false);
showLiveContentWarning(result.getLiveContentCheckResult());
}
}
});
@@ -1222,16 +1355,22 @@ public class LandscapeManagementPanel extends SimplePanel {
selectedMongoEndpointForDBArchiving, sshKeyManagementPanel.getSelectedKeyPair().getName(),
sshKeyManagementPanel.getPassphraseForPrivateKeyDecryption() != null
? sshKeyManagementPanel.getPassphraseForPrivateKeyDecryption().getBytes() : null,
new AsyncCallback<Triple<DataImportProgress, CompareServersResultDTO, String>>() {
/* force */ false,
new AsyncCallback<LiveContentAwareOperationResult<Triple<DataImportProgress, CompareServersResultDTO, String>>>() {
@Override
public void onFailure(Throwable caught) {
public void onFailure(final Throwable caught) {
applicationReplicaSetsBusy.setBusy(false);
errorReporter.reportError(caught.getMessage());
}
@Override
public void onSuccess(Triple<DataImportProgress, CompareServersResultDTO, String> result) {
public void onSuccess(final LiveContentAwareOperationResult<Triple<DataImportProgress, CompareServersResultDTO, String>> operationResult) {
applicationReplicaSetsBusy.setBusy(false);
if (!operationResult.isSuccessful()) {
showLiveContentWarning(operationResult.getLiveContentCheckResult());
} else {
final Triple<DataImportProgress, CompareServersResultDTO, String> result =
operationResult.getSuccessfulResult();
final String mongoDBArchivingErrorMessage = result.getC();
if (result == null || result.getA() == null || result.getA().failed()) {
errorReporter.reportError(stringMessages.errorDuringImport(result==null||result.getA()==null?"":result.getA().getErrorMessage()));
@@ -1254,6 +1393,7 @@ public class LandscapeManagementPanel extends SimplePanel {
Notification.notify(stringMessages.successfullyArchivedReplicaSet(
applicationReplicaSetToArchive.getName()), NotificationType.SUCCESS);
}
}
}
});
}
@@ -1424,24 +1564,26 @@ public class LandscapeManagementPanel extends SimplePanel {
sshKeyManagementPanel.getPassphraseForPrivateKeyDecryption() != null
? sshKeyManagementPanel.getPassphraseForPrivateKeyDecryption().getBytes() : null,
upgradeInstructions.getReplicaReplicationBearerToken(),
new AsyncCallback<SailingApplicationReplicaSetDTO<String>>() {
/* force */ false,
new AsyncCallback<LiveContentAwareOperationResult<SailingApplicationReplicaSetDTO<String>>>() {
@Override
public void onFailure(Throwable caught) {
public void onFailure(final Throwable caught) {
decrementHowManyMoreToGoAndSetNonBusyIfDone(howManyMoreToGo);
errorReporter.reportError(caught.getMessage());
}
@Override
public void onSuccess(SailingApplicationReplicaSetDTO<String> result) {
public void onSuccess(final LiveContentAwareOperationResult<SailingApplicationReplicaSetDTO<String>> operationResult) {
decrementHowManyMoreToGoAndSetNonBusyIfDone(howManyMoreToGo);
if (result != null) {
if (operationResult.isSuccessful()) {
final SailingApplicationReplicaSetDTO<String> result =
operationResult.getSuccessfulResult();
Notification.notify(stringMessages.successfullyUpgradedApplicationReplicaSet(
result.getName(), result.getVersion()), NotificationType.SUCCESS);
applicationReplicaSetsTable.replaceBasedOnEntityIdentityComparator(result);
applicationReplicaSetsTable.refresh();
} else {
Notification.notify(stringMessages.upgradingApplicationReplicaSetFailed(replicaSet.getName()),
NotificationType.ERROR);
showLiveContentWarning(operationResult.getLiveContentCheckResult());
}
}
});
@@ -2,9 +2,12 @@ package com.sap.sailing.landscape.ui.client;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import com.google.gwt.user.client.rpc.RemoteService;
import com.sap.sailing.domain.common.DataImportProgress;
import com.sap.sailing.landscape.common.LiveContentAwareOperationResult;
import com.sap.sailing.landscape.common.LiveContentCheckResult;
import com.sap.sailing.landscape.ui.shared.AmazonMachineImageDTO;
import com.sap.sailing.landscape.ui.shared.AvailabilityZoneDTO;
import com.sap.sailing.landscape.ui.shared.AwsInstanceDTO;
@@ -13,6 +16,7 @@ import com.sap.sailing.landscape.ui.shared.CompareServersResultDTO;
import com.sap.sailing.landscape.ui.shared.LeaderboardNameDTO;
import com.sap.sailing.landscape.ui.shared.MongoEndpointDTO;
import com.sap.sailing.landscape.ui.shared.MongoScalingInstructionsDTO;
import com.sap.sailing.landscape.ui.shared.MoveAllApplicationProcessesResultDTO;
import com.sap.sailing.landscape.ui.shared.ProcessDTO;
import com.sap.sailing.landscape.ui.shared.ReleaseDTO;
import com.sap.sailing.landscape.ui.shared.ReverseProxyDTO;
@@ -105,28 +109,31 @@ public interface LandscapeManagementWriteService extends RemoteService {
void defineDefaultRedirect(String regionId, String hostname, RedirectDTO redirect, String keyName, String passphraseForPrivateKeyDecryption);
String removeApplicationReplicaSet(String regionId,
LiveContentAwareOperationResult<String> removeApplicationReplicaSet(String regionId,
SailingApplicationReplicaSetDTO<String> applicationReplicaSetToRemove, MongoEndpointDTO moveDatabaseHere,
String optionalKeyName, byte[] passphraseForPrivateKeyDescryption) throws Exception;
String optionalKeyName, byte[] passphraseForPrivateKeyDescryption, boolean force) throws Exception;
SailingApplicationReplicaSetDTO<String> createDefaultLoadBalancerMappings(String regionId,
SailingApplicationReplicaSetDTO<String> applicationReplicaSetToCreateLoadBalancerMappingFor,
boolean useDynamicLoadBalancer, String optionalDomainName, boolean forceDNSUpdate) throws Exception;
SailingApplicationReplicaSetDTO<String> upgradeApplicationReplicaSet(String regionId,
SailingApplicationReplicaSetDTO<String> applicationReplicaSetToUpgrade, String releaseOrNullForLatestMaster,
String optionalKeyName, byte[] privateKeyEncryptionPassphrase, String securityReplicationBearerToken) throws Exception;
LiveContentCheckResult checkForLiveContent(String regionId,
Iterable<SailingApplicationReplicaSetDTO<String>> applicationReplicaSets, String bearerToken,
String optionalKeyName, byte[] privateKeyEncryptionPassphrase) throws Exception;
LiveContentAwareOperationResult<SailingApplicationReplicaSetDTO<String>> upgradeApplicationReplicaSet(
String regionId, SailingApplicationReplicaSetDTO<String> applicationReplicaSetToUpgrade,
String releaseOrNullForLatestMaster, String optionalKeyName, byte[] privateKeyEncryptionPassphrase,
String securityReplicationBearerToken, boolean force) throws Exception;
ArrayList<ReleaseDTO> getReleases();
Triple<DataImportProgress, CompareServersResultDTO, String> archiveReplicaSet(String regionId,
SailingApplicationReplicaSetDTO<String> applicationReplicaSetToArchive,
String bearerTokenOrNullForApplicationReplicaSetToArchive,
String bearerTokenOrNullForArchive,
Duration durationToWaitBeforeCompareServers,
int maxNumberOfCompareServerAttempts, boolean removeApplicationReplicaSet,
MongoEndpointDTO moveDatabaseHere, String optionalKeyName, byte[] passphraseForPrivateKeyDecryption)
throws Exception;
LiveContentAwareOperationResult<Triple<DataImportProgress, CompareServersResultDTO, String>> archiveReplicaSet(
String regionId, SailingApplicationReplicaSetDTO<String> applicationReplicaSetToArchive,
String bearerTokenOrNullForApplicationReplicaSetToArchive, String bearerTokenOrNullForArchive,
Duration durationToWaitBeforeCompareServers, int maxNumberOfCompareServerAttempts,
boolean removeApplicationReplicaSet, MongoEndpointDTO moveDatabaseHere, String optionalKeyName,
byte[] passphraseForPrivateKeyDecryption, boolean force) throws Exception;
SailingApplicationReplicaSetDTO<String> deployApplicationToExistingHost(String replicaSetName,
AwsInstanceDTO hostToDeployTo, String replicaInstanceType, boolean dynamicLoadBalancerMapping,
@@ -137,9 +144,11 @@ public interface LandscapeManagementWriteService extends RemoteService {
Integer optionalIgtimiRiotPort, AwsInstanceDTO optionalPreferredInstanceToDeployUnmanagedReplicaTo) throws Exception;
Boolean ensureAtLeastOneReplicaExistsStopReplicatingAndRemoveMasterFromTargetGroups(String regionId,
SailingApplicationReplicaSetDTO<String> applicationReplicaSet, String optionalKeyName,
byte[] privateKeyEncryptionPassphrase, String replicaReplicationBearerToken) throws Exception;
LiveContentAwareOperationResult<Boolean>
ensureAtLeastOneReplicaExistsStopReplicatingAndRemoveMasterFromTargetGroups(String regionId,
SailingApplicationReplicaSetDTO<String> applicationReplicaSet, String optionalKeyName,
byte[] privateKeyEncryptionPassphrase, String replicaReplicationBearerToken, boolean force)
throws Exception;
ArrayList<SailingApplicationReplicaSetDTO<String>> updateImageForReplicaSets(String regionId,
ArrayList<SailingApplicationReplicaSetDTO<String>> applicationReplicaSetsToUpdate,
@@ -156,11 +165,12 @@ public interface LandscapeManagementWriteService extends RemoteService {
Integer optionalMemoryInMegabytesOrNull, Integer optionalMemoryTotalSizeFactorOrNull,
String optionalSharedReplicaInstanceType) throws Exception;
SailingApplicationReplicaSetDTO<String> moveMasterToOtherInstance(
LiveContentAwareOperationResult<SailingApplicationReplicaSetDTO<String>> moveMasterToOtherInstance(
SailingApplicationReplicaSetDTO<String> applicationReplicaSetDTO, boolean useSharedInstance,
String optionalInstanceTypeOrNull, String optionalKeyName, byte[] privateKeyEncryptionPassphrase,
String optionalMasterReplicationBearerTokenOrNull, String optionalReplicaReplicationBearerTokenOrNull,
Integer optionalMemoryInMegabytesOrNull, Integer optionalMemoryTotalSizeFactorOrNull) throws Exception;
Integer optionalMemoryInMegabytesOrNull, Integer optionalMemoryTotalSizeFactorOrNull, boolean force)
throws Exception;
SailingApplicationReplicaSetDTO<String> changeAutoScalingReplicasInstanceType(
SailingApplicationReplicaSetDTO<String> replicaSet, String instanceTypeName,
@@ -184,8 +194,9 @@ public interface LandscapeManagementWriteService extends RemoteService {
void removeShardingKeysFromShard(Iterable<LeaderboardNameDTO> shardingKeysToRemove, String region, String shardName, SailingApplicationReplicaSetDTO<String> replicaSet, String bearerToken, String optionalKeyName, byte[] privateKeyEncryptionPassphrase) throws Exception;
void moveAllApplicationProcessesAwayFrom(AwsInstanceDTO host, String optionalInstanceTypeForNewInstance,
String optionalKeyName, byte[] privateKeyEncryptionPassphrase) throws Exception;
LiveContentAwareOperationResult<MoveAllApplicationProcessesResultDTO> moveAllApplicationProcessesAwayFrom(
AwsInstanceDTO host, String optionalInstanceTypeForNewInstance, String optionalKeyName,
byte[] privateKeyEncryptionPassphrase, Set<String> forceMasterReplicaSetNames) throws Exception;
boolean hasDNSResourceRecordsForReplicaSet(String replicaSetName, String optionalDomainName);
@@ -2,10 +2,13 @@ package com.sap.sailing.landscape.ui.client;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.domain.common.DataImportProgress;
import com.sap.sailing.landscape.SailingAnalyticsHost;
import com.sap.sailing.landscape.common.LiveContentAwareOperationResult;
import com.sap.sailing.landscape.common.LiveContentCheckResult;
import com.sap.sailing.landscape.common.SharedLandscapeConstants;
import com.sap.sailing.landscape.ui.shared.AmazonMachineImageDTO;
import com.sap.sailing.landscape.ui.shared.AvailabilityZoneDTO;
@@ -15,6 +18,7 @@ import com.sap.sailing.landscape.ui.shared.CompareServersResultDTO;
import com.sap.sailing.landscape.ui.shared.LeaderboardNameDTO;
import com.sap.sailing.landscape.ui.shared.MongoEndpointDTO;
import com.sap.sailing.landscape.ui.shared.MongoScalingInstructionsDTO;
import com.sap.sailing.landscape.ui.shared.MoveAllApplicationProcessesResultDTO;
import com.sap.sailing.landscape.ui.shared.ProcessDTO;
import com.sap.sailing.landscape.ui.shared.ReleaseDTO;
import com.sap.sailing.landscape.ui.shared.ReverseProxyDTO;
@@ -27,7 +31,7 @@ import com.sap.sse.landscape.aws.common.shared.RedirectDTO;
public interface LandscapeManagementWriteServiceAsync {
void getRegions(AsyncCallback<ArrayList<String>> callback);
/**
* @param canBeDeployedInNlbInstanceBasedTargetGroup
* A boolean indicating, if true, that the list of available instance types should not contain those,
@@ -39,15 +43,15 @@ public interface LandscapeManagementWriteServiceAsync {
void getMongoEndpoint(String region, String replicaSetName,
AsyncCallback<MongoEndpointDTO> callback);
void getReverseProxies(String regionId, AsyncCallback<ArrayList<ReverseProxyDTO>> callback);
/**
* Removes a reverse proxy from the given cluster and terminates it.
* @return Returns true if a success.
*/
void removeReverseProxy(ReverseProxyDTO instance, String region, String optionalKeyName, byte[] privateKeyEncryptionPassphrase, AsyncCallback<Void> callback);
/**
* Rotates the httpd logs on a proxy instance.
* @param optionalKeyName Name of key used to connect to the instance to restart
@@ -55,18 +59,18 @@ public interface LandscapeManagementWriteServiceAsync {
*/
void rotateHttpdLogs(ReverseProxyDTO proxy, String region, String optionalKeyName,
byte[] passphraseForPrivateKeyDecryption, AsyncCallback<Void> callback);
/**
* Adds a reverse proxy to the cluster and the right load balancer's target group.
*/
void addReverseProxy(String instanceName, String instanceType, String region, String launchKey, AvailabilityZoneDTO availabilityZoneDTO, AsyncCallback<Void> callback);
/**
* Gets all availability zones in a region.
*/
void describeAvailabilityZones(String region,AsyncCallback<ArrayList<AvailabilityZoneDTO>> asyncCallback);
/**
* The calling subject will see only those keys for which it has the {@code READ} permission.
*/
@@ -82,7 +86,7 @@ public interface LandscapeManagementWriteServiceAsync {
* {@link CREATE_OBJECT} permission on the server on which this is called.
*/
void generateSshKeyPair(String regionId, String keyName, String privateKeyEncryptionPassphrase, AsyncCallback<SSHKeyPairDTO> callback);
/**
* Verifies a passphrase for an SSH private key. Returns {@code true} if the passphrase can decipher the private key
* and {@code false} if this does not work or the key is invalid, or the key is {@code null}.
@@ -114,7 +118,7 @@ public interface LandscapeManagementWriteServiceAsync {
* credentials.
*/
void hasValidSessionCredentials(AsyncCallback<Boolean> callback);
/**
* For a combination of an AWS access key ID, the corresponding secret plus an MFA token code produces new session
* credentials and stores them in the user's preference store from where they can be obtained again using
@@ -139,7 +143,7 @@ public interface LandscapeManagementWriteServiceAsync {
* {@link #createMfaSessionCredentials(String, String, String)}.
*/
void clearSessionCredentials(AsyncCallback<Void> callback);
void getApplicationReplicaSets(String regionId, String optionalKeyName, byte[] privateKeyEncryptionPassphrase,
AsyncCallback<ArrayList<SailingApplicationReplicaSetDTO<String>>> callback);
@@ -160,17 +164,23 @@ public interface LandscapeManagementWriteServiceAsync {
void removeApplicationReplicaSet(String regionId,
SailingApplicationReplicaSetDTO<String> applicationReplicaSetToRemove, MongoEndpointDTO moveDatabaseHere,
String optionalKeyName, byte[] passphraseForPrivateKeyDescryption, AsyncCallback<String> callback);
String optionalKeyName, byte[] passphraseForPrivateKeyDescryption, boolean force,
AsyncCallback<LiveContentAwareOperationResult<String>> callback);
void createDefaultLoadBalancerMappings(String regionId,
SailingApplicationReplicaSetDTO<String> applicationReplicaSetToCreateLoadBalancerMappingFor,
boolean useDynamicLoadBalancer, String optionalDomainName, boolean forceDNSUpdate,
AsyncCallback<SailingApplicationReplicaSetDTO<String>> callback);
void checkForLiveContent(String regionId,
Iterable<SailingApplicationReplicaSetDTO<String>> applicationReplicaSets, String bearerToken,
String optionalKeyName, byte[] privateKeyEncryptionPassphrase,
AsyncCallback<LiveContentCheckResult> callback);
void upgradeApplicationReplicaSet(String regionId,
SailingApplicationReplicaSetDTO<String> applicationReplicaSetToUpgrade, String releaseOrNullForLatestMaster,
String optionalKeyName, byte[] privateKeyEncryptionPassphrase, String replicationBearerToken,
AsyncCallback<SailingApplicationReplicaSetDTO<String>> callback);
boolean force, AsyncCallback<LiveContentAwareOperationResult<SailingApplicationReplicaSetDTO<String>>> callback);
void getReleases(AsyncCallback<ArrayList<ReleaseDTO>> asyncCallback);
@@ -178,8 +188,8 @@ public interface LandscapeManagementWriteServiceAsync {
String bearerTokenOrNullForApplicationReplicaSetToArchive, String bearerTokenOrNullForArchive,
Duration durationToWaitBeforeCompareServers, int maxNumberOfCompareServerAttempts,
boolean removeApplicationReplicaSet, MongoEndpointDTO moveDatabaseHere, String optionalKeyName,
byte[] passphraseForPrivateKeyDecryption,
AsyncCallback<Triple<DataImportProgress, CompareServersResultDTO, String>> callback);
byte[] passphraseForPrivateKeyDecryption, boolean force,
AsyncCallback<LiveContentAwareOperationResult<Triple<DataImportProgress, CompareServersResultDTO, String>>> callback);
void deployApplicationToExistingHost(String replicaSetName, AwsInstanceDTO hostToDeployTo,
String replicaInstanceType, boolean dynamicLoadBalancerMapping, String releaseNameOrNullForLatestMaster,
@@ -189,7 +199,7 @@ public interface LandscapeManagementWriteServiceAsync {
Integer optionalMemoryInMegabytesOrNull, Integer optionalMemoryTotalSizeFactorOrNull, Integer optionalIgtimiRiotPort,
AwsInstanceDTO optionalPreferredInstanceToDeployUnmanagedReplicaTo,
AsyncCallback<SailingApplicationReplicaSetDTO<String>> callback);
void createArchiveReplicaSet(String regionId, SailingApplicationReplicaSetDTO<String> applicationReplicaSetToUpgrade,
String optionalSharedInstanceType, String releaseOrNullForLatestMaster, String optionalKeyName,
byte[] privateKeyEncryptionPassphrase, String securityReplicationBearerToken, String replicaReplicationBearerToken,
@@ -198,6 +208,7 @@ public interface LandscapeManagementWriteServiceAsync {
void makeCandidateArchiveServerGoLive(String regionId,
SailingApplicationReplicaSetDTO<String> archiveReplicaSetToUpgrade, String optionalKeyName,
byte[] privateKeyEncryptionPassphrase, AsyncCallback<Void> callback);
/**
* For the given replica set ensures there is at least one healthy replica, then stops replicating on all replicas and
* removes the master from the public and master target groups. This can be used as a preparatory action for upgrading
@@ -208,8 +219,8 @@ public interface LandscapeManagementWriteServiceAsync {
*/
void ensureAtLeastOneReplicaExistsStopReplicatingAndRemoveMasterFromTargetGroups(String regionId,
SailingApplicationReplicaSetDTO<String> applicationReplicaSet, String optionalKeyName,
byte[] privateKeyEncryptionPassphrase, String replicaReplicationBearerToken,
AsyncCallback<Boolean> callback);
byte[] privateKeyEncryptionPassphrase, String replicaReplicationBearerToken, boolean force,
AsyncCallback<LiveContentAwareOperationResult<Boolean>> callback);
/**
* Updates the AMI to use in the launch template version of those of the {@code replicaSets} that have an auto-scaling group.
@@ -242,8 +253,8 @@ public interface LandscapeManagementWriteServiceAsync {
boolean useSharedInstance, String optionalInstanceTypeOrNull, String optionalKeyName,
byte[] privateKeyEncryptionPassphrase, String optionalMasterReplicationBearerTokenOrNull,
String optionalReplicaReplicationBearerTokenOrNull, Integer optionalMemoryInMegabytesOrNull,
Integer optionalMemoryTotalSizeFactorOrNull,
AsyncCallback<SailingApplicationReplicaSetDTO<String>> callback);
Integer optionalMemoryTotalSizeFactorOrNull, boolean force,
AsyncCallback<LiveContentAwareOperationResult<SailingApplicationReplicaSetDTO<String>>> callback);
void changeAutoScalingReplicasInstanceType(SailingApplicationReplicaSetDTO<String> replicaSet,
String instanceTypeName, String optionalKeyName, byte[] privateKeyEncryptionPassphrase,
@@ -354,7 +365,8 @@ public interface LandscapeManagementWriteServiceAsync {
* where the processes are moved away ({@code host})
*/
void moveAllApplicationProcessesAwayFrom(AwsInstanceDTO host, String optionalInstanceTypeForNewInstance,
String optionalKeyName, byte[] privateKeyEncryptionPassphrase, AsyncCallback<Void> callback);
String optionalKeyName, byte[] privateKeyEncryptionPassphrase, Set<String> forceMasterReplicaSetNames,
AsyncCallback<LiveContentAwareOperationResult<MoveAllApplicationProcessesResultDTO>> callback);
void hasDNSResourceRecordsForReplicaSet(String replicaSetName, String optionalDomainName, AsyncCallback<Boolean> callback);
}
@@ -116,6 +116,9 @@ com.sap.sse.gwt.adminconsole.StringMessages {
String ensureAtLeastOneReplicaExistsStopReplicatingAndRemoveMasterFromTargetGroups();
String successfullyStoppedReplicatingAndRemovedMasterFromTargetGroups(String replicaSetName);
String stopReplicating();
String liveContentWarningTitle();
String liveContentWarning(String liveContentDetails);
String proceedDespiteLiveContent();
String sameAsMaster();
String firstReplicaOnSharedInstance();
String machineImageId();
@@ -105,6 +105,9 @@ launchAnotherReplicaSetOnThisMaster=Launch another replica set on this master
ensureAtLeastOneReplicaExistsStopReplicatingAndRemoveMasterFromTargetGroups=Ensure at least one replica is running, then stop replication for all replicas and remove master from both target groups
successfullyStoppedReplicatingAndRemovedMasterFromTargetGroups=Successfully stopped replicating and removed master from both target groups for replica set {0}
stopReplicating=Stop Replicating
liveContentWarningTitle=Live race tracking in progress
liveContentWarning=The operation was not performed because live race tracking is in progress:\n{0}
proceedDespiteLiveContent=Proceed despite live content
sameAsMaster=Same as master
firstReplicaOnSharedInstance=First replica on shared instance
machineImageId=AMI ID
@@ -104,6 +104,9 @@ memoryTotalSizeFactor=Speicher als Faktor zum gesamten verfügbaren
launchAnotherReplicaSetOnThisMaster=Ein zusätzliches Anwendungs-Cluster auf diesem Maste, dass mindestens eine Replika läuft, dann Replikation auf allen Replikas stoppen und Master aus beiden Target Groups entfernen
successfullyStoppedReplicatingAndRemovedMasterFromTargetGroups=Replikation erfolgreich angehalten; master wurde erfolgreich aus beiden Target Groups des Anwendungs-Clusters {0} entfernt.
stopReplicating=Replikation stoppen
liveContentWarningTitle=Live-Regatta-Tracking läuft
liveContentWarning=Die Aktion wurde nicht ausgeführt, weil Live-Regatta-Tracking läuft:\n{0}
proceedDespiteLiveContent=Trotz Live-Inhalten fortfahren
sameAsMaster=Gleich zu Master
firstReplicaOnSharedInstance=Erste Replika auf geteilter Instanz
machineImageId=AMI ID
@@ -35,10 +35,13 @@ import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.sap.sailing.domain.common.DataImportProgress;
import com.sap.sailing.landscape.LandscapeService;
import com.sap.sailing.landscape.LiveContentConflictException;
import com.sap.sailing.landscape.SailingAnalyticsHost;
import com.sap.sailing.landscape.SailingAnalyticsMetrics;
import com.sap.sailing.landscape.SailingAnalyticsProcess;
import com.sap.sailing.landscape.SailingReleaseRepository;
import com.sap.sailing.landscape.common.LiveContentAwareOperationResult;
import com.sap.sailing.landscape.common.LiveContentCheckResult;
import com.sap.sailing.landscape.common.RemoteServiceMappingConstants;
import com.sap.sailing.landscape.common.SharedLandscapeConstants;
import com.sap.sailing.landscape.impl.SailingAnalyticsHostImpl;
@@ -59,6 +62,7 @@ import com.sap.sailing.landscape.ui.shared.LeaderboardNameDTO;
import com.sap.sailing.landscape.ui.shared.MongoEndpointDTO;
import com.sap.sailing.landscape.ui.shared.MongoProcessDTO;
import com.sap.sailing.landscape.ui.shared.MongoScalingInstructionsDTO;
import com.sap.sailing.landscape.ui.shared.MoveAllApplicationProcessesResultDTO;
import com.sap.sailing.landscape.ui.shared.ProcessDTO;
import com.sap.sailing.landscape.ui.shared.ReleaseDTO;
import com.sap.sailing.landscape.ui.shared.ReverseProxyDTO;
@@ -798,26 +802,33 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
}
@Override
public Triple<DataImportProgress, CompareServersResultDTO, String> archiveReplicaSet(String regionId, SailingApplicationReplicaSetDTO<String> applicationReplicaSetToArchive,
String bearerTokenOrNullForApplicationReplicaSetToArchive,
String bearerTokenOrNullForArchive,
Duration durationToWaitBeforeCompareServers,
int maxNumberOfCompareServerAttempts, boolean removeApplicationReplicaSet, MongoEndpointDTO moveDatabaseHere,
String optionalKeyName, byte[] passphraseForPrivateKeyDecryption)
throws Exception {
public LiveContentAwareOperationResult<Triple<DataImportProgress, CompareServersResultDTO, String>> archiveReplicaSet(
String regionId, SailingApplicationReplicaSetDTO<String> applicationReplicaSetToArchive,
String bearerTokenOrNullForApplicationReplicaSetToArchive, String bearerTokenOrNullForArchive,
Duration durationToWaitBeforeCompareServers, int maxNumberOfCompareServerAttempts,
boolean removeApplicationReplicaSet, MongoEndpointDTO moveDatabaseHere, String optionalKeyName,
byte[] passphraseForPrivateKeyDecryption, boolean force) throws Exception {
checkLandscapeManageAwsPermission();
if (removeApplicationReplicaSet) {
getSecurityService().checkCurrentUserDeletePermission(SecuredSecurityTypes.SERVER.getQualifiedObjectIdentifier(
new TypeRelativeObjectIdentifier(applicationReplicaSetToArchive.getReplicaSetName())));
}
final Triple<DataImportProgress, CompareServersResult, String> result = getLandscapeService().archiveReplicaSet(regionId,
convertFromApplicationReplicaSetDTO(new AwsRegion(regionId, getLandscape()), applicationReplicaSetToArchive, optionalKeyName, passphraseForPrivateKeyDecryption),
bearerTokenOrNullForApplicationReplicaSetToArchive, bearerTokenOrNullForArchive,
durationToWaitBeforeCompareServers, maxNumberOfCompareServerAttempts, removeApplicationReplicaSet,
getMongoEndpoint(moveDatabaseHere), optionalKeyName, passphraseForPrivateKeyDecryption);
final String mongoDBArchivingFailureReason = result.getC();
final CompareServersResultDTO compareServersResultDTO = createCompareServersResultDTO(result);
return new Triple<>(result.getA(), compareServersResultDTO, mongoDBArchivingFailureReason);
LiveContentAwareOperationResult<Triple<DataImportProgress, CompareServersResultDTO, String>> result;
try {
final Triple<DataImportProgress, CompareServersResult, String> archiveResult = getLandscapeService()
.archiveReplicaSet(regionId,
convertFromApplicationReplicaSetDTO(new AwsRegion(regionId, getLandscape()),
applicationReplicaSetToArchive, optionalKeyName, passphraseForPrivateKeyDecryption),
bearerTokenOrNullForApplicationReplicaSetToArchive, bearerTokenOrNullForArchive,
durationToWaitBeforeCompareServers, maxNumberOfCompareServerAttempts,
removeApplicationReplicaSet, getMongoEndpoint(moveDatabaseHere), optionalKeyName,
passphraseForPrivateKeyDecryption, force);
result = LiveContentAwareOperationResult.success(new Triple<>(archiveResult.getA(),
createCompareServersResultDTO(archiveResult), archiveResult.getC()));
} catch (LiveContentConflictException e) {
result = LiveContentAwareOperationResult.liveContentConflict(e.getLiveContentCheckResult());
}
return result;
}
private CompareServersResultDTO createCompareServersResultDTO(
@@ -831,17 +842,23 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
}
@Override
public String removeApplicationReplicaSet(String regionId,
public LiveContentAwareOperationResult<String> removeApplicationReplicaSet(String regionId,
SailingApplicationReplicaSetDTO<String> applicationReplicaSetToRemove, MongoEndpointDTO moveDatabaseHere,
String optionalKeyName, byte[] passphraseForPrivateKeyDecryption)
throws Exception {
String optionalKeyName, byte[] passphraseForPrivateKeyDecryption, boolean force) throws Exception {
checkLandscapeManageAwsPermission();
getSecurityService().checkCurrentUserDeletePermission(SecuredSecurityTypes.SERVER.getQualifiedObjectIdentifier(
new TypeRelativeObjectIdentifier(applicationReplicaSetToRemove.getReplicaSetName())));
final String mongoDbArchivingErrorMessage = getLandscapeService().removeApplicationReplicaSet(regionId, convertFromApplicationReplicaSetDTO(
new AwsRegion(regionId, getLandscape()), applicationReplicaSetToRemove, optionalKeyName, passphraseForPrivateKeyDecryption), getMongoEndpoint(moveDatabaseHere),
optionalKeyName, passphraseForPrivateKeyDecryption);
return mongoDbArchivingErrorMessage;
LiveContentAwareOperationResult<String> result;
try {
final String mongoDbArchivingErrorMessage = getLandscapeService().removeApplicationReplicaSet(regionId,
convertFromApplicationReplicaSetDTO(new AwsRegion(regionId, getLandscape()),
applicationReplicaSetToRemove, optionalKeyName, passphraseForPrivateKeyDecryption),
getMongoEndpoint(moveDatabaseHere), optionalKeyName, passphraseForPrivateKeyDecryption, force);
result = LiveContentAwareOperationResult.success(mongoDbArchivingErrorMessage);
} catch (LiveContentConflictException e) {
result = LiveContentAwareOperationResult.liveContentConflict(e.getLiveContentCheckResult());
}
return result;
}
private AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> convertFromApplicationReplicaSetDTO(
@@ -911,18 +928,44 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
}
@Override
public Boolean ensureAtLeastOneReplicaExistsStopReplicatingAndRemoveMasterFromTargetGroups(String regionId,
SailingApplicationReplicaSetDTO<String> applicationReplicaSet,
String optionalKeyName, byte[] privateKeyEncryptionPassphrase, String replicaReplicationBearerToken) throws Exception {
public LiveContentAwareOperationResult<Boolean>
ensureAtLeastOneReplicaExistsStopReplicatingAndRemoveMasterFromTargetGroups(String regionId,
SailingApplicationReplicaSetDTO<String> applicationReplicaSet, String optionalKeyName,
byte[] privateKeyEncryptionPassphrase, String replicaReplicationBearerToken, boolean force)
throws Exception {
checkLandscapeManageAwsPermission();
final AwsRegion region = new AwsRegion(regionId, getLandscape());
final AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> replicaSet =
convertFromApplicationReplicaSetDTO(region, applicationReplicaSet, optionalKeyName, privateKeyEncryptionPassphrase);
final String effectiveReplicaReplicationBearerToken = getLandscapeService().getEffectiveBearerToken(replicaReplicationBearerToken);
final SailingAnalyticsProcess<String> additionalReplicaStarted = getLandscapeService()
.ensureAtLeastOneReplicaExistsStopReplicatingAndRemoveMasterFromTargetGroups(replicaSet,
optionalKeyName, privateKeyEncryptionPassphrase, effectiveReplicaReplicationBearerToken);
return additionalReplicaStarted != null;
convertFromApplicationReplicaSetDTO(region, applicationReplicaSet, optionalKeyName,
privateKeyEncryptionPassphrase);
final String effectiveReplicaReplicationBearerToken = getLandscapeService()
.getEffectiveBearerToken(replicaReplicationBearerToken);
LiveContentAwareOperationResult<Boolean> result;
try {
final SailingAnalyticsProcess<String> additionalReplicaStarted = getLandscapeService()
.ensureAtLeastOneReplicaExistsStopReplicatingAndRemoveMasterFromTargetGroups(replicaSet,
optionalKeyName, privateKeyEncryptionPassphrase, effectiveReplicaReplicationBearerToken,
force);
result = LiveContentAwareOperationResult.success(additionalReplicaStarted != null);
} catch (LiveContentConflictException e) {
result = LiveContentAwareOperationResult.liveContentConflict(e.getLiveContentCheckResult());
}
return result;
}
@Override
public LiveContentCheckResult checkForLiveContent(final String regionId,
final Iterable<SailingApplicationReplicaSetDTO<String>> applicationReplicaSets, final String bearerToken,
final String optionalKeyName, final byte[] privateKeyEncryptionPassphrase) throws Exception {
checkLandscapeManageAwsPermission();
final AwsRegion region = new AwsRegion(regionId, getLandscape());
final List<AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>>> replicaSets =
new ArrayList<>();
for (final SailingApplicationReplicaSetDTO<String> applicationReplicaSet : applicationReplicaSets) {
replicaSets.add(convertFromApplicationReplicaSetDTO(region, applicationReplicaSet, optionalKeyName,
privateKeyEncryptionPassphrase));
}
return getLandscapeService().checkForLiveContent(replicaSets, bearerToken);
}
/**
@@ -932,30 +975,44 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
* for details.
*/
@Override
public SailingApplicationReplicaSetDTO<String> upgradeApplicationReplicaSet(String regionId,
SailingApplicationReplicaSetDTO<String> applicationReplicaSetToUpgrade, String releaseOrNullForLatestMaster,
String optionalKeyName, byte[] privateKeyEncryptionPassphrase, String replicaReplicationBearerToken) throws Exception {
public LiveContentAwareOperationResult<SailingApplicationReplicaSetDTO<String>> upgradeApplicationReplicaSet(
String regionId, SailingApplicationReplicaSetDTO<String> applicationReplicaSetToUpgrade,
String releaseOrNullForLatestMaster, String optionalKeyName, byte[] privateKeyEncryptionPassphrase,
String replicaReplicationBearerToken, boolean force) throws Exception {
checkLandscapeManageAwsPermission();
final AwsRegion region = new AwsRegion(regionId, getLandscape());
final AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> replicaSet =
convertFromApplicationReplicaSetDTO(region, applicationReplicaSetToUpgrade, optionalKeyName, privateKeyEncryptionPassphrase);
final AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> upgradedReplicaSet =
getLandscapeService().upgradeApplicationReplicaSet(region, replicaSet,
releaseOrNullForLatestMaster, optionalKeyName, privateKeyEncryptionPassphrase,
replicaReplicationBearerToken);
final SailingAnalyticsProcess<String> oldMaster = replicaSet.getMaster();
final Release release = upgradedReplicaSet.getVersion(Landscape.WAIT_FOR_PROCESS_TIMEOUT, Optional.ofNullable(optionalKeyName), privateKeyEncryptionPassphrase);
return new SailingApplicationReplicaSetDTO<String>(applicationReplicaSetToUpgrade.getName(),
convertToSailingAnalyticsProcessDTO(oldMaster, Optional.ofNullable(optionalKeyName), privateKeyEncryptionPassphrase),
Util.map(upgradedReplicaSet.getReplicas(), r->{
try {
return convertToSailingAnalyticsProcessDTO(r, Optional.ofNullable(optionalKeyName), privateKeyEncryptionPassphrase);
} catch (Exception e) {
throw new RuntimeException(e);
}
}),
release.getName(), release.getReleaseNotesURL().toString(),
applicationReplicaSetToUpgrade.getHostname(), applicationReplicaSetToUpgrade.getDefaultRedirectPath(), applicationReplicaSetToUpgrade.getAutoScalingGroupAmiId());
convertFromApplicationReplicaSetDTO(region, applicationReplicaSetToUpgrade, optionalKeyName,
privateKeyEncryptionPassphrase);
LiveContentAwareOperationResult<SailingApplicationReplicaSetDTO<String>> result;
try {
final AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> upgradedReplicaSet =
getLandscapeService().upgradeApplicationReplicaSet(region, replicaSet,
releaseOrNullForLatestMaster, optionalKeyName, privateKeyEncryptionPassphrase,
replicaReplicationBearerToken, force);
final SailingAnalyticsProcess<String> oldMaster = replicaSet.getMaster();
final Release release = upgradedReplicaSet.getVersion(Landscape.WAIT_FOR_PROCESS_TIMEOUT,
Optional.ofNullable(optionalKeyName), privateKeyEncryptionPassphrase);
final SailingApplicationReplicaSetDTO<String> upgradedReplicaSetDTO =
new SailingApplicationReplicaSetDTO<String>(applicationReplicaSetToUpgrade.getName(),
convertToSailingAnalyticsProcessDTO(oldMaster, Optional.ofNullable(optionalKeyName),
privateKeyEncryptionPassphrase),
Util.map(upgradedReplicaSet.getReplicas(), r->{
try {
return convertToSailingAnalyticsProcessDTO(r, Optional.ofNullable(optionalKeyName),
privateKeyEncryptionPassphrase);
} catch (Exception e) {
throw new RuntimeException(e);
}
}), release.getName(), release.getReleaseNotesURL().toString(),
applicationReplicaSetToUpgrade.getHostname(),
applicationReplicaSetToUpgrade.getDefaultRedirectPath(),
applicationReplicaSetToUpgrade.getAutoScalingGroupAmiId());
result = LiveContentAwareOperationResult.success(upgradedReplicaSetDTO);
} catch (LiveContentConflictException e) {
result = LiveContentAwareOperationResult.liveContentConflict(e.getLiveContentCheckResult());
}
return result;
}
@Override
@@ -1021,22 +1078,33 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
}
@Override
public SailingApplicationReplicaSetDTO<String> moveMasterToOtherInstance(
public LiveContentAwareOperationResult<SailingApplicationReplicaSetDTO<String>> moveMasterToOtherInstance(
SailingApplicationReplicaSetDTO<String> applicationReplicaSetDTO, boolean useSharedInstance,
String optionalInstanceTypeOrNull,
String optionalKeyName, byte[] privateKeyEncryptionPassphrase, String optionalMasterReplicationBearerTokenOrNull,
String optionalReplicaReplicationBearerTokenOrNull, Integer optionalMemoryInMegabytesOrNull,
Integer optionalMemoryTotalSizeFactorOrNull) throws Exception {
String optionalInstanceTypeOrNull, String optionalKeyName, byte[] privateKeyEncryptionPassphrase,
String optionalMasterReplicationBearerTokenOrNull, String optionalReplicaReplicationBearerTokenOrNull,
Integer optionalMemoryInMegabytesOrNull, Integer optionalMemoryTotalSizeFactorOrNull, boolean force)
throws Exception {
checkLandscapeManageAwsPermission();
final AwsRegion region = new AwsRegion(applicationReplicaSetDTO.getMaster().getHost().getRegion(), getLandscape());
final AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> replicaSet =
convertFromApplicationReplicaSetDTO(region, applicationReplicaSetDTO, optionalKeyName, privateKeyEncryptionPassphrase);
return convertToSailingApplicationReplicaSetDTO(
getLandscapeService().moveMasterToOtherInstance(replicaSet, useSharedInstance,
optionalInstanceTypeOrNull==null?Optional.empty():Optional.of(InstanceType.valueOf(optionalInstanceTypeOrNull)),
/* optionalPreferredInstanceToDeployTo */ Optional.empty(), optionalKeyName,
privateKeyEncryptionPassphrase, optionalMasterReplicationBearerTokenOrNull, optionalReplicaReplicationBearerTokenOrNull,
optionalMemoryInMegabytesOrNull, optionalMemoryTotalSizeFactorOrNull), Optional.ofNullable(optionalKeyName), privateKeyEncryptionPassphrase);
convertFromApplicationReplicaSetDTO(region, applicationReplicaSetDTO, optionalKeyName,
privateKeyEncryptionPassphrase);
LiveContentAwareOperationResult<SailingApplicationReplicaSetDTO<String>> result;
try {
final AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> movedReplicaSet =
getLandscapeService().moveMasterToOtherInstance(replicaSet, useSharedInstance,
optionalInstanceTypeOrNull == null ? Optional.empty()
: Optional.of(InstanceType.valueOf(optionalInstanceTypeOrNull)),
/* optionalPreferredInstanceToDeployTo */ Optional.empty(), optionalKeyName,
privateKeyEncryptionPassphrase, optionalMasterReplicationBearerTokenOrNull,
optionalReplicaReplicationBearerTokenOrNull, optionalMemoryInMegabytesOrNull,
optionalMemoryTotalSizeFactorOrNull, force);
result = LiveContentAwareOperationResult.success(convertToSailingApplicationReplicaSetDTO(movedReplicaSet,
Optional.ofNullable(optionalKeyName), privateKeyEncryptionPassphrase));
} catch (LiveContentConflictException e) {
result = LiveContentAwareOperationResult.liveContentConflict(e.getLiveContentCheckResult());
}
return result;
}
@Override
@@ -1141,14 +1209,24 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
}
@Override
public void moveAllApplicationProcessesAwayFrom(AwsInstanceDTO host, String optionalInstanceTypeForNewInstance,
String optionalKeyName, byte[] privateKeyEncryptionPassphrase) throws Exception {
public LiveContentAwareOperationResult<MoveAllApplicationProcessesResultDTO> moveAllApplicationProcessesAwayFrom(
AwsInstanceDTO host, String optionalInstanceTypeForNewInstance, String optionalKeyName,
byte[] privateKeyEncryptionPassphrase, Set<String> forceMasterReplicaSetNames) throws Exception {
checkLandscapeManageAwsPermission();
final SailingAnalyticsHost<String> sailingAnalyticsHost = getHostFromInstanceDTO(host);
getLandscapeService().moveAllApplicationProcessesAwayFrom(sailingAnalyticsHost,
Optional.ofNullable(optionalInstanceTypeForNewInstance == null ? null
: InstanceType.valueOf(optionalInstanceTypeForNewInstance)),
optionalKeyName, privateKeyEncryptionPassphrase);
LiveContentAwareOperationResult<MoveAllApplicationProcessesResultDTO> result;
try {
final Triple<SailingAnalyticsHost<String>, Map<String, SailingAnalyticsProcess<String>>, Map<String, SailingAnalyticsProcess<String>>> moveResult =
getLandscapeService().moveAllApplicationProcessesAwayFrom(sailingAnalyticsHost,
Optional.ofNullable(optionalInstanceTypeForNewInstance == null ? null
: InstanceType.valueOf(optionalInstanceTypeForNewInstance)), optionalKeyName,
privateKeyEncryptionPassphrase, forceMasterReplicaSetNames);
result = LiveContentAwareOperationResult.success(
new MoveAllApplicationProcessesResultDTO(moveResult.getA().getId()));
} catch (LiveContentConflictException e) {
result = LiveContentAwareOperationResult.liveContentConflict(e.getLiveContentCheckResult());
}
return result;
}
@Override
@@ -0,0 +1,17 @@
package com.sap.sailing.landscape.ui.shared;
import java.io.Serializable;
/** Identifies the destination host created while moving all application processes away from another host. */
public final class MoveAllApplicationProcessesResultDTO implements Serializable {
private static final long serialVersionUID = -3986005892785249498L;
private final String newHostId;
public MoveAllApplicationProcessesResultDTO(final String newHostId) {
this.newHostId = newHostId;
}
public String getNewHostId() {
return newHostId;
}
}
@@ -5,10 +5,12 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import com.sap.sailing.domain.common.DataImportProgress;
import com.sap.sailing.landscape.common.LiveContentCheckResult;
import com.sap.sailing.landscape.common.SharedLandscapeConstants;
import com.sap.sailing.landscape.procedures.DeployProcessOnMultiServer;
import com.sap.sailing.landscape.procedures.SailingAnalyticsMasterConfiguration;
@@ -221,12 +223,15 @@ public interface LandscapeService {
/**
* @return the reports on the master data import and content comparison;
*/
LiveContentCheckResult checkForLiveContent(Iterable<AwsApplicationReplicaSet<String, SailingAnalyticsMetrics,
SailingAnalyticsProcess<String>>> applicationReplicaSets, String bearerToken) throws Exception;
Triple<DataImportProgress, CompareServersResult, String> archiveReplicaSet(String regionId,
AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> applicationReplicaSetToArchive,
String bearerTokenOrNullForApplicationReplicaSetToArchive, String bearerTokenOrNullForArchive,
Duration durationToWaitBeforeCompareServers, int maxNumberOfCompareServerAttempts,
boolean removeApplicationReplicaSet, MongoEndpoint moveDatabaseHere, String optionalKeyName,
byte[] passphraseForPrivateKeyDecryption) throws Exception;
byte[] passphraseForPrivateKeyDecryption, boolean force) throws Exception;
/**
* If the replica set is mapped through DNS, the DNS record is removed first, before any attempts are made to shut
@@ -244,7 +249,8 @@ public interface LandscapeService {
*/
String removeApplicationReplicaSet(String regionId,
AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> applicationReplicaSetToRemove,
MongoEndpoint moveDatabaseHere, String optionalKeyName, byte[] passphraseForPrivateKeyDecryption) throws Exception;
MongoEndpoint moveDatabaseHere, String optionalKeyName, byte[] passphraseForPrivateKeyDecryption,
boolean force) throws Exception;
Release getRelease(String releaseNameOrNullForLatestMaster);
@@ -289,7 +295,7 @@ public interface LandscapeService {
AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> upgradeApplicationReplicaSet(AwsRegion region,
AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> replicaSet,
String releaseOrNullForLatestMaster, String optionalKeyName, byte[] privateKeyEncryptionPassphrase,
String replicaReplicationBearerToken) throws InterruptedException, ExecutionException,
String replicaReplicationBearerToken, boolean force) throws InterruptedException, ExecutionException,
MalformedURLException, IOException, TimeoutException, Exception;
/**
@@ -299,8 +305,8 @@ public interface LandscapeService {
SailingAnalyticsProcess<String> ensureAtLeastOneReplicaExistsStopReplicatingAndRemoveMasterFromTargetGroups(
AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> replicaSet,
String optionalKeyName, byte[] privateKeyEncryptionPassphrase,
String effectiveReplicaReplicationBearerToken) throws Exception, MalformedURLException, IOException,
TimeoutException, InterruptedException, ExecutionException;
String effectiveReplicaReplicationBearerToken, boolean force) throws Exception, MalformedURLException,
IOException, TimeoutException, InterruptedException, ExecutionException;
/**
* For an existing replica set deploys a new replica onto an existing host. The host may be shared by multiple
@@ -443,8 +449,8 @@ public interface LandscapeService {
Optional<SailingAnalyticsHost<String>> optionalPreferredInstanceToDeployTo, String optionalKeyName,
byte[] privateKeyEncryptionPassphrase, String optionalMasterReplicationBearerTokenOrNull,
String optionalReplicaReplicationBearerTokenOrNull, Integer optionalMemoryInMegabytesOrNull,
Integer optionalMemoryTotalSizeFactorOrNull) throws MalformedURLException, IOException, TimeoutException,
InterruptedException, ExecutionException, Exception;
Integer optionalMemoryTotalSizeFactorOrNull, boolean force) throws MalformedURLException, IOException,
TimeoutException, InterruptedException, ExecutionException, Exception;
/**
* If the {@code replicaSet} provided has one or more auto-scaling groups, their default launch template version is
@@ -517,7 +523,8 @@ public interface LandscapeService {
Triple<SailingAnalyticsHost<String>, Map<String, SailingAnalyticsProcess<String>>, Map<String, SailingAnalyticsProcess<String>>>
moveAllApplicationProcessesAwayFrom(SailingAnalyticsHost<String> host,
Optional<InstanceType> optionalInstanceTypeForNewInstance,
String optionalKeyName, byte[] privateKeyEncryptionPassphrase) throws Exception;
String optionalKeyName, byte[] privateKeyEncryptionPassphrase,
Set<String> forceMasterReplicaSetNames) throws Exception;
String getHostname(String replicaSetName, String optionalDomainName);
@@ -0,0 +1,19 @@
package com.sap.sailing.landscape;
import com.sap.sailing.landscape.common.LiveContentCheckResult;
/** Raised before a landscape operation would affect a replica set containing live tracked races. */
public final class LiveContentConflictException extends Exception {
private static final long serialVersionUID = 291018425020458180L;
private final LiveContentCheckResult liveContentCheckResult;
public LiveContentConflictException(final LiveContentCheckResult liveContentCheckResult) {
super("The operation would affect " + liveContentCheckResult.getReplicaSetsWithLiveContent().size()
+ " application replica set(s) containing live tracked races");
this.liveContentCheckResult = liveContentCheckResult;
}
public LiveContentCheckResult getLiveContentCheckResult() {
return liveContentCheckResult;
}
}
@@ -37,11 +37,14 @@ import com.sap.sailing.landscape.ALBToReverseProxyArchiveRedirectMapper;
import com.sap.sailing.landscape.AwsSessionCredentialsWithExpiry;
import com.sap.sailing.landscape.EligibleInstanceForReplicaSetFindingStrategy;
import com.sap.sailing.landscape.LandscapeService;
import com.sap.sailing.landscape.LiveContentConflictException;
import com.sap.sailing.landscape.SailingAnalyticsHost;
import com.sap.sailing.landscape.SailingAnalyticsMetrics;
import com.sap.sailing.landscape.SailingAnalyticsProcess;
import com.sap.sailing.landscape.SailingReleaseRepository;
import com.sap.sailing.landscape.common.LiveContentCheckResult;
import com.sap.sailing.landscape.common.RemoteServiceMappingConstants;
import com.sap.sailing.landscape.common.ReplicaSetLiveContent;
import com.sap.sailing.landscape.common.SharedLandscapeConstants;
import com.sap.sailing.landscape.procedures.CreateLaunchTemplateAndAutoScalingGroup;
import com.sap.sailing.landscape.procedures.DeployProcessOnMultiServer;
@@ -588,6 +591,32 @@ public class LandscapeServiceImpl implements LandscapeService {
return replica;
}
@Override
public LiveContentCheckResult checkForLiveContent(
final Iterable<AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>>> applicationReplicaSets,
final String bearerToken) throws Exception {
final String effectiveBearerToken = getEffectiveBearerToken(bearerToken);
final long checkedAtMillis = TimePoint.now().asMillis();
final List<ReplicaSetLiveContent> replicaSetsWithLiveContent = new ArrayList<>();
for (final AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> replicaSet : applicationReplicaSets) {
final SailingServer server = sailingServerFactoryTracker.getService().getSailingServer(new URL("https", replicaSet.getHostname(), "/"), effectiveBearerToken);
final LiveContentCheckResult replicaSetResult = server.getLiveContent(checkedAtMillis);
replicaSetsWithLiveContent.addAll(replicaSetResult.getReplicaSetsWithLiveContent());
}
return new LiveContentCheckResult(checkedAtMillis, replicaSetsWithLiveContent);
}
private void checkForLiveContentUnlessForced(
final AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> replicaSet,
final String bearerToken, final boolean force) throws Exception {
if (!force) {
final LiveContentCheckResult liveContentCheckResult = checkForLiveContent(Collections.singleton(replicaSet), bearerToken);
if (liveContentCheckResult.hasLiveContent()) {
throw new LiveContentConflictException(liveContentCheckResult);
}
}
}
@Override
public Util.Triple<DataImportProgress, CompareServersResult, String> archiveReplicaSet(String regionId,
AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> applicationReplicaSetToArchive,
@@ -595,8 +624,9 @@ public class LandscapeServiceImpl implements LandscapeService {
String bearerTokenOrNullForArchive,
Duration durationToWaitBeforeCompareServers,
int maxNumberOfCompareServerAttempts, boolean removeApplicationReplicaSet, MongoEndpoint moveDatabaseHere,
String optionalKeyName, byte[] passphraseForPrivateKeyDecryption)
String optionalKeyName, byte[] passphraseForPrivateKeyDecryption, boolean force)
throws Exception {
checkForLiveContentUnlessForced(applicationReplicaSetToArchive, bearerTokenOrNullForApplicationReplicaSetToArchive, force);
if (removeApplicationReplicaSet && applicationReplicaSetToArchive.isLocalReplicaSet()) {
throw new IllegalArgumentException("A replica set cannot archive itself if it is going to be removed. Current replica set: "+ServerInfo.getName());
}
@@ -661,6 +691,7 @@ public class LandscapeServiceImpl implements LandscapeService {
getLandscape().getCentralReverseProxy(region);
// TODO bug5311: when refactoring this for general scope migration, moving to a dedicated replica set will not require this
// TODO bug5311: when refactoring this for general scope migration, moving into a cold storage server other than ARCHIVE will require ALBToReverseProxyRedirectMapper instead
checkForLiveContentUnlessForced(applicationReplicaSetToArchive, bearerTokenOrNullForApplicationReplicaSetToArchive, force);
logger.info("Adding reverse proxy rules for migrated content pointing to ARCHIVE");
defaultRedirect.accept(new ALBToReverseProxyArchiveRedirectMapper<>(
reverseProxyCluster, hostnameFromWhichToArchive, Optional.ofNullable(optionalKeyName), passphraseForPrivateKeyDecryption));
@@ -674,7 +705,7 @@ public class LandscapeServiceImpl implements LandscapeService {
"; probably such a reference didn't exist");
}
logger.info("Removing the application replica set archived ("+from+") was requested");
mongoDbArchivingErrorMessage = removeApplicationReplicaSet(regionId, applicationReplicaSetToArchive, moveDatabaseHere, optionalKeyName, passphraseForPrivateKeyDecryption);
mongoDbArchivingErrorMessage = removeApplicationReplicaSetInternal(regionId, applicationReplicaSetToArchive, moveDatabaseHere, optionalKeyName, passphraseForPrivateKeyDecryption);
} else {
mongoDbArchivingErrorMessage = null;
logger.info("Removing remote sailing server references to events on "+from+" with IDs "+eventIDs+" from archive server "+archive);
@@ -721,7 +752,15 @@ public class LandscapeServiceImpl implements LandscapeService {
}
@Override
public String removeApplicationReplicaSet(String regionId,
public String removeApplicationReplicaSet(final String regionId,
final AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> applicationReplicaSet,
final MongoEndpoint moveDatabaseHere, final String optionalKeyName,
final byte[] passphraseForPrivateKeyDecryption, final boolean force) throws Exception {
checkForLiveContentUnlessForced(applicationReplicaSet, /* bearerToken */ null, force);
return removeApplicationReplicaSetInternal(regionId, applicationReplicaSet, moveDatabaseHere, optionalKeyName, passphraseForPrivateKeyDecryption);
}
private String removeApplicationReplicaSetInternal(String regionId,
AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> applicationReplicaSet,
MongoEndpoint moveDatabaseHere, String optionalKeyName, byte[] passphraseForPrivateKeyDecryption)
throws Exception {
@@ -1209,8 +1248,9 @@ public class LandscapeServiceImpl implements LandscapeService {
public AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> upgradeApplicationReplicaSet(AwsRegion region,
AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> replicaSet,
String releaseOrNullForLatestMaster, String optionalKeyName, byte[] privateKeyEncryptionPassphrase,
String replicaReplicationBearerToken)
String replicaReplicationBearerToken, boolean force)
throws MalformedURLException, IOException, TimeoutException, Exception {
checkForLiveContentUnlessForced(replicaSet, replicaReplicationBearerToken, force);
if (replicaSet.isLocalReplicaSet()) {
throw new IllegalArgumentException(
"A replica set cannot upgrade itself. Current replica set: " + ServerInfo.getName());
@@ -1241,7 +1281,8 @@ public class LandscapeServiceImpl implements LandscapeService {
final Set<SailingAnalyticsProcess<String>> replicasToStopAfterUpgradingMaster = new HashSet<>();
Util.addAll(replicaSet.getReplicas(), replicasToStopAfterUpgradingMaster);
final SailingAnalyticsProcess<String> additionalReplicaStarted = ensureAtLeastOneReplicaExistsStopReplicatingAndRemoveMasterFromTargetGroups(
replicaSet, optionalKeyName, privateKeyEncryptionPassphrase, effectiveReplicaReplicationBearerToken);
replicaSet, optionalKeyName, privateKeyEncryptionPassphrase, effectiveReplicaReplicationBearerToken,
/* force: outer operation already checked */ true);
if (replicaSet.getAutoScalingGroup() != null) {
getLandscape().updateReleaseInAutoScalingGroups(region, replicaSet.getAutoScalingGroup().getLaunchTemplate(),
affectedAutoScalingGroups, replicaSet.getName(), release);
@@ -1477,8 +1518,9 @@ public class LandscapeServiceImpl implements LandscapeService {
public SailingAnalyticsProcess<String> ensureAtLeastOneReplicaExistsStopReplicatingAndRemoveMasterFromTargetGroups(
final AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> replicaSet,
String optionalKeyName, byte[] privateKeyEncryptionPassphrase,
final String effectiveReplicaReplicationBearerToken) throws Exception, MalformedURLException, IOException,
final String effectiveReplicaReplicationBearerToken, final boolean force) throws Exception, MalformedURLException, IOException,
TimeoutException, InterruptedException, ExecutionException {
checkForLiveContentUnlessForced(replicaSet, effectiveReplicaReplicationBearerToken, force);
final Set<SailingAnalyticsProcess<String>> replicasToStopReplicating = new HashSet<>();
Util.addAll(replicaSet.getReplicas(), replicasToStopReplicating);
final SailingAnalyticsProcess<String> additionalReplicaStarted;
@@ -1770,16 +1812,18 @@ public class LandscapeServiceImpl implements LandscapeService {
final boolean useSharedInstance, Optional<InstanceType> optionalInstanceType,
Optional<SailingAnalyticsHost<String>> optionalPreferredInstanceToDeployTo, String optionalKeyName,
byte[] privateKeyEncryptionPassphrase, String optionalMasterReplicationBearerTokenOrNull, final String optionalReplicaReplicationBearerTokenOrNull, Integer optionalMemoryInMegabytesOrNull,
Integer optionalMemoryTotalSizeFactorOrNull)
Integer optionalMemoryTotalSizeFactorOrNull, final boolean force)
throws MalformedURLException,
IOException, TimeoutException, InterruptedException, ExecutionException, Exception {
checkForLiveContentUnlessForced(replicaSet, optionalMasterReplicationBearerTokenOrNull, force);
final Integer igtimiRiotPort = replicaSet.getMaster().getIgtimiRiotPort(Landscape.WAIT_FOR_PROCESS_TIMEOUT, Optional.ofNullable(optionalKeyName), privateKeyEncryptionPassphrase);
if (replicaSet.isLocalReplicaSet()) {
throw new IllegalArgumentException("A replica set cannot move its own master process. Current replica set: "+ServerInfo.getName());
}
final SailingAnalyticsProcess<String> newTemporaryReplica = ensureAtLeastOneReplicaExistsStopReplicatingAndRemoveMasterFromTargetGroups(replicaSet,
optionalKeyName, privateKeyEncryptionPassphrase,
getEffectiveBearerToken(optionalReplicaReplicationBearerTokenOrNull));
getEffectiveBearerToken(optionalReplicaReplicationBearerTokenOrNull),
/* force: move operation already checked */ true);
// important to obtain the release before stopping master:
final Release release = replicaSet.getVersion(Landscape.WAIT_FOR_PROCESS_TIMEOUT, Optional.ofNullable(optionalKeyName), privateKeyEncryptionPassphrase);
final AwsRegion region = replicaSet.getMaster().getHost().getRegion();
@@ -2153,7 +2197,7 @@ public class LandscapeServiceImpl implements LandscapeService {
public Triple<SailingAnalyticsHost<String>, Map<String, SailingAnalyticsProcess<String>>, Map<String, SailingAnalyticsProcess<String>>>
moveAllApplicationProcessesAwayFrom(SailingAnalyticsHost<String> host,
Optional<InstanceType> optionalInstanceTypeForNewInstance,
String optionalKeyName, byte[] privateKeyEncryptionPassphrase) throws Exception {
String optionalKeyName, byte[] privateKeyEncryptionPassphrase, Set<String> forceMasterReplicaSetNames) throws Exception {
if (!host.getInstance().tags().stream().anyMatch(tag->
tag.key().equals(SharedLandscapeConstants.SAILING_ANALYTICS_APPLICATION_HOST_TAG) &&
tag.value().equals(SharedLandscapeConstants.MULTI_PROCESS_INSTANCE_TAG_VALUE))) {
@@ -2164,6 +2208,21 @@ public class LandscapeServiceImpl implements LandscapeService {
logger.info("Moving all application processes from shared host "+host+" to a newly started shared host.");
final AwsRegion region = host.getRegion();
final HostSupplier<String, SailingAnalyticsHost<String>> hostSupplier = new SailingAnalyticsHostSupplier<>();
final Iterable<AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>>> replicaSets = getLandscape()
.getApplicationReplicaSetsByTag(region, SharedLandscapeConstants.SAILING_ANALYTICS_APPLICATION_HOST_TAG,
hostSupplier, Landscape.WAIT_FOR_PROCESS_TIMEOUT, Optional.ofNullable(optionalKeyName),
privateKeyEncryptionPassphrase);
final List<AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>>> unforcedMasterReplicaSets = new ArrayList<>();
for (final AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> replicaSet : replicaSets) {
if (replicaSet.getMaster().getHost().getId().equals(host.getId())
&& !forceMasterReplicaSetNames.contains(replicaSet.getName())) {
unforcedMasterReplicaSets.add(replicaSet);
}
}
final LiveContentCheckResult liveContentCheckResult = checkForLiveContent(unforcedMasterReplicaSets, /* bearerToken */ null);
if (liveContentCheckResult.hasLiveContent()) {
throw new LiveContentConflictException(liveContentCheckResult);
}
final SailingAnalyticsHost<String> targetHost = createEmptyMultiServer(region,
Optional.of(optionalInstanceTypeForNewInstance.orElse(host.getInstanceType())),
Optional.of(host.getAvailabilityZone()),
@@ -2171,10 +2230,7 @@ public class LandscapeServiceImpl implements LandscapeService {
Optional.ofNullable(optionalKeyName), privateKeyEncryptionPassphrase);
final Map<String, SailingAnalyticsProcess<String>> masterProcessesMoved = new HashMap<>();
final Map<String, SailingAnalyticsProcess<String>> replicaProcessesMoved = new HashMap<>();
for (final AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> replicaSet : getLandscape()
.getApplicationReplicaSetsByTag(region, SharedLandscapeConstants.SAILING_ANALYTICS_APPLICATION_HOST_TAG,
hostSupplier, Landscape.WAIT_FOR_PROCESS_TIMEOUT, Optional.ofNullable(optionalKeyName),
privateKeyEncryptionPassphrase)) {
for (final AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> replicaSet : replicaSets) {
if (replicaSet.getMaster().getHost().getId().equals(host.getId())) {
// We're moving a master process:
logger.info("Found master process "+replicaSet.getMaster()+" on host "+host+" to move to "+targetHost);
@@ -2208,7 +2264,7 @@ public class LandscapeServiceImpl implements LandscapeService {
privateKeyEncryptionPassphrase),
replicaReplicationBearerToken,
totalMemorySizeFactor == null ? getMemoryInMegabytes(optionalKeyName, privateKeyEncryptionPassphrase, replicaSet.getMaster()) : null,
totalMemorySizeFactor);
totalMemorySizeFactor, /* force: all affected masters were checked above */ true);
logger.info("Done moving master of "+replicaSet.getName()+" from "+host+" to "+targetHost);
} else {
final SailingAnalyticsProcess<String> replica;
@@ -8,6 +8,7 @@ Automatic-Module-Name: com.sap.sailing.server.gateway.interface
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Export-Package: com.sap.sailing.server.gateway.interfaces
Import-Package: com.sap.sailing.domain.common,
com.sap.sailing.landscape.common,
com.sap.sse.common,
com.sap.sse.security.util
Require-Bundle: org.json.simple,
@@ -12,6 +12,7 @@ import org.json.simple.parser.ParseException;
import com.sap.sailing.domain.base.EventBase;
import com.sap.sailing.domain.base.RemoteSailingServerReference;
import com.sap.sailing.domain.common.DataImportProgress;
import com.sap.sailing.landscape.common.LiveContentCheckResult;
import com.sap.sse.security.util.SecuredServer;
import com.sap.sse.shared.json.JsonDeserializationException;
@@ -55,6 +56,8 @@ public interface SailingServer extends SecuredServer {
*/
Iterable<EventBase> getEvents() throws Exception;
LiveContentCheckResult getLiveContent(long checkedAtMillis) throws Exception;
MasterDataImportResult importMasterData(SailingServer from, Iterable<UUID> leaderboardGroupIds, boolean override,
boolean compress, boolean exportWind, boolean exportDeviceConfigs,
boolean exportTrackedRacesAndStartTracking, Optional<UUID> progressTrackingUuid) throws Exception;
@@ -7,6 +7,7 @@ Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package:
com.sap.sailing.declination,
com.sap.sailing.landscape.common,
org.json.simple
Require-Bundle: com.sap.sailing.domain.common,
com.sap.sailing.domain.shared.android,
@@ -0,0 +1,55 @@
package com.sap.sailing.server.gateway.deserialization.impl;
import java.util.ArrayList;
import java.util.List;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.sap.sailing.landscape.common.EventLiveContent;
import com.sap.sailing.landscape.common.LiveContentCheckResult;
import com.sap.sailing.landscape.common.RaceLiveContent;
import com.sap.sailing.landscape.common.ReplicaSetLiveContent;
import com.sap.sailing.server.gateway.serialization.impl.LiveContentCheckResultJsonSerializer;
import com.sap.sse.shared.json.JsonDeserializationException;
import com.sap.sse.shared.json.JsonDeserializer;
public final class LiveContentCheckResultJsonDeserializer implements JsonDeserializer<LiveContentCheckResult> {
@Override
public LiveContentCheckResult deserialize(final JSONObject object) throws JsonDeserializationException {
final List<ReplicaSetLiveContent> replicaSets = new ArrayList<>();
for (final Object serializedReplicaSetObject : (JSONArray) object.get(
LiveContentCheckResultJsonSerializer.REPLICA_SETS)) {
final JSONObject serializedReplicaSet = (JSONObject) serializedReplicaSetObject;
final List<EventLiveContent> events = new ArrayList<>();
for (final Object serializedEventObject : (JSONArray) serializedReplicaSet.get(
LiveContentCheckResultJsonSerializer.EVENTS)) {
final JSONObject serializedEvent = (JSONObject) serializedEventObject;
final List<RaceLiveContent> races = new ArrayList<>();
for (final Object serializedRaceObject : (JSONArray) serializedEvent.get(
LiveContentCheckResultJsonSerializer.RACES)) {
final JSONObject serializedRace = (JSONObject) serializedRaceObject;
races.add(new RaceLiveContent(
(String) serializedRace.get(LiveContentCheckResultJsonSerializer.REGATTA_NAME),
(String) serializedRace.get(LiveContentCheckResultJsonSerializer.RACE_NAME),
((Number) serializedRace.get(LiveContentCheckResultJsonSerializer.TRACKING_START_MILLIS))
.longValue(),
asLong(serializedRace.get(LiveContentCheckResultJsonSerializer.TRACKING_END_MILLIS))));
}
events.add(new EventLiveContent(
(String) serializedEvent.get(LiveContentCheckResultJsonSerializer.EVENT_ID),
(String) serializedEvent.get(LiveContentCheckResultJsonSerializer.EVENT_NAME),
asLong(serializedEvent.get(LiveContentCheckResultJsonSerializer.EVENT_START_MILLIS)),
asLong(serializedEvent.get(LiveContentCheckResultJsonSerializer.EVENT_END_MILLIS)), races));
}
replicaSets.add(new ReplicaSetLiveContent(
(String) serializedReplicaSet.get(LiveContentCheckResultJsonSerializer.REPLICA_SET_NAME), events));
}
return new LiveContentCheckResult(
((Number) object.get(LiveContentCheckResultJsonSerializer.CHECKED_AT_MILLIS)).longValue(), replicaSets);
}
private Long asLong(final Object value) {
return value == null ? null : ((Number) value).longValue();
}
}
@@ -0,0 +1,60 @@
package com.sap.sailing.server.gateway.serialization.impl;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.sap.sailing.landscape.common.EventLiveContent;
import com.sap.sailing.landscape.common.LiveContentCheckResult;
import com.sap.sailing.landscape.common.RaceLiveContent;
import com.sap.sailing.landscape.common.ReplicaSetLiveContent;
import com.sap.sse.shared.json.JsonSerializer;
public final class LiveContentCheckResultJsonSerializer implements JsonSerializer<LiveContentCheckResult> {
public static final String CHECKED_AT_MILLIS = "checkedAtMillis";
public static final String REPLICA_SETS = "replicaSets";
public static final String REPLICA_SET_NAME = "replicaSetName";
public static final String EVENTS = "events";
public static final String EVENT_ID = "eventId";
public static final String EVENT_NAME = "eventName";
public static final String EVENT_START_MILLIS = "eventStartMillis";
public static final String EVENT_END_MILLIS = "eventEndMillis";
public static final String RACES = "races";
public static final String REGATTA_NAME = "regattaName";
public static final String RACE_NAME = "raceName";
public static final String TRACKING_START_MILLIS = "trackingStartMillis";
public static final String TRACKING_END_MILLIS = "trackingEndMillis";
@Override
public JSONObject serialize(final LiveContentCheckResult liveContentCheckResult) {
final JSONObject result = new JSONObject();
result.put(CHECKED_AT_MILLIS, liveContentCheckResult.getCheckedAtMillis());
final JSONArray replicaSets = new JSONArray();
for (final ReplicaSetLiveContent replicaSet : liveContentCheckResult.getReplicaSetsWithLiveContent()) {
final JSONObject serializedReplicaSet = new JSONObject();
serializedReplicaSet.put(REPLICA_SET_NAME, replicaSet.getReplicaSetName());
final JSONArray events = new JSONArray();
for (final EventLiveContent event : replicaSet.getEventsWithLiveContent()) {
final JSONObject serializedEvent = new JSONObject();
serializedEvent.put(EVENT_ID, event.getEventId());
serializedEvent.put(EVENT_NAME, event.getEventName());
serializedEvent.put(EVENT_START_MILLIS, event.getEventStartMillis());
serializedEvent.put(EVENT_END_MILLIS, event.getEventEndMillis());
final JSONArray races = new JSONArray();
for (final RaceLiveContent race : event.getRacesWithLiveContent()) {
final JSONObject serializedRace = new JSONObject();
serializedRace.put(REGATTA_NAME, race.getRegattaName());
serializedRace.put(RACE_NAME, race.getRaceName());
serializedRace.put(TRACKING_START_MILLIS, race.getTrackingStartMillis());
serializedRace.put(TRACKING_END_MILLIS, race.getTrackingEndMillis());
races.add(serializedRace);
}
serializedEvent.put(RACES, races);
events.add(serializedEvent);
}
serializedReplicaSet.put(EVENTS, events);
replicaSets.add(serializedReplicaSet);
}
result.put(REPLICA_SETS, replicaSets);
return result;
}
}
@@ -71,7 +71,8 @@ Require-Bundle: com.sap.sailing.domain,
org.apache.httpcomponents.httpclient,
org.apache.httpcomponents.httpcore,
com.sap.sailing.server.gateway.interfaces,
com.sap.sailing.landscape.common
com.sap.sailing.landscape.common,
com.sap.sse.landscape.common
Bundle-ClassPath: .
Web-ContextPath: /sailingserver
Export-Package: com.sap.sailing.server.gateway,
@@ -29,11 +29,13 @@ import com.sap.sailing.domain.base.EventBase;
import com.sap.sailing.domain.base.RemoteSailingServerReference;
import com.sap.sailing.domain.common.DataImportProgress;
import com.sap.sailing.domain.common.sharding.ShardingType;
import com.sap.sailing.landscape.common.LiveContentCheckResult;
import com.sap.sailing.server.gateway.deserialization.impl.CompareServersResultJsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.CourseAreaJsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.DataImportProgressJsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.EventBaseJsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardGroupBaseJsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.LiveContentCheckResultJsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.MasterDataImportResultJsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.RemoteSailingServerReferenceJsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.TrackingConnectorInfoJsonDeserializer;
@@ -45,6 +47,7 @@ import com.sap.sailing.server.gateway.jaxrs.api.CompareServersResource;
import com.sap.sailing.server.gateway.jaxrs.api.EventsResource;
import com.sap.sailing.server.gateway.jaxrs.api.LeaderboardGroupsResource;
import com.sap.sailing.server.gateway.jaxrs.api.LeaderboardsResource;
import com.sap.sailing.server.gateway.jaxrs.api.LiveContentResource;
import com.sap.sailing.server.gateway.jaxrs.api.MasterDataImportResource;
import com.sap.sailing.server.gateway.jaxrs.api.RemoteServerReferenceResource;
import com.sap.sailing.server.gateway.serialization.LeaderboardGroupConstants;
@@ -126,6 +129,16 @@ public class SailingServerImpl extends SecuredServerImpl implements SailingServe
return result;
}
@Override
public LiveContentCheckResult getLiveContent(final long checkedAtMillis) throws ClientProtocolException, IOException,
ParseException, JsonDeserializationException {
final URL liveContentUrl = new URL(getBaseUrl(), GATEWAY_URL_PREFIX + LiveContentResource.V1_LIVE_CONTENT +
"?" + LiveContentResource.CHECKED_AT_MILLIS_QUERY_PARAM + "=" + checkedAtMillis);
final HttpGet getLiveContent = new HttpGet(liveContentUrl.toString());
final JSONObject jsonResponse = (JSONObject) getJsonParsedResponse(getLiveContent).getA();
return new LiveContentCheckResultJsonDeserializer().deserialize(jsonResponse);
}
@Override
public MasterDataImportResult importMasterData(SailingServer from, Iterable<UUID> leaderboardGroupIds,
boolean override, boolean compress, boolean exportWind, boolean exportDeviceConfigs,
@@ -0,0 +1,73 @@
package com.sap.sailing.server.gateway.jaxrs.api;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import org.apache.shiro.SecurityUtils;
import com.sap.sailing.domain.base.Event;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.landscape.common.EventLiveContent;
import com.sap.sailing.landscape.common.LiveContentCheckResult;
import com.sap.sailing.landscape.common.RaceLiveContent;
import com.sap.sailing.landscape.common.ReplicaSetLiveContent;
import com.sap.sailing.server.gateway.serialization.impl.LiveContentCheckResultJsonSerializer;
import com.sap.sailing.shared.server.gateway.jaxrs.AbstractSailingServerResource;
import com.sap.sse.ServerInfo;
import com.sap.sse.common.TimePoint;
import com.sap.sse.landscape.common.shared.SecuredLandscapeTypes;
import com.sap.sse.security.shared.TypeRelativeObjectIdentifier;
@Path(LiveContentResource.V1_LIVE_CONTENT)
public final class LiveContentResource extends AbstractSailingServerResource {
public static final String V1_LIVE_CONTENT = "/v1/livecontent";
public static final String CHECKED_AT_MILLIS_QUERY_PARAM = "checkedAtMillis";
@GET
@Produces("application/json;charset=UTF-8")
public Response getLiveContent(@QueryParam(CHECKED_AT_MILLIS_QUERY_PARAM) final Long checkedAtMillis) {
SecurityUtils.getSubject().checkPermission(SecuredLandscapeTypes.LANDSCAPE.getStringPermissionForTypeRelativeIdentifier(
SecuredLandscapeTypes.LandscapeActions.MANAGE, new TypeRelativeObjectIdentifier("AWS")));
final TimePoint checkedAt = checkedAtMillis == null ? TimePoint.now() : TimePoint.of(checkedAtMillis);
final List<EventLiveContent> eventsWithLiveContent = new ArrayList<>();
for (final Event event : getService().getAllEvents()) {
if (mayBeLiveAt(event, checkedAt)) {
final List<RaceLiveContent> racesWithLiveContent = new ArrayList<>();
for (final TrackedRace trackedRace : getService().getAllTrackedRacesForEventTrackingAt(event,
checkedAt)) {
racesWithLiveContent.add(new RaceLiveContent(trackedRace.getRaceIdentifier().getRegattaName(),
trackedRace.getRaceIdentifier().getRaceName(), trackedRace.getStartOfTracking().asMillis(),
trackedRace.getEndOfTracking() == null ? null : trackedRace.getEndOfTracking().asMillis()));
}
racesWithLiveContent.sort(Comparator.comparing(RaceLiveContent::getRegattaName)
.thenComparing(RaceLiveContent::getRaceName));
if (!racesWithLiveContent.isEmpty()) {
eventsWithLiveContent.add(new EventLiveContent(event.getId().toString(), event.getName(),
event.getStartDate() == null ? null : event.getStartDate().asMillis(),
event.getEndDate() == null ? null : event.getEndDate().asMillis(), racesWithLiveContent));
}
}
}
eventsWithLiveContent.sort(Comparator.comparing(EventLiveContent::getEventName)
.thenComparing(EventLiveContent::getEventId));
final List<ReplicaSetLiveContent> replicaSetsWithLiveContent = new ArrayList<>();
if (!eventsWithLiveContent.isEmpty()) {
replicaSetsWithLiveContent.add(new ReplicaSetLiveContent(ServerInfo.getName(), eventsWithLiveContent));
}
final LiveContentCheckResult result = new LiveContentCheckResult(checkedAt.asMillis(),
replicaSetsWithLiveContent);
return Response.ok(streamingOutput(new LiveContentCheckResultJsonSerializer().serialize(result))).build();
}
private boolean mayBeLiveAt(final Event event, final TimePoint checkedAt) {
return (event.getStartDate() == null || !event.getStartDate().after(checkedAt))
&& (event.getEndDate() == null || !event.getEndDate().before(checkedAt));
}
}