Merge branch 'master' of ssh://sapsailing.com/home/trac/git

This commit is contained in:
Axel Uhl
2023-03-28 00:23:49 +02:00
16 changed files with 95 additions and 10 deletions
@@ -33,7 +33,7 @@ import com.sap.sse.common.Util.Pair;
* <p>
*
* Those carried wins are added to the
* {@link #isWin(com.sap.sailing.domain.leaderboard.Leaderboard, com.sap.sailing.domain.base.Competitor, com.sap.sailing.domain.base.RaceColumn, com.sap.sse.common.TimePoint, com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingAndORCPerformanceCurveCache, Function<Competitor, Double>)
* {@link #isWin(Leaderboard, Competitor, RaceColumn, TimePoint, Function, WindLegTypeAndLegBearingAndORCPerformanceCurveCache)
* wins} achieved in the medal series. More wins rank better. Equal numbers of races won make the score in the last race
* in that series the first tie-breaker. Note that "last race" can be different races in case of multiple fleets in that
* medal series, such as in a semi-final medal series split into fleets A and B. Should the tie not be resolved this
@@ -26,6 +26,9 @@
solved this issue.</li>
<li>Data mining now offers administrators with the necessary permissions to analyze role, permission,
session and subscription data for administrative purposes.</li>
<li>The <tt>.../v2/leaderboards</tt> API now supports the <tt>LEG_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS</tt> detail type,
also included when specifying <tt>ALL</tt>. It produces the field <tt>currentLegAverageSpeedOverGround-kts</tt>
in the <tt>data</tt> part of the result document.</li>
</ul>
<h5 class="articleSubheadline">January 2023</h5>
<ul class="bulletList">
@@ -1,5 +1,6 @@
package com.sap.sailing.landscape.ui.client;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.Grid;
@@ -11,6 +12,7 @@ import com.sap.sailing.landscape.common.SharedLandscapeConstants;
import com.sap.sailing.landscape.ui.client.i18n.StringMessages;
import com.sap.sse.common.Util;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
import com.sap.sse.gwt.client.controls.IntegerBox;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
@@ -114,9 +116,39 @@ public class CreateApplicationReplicaSetDialog extends AbstractApplicationReplic
private static class Validator implements DataEntryDialog.Validator<CreateApplicationReplicaSetInstructions> {
private final StringMessages stringMessages;
private final LandscapeManagementWriteServiceAsync landscapeManagementService;
public Validator(StringMessages stringMessages) {
public Validator(StringMessages stringMessages, LandscapeManagementWriteServiceAsync landscapeManagementService) {
this.stringMessages = stringMessages;
this.landscapeManagementService = landscapeManagementService;
}
@Override
public void validate(CreateApplicationReplicaSetInstructions valueToValidate, AsyncCallback<String> callback,
AsyncActionsExecutor validationExecutor) {
// TODO Auto-generated method stub
final String localErrorMessage = getErrorMessage(valueToValidate);
if (localErrorMessage != null) {
validationExecutor.execute(cb->cb.onSuccess(localErrorMessage), VALIDATION_ACTION_CATEGORY, callback);
} else {
// check availability of DNS name remotely:
landscapeManagementService.hasDNSResourceRecordsForReplicaSet(valueToValidate.getName(), valueToValidate.getOptionalDomainName(),
new AsyncCallback<Boolean>() {
@Override
public void onFailure(Throwable caught) {
callback.onFailure(caught);
}
@Override
public void onSuccess(Boolean hasDNSResourceRecordsForReplicaSet) {
if (hasDNSResourceRecordsForReplicaSet) {
callback.onSuccess(stringMessages.dnsNameAlreadyInUse());
} else {
callback.onSuccess(null); // no error
}
}
});
}
}
@Override
@@ -154,7 +186,7 @@ public class CreateApplicationReplicaSetDialog extends AbstractApplicationReplic
boolean mayUseDynamicLoadBalancer) {
super(stringMessages
.createApplicationReplicaSet(), landscapeManagementService, releaseNames, stringMessages,
errorReporter, new Validator(stringMessages), callback);
errorReporter, new Validator(stringMessages, landscapeManagementService), callback);
this.stringMessages = stringMessages;
this.useExistingSharedMasterInstance = useExistingSharedMasterInstance;
nameBox = createTextBox("", 40);
@@ -160,4 +160,6 @@ public interface LandscapeManagementWriteService extends RemoteService {
void moveAllApplicationProcessesAwayFrom(AwsInstanceDTO host, String optionalInstanceTypeForNewInstance,
String optionalKeyName, byte[] privateKeyEncryptionPassphrase) throws Exception;
boolean hasDNSResourceRecordsForReplicaSet(String replicaSetName, String optionalDomainName);
}
@@ -300,4 +300,6 @@ public interface LandscapeManagementWriteServiceAsync {
*/
void moveAllApplicationProcessesAwayFrom(AwsInstanceDTO host, String optionalInstanceTypeForNewInstance,
String optionalKeyName, byte[] privateKeyEncryptionPassphrase, AsyncCallback<Void> callback);
void hasDNSResourceRecordsForReplicaSet(String replicaSetName, String optionalDomainName, AsyncCallback<Boolean> callback);
}
@@ -163,4 +163,5 @@ com.sap.sse.gwt.adminconsole.StringMessages {
String moveTheFollowingMasterAndReplicaProcessesAway(String fromHostWithId, String masterProcesses, String replicaProcesses);
String processOfReplicaSet(int portOfProcess, String replicaSetName);
String successfullyMovedAllProcessesAwayFromHost(String hostname);
String dnsNameAlreadyInUse();
}
@@ -151,4 +151,5 @@ moveAllApplicationProcessesAwayFromMaster=Move all application processes away fr
sameAsExistingHost=Same as existing host
moveTheFollowingMasterAndReplicaProcessesAway=Moving master processes [{1}] and replica processes [{2}] from host with ID {0} to a new host.
processOfReplicaSet=on port {0} (application replica set {1})
successfullyMovedAllProcessesAwayFromHost=Successfully moved all processes away from host {0}
successfullyMovedAllProcessesAwayFromHost=Successfully moved all processes away from host {0}
dnsNameAlreadyInUse=DNS name already in use
@@ -150,4 +150,5 @@ moveAllApplicationProcessesAwayFromMaster=Alle Anwendungs-Prozesse vom Master-Se
sameAsExistingHost=Gleich wie beim bestehenden Server
moveTheFollowingMasterAndReplicaProcessesAway=Verschieben der Master-Prozesse [{1}] und Replika-Prozesse [{2}] vom Server mit der Kennung {0} auf einen neuen Server
processOfReplicaSet=auf Port {0} (Anwendungs-Cluster {1})
successfullyMovedAllProcessesAwayFromHost=Alle Prozesse erfolgreich von Server {0} verschoben
successfullyMovedAllProcessesAwayFromHost=Alle Prozesse erfolgreich von Server {0} verschoben
dnsNameAlreadyInUse=DNS-Name wird bereits benutzt
@@ -110,6 +110,7 @@ import com.sap.sse.util.ThreadPoolUtil;
import software.amazon.awssdk.services.ec2.model.AvailabilityZone;
import software.amazon.awssdk.services.ec2.model.InstanceType;
import software.amazon.awssdk.services.ec2.model.KeyPairInfo;
import software.amazon.awssdk.services.route53.model.ResourceRecordSet;
public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRemoteServiceServlet
implements LandscapeManagementWriteService {
@@ -950,4 +951,14 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
: InstanceType.valueOf(optionalInstanceTypeForNewInstance)),
optionalKeyName, privateKeyEncryptionPassphrase);
}
@Override
public boolean hasDNSResourceRecordsForReplicaSet(String replicaSetName, String optionalDomainName) {
final AwsLandscape<String> landscape = getLandscape();
final LandscapeService landscapeService = getLandscapeService();
final String hostname = landscapeService.getHostname(replicaSetName, optionalDomainName);
final Iterable<ResourceRecordSet> existingDNSRulesForHostname = landscape.getResourceRecordSets(hostname);
// Failing early in case DNS record already exists (see also bug 5826):
return existingDNSRulesForHostname != null && !Util.isEmpty(existingDNSRulesForHostname);
}
}
@@ -449,4 +449,6 @@ public interface LandscapeService {
moveAllApplicationProcessesAwayFrom(SailingAnalyticsHost<String> host,
Optional<InstanceType> optionalInstanceTypeForNewInstance,
String optionalKeyName, byte[] privateKeyEncryptionPassphrase) throws Exception;
String getHostname(String replicaSetName, String optionalDomainName);
}
@@ -127,6 +127,7 @@ import software.amazon.awssdk.services.elasticloadbalancingv2.model.Listener;
import software.amazon.awssdk.services.elasticloadbalancingv2.model.Rule;
import software.amazon.awssdk.services.elasticloadbalancingv2.model.TargetHealthDescription;
import software.amazon.awssdk.services.route53.model.RRType;
import software.amazon.awssdk.services.route53.model.ResourceRecordSet;
import software.amazon.awssdk.services.sts.model.Credentials;
public class LandscapeServiceImpl implements LandscapeService {
@@ -168,6 +169,12 @@ public class LandscapeServiceImpl implements LandscapeService {
Integer optionalMemoryTotalSizeFactorOrNull, Optional<Integer> minimumAutoScalingGroupSize,
Optional<Integer> maximumAutoScalingGroupSize) throws Exception {
final AwsLandscape<String> landscape = getLandscape();
final String hostname = getHostname(name, optionalDomainName);
final Iterable<ResourceRecordSet> existingDNSRulesForHostname = landscape.getResourceRecordSets(hostname);
// Failing early in case DNS record already exists (see also bug 5826):
if (existingDNSRulesForHostname != null && !Util.isEmpty(existingDNSRulesForHostname)) {
throw new IllegalArgumentException("DNS record for "+hostname+" already exists");
}
final AwsRegion region = new AwsRegion(regionId, landscape);
final Release release = getRelease(releaseNameOrNullForLatestMaster);
final com.sap.sailing.landscape.procedures.SailingAnalyticsMasterConfiguration.Builder<?, String> masterConfigurationBuilder =
@@ -217,7 +224,6 @@ public class LandscapeServiceImpl implements LandscapeService {
});
// if an unmanaged replica process was launched, return a replica set that contains it; otherwise use the one we already have (without any replica)
return unmanagedReplica.map(ur->{
try {
return getLandscape().getApplicationReplicaSet(region, name, master, Collections.singleton(ur));
} catch (InterruptedException | ExecutionException | TimeoutException e) {
@@ -318,6 +324,12 @@ public class LandscapeServiceImpl implements LandscapeService {
Integer optionalMemoryTotalSizeFactorOrNull, Optional<InstanceType> optionalInstanceType,
Optional<SailingAnalyticsHost<String>> optionalPreferredInstanceToDeployTo) throws Exception {
final AwsLandscape<String> landscape = getLandscape();
final String hostname = getHostname(replicaSetName, optionalDomainName);
final Iterable<ResourceRecordSet> existingDNSRulesForHostname = landscape.getResourceRecordSets(hostname);
// Failing early in case DNS record already exists (see also bug 5826):
if (existingDNSRulesForHostname != null && !Util.isEmpty(existingDNSRulesForHostname)) {
throw new IllegalArgumentException("DNS record for "+hostname+" already exists");
}
final Release release = getRelease(releaseNameOrNullForLatestMaster);
final AppConfigBuilderT masterConfigurationBuilder = createMasterConfigurationBuilder(replicaSetName,
masterReplicationBearerToken, optionalMemoryInMegabytesOrNull, optionalMemoryTotalSizeFactorOrNull,
@@ -834,8 +846,7 @@ public class LandscapeServiceImpl implements LandscapeService {
}
final CreateLoadBalancerMapping.Builder<?, ?, String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> createLoadBalancerMappingBuilder =
dynamicLoadBalancerMapping ? CreateDynamicLoadBalancerMapping.builder() : CreateDNSBasedLoadBalancerMapping.builder();
final String domainName = Optional.ofNullable(optionalDomainName).orElse(SharedLandscapeConstants.DEFAULT_DOMAIN_NAME);
final String masterHostname = replicaSetName+"."+domainName;
final String masterHostname = getHostname(replicaSetName, optionalDomainName);
final CreateLoadBalancerMapping<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> createLoadBalancerMapping = createLoadBalancerMappingBuilder
.setProcess(master)
.setHostname(masterHostname)
@@ -885,6 +896,13 @@ public class LandscapeServiceImpl implements LandscapeService {
return applicationReplicaSet;
}
@Override
public String getHostname(String replicaSetName, String optionalDomainName) {
final String domainName = Optional.ofNullable(optionalDomainName).orElse(SharedLandscapeConstants.DEFAULT_DOMAIN_NAME);
final String masterHostname = replicaSetName+"."+domainName;
return masterHostname;
}
@Override
public AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> upgradeApplicationReplicaSet(AwsRegion region,
AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> replicaSet,
@@ -320,6 +320,7 @@ public class LeaderboardsResourceV2 extends AbstractLeaderboardsResource {
DetailType.LEG_WINDWARD_DISTANCE_TO_GO_IN_METERS,
DetailType.LEG_CURRENT_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS,
DetailType.LEG_CURRENT_SIGNED_CROSS_TRACK_ERROR_IN_METERS,
DetailType.LEG_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS,
DetailType.OVERALL_TIME_ON_TIME_FACTOR,
DetailType.OVERALL_TIME_ON_DISTANCE_ALLOWANCE_IN_SECONDS_PER_NAUTICAL_MILE };
}
@@ -463,6 +464,12 @@ public class LeaderboardsResourceV2 extends AbstractLeaderboardsResource {
value = currentLegEntry.currentOrAverageSignedCrossTrackErrorInMeters;
}
break;
case LEG_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS:
name = "currentLegAverageSpeedOverGround-kts";
if (currentLegEntry != null && currentLegEntry.averageSpeedOverGroundInKnots != null) {
value = currentLegEntry.averageSpeedOverGroundInKnots;
}
break;
case OVERALL_TIME_ON_TIME_FACTOR:
name = CompetitorJsonConstants.FIELD_TIME_ON_TIME_FACTOR;
value = leaderboardRowDTO.effectiveTimeOnTimeFactor;
@@ -31,6 +31,7 @@ The supported race detail constants right now are:<br/>
- LEG_WINDWARD_DISTANCE_TO_GO_IN_METERS<br/>
- LEG_CURRENT_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS<br/>
- LEG_CURRENT_SIGNED_CROSS_TRACK_ERROR_IN_METERS<br/>
- LEG_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS<br/>
- OVERALL_MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS
- OVERALL_TIME_ON_TIME_FACTOR<br/>
- OVERALL_TIME_ON_DISTANCE_ALLOWANCE_IN_SECONDS_PER_NAUTICAL_MILE<br/>
@@ -4748,7 +4748,7 @@ Replicator {
public Map<Integer, Statistics> getLocalStatisticsByYear() {
final Map<Integer, StatisticsCalculator> calculators = new HashMap<>();
getAllEvents().forEach((event) -> {
if (getSecurityService().hasCurrentUserReadPermission(event)) {
if (getSecurityService().hasCurrentUserReadPermission(event) && event.isPublic()) {
final Integer eventYear = EventUtil.getYearOfEvent(event);
// The year may be null if the event has no start date set
// In this case the event is ignored for the yearly
@@ -48,6 +48,10 @@
for AWS landscape management. This removes the requirement to convert new OpenSSH keys to PEM format before
starting to work with them.
</li>
<li>For the DNS name resulting from a new replica set name and an optional domain name or the default domain name
(<tt>sapsailing.com</tt>) it is now checked that this name is not taken yet. An error message will be shown
otherwise.
</li>
</ul>
<h2 class="articleSubheadline">February 2023</h2>
<ul class="bulletList">
@@ -72,7 +72,7 @@ public abstract class DataEntryDialog<T> {
private final FlowPanel rightButtonPanel;
private final FlowPanel leftButtonPanel;
private final AsyncActionsExecutor validationExecutor;
private static final String VALIDATION_ACTION_CATEGORY = "validation";
protected static final String VALIDATION_ACTION_CATEGORY = "validation";
private boolean dialogInInvalidState = false;